Alembic v4.0.0 Alembic.FromJson behaviour View Source
JSON objects that have constrained members in the JSON API format are represented as
struct
s. In order to convert plain, decoded JSON in map
s and list
s, from_json/2
can be implemented by a
module to convert to its struct
.
Link to this section Summary
Types
The action that generated the Alembic.json
. :create
and :update
allow additional formats
A result that can collect singleton_result
s
A value that can collect singleton_value
s
Tagged-tuple returned when an error has occured in from_json/2
The name of a field in a struct
A result that has been tagged with the field
in a struct to which it should be put when merged to the
collectable_ok
A key in a map or struct output from from_json/2
A result that has been tagged with the key
in a struct or map to which it should be put when merged to the
collectable_ok
Whether the :client
or :server
sent the json
. The :client
is allowed more formats on :create
and :update
action
A result that can be merged into a collectable_ok
whose collectable_value
is a list
A single value that can be merged into a collective_value
Functions
Converts a JSON array using the element_module
or element_from_json
function
Converts the member of a json object into a field_result
that can be merged into a struct
Ensurs that json
is an integer
Ensures integer
is positive
Merges the singleton_result
into the collectable_result
Add key
to singleton_ok
tuple, otherwise, does nothing
Reduces singleton_results
into an Alembic.FromJson.collectable_result
Ensures that json
is a String.t
Callbacks
Takes decoded JSON, such as from Poison.decode/1
, and validates it for format and converts it to struct
Link to this section Types
The action that generated the Alembic.json
. :create
and :update
allow additional formats.
A result that can collect singleton_result
s.
collectable_result() :: collectable_ok() | error()
A value that can collect singleton_value
s.
Tagged-tuple returned when an error has occured in from_json/2
.
The format errors are in the errors section of the Alembic.Document.t
, which can be sent back to
sender of the original JSON API document so they can correct the errors.
The name of a field in a struct
A result that has been tagged with the field
in a struct to which it should be put when merged to the
collectable_ok
.
A key in a map or struct output from from_json/2
A result that has been tagged with the key
in a struct or map to which it should be put when merged to the
collectable_ok
.
Whether the :client
or :server
sent the json
. The :client
is allowed more formats on :create
and :update
action
A result that can be merged into a collectable_ok
whose collectable_value
is a list
.
A single value that can be merged into a collective_value
.
Link to this section Functions
from_json_array(Alembic.json(), Alembic.Error.t(), module()) :: {:ok, [singleton_value()]} | error()
from_json_array( Alembic.json(), Alembic.Error.t(), (Alembic.json(), Alembic.Error.t() -> singleton_result()) ) :: {:ok, [singleton_value()]} | error()
Converts a JSON array using the element_module
or element_from_json
function.
The element_module
MUST implement the Alembic.FromJson
behaviour.
from_parent_json_to_field_result(%{ parent: %{json: Alembic.json_object(), error_template: Alembic.Error.t()}, member: %{ :from_json => (Alembic.json(), Origin.t() -> singleton_result()), :name => String.t(), optional(:required) => boolean() } | %{name: String.t(), module: module()}, field: atom() }) :: field_result() | error() | :error
Converts the member of a json object into a field_result
that can be merged into a struct.
Examples
No Member
If there is no member, then :error will be returned
iex> Alembic.FromJson.from_parent_json_to_field_result(
...> %{
...> field: :data,
...> member: %{
...> module: Alembic.ResourceLinkage,
...> name: "data"
...> },
...> parent: %{
...> json: %{},
...> error_template: %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: "/data/relationships/author"
...> }
...> }
...> }
...> }
...> )
:error
If there is no member, and the :member
map contains required: true
, then an error will be returned that the member
is missing
iex> Alembic.FromJson.from_parent_json_to_field_result(
...> %{
...> field: :id,
...> member: %{
...> from_json: &Alembic.FromJson.string_from_json/2,
...> name: "id",
...> required: true
...> },
...> parent: %{
...> json: %{},
...> error_template: %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: "/data/relationships/author"
...> }
...> }
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/data/relationships/author/id` is missing",
meta: %{
"child" => "id"
},
source: %Alembic.Source{
pointer: "/data/relationships/author"
},
status: "422",
title: "Child missing"
}
]
}
}
nil
member value
If there is a member, but it’s value is nil
(meaning it was null
in the unparsed JSON) then nil
will be passed
to the member_module
’s from_json/2
callback. In most cases, the implementation should return {:ok, nil}
, which
will be tagged with the field_name
.
iex> Alembic.FromJson.from_parent_json_to_field_result(
...> %{
...> field: :links,
...> member: %{
...> module: Alembic.Links,
...> name: "links"
...> },
...> parent: %{
...> json: %{"links" => nil},
...> error_template: %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: "/data/relationships/author"
...> }
...> }
...> }
...> }
...> )
{:ok, {:links, nil}}
Error on member
Any errors from member_module
’s from_json/2
will be returned, but with the origin set to parent_origin
iex> Alembic.FromJson.from_parent_json_to_field_result(
...> %{
...> field: :links,
...> member: %{
...> module: Alembic.Links,
...> name: "links"
...> },
...> parent: %{
...> json: %{"links" => []},
...> error_template: %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: "/data/relationships/author"
...> }
...> }
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/data/relationships/author/links` type is not links object",
meta: %{
"type" => "links object"
},
source: %Alembic.Source{
pointer: "/data/relationships/author/links"
},
status: "422",
title: "Type is wrong"
}
]
}
}
Returns
{:ok, {field_name, value}}
- a value (converted bymember_module
’sfrom_json/2
) tagged with thefield_name
, so that it can be passed tomerge/2
.{:error, %Alembic.Document{errors: [Alembic.Error.t]}}
- an error frommember_module
’sfrom_json/2
.:error
if themember_name
isn’t inparent_json
at all. This is to help distinguish no member fromnil
member values, as JSON API allows fornull
members in the case of empty to-one relationships.
integer_from_json(integer(), Alembic.Error.t()) :: {:ok, integer()}
integer_from_json( nil | true | false | list() | float() | String.t() | Alembic.json_object(), Alembic.Error.t() ) :: {:error, Alembic.Document.t()}
Ensurs that json
is an integer
.
An integer will be returned with an ok
tuple
iex> Alembic.FromJson.integer_from_json(
...> 1,
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: "/page/number"
...> }
...> }
...> )
{:ok, 1}
A non-integer will be returned in an error
tuple where the errors Alembic.Document.t
has a member type error.
iex> Alembic.FromJson.integer_from_json(
...> 1.5,
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: "/page/number"
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/page/number` type is not integer",
meta: %{
"type" => "integer"
},
source: %Alembic.Source{
pointer: "/page/number"
},
status: "422",
title: "Type is wrong"
}
]
}
}
integer_to_positive_integer(0, Alembic.Error.t()) :: error()
integer_to_positive_integer(neg_integer(), Alembic.Error.t()) :: error()
integer_to_positive_integer(pos_integer(), Alembic.Error.t()) :: {:ok, pos_integer()}
Ensures integer
is positive.
If greater than 0, then it’s ok
iex> Alembic.FromJson.integer_to_positive_integer(
...> 1,
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: "/page/number"
...> }
...> }
...> )
{:ok, 1}
If it’s 0, then it’s a type error
iex> Alembic.FromJson.integer_to_positive_integer(
...> 0,
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: "/page/number"
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/page/number` type is not positive integer",
meta: %{
"type" => "positive integer"
},
source: %Alembic.Source{
pointer: "/page/number"
},
status: "422",
title: "Type is wrong"
}
]
}
}
merge({:ok, list()}, {:ok, singleton_value()}) :: {:ok, list()}
merge({:ok, map()}, {:ok, {key(), singleton_value()}}) :: {:ok, map()}
merge({:ok, struct()}, {:ok, field(), singleton_value()}) :: {:ok, struct()}
merge(collectable_ok(), error()) :: error()
merge(error(), key_ok()) :: error()
merge(error(), error()) :: error()
Merges the singleton_result
into the collectable_result
.
collectable_ok
If the collectable_result
is a collectable_ok
, then the singleton_result
controls whether another
collectable_ok
or error
is produced.
error
If the singleton_result
is an error, then it becomes the merged result
iex> collectable_ok = {:ok, ["One"]}
iex> error = {
...> :error,
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: "/data/1"
...> }
...> }
...> ]
...> }
...> }
...> merged_result = Alembic.FromJson.merge(collectable_ok, error)
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
source: %Alembic.Source{
pointer: "/data/1"
}
}
]
}
}
iex> merged_result == error
true
field_ok
If the the result being merged in is for a field, then the field in the struct in collectable_ok
is updated with the
value from the field_ok
.
iex> collectable_ok = {:ok, %Alembic.Link{}}
iex> Alembic.FromJson.merge(
...> collectable_ok,
...> {:ok, {:href, "http://example.com"}}
...> )
{
:ok,
%Alembic.Link{
href: "http://example.com"
}
}
key_ok
If the result being merged is for a key, then the key in the map in collectable_ok
is updated with the value from
key_ok
iex> collectable_ok = {:ok, %{}}
iex> Alembic.FromJson.merge(
...> collectable_ok,
...> {
...> :ok,
...> {
...> "link_object",
...> %Alembic.Link{
...> href: "http://example.com",
...> meta: %{
...> "last_updated_on" => "2015-12-21"
...> }
...> }
...> }
...> }
...> )
{
:ok,
%{
"link_object" => %Alembic.Link{
href: "http://example.com",
meta: %{"last_updated_on" => "2015-12-21"}
}
}
}
singleton_ok
If the result being merged is a singleton value, then it is add to the head of collectable_ok
’s list
.
iex> collectable_ok = {:ok, []}
iex> Alembic.FromJson.merge(
...> collectable_ok,
...> {
...> :ok,
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> }
...> )
{
:ok,
[
%Alembic.Error{
source: %Alembic.Source{
pointer: "/data"
}
}
]
}
error
ok
If the current collectable is an error
, then ok results are ignored
iex> error = {
...> :error,
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: "/data/0"
...> }
...> }
...> ]
...> }
...> }
iex> merged_error = Alembic.FromJson.merge(
...> error,
...> {:ok, {:field, "value"}}
...> )
iex> merged_error == error
true
error
If the collectable is an error
and the singleton_result
is also an error, then the
Alembic.Document.t
are merged so that singleton_result
’s errors appear at the head of merged errors.
iex> error = {
...> :error,
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: "/data/0"
...> }
...> }
...> ]
...> }
...> }
iex> Alembic.FromJson.merge(
...> error,
...> {
...> :error,
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: "/data/1"
...> }
...> }
...> ]
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
source: %Alembic.Source{
pointer: "/data/1"
}
},
%Alembic.Error{
source: %Alembic.Source{
pointer: "/data/0"
}
}
]
}
}
If you want to get the Alembic.Document.t
errors
back in orginal order, use reverse/1
. reduce/2
automatically does the reverse/1
.
Add key
to singleton_ok
tuple, otherwise, does nothing.
When result is singleton_ok
, adds the key
iex> Alembic.FromJson.put_key({:ok, %{}}, :data)
{:ok, {:data, %{}}}
But will not add a key if already present
iex> try do
...> Alembic.FromJson.put_key({:ok, {:data, %{}}}, :data)
...> rescue
...> error -> error
...> end
%FunctionClauseError{arity: 2, function: :put_key, module: Alembic.FromJson}
When result is error
, does nothing
iex> result = {
...> :error,
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> ]
...> }
...> }
iex> Alembic.FromJson.put_key(result, :data) == result
true
reduce([singleton_result()] | Enumerable.t(), collectable_result()) :: collectable_result()
Reduces singleton_results
into an Alembic.FromJson.collectable_result
.
If there are any errors in singleton_results, then all the errors are accumulated into a single
Alembic.FromJson.error
.
Since merge/2
adds new singleton_values
to the beginning of a collectable_ok
list or
Alembic.Error.t
s to the beginning of the Alembic.Document.t
errors
, those lists
need to be reversed to maintain original ordering after a series of merge/2
calls.
error
Reverses the Alembic.Document.t
errors
to undo tail prepending done by merge/2
iex> accumulated_error = {
...> :error,
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{
...> detail: "The index `2` of `/data` is not a resource",
...> source: %Alembic.Source{
...> pointer: "/data/2"
...> },
...> title: "Element is not a resource"
...> },
...> %Alembic.Error{
...> detail: "The index `1` of `/data` is not a resource",
...> source: %Alembic.Source{
...> pointer: "/data/1"
...> },
...> title: "Element is not a resource"
...> }
...> ]
...> }
...> }
iex> Alembic.FromJson.reverse(accumulated_error)
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "The index `1` of `/data` is not a resource",
source: %Alembic.Source{
pointer: "/data/1"
},
title: "Element is not a resource"
},
%Alembic.Error{
detail: "The index `2` of `/data` is not a resource",
source: %Alembic.Source{
pointer: "/data/2"
},
title: "Element is not a resource"
}
]
}
}
collectable_ok
lists
List are ordered, but collect by prepending, so they need to be reversed
iex> collectable_ok = {:ok, [3..4, 1..2]}
iex> Alembic.FromJson.reverse(collectable_ok)
{:ok, [1..2, 3..4]}
maps
Maps are unordered, so they just pass through
iex> collectable_ok = {:ok, %{"b" => 2, "a" => 1}}
iex> reversed_ok = Alembic.FromJson.reverse(collectable_ok)
{:ok, %{"a" => 1, "b" => 2}}
iex> reversed_ok == collectable_ok
true
string_from_json(String.t(), Alembic.Error.t()) :: {:ok, String.t()}
string_from_json( nil | true | false | list() | float() | integer() | Alembic.json_object(), Alembic.Error.t() ) :: error()
Ensures that json
is a String.t
A string will be returned in an ok
tuple
iex> Alembic.FromJson.string_from_json(
...> "422",
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: "/errors/0/status"
...> }
...> }
...> )
{:ok, "422"}
A non-string will be returned in an error
tuple where the errors Alembic.Document.t
has a member
type error
iex> Alembic.FromJson.string_from_json(
...> 422,
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: "/errors/0/status"
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/errors/0/status` type is not string",
meta: %{
"type" => "string"
},
source: %Alembic.Source{
pointer: "/errors/0/status"
},
status: "422",
title: "Type is wrong"
}
]
}
}
Link to this section Callbacks
from_json(decoded_json :: Alembic.json(), error_template :: Alembic.Error.t()) :: singleton_result()
Takes decoded JSON, such as from Poison.decode/1
, and validates it for format and converts it to struct.
Parameters
json
- the decoded JSON fromPoison.decode/1
or some other JSON decoder.error_template
- A prepolated error that includes a pointer for error repointing and meta information that influencing validation. For example, some formats are only accepted on:create
or:update
from the:client
.%Alembic.Error{ meta: %{
"action" => Alembic.FromJson.action, "sender" => Alembic.FromJson.sender
}, source: %Alembic.Source{
parameter: nil, pointer: Alembic.json_pointer
} }
Returns
{:ok, map | struct}
- A validated type from underAlembic
.{:error, %Alembic.Document{errors: [Alembic.Error.t]}}
- one or more errors was encountered when converting from decoded JSON to a validated JSON API document. The format errors are in the errors section of theAlembic.Document.t
, which can be sent back to sender of the original JSON API document so they can correct the errors.NOTE: the
meta
from the passed inAlembic.Error
should not be in the returnedAlembic.Document.t
’serrors
, as thatmeta
information is an implementation detail and for internal use in recursive calls toAlembic.FromJson.from_json/2
only.