Alembic v3.4.0 Alembic.FromJson behaviour View Source
JSON objects that have constrained members in the JSON API format are represented as
structs. In order to convert plain, decoded JSON in maps and lists, 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_results
A value that can collect singleton_values
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_results.
A value that can collect singleton_values.
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.
singleton_value() :: [struct] | map | nil | String.t | struct
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.:errorif themember_nameisn’t inparent_jsonat all. This is to help distinguish no member fromnilmember values, as JSON API allows fornullmembers 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.
put_key({:ok, value}, key) :: {:ok, {key, value}} when key: String.t | atom, value: singleton_value
put_key(error, key) :: error when key: atom
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.ts 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/1or 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:createor:updatefrom 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
metafrom the passed inAlembic.Errorshould not be in the returnedAlembic.Document.t’serrors, as thatmetainformation is an implementation detail and for internal use in recursive calls toAlembic.FromJson.from_json/2only.