Current section
Files
Jump to
Current section
Files
lib/type_decoder.ex
defmodule ExOura.TypeDecoder do
@moduledoc """
Decode a response body based on types from the operation
"""
@spec decode_response(status :: pos_integer(), body :: term(), operation :: map()) ::
{:ok, term()} | {:error, :unable_to_decode}
def decode_response(200 = _status, [], _operation), do: {:ok, []}
def decode_response(status, body, operation) do
case get_type(operation, status) do
{:ok, response_type} ->
{:ok, decode(body, response_type)}
{:error, :unable_to_decode} = error ->
error
end
end
defp get_type(%{response: response} = _operation, status) do
case response |> Enum.into(%{}) |> Map.get(status, nil) do
[{_response_type, :t}] = type ->
{:ok, type}
{_response_type, :t} = type ->
{:ok, type}
_unexpected ->
{:error, :unable_to_decode}
end
end
defp decode(nil, _response_type), do: nil
defp decode(value, {:string, :date}), do: Date.from_iso8601!(value)
defp decode(value, {:union, types}), do: decode(value, union(value, types))
defp decode(value, [type]), do: Enum.map(value, &decode(&1, type))
defp decode(%{} = value, {module, type}) do
base = prepare_base(module)
fields = module.__fields__(type)
for {field_name, field_type} <- fields, reduce: base do
decoded_value ->
case Map.get(value, field_name) do
nil ->
decoded_value
field_value ->
Map.put(decoded_value, field_name, decode(field_value, field_type))
end
end
end
defp decode(value, {type, :t}) do
with true <- type |> to_string() |> String.ends_with?("Timestamp"),
{:ok, dt, _} <- DateTime.from_iso8601(value) do
dt
else
_error ->
value
end
end
defp decode(value, _type), do: value
defp prepare_base(module) do
Code.ensure_loaded(module)
if function_exported?(module, :__struct__, 0) do
struct(module)
else
%{}
end
end
defp union(_value, [type, :null]), do: type
defp union(value, [:number, {:string, :generic}]) when is_number(value), do: :number
defp union(_value, [:number, {:string, :generic}]), do: :string
defp union(value, [:integer, {:string, :generic}]) when is_number(value), do: :integer
defp union(_value, [:integer, {:string, :generic}]), do: :string
defp union(_value, types) do
raise "TypedDecoder: Unable to decode union type #{inspect(types)}"
end
end