Current section
Files
Jump to
Current section
Files
lib/ps2/api/query.ex
defmodule PS2.API.Query do
@moduledoc """
A data structure representing an API query. Create a struct using %PS2.API.Query{} or the new/0 or new/1 functions.
"""
alias PS2.API.Query
alias PS2.API.Query.Join
alias PS2.API.Query.Tree
@enforce_keys [:collection]
defstruct collection: nil, params: %{}, joins: [], tree: nil, sort: nil
@type t() :: %Query{
collection: String.t(),
params: map(),
joins: [Join.t()],
tree: Tree.t() | nil,
sort: sort_terms() | nil
}
@typedoc """
The key of a term is the field name, and the value is the sort direction. Set the value to `nil` for the default sort direction.
"""
@type sort_terms() :: map() | Keyword.t()
@type opts() :: Keyword.t()
@spec new() :: t()
@spec new(opts()) :: t()
def new(opts \\ []), do: struct!(__MODULE__, opts)
@doc """
Encodes a Query as a URL, using the given
"""
@spec encode(__MODULE__.t()) :: URI.t()
def encode(%__MODULE__{} = query) do
"/#{query.collection}"
|> URI.new!()
|> URI.append_query(encode_params(query.params))
|> URI.append_query(encode_joins(query.joins))
|> URI.append_query(encode_tree(query.tree))
|> URI.append_query(encode_sort(query.sort))
end
defp encode_params(values) do
Enum.map_join(values, "&", fn
{key, {modifier, val}} when not is_nil(val) ->
"#{key}=#{modifier}#{encode_param_values(val)}"
{key, val} when not is_nil(val) ->
"#{key}=#{encode_param_values(val)}"
end)
end
defp encode_joins([]), do: ""
defp encode_joins([%Join{} | _] = joins) do
"c:join=#{Enum.map_join(joins, ",", &encode_join/1)}"
end
defp encode_join(%Join{collection: col} = join) when not is_nil(col) do
"#{col}#{(map_size(join.params) > 0 && "^") || ""}" <>
Enum.map_join(join.params, "^", fn
{:terms, terms} when map_size(terms) > 0 -> "terms:#{encode_join_terms(terms)}"
{key, val} when not is_nil(val) -> "#{key}:#{encode_param_values(val, "'")}"
end) <>
((length(join.joins) > 0 && "(#{Enum.map_join(join.joins, ",", &encode_join/1)})") || "")
end
defp encode_tree(nil), do: ""
defp encode_tree(%Tree{field: field} = tree) when not is_nil(field) do
terms =
tree
|> Map.from_struct()
|> Stream.reject(fn {_key, val} -> is_nil(val) end)
|> Enum.map_join("^", fn {key, val} -> "#{key}:#{val}" end)
"c:tree=#{terms}"
end
defp encode_sort(nil), do: ""
defp encode_sort(terms) when is_list(terms) or is_map(terms) do
sort_terms =
if Keyword.keyword?(terms) or is_map(terms) do
Enum.map_join(terms, ",", fn
{key, nil} -> "#{key}"
{key, val} -> "#{key}:#{val}"
end)
else
Enum.join(terms, ",")
end
"c:sort=#{sort_terms}"
end
defp encode_join_terms(terms) when is_map(terms) do
Enum.map_join(terms, "'", fn
{key, {modifier, val_list}} when is_list(val_list) ->
Enum.map_join(val_list, "'", &"#{key}=#{modifier}#{&1}")
{key, {modifier, val}} when not is_nil(val) ->
"#{key}=#{modifier}#{val}"
end)
end
defp encode_param_values(values, separator \\ ",")
defp encode_param_values(values, separator) when is_list(values), do: Enum.join(values, separator)
defp encode_param_values(value, _separator)
when is_bitstring(value) or is_boolean(value) or is_integer(value) or is_atom(value),
do: value
defimpl String.Chars do
def to_string(q), do: q |> PS2.API.Query.encode() |> URI.to_string()
end
end