defprotocol ExAws.Dynamo.Encodable do
@type t :: any
@doc "Converts an Elixir value into a map tagging the value with its Dynamo type"
def encode(value, options)
end
defimpl ExAws.Dynamo.Encodable, for: Atom do
def encode(true, _), do: %{"BOOL" => true}
def encode(false, _), do: %{"BOOL" => false}
def encode(nil, _), do: %{"NULL" => true}
def encode(value, _), do: %{"S" => value |> Atom.to_string()}
end
defimpl ExAws.Dynamo.Encodable, for: Integer do
def encode(val, _) do
%{"N" => val |> Integer.to_string()}
end
end
defimpl ExAws.Dynamo.Encodable, for: Float do
def encode(val, _) do
%{"N" => String.Chars.Float.to_string(val)}
end
end
defimpl ExAws.Dynamo.Encodable, for: HashDict do
def encode(hashdict, _) do
%{"M" => ExAws.Dynamo.Encodable.Map.do_encode(hashdict)}
end
end
defimpl ExAws.Dynamo.Encodable, for: Any do
defmacro __deriving__(module, struct, options) do
deriving(module, struct, options)
end
def deriving(module, _struct, options) do
extractor =
if only = options[:only] do
quote(do: Map.take(struct, unquote(only)))
else
quote(do: :maps.remove(:__struct__, struct))
end
quote do
defimpl ExAws.Dynamo.Encodable, for: unquote(module) do
def encode(struct, options) do
ExAws.Dynamo.Encodable.Map.encode(unquote(extractor), options)
end
end
end
end
def encode(_, _), do: raise("ExAws.Dynamo.Encodable does not fallback to any")
end
defimpl ExAws.Dynamo.Encodable, for: Map do
defmacro __deriving__(module, struct, options) do
ExAws.Dynamo.Encodable.Any.deriving(module, struct, options)
end
def encode(map, options) do
%{"M" => do_encode(map, options)}
end
def do_encode(map, only: only) do
map
|> Map.take(only)
|> do_encode
end
def do_encode(map, except: except) do
:maps.without(except, map)
|> do_encode
end
def do_encode(map, _), do: do_encode(map)
def do_encode(map) do
Enum.reduce(map, %{}, fn
{k, v}, map when is_binary(k) ->
Map.put(map, k, ExAws.Dynamo.Encodable.encode(v, []))
{k, v}, map ->
key = String.Chars.to_string(k)
Map.put(map, key, ExAws.Dynamo.Encodable.encode(v, []))
end)
end
end
defimpl ExAws.Dynamo.Encodable, for: BitString do
def encode(val, _) do
if String.valid?(val) do
%{"S" => val}
else
%{"B" => Base.encode64(val)}
end
end
end
defimpl ExAws.Dynamo.Encodable, for: List do
alias ExAws.Dynamo.Encodable
def encode([], _), do: %{"L" => []}
@doc """
DynamoDB offers typed sets and L, a generic list of typed attributes.
"""
def encode(list, _) do
typed_values =
for value <- list do
Encodable.encode(value, [])
end
%{"L" => typed_values}
end
end
defimpl ExAws.Dynamo.Encodable, for: MapSet do
def encode(mapset, _) do
cond do
MapSet.size(mapset) == 0 ->
raise "Cannot determine a proper data type for an empty MapSet"
Enum.all?(mapset, &is_number/1) ->
%{"NS" => number_set_to_list(mapset)}
Enum.all?(mapset, &is_binary/1) ->
%{"SS" => MapSet.to_list(mapset)}
true ->
raise "All elements in a MapSet must be only numbers or only strings"
end
end
defp number_set_to_list(number_mapset) do
number_mapset
|> MapSet.to_list()
|> Enum.map(fn
n when is_integer(n) -> Integer.to_string(n)
n when is_float(n) -> Float.to_string(n)
end)
end
end