Alembic v2.1.0 Alembic.Document
JSON API refers to the top-level JSON structure as a document.
Summary
Functions
Tries to determine the common Alembic.Error.t
status
between all errors
in the document
Converts a JSON object into a JSON API Document, t
Lookup table of included
resources, so that Alembic.ResourceIdentifier.t
can be
converted to full Alembic.Resource.t
Merges the errors from two documents together
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
Converts a document into one or more Ecto.Schema.t
structs
Call to_ecto_schema/2
instead to automatically generate attributes_by_id_by_type
Extract paged pagination information
Transforms a t
into the nested params format used by
Ecto.Changeset.cast/4
Transforms a t
into the nested params format used by
Ecto.Changeset.cast/4
using the given
resources_by_id_by_type
Transforms a t
into the nested params format used by
Ecto.Changeset.cast/4
using the given
resources_by_id_by_type
and converted_by_id_by_type
Types
t ::
%Alembic.Document{data: nil, errors: [Alembic.Error.t], included: nil, jsonapi: term, links: Alembic.Links.t | nil, meta: Alembic.Meta.t | nil} |
%Alembic.Document{data: nil, errors: nil, included: nil, jsonapi: term, 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, jsonapi: term, 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
Tries to determine the common Alembic.Error.t
status
between all errors
in the document
.
If it is not an errors document, nil
is returned.
iex> Alembic.Document.error_status_consensus(
...> %Alembic.Document{data: []}
...> )
nil
Single error
If there is one error, its status is returned. This could be nil as no field is required in a JSONAPI error.
iex> Alembic.Document.error_status_consensus(
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{
...> status: "404"
...> }
...> ]
...> }
...> )
"404"
iex> Alembic.Document.error_status_consensus(
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{}
...> ]
...> }
...> )
nil
Multiple errors
If there are multiple errors with the same status, then that is the consensus
iex> Alembic.Document.error_status_consensus(
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{
...> status: "404"
...> },
...> %Alembic.Error{
...> status: "404"
...> }
...> ]
...> }
...> )
"404"
If there are multiple errors, but some errors don’t have statuses, they are ignored
iex> Alembic.Document.error_status_consensus(
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{},
...> %Alembic.Error{
...> status: "404"
...> }
...> ]
...> }
...> )
"404"
If there are multiple errors, but they disagree within the same 100s block, then that is the consensus
iex> Alembic.Document.error_status_consensus(
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{
...> status: "404"
...> },
...> %Alembic.Error{
...> status: "422"
...> }
...> ]
...> }
...> )
"400"
If there are multiple errors, but they disagree without the same 100s block, then the greater 100s block is the consensus
iex> Alembic.Document.error_status_consensus(
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{
...> status: "422"
...> },
...> %Alembic.Error{
...> status: "500"
...> }
...> ]
...> }
...> )
"500"
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 an :error
tuple will be
returned. In the tuple with :error
is an Alembic.Document.t
that is a proper JSONAPI errors document that can
be set back to the sender.
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
included_resource_by_id_by_type(t) :: Alembic.ToParams.resource_by_id_by_type
Lookup table of included
resources, so that Alembic.ResourceIdentifier.t
can be
converted to full Alembic.Resource.t
.
No included resources
With no included resources, an empty map is returned
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => %{
...> "type" => "post",
...> "id" => "1"
...> }
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
...> Alembic.Document.included_resource_by_id_by_type(document)
%{}
Included resources
With included resources, a nest map is built with the outer layer keyed by the Alembic.Resource.type
,
then the next layer keyed by the Alembic.Resource.id
with the values being the full
Alembic.Resource.t
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => [
...> %{
...> "type" => "articles",
...> "id" => "1",
...> "relationships" => %{
...> "author" => %{
...> "data" => %{
...> "type" => "people",
...> "id" => "9"
...> }
...> },
...> "comments" => %{
...> "data" => [
...> %{
...> "type" => "comments",
...> "id" => "5"
...> },
...> %{
...> "type" => "comments",
...> "id" => "12"
...> }
...> ]
...> }
...> }
...> }
...> ],
...> "included" => [
...> %{
...> "type" => "people",
...> "id" => "9",
...> "attributes" => %{
...> "first-name" => "Dan",
...> "last-name" => "Gebhardt",
...> "twitter" => "dgeb"
...> }
...> },
...> %{
...> "type" => "comments",
...> "id" => "5",
...> "attributes" => %{
...> "body" => "First!"
...> },
...> "relationships" => %{
...> "author" => %{
...> "data" => %{
...> "type" => "people",
...> "id" => "2"
...> }
...> }
...> }
...> },
...> %{
...> "type" => "comments",
...> "id" => "12",
...> "attributes" => %{
...> "body" => "I like XML better"
...> },
...> "relationships" => %{
...> "author" => %{
...> "data" => %{
...> "type" => "people",
...> "id" => "9"
...> }
...> }
...> }
...> }
...> ]
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
...> Alembic.Document.included_resource_by_id_by_type(document)
%{
"comments" => %{
"12" => %Alembic.Resource{
attributes: %{
"body" => "I like XML better"
},
id: "12",
relationships: %{
"author" => %Alembic.Relationship{
data: %Alembic.ResourceIdentifier{
id: "9",
type: "people"
}
}
},
type: "comments"
},
"5" => %Alembic.Resource{
attributes: %{
"body" => "First!"
},
id: "5",
relationships: %{
"author" => %Alembic.Relationship{
data: %Alembic.ResourceIdentifier{
id: "2",
type: "people"
}
}
},
type: "comments"
}
},
"people" => %{
"9" => %Alembic.Resource{
attributes: %{
"first-name" => "Dan",
"last-name" => "Gebhardt",
"twitter" => "dgeb"
},
id: "9",
type: "people"
}
}
}
Specs
merge(%Alembic.Document{data: term, errors: [Alembic.Error.t], included: term, jsonapi: term, links: term, meta: term}, %Alembic.Document{data: term, errors: [Alembic.Error.t], included: term, jsonapi: term, links: term, meta: term}) :: %Alembic.Document{data: term, errors: [Alembic.Error.t], included: term, jsonapi: 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"
}
]
}
Specs
Converts a document into one or more Ecto.Schema.t
structs.
Parameters
document
- supplies resource attributes and associations (throughincluded
)ecto_schema_module_by_type
- Maps theAlembic.Resource.t
type
String.t
to theEcto.Schema
module to use withEcto.Changeset.cast/4
.
Returns
{:ok, nil}
- an empty single resource{:ok, struct}
- a single resource{:ok, []}
- an empty resource collection{:ok, [struct]}
- a non-empty resource collection{:error, t}
- thedocument
have errors and so won’t be converted to struct(s).
Examples
No resource
No resource has to return nil
for Ecto schema because the type is not present.
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => nil
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_ecto_schema(document, %{})
{:ok, nil}
Single resource
A single resource is converted to a single Ecto schema struct corresponding to the type.
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => %{
...> "attributes" => %{
...> "name" => "Thing 1"
...> },
...> "id" => "1",
...> "type" => "thing"
...> }
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_ecto_schema(
...> document,
...> %{
...> "thing" => Alembic.TestThing
...> }
...> )
{
:ok,
%Alembic.TestThing{
__meta__: %Ecto.Schema.Metadata{
source: {nil, "things"},
state: :built
},
id: 1,
name: "Thing 1"
}
}
Relationships
Relationships are merged into the struct for the resource
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => %{
...> "attributes" => %{
...> "name" => "Thing 1"
...> },
...> "relationships" => %{
...> "shirt" => %{
...> "data" => %{
...> "attributes" => %{
...> "size" => "L"
...> },
...> "type" => "shirt"
...> }
...> }
...> },
...> "type" => "thing"
...> }
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_ecto_schema(
...> document,
...> %{
...> "shirt" => Alembic.TestShirt,
...> "thing" => Alembic.TestThing
...> }
...> )
{
:ok,
%Alembic.TestThing{
__meta__: %Ecto.Schema.Metadata{
source: {nil, "things"},
state: :built
},
name: "Thing 1",
shirt: %Alembic.TestShirt{
__meta__: %Ecto.Schema.Metadata{
source: {nil, "shirts"},
state: :built
},
size: "L"
}
}
}
Multiple resources
Multiple resources are converted to a struct list where each element is a struct that combines the id and attributes
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => [
...> %{
...> "type" => "post",
...> "id" => "1",
...> "attributes" => %{
...> "text" => "Welcome"
...> }
...> },
...> %{
...> "type" => "post",
...> "id" => "2",
...> "attributes" => %{
...> "text" => "It's been awhile"
...> }
...> }
...> ]
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_ecto_schema(
...> document,
...> %{
...> "post" => Alembic.TestPost
...> }
...> )
{
:ok,
[
%Alembic.TestPost{
__meta__: %Ecto.Schema.Metadata{
source: {nil, "posts"},
state: :built
},
author: %Ecto.Association.NotLoaded{
__cardinality__: :one,
__field__: :author,
__owner__: Alembic.TestPost
},
author_id: nil,
id: 1,
text: "Welcome"
},
%Alembic.TestPost{
__meta__: %Ecto.Schema.Metadata{
source: {nil, "posts"},
state: :built
},
author: %Ecto.Association.NotLoaded{
__cardinality__: :one,
__field__: :author,
__owner__: Alembic.TestPost
},
author_id: nil,
id: 2,
text: "It's been awhile"
}
]
}
Relationships
Relationships are merged into the structs for the corresponding resource
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => [
...> %{
...> "type" => "post",
...> "id" => "1",
...> "attributes" => %{
...> "text" => "Welcome"
...> },
...> "relationships" => %{
...> "comments" => %{
...> "data" => [
...> %{
...> "type" => "comment",
...> "id" => "3"
...> }
...> ]
...> }
...> }
...> },
...> %{
...> "type" => "post",
...> "id" => "2",
...> "attributes" => %{
...> "text" => "It's been awhile"
...> },
...> "relationships" => %{
...> "comments" => %{
...> "data" => []
...> }
...> }
...> }
...> ],
...> "included" => [
...> %{
...> "type" => "comment",
...> "id" => "3",
...> "attributes" => %{
...> "text" => "First!"
...> }
...> }
...> ]
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_ecto_schema(
...> document,
...> %{
...> "comment" => Alembic.TestComment,
...> "post" => Alembic.TestPost
...> }
...> )
{
:ok,
[
%Alembic.TestPost{
__meta__: %Ecto.Schema.Metadata{
source: {nil, "posts"},
state: :built
},
author: %Ecto.Association.NotLoaded{
__cardinality__: :one,
__field__: :author,
__owner__: Alembic.TestPost
},
author_id: nil,
comments: [
%Alembic.TestComment{
__meta__: %Ecto.Schema.Metadata{
source: {nil, "comments"},
state: :built
},
id: 3,
post: %Ecto.Association.NotLoaded{
__cardinality__: :one,
__field__: :post,
__owner__: Alembic.TestComment
},
post_id: nil,
text: "First!"
}
],
id: 1,
text: "Welcome"
},
%Alembic.TestPost{
__meta__: %Ecto.Schema.Metadata{
source: {nil, "posts"},
state: :built
},
author: %Ecto.Association.NotLoaded{
__cardinality__: :one,
__field__: :author,
__owner__: Alembic.TestPost
},
author_id: nil,
comments: [],
id: 2,
text: "It's been awhile"
}
]
}
Specs
to_ecto_schema(%Alembic.Document{data: [Alembic.Resource.t] | Alembic.Resource.t, errors: term, included: term, jsonapi: term, links: term, meta: term}, Alembic.ToParams.resource_by_id_by_type, Alembic.ToEctoSchema.ecto_schema_module_by_type) ::
[struct] |
struct
Call to_ecto_schema/2
instead to automatically generate attributes_by_id_by_type
Specs
to_pagination(t) :: Alembic.Pagination.t | nil
Extract paged pagination information.
To support pagination, a document at minimum must have a "record_count"
meta
entry.
iex> Alembic.Document.to_pagination(
...> %Alembic.Document{meta: %{ "record_count" => 10 }}
...> )
%Alembic.Pagination{ total_size: 10 }
Without it, the "record_count"
meta
entry, the pagination will be nil
iex> Alembic.Document.to_pagination(%Alembic.Document{})
nil
Single Page
When there is only one page, there will be a "first"
and "last"
link pointing to the same page, but no
“next” or “prev” links. The total_size
as given by the meta
"record_count"
will between 0
and
the page size because the last is not necessarily full.
iex> Alembic.Document.to_pagination(
...> %Alembic.Document{
...> links: %{
...> "first" => "https://example.com/api/v1/users?page%5Bnumber%5D=1&page%5Bsize%5D=10",
...> "last" => "https://example.com/api/v1/users?page%5Bnumber%5D=1&page%5Bsize%5D=10"
...> },
...> meta: %{ "record_count" => 5 }
...> }
...> )
%Alembic.Pagination{
first: %Alembic.Pagination.Page{
number: 1,
size: 10
},
last: %Alembic.Pagination.Page{
number: 1,
size: 10
},
total_size: 5
}
Multiple Pages
When there are multiple pages, every page will have a "first"
and "last"
link pointing to the respective,
different pages. The total_size
as given by the meta
"record_count"
will be between
(last.number - 1) * last.size
and last.number * last.size
because the last page is not necessarily full.
On the first page, the "next"
link will be set, but not the "prev"
link.
iex> Alembic.Document.to_pagination(
...> %Alembic.Document{
...> links: %{
...> "first" => "https://example.com/api/v1/users?page%5Bnumber%5D=1&page%5Bsize%5D=10",
...> "last" => "https://example.com/api/v1/users?page%5Bnumber%5D=3&page%5Bsize%5D=10",
...> "next" => "https://example.com/api/v1/users?page%5Bnumber%5D=2&page%5Bsize%5D=10"
...> },
...> meta: %{ "record_count" => 25 }
...> }
...> )
%Alembic.Pagination{
first: %Alembic.Pagination.Page{
number: 1,
size: 10
},
last: %Alembic.Pagination.Page{
number: 3,
size: 10
},
next: %Alembic.Pagination.Page{
number: 2,
size: 10
},
total_size: 25
}
On any middle page, both the "next"
and "prev"
links will be set.
iex> Alembic.Document.to_pagination(
...> %Alembic.Document{
...> links: %{
...> "first" => "https://example.com/api/v1/users?page%5Bnumber%5D=1&page%5Bsize%5D=10",
...> "last" => "https://example.com/api/v1/users?page%5Bnumber%5D=3&page%5Bsize%5D=10",
...> "next" => "https://example.com/api/v1/users?page%5Bnumber%5D=3&page%5Bsize%5D=10",
...> "prev" => "https://example.com/api/v1/users?page%5Bnumber%5D=1&page%5Bsize%5D=10"
...> },
...> meta: %{ "record_count" => 25 }
...> }
...> )
%Alembic.Pagination{
first: %Alembic.Pagination.Page{
number: 1,
size: 10
},
last: %Alembic.Pagination.Page{
number: 3,
size: 10
},
next: %Alembic.Pagination.Page{
number: 3,
size: 10
},
previous: %Alembic.Pagination.Page{
number: 1,
size: 10
},
total_size: 25
}
On the last page, the "prev"
link will be set, but not the "next"
link.
iex> Alembic.Document.to_pagination(
...> %Alembic.Document{
...> links: %{
...> "first" => "https://example.com/api/v1/users?page%5Bnumber%5D=1&page%5Bsize%5D=10",
...> "last" => "https://example.com/api/v1/users?page%5Bnumber%5D=3&page%5Bsize%5D=10",
...> "prev" => "https://example.com/api/v1/users?page%5Bnumber%5D=2&page%5Bsize%5D=10"
...> },
...> meta: %{ "record_count" => 25 }
...> }
...> )
%Alembic.Pagination{
first: %Alembic.Pagination.Page{
number: 1,
size: 10
},
last: %Alembic.Pagination.Page{
number: 3,
size: 10
},
previous: %Alembic.Pagination.Page{
number: 2,
size: 10
},
total_size: 25
}
Specs
to_params(t) :: [map] | map
Transforms a t
into the nested params format used by
Ecto.Changeset.cast/4
.
No resource
No resource is transformed to an empty map
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => nil
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_params(document)
%{}
Single resource
A single resource is converted to a params map that combines the id and attributes.
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => %{
...> "attributes" => %{
...> "name" => "Thing 1"
...> },
...> "id" => "1",
...> "type" => "thing"
...> }
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_params(document)
%{
"id" => "1",
"name" => "Thing 1"
}
Relationships
Relationships are merged into the params for the resource
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => %{
...> "attributes" => %{
...> "name" => "Thing 1"
...> },
...> "id" => "1",
...> "relationships" => %{
...> "shirt" => %{
...> "data" => %{
...> "attributes" => %{
...> "size" => "L"
...> },
...> "type" => "shirt"
...> }
...> }
...> },
...> "type" => "thing"
...> }
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_params(document)
%{
"id" => "1",
"name" => "Thing 1",
"shirt" => %{
"size" => "L"
}
}
Multiple resources
Multiple resources are converted to a params list where each element is a map that combines the id and attributes
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => [
...> %{
...> "type" => "post",
...> "id" => "1",
...> "attributes" => %{
...> "text" => "Welcome"
...> }
...> },
...> %{
...> "type" => "post",
...> "id" => "2",
...> "attributes" => %{
...> "text" => "It's been awhile"
...> }
...> }
...> ]
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_params(document)
[
%{
"id" => "1",
"text" => "Welcome"
},
%{
"id" => "2",
"text" => "It's been awhile"
}
]
Relationships
Relationships are merged into the params for the corresponding resource
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => [
...> %{
...> "type" => "post",
...> "id" => "1",
...> "attributes" => %{
...> "text" => "Welcome"
...> },
...> "relationships" => %{
...> "comments" => %{
...> "data" => [
...> %{
...> "type" => "comment",
...> "id" => "1"
...> }
...> ]
...> }
...> }
...> },
...> %{
...> "type" => "post",
...> "id" => "2",
...> "attributes" => %{
...> "text" => "It's been awhile"
...> },
...> "relationships" => %{
...> "comments" => %{
...> "data" => []
...> }
...> }
...> }
...> ],
...> "included" => [
...> %{
...> "type" => "comment",
...> "id" => "1",
...> "attributes" => %{
...> "text" => "First!"
...> }
...> }
...> ]
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_params(document)
[
%{
"id" => "1",
"text" => "Welcome",
"comments" => [
%{
"id" => "1",
"text" => "First!"
}
]
},
%{
"id" => "2",
"text" => "It's been awhile",
"comments" => []
}
]
Specs
to_params(%Alembic.Document{data: [Alembic.Resource.t] | Alembic.Resource.t | nil, errors: term, included: term, jsonapi: term, links: term, meta: term}, Alembic.ToParams.resource_by_id_by_type) :: Alembic.ToParams.params
Transforms a t
into the nested params format used by
Ecto.Changeset.cast/4
using the given
resources_by_id_by_type
.
Specs
to_params(%Alembic.Document{data: [Alembic.Resource.t] | Alembic.Resource.t | nil, errors: term, included: term, jsonapi: term, links: term, meta: term}, Alembic.ToParams.resource_by_id_by_type, Alembic.ToParams.converted_by_id_by_type) :: Alembic.ToParams.params
Transforms a t
into the nested params format used by
Ecto.Changeset.cast/4
using the given
resources_by_id_by_type
and converted_by_id_by_type
.
See InterpreterServer.Api.Document.to_params/1