Alembic v1.0.0 Alembic.Document
JSON API refers to the top-level JSON structure as a document.
Summary
Types
t ::
%Alembic.Document{data: nil, errors: [Alembic.Error.t], included: nil, links: Alembic.Links.t | nil, meta: Alembic.Meta.t | nil} |
%Alembic.Document{data: nil, errors: nil, included: nil, links: Alembic.Links.t | nil, meta: Alembic.Meta.t} |
%Alembic.Document{data: [Alembic.Resource.t] | Alembic.Resource.t, errors: nil, included: [Alembic.Resource.t] | nil, links: Alembic.Links.t | nil, meta: Alembic.Meta.t | nil}
A JSON API Document.
Data
When there are no errors, data
are returned in the document and errors
are not returned in the document.
Field | Included/Excluded/Optional |
---|---|
data | Included |
errors | Excluded |
included | Optional |
links | Optional |
meta | Optional |
Errors
When an error occurs, errors
are returned in the document and data
are not returned in the document.
Field | Included/Excluded/Optional |
---|---|
data | Excluded |
errors | Included |
included | Excluded |
links | Optional |
meta | Optional |
Meta
JSON API allows a meta
only document, in which case data
and errors
are not returned in the document.
Field | Included/Excluded/Optional |
---|---|
data | Excluded |
errors | Excluded |
included | Excluded |
links | Optional |
meta | Included |
Functions
Converts a JSON object into a JSON API Document, t
.
Data documents
Single
An empty single resource can represented as "data": null
in encoded JSON, so it comes into from_json
as
data: nil
iex> Alembic.Document.from_json(
...> %{ "data" => nil },
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:ok,
%Alembic.Document{
data: nil
}
}
A present single can be a resource
iex> Alembic.Document.from_json(
...> %{
...> "data" => %{
...> "attributes" => %{
...> "text" => "First Post!"
...> },
...> "id" => "1",
...> "type" => "post"
...> }
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:ok,
%Alembic.Document{
data: %Alembic.Resource{
attributes: %{
"text" => "First Post!"
},
id: "1",
type: "post"
}
}
}
… or a present single can be just a resource identifier
iex> Alembic.Document.from_json(
...> %{
...> "data" => %{
...> "id" => "1",
...> "type" => "post"
...> }
...> },
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:ok,
%Alembic.Document{
data: %Alembic.ResourceIdentifier{
id: "1",
type: "post"
}
}
}
You notice that whether a JSON object in "data"
is treated as a Alembic.Resource.t
or
Alembic.ResourceIdentifier.t
hinges on whether "attributes"
or "relationships"
is present as those
members are only allowed for resources.
Collection
Resources
A collection can be a list of resources
iex> Alembic.Document.from_json( …> %{ …> “data” => [ ...> %{ ...> "attributes" => %{ ...> "text" => "First Post!" ...> }, ...> "id" => "1", ...> "relationships" => %{ ...> "comments" => %{ ...> "data" => [ ...> %{ ...> "id" => "1", ...> "type" => "comment" ...> } ...> ] ...> } ...> }, ...> "type" => "post" ...> } ...> ] …> }, …> %Alembic.Error{ …> meta: %{ …> “action” => :create, …> “sender” => :client …> }, …> source: %Alembic.Source{ …> pointer: “” …> } …> } …> ) {
:ok,
%Alembic.Document{
data: [
%Alembic.Resource{
attributes: %{
"text" => "First Post!"
},
id: "1",
relationships: %{
"comments" => %Alembic.Relationship{
data: [
%Alembic.ResourceIdentifier{
id: "1",
type: "comment"
}
]
}
},
type: "post"
}
]
}
}
With "relationships"
, a resources collection can optionally have "included"
for the attributes for the resource
identifiers. If "included"
is not given or the "id"
and "type"
for a resource identifier, then the resource
identifier should just be considered a foreign key reference that needs to be fetched with another API query.
iex> Alembic.Document.from_json( …> %{ …> “data” => [ ...> %{ ...> "attributes" => %{ ...> "text" => "First Post!" ...> }, ...> "id" => "1", ...> "relationships" => %{ ...> "comments" => %{ ...> "data" => [ ...> %{ ...> "id" => "1", ...> "type" => "comment" ...> } ...> ] ...> } ...> }, ...> "type" => "post" ...> } ...> ], …> “included” => [ ...> %{ ...> "attributes" => %{ ...> "text" => "First Comment!" ...> }, ...> "id" => "1", ...> "type" => "comment" ...> } ...> ] …> }, …> %Alembic.Error{ …> meta: %{ …> “action” => :create, …> “sender” => :client …> }, …> source: %Alembic.Source{ …> pointer: “” …> } …> } …> ) {
:ok,
%Alembic.Document{
data: [
%Alembic.Resource{
attributes: %{
"text" => "First Post!"
},
id: "1",
relationships: %{
"comments" => %Alembic.Relationship{
data: [
%Alembic.ResourceIdentifier{
id: "1",
type: "comment"
}
]
}
},
type: "post"
}
],
included: [
%Alembic.Resource{
attributes: %{
"text" => "First Comment!"
},
id: "1",
type: "comment"
}
]
}
}
Resource Identifiers
Or a list of resource identifiers
iex> Alembic.Document.from_json(
...> %{
...> "data" => [
...> %{
...> "id" => "1",
...> "type" => "post"
...> }
...> ]
...> },
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:ok,
%Alembic.Document{
data: [
%Alembic.ResourceIdentifier{
id: "1",
type: "post"
}
]
}
}
Empty
An empty collection can be signified with []
. Because there is no type information, it’s not possible to tell
whether it is an empty list of Alembic.Resource.t
or Alembic.ResourceIdentifier.t
.
iex> Alembic.Document.from_json(
...> %{
...> "data" => []
...> },
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:ok,
%Alembic.Document{
data: []
}
}
Errors documents
Errors from the sender must have an "errors"
key set to a list of errors.
iex> Alembic.Document.from_json(
...> %{
...> "errors" => [
...> %{
...> "code" => "1",
...> "detail" => "There was an error in data",
...> "id" => "2",
...> "links" => %{
...> "about" => %{
...> "href" => "/errors/2",
...> "meta" => %{
...> "extra" => "about meta"
...> }
...> }
...> },
...> "meta" => %{
...> "extra" => "error meta"
...> },
...> "source" => %{
...> "pointer" => "/data"
...> },
...> "status" => "422",
...> "title" => "There was an error"
...> }
...> ]
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:ok,
%Alembic.Document{
errors: [
%Alembic.Error{
code: "1",
detail: "There was an error in data",
id: "2",
links: %{
"about" => %Alembic.Link{
href: "/errors/2",
meta: %{
"extra" => "about meta"
}
}
},
meta: %{
"extra" => "error meta"
},
source: %Alembic.Source{
pointer: "/data"
},
status: "422",
title: "There was an error"
}
]
}
}
Error objects MUST be returned as an array keyed by "errors"
in the top level of a JSON API document.
iex> Alembic.Document.from_json(
...> %{"errors" => "Lots of errors"},
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/errors` type is not array",
meta: %{
"type" => "array"
},
source: %Alembic.Source{
pointer: "/errors"
},
status: "422",
title: "Type is wrong"
}
]
}
}
Meta documents
Returned documents can contain just "meta"
with neither "data"
nor "errors"
iex> Alembic.Document.from_json(
...> %{
...> "meta" => %{
...> "copyright" => "2016"
...> }
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:ok,
%Alembic.Document{
meta: %{
"copyright" => "2016"
}
}
}
Incomplete documents
If neither "errors"
, "data"
, nor "meta"
is present, then the document is invalid and a `Alembic.
iex> Alembic.Document.from_json(
…> %{},
…> %Alembic.Error{
…> meta: %{
…> “action” => :create,
…> “sender” => :server
…> },
…> source: %Alembic.Source{
…> pointer: “”
…> }
…> }
…> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: “At least one of the following children of `` must be present:\n” <>
"data\n" <>
"errors\n" <>
"meta",
meta: %{
"children" => [
"data",
"errors",
"meta"
]
},
source: %Alembic.Source{
pointer: ""
},
status: "422",
title: "Not enough children"
}
],
}
}
Specs
merge(%Alembic.Document{data: term, errors: [Alembic.Error.t], included: term, links: term, meta: term}, %Alembic.Document{data: term, errors: [Alembic.Error.t], included: term, links: term, meta: term}) :: %Alembic.Document{data: term, errors: [Alembic.Error.t], included: term, links: term, meta: term}
Merges the errors from two documents together.
The errors from the second document are prepended to the errors of the first document so that the errors as a whole
can be reversed with reverse/1
Since merge/2
adds the second errors
to the beginning of a first
document’s errors
list, the final merged
errors
needs to be reversed to maintain the original order.
iex> merged = %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.Document.reverse(merged)
%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"
}
]
}