Packages
raxx
0.10.3
1.1.0
1.0.1
1.0.0
1.0.0-rc.3
1.0.0-rc.2
retired
1.0.0-rc.1
retired
1.0.0-rc.0
retired
0.18.1
0.18.0
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.1
0.16.0
retired
0.15.11
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.14
0.14.13
0.14.12
0.14.11
0.14.10
0.14.9
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.0
0.12.3
0.12.2
0.12.1
0.12.0
0.11.1
0.11.0
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.8.2
0.8.1
0.8.0
0.7.1
0.7.0
0.6.0
0.5.2
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
0.0.1
Interface for HTTP webservers, frameworks and clients.
Current section
Files
Jump to
Current section
Files
lib/uri2_query.ex
defmodule URI2.Query do
@moduledoc """
Utilities for working with url query strings.
Namespaced under `URI2` as it is a replacement for the native `URI` module.
Calls the original module until the query API is finalised.
All functions are pure,
They return try tuples when there is a possibility of failure
"""
@doc """
Returns the query as a list of keyvalue pairs.
This parsing looses no information
## Examples
iex> URI2.Query.parse("foo=1&bar=2")
{:ok, [{"foo", "1"}, {"bar", "2"}]}
iex> URI2.Query.parse("%ZZ")
{:error, :invalid_query}
*Directly using `URI.decode_next_query_pair/1` is not possible as it is private*
"""
def parse(query_string) when is_binary(query_string) do
try do
{:ok, URI.query_decoder(query_string) |> Enum.to_list()}
rescue
_error in ArgumentError ->
{:error, :invalid_query}
end
end
@doc """
Decode a query string into a nested map of values
iex> URI2.Query.decode("percentages[]=15&percentages[]=99+%21")
{:ok, %{"percentages" => ["15", "99 !"]}}
"""
def decode(query_string) when is_binary(query_string) do
case parse(query_string) do
{:ok, key_value_pairs} ->
build_nested(key_value_pairs)
{:error, reason} ->
{:error, reason}
end
end
@doc """
Use bracket notation for nested queries.
Note this is not a formal part of the query specification.
## Examples
iex> URI2.Query.build_nested([{"foo", "1"}, {"bar", "2"}])
{:ok, %{"foo" => "1", "bar" => "2"}}
iex> URI2.Query.build_nested([{"foo[]", "1"}, {"foo[]", "2"}])
{:ok, %{"foo" => ["1", "2"]}}
iex> URI2.Query.build_nested([{"foo[bar]", "1"}, {"foo[baz]", "2"}])
{:ok, %{"foo" => %{"bar" => "1", "baz" => "2"}}}
iex> URI2.Query.build_nested([{"foo[bar][baz]", "1"}])
{:ok, %{"foo" => %{"bar" => %{"baz" => "1"}}}}
iex> URI2.Query.build_nested([{"foo[bar][]", "1"}, {"foo[bar][]", "2"}])
{:ok, %{"foo" => %{"bar" => ["1", "2"]}}}
# I think this case does not work because it is ambiguous whether the second kv item should be added to the first list item.
# iex> URI2.Query.build_nested([{"foo[][bar]", "1"}, {"foo[][baz]", "2"}])
# {:ok, %{"foo" => [%{"bar" => "1"}, %{"baz" => "2"}]}}
"""
def build_nested(key_value_pairs, nested \\ %{})
def build_nested([], nested) do
{:ok, nested}
end
def build_nested([{key, value} | key_value_pairs], nested) do
updated = case :binary.split(key, "[") do
[key] ->
put_single_value(nested, key, value)
[key, "]"] ->
put_array_entry(nested, key, value)
[key, rest] ->
case :binary.split(rest, "]") do
[subkey, rest] ->
put_sub_query(nested, key, [{subkey <> rest, value}])
end
end
case updated do
{:ok, nested} ->
build_nested(key_value_pairs, nested)
{:error, reason} ->
{:error, reason}
end
end
defp put_sub_query(map, key, key_value_pairs) do
case Map.get(map, key, %{}) do
subquery = %{} ->
{:ok, subquery} = build_nested(key_value_pairs, subquery)
{:ok, Map.put(map, key, subquery)}
other ->
{:error, {:key_already_defined_as, other}}
end
end
defp put_single_value(map, key, value) do
case Map.has_key?(map, key) do
false ->
{:ok, Map.put_new(map, key, value)}
true ->
{:error, {:key_already_defined_as, Map.get(map, key)}}
end
end
defp put_array_entry(map, key, value) do
case Map.get(map, key, []) do
values when is_list(values) ->
{:ok, Map.put(map, key, values ++ [value])}
other ->
{:error, {:key_already_defined_as, other}}
end
end
end