The JSON adapter (spec/01 §5, §6): a document's map form and JSON form, and reading a JSON document through the gates to its value.
The value is the document as Elixir holds it: atom keys where the type has a key,
string names elsewhere, atoms, times. It is the one true form: the type judges it
(Macaw.Layers.Document.check/2), and it is what is built, stored and compared. Its
map form is JSON-ready: string keys, and a tag (Macaw.Layers.Tags) for every value
JSON can't express. Its JSON form is that map encoded. Both are representations, made
here at the edge.
The codec, to_map/2 and from_map/2, converts between the value and the map form,
exact inverses with no gates and no validation. For a value with a form:
from_map(to_map(value)) == {:ok, value}
to_map(from_map(map)) == {:ok, map} # for a map to_map/2 wroteThe adapter reads an encoding that comes from elsewhere: gates/3 takes a JSON
document, or its decoded map form, through the gates (the bytes, the scan, the version,
the migration, the recount) to the map form at the type's current version, and read/3
then reads the value from it and judges the value by its type. to_json/2 writes the
value's JSON form, stamped with the type's version. A value with no form fails here, at
its path, never where it was built.
A document type implements this behavior: its tag table, the keys it reads as atoms, and
the atoms a $atom may spell, each a closed table (Macaw.Layers.Vocabulary), so no
atom is ever made from a document. A type with values of its own (a struct, a tuple)
writes and reads them through encode_value/1 and decode_value/2, under its own
live tags.
The kernel's terms:
| Value | Map form |
|---|---|
nil, a boolean, a number, a string | as it is |
| another atom | {"$atom": "spelling"}, read back through atoms/0 |
a DateTime in Etc/UTC | {"$time": "ISO 8601"} |
| a list | a list |
| a map | an object: an atom key by its spelling, a string key as it is, escaped when it starts with $ |
A function, a PID, a reference, a port, a tuple or a struct the type does not write has
no form: it is refused at its path as {:type, :term}.
A name map is a map whose keys are names, never the type's keys (a fragment type's
:union and :deep keys hold one, spec/02 §9). A type says where its name maps are by
name_map?/1; at one, a string key is written and read as the name it is, escaped
when it starts with $, whatever its spelling, and an atom key is {:type, :name},
since names are strings. Every other map is a node, keyed as the table says. A type
without name_map?/1 has no name maps. Below a tag, a map's place is its place in
the payload: {:tag, tag} and then the path within the payload, which a type writing a
payload part that sits at the type's positions (a use site's local fragment) gives
encode_term/3 as at.
Summary
Types
Words a reader takes beyond the type's own tables, merged over them: keys, read as
atom keys, and atoms, the atoms a $atom may spell. Lists of atoms, given by code
(an adopter's registered words), never by a document.
Callbacks
The atoms a $atom may spell in this type's documents.
The value of one of the type's own live tags, its payload already read, or :default
when the tag or payload is not one it reads.
A value of the type's own, in its map form, or :default for the kernel's rules.
The keys the type reads as atoms, by spelling.
Whether the map at path is a name map: its keys are names, never the type's keys.
path is from the document's root, as the value holds it: atom keys, string names and
list indexes. Below a tag it is {:tag, tag} followed by the path within the tag's
payload, as it is read. A fragment type answers with Macaw.Layers.Fragment.name_map?/2.
The type's tag table: a module that uses Macaw.Layers.Tags.
Functions
One term's map form, for a type writing its own tag's payload (spec/01 §6): {:ok, form},
or every value and key without one, by path from the term.
The value a map form holds: its tags held to the type's table, its keys read against the
type's keys, its $atoms against the type's atoms, each with the vocabulary: given
(see read/3); {:ok, value} or every fault.
A document from elsewhere through the gates, in order (spec/01 §5): {:ok, map}, its
map form at the type's current version, stamp included, or every fault of the first gate
that refuses it.
The value a document from elsewhere holds: through gates/3, then read from its map form
(from_map/2, the stamp dropped), then judged by its type
(Macaw.Layers.Document.check/2); {:ok, value}, or the faults of the first step that
refuses it.
The value's JSON form, stamped "$version" with the type's current version where the
type has a version line (Macaw.Layers.Version.current/0): {:ok, json}, or every
value and key without a form, by path. The value is not judged here: that is
Macaw.Layers.Document.check/2. vocabulary: as to_map/3 takes it.
The value's map form: {:ok, map}, or every value and key without one, by path.
vocabulary: the words the type's keys are taken with (see read/3).
Types
@type option() :: {:limits, keyword() | map()} | {:vocabulary, vocabulary()}
gates/3's and read/3's options; the codec takes vocabulary:.
Words a reader takes beyond the type's own tables, merged over them: keys, read as
atom keys, and atoms, the atoms a $atom may spell. Lists of atoms, given by code
(an adopter's registered words), never by a document.
Callbacks
@callback atoms() :: Macaw.Layers.Vocabulary.table()
The atoms a $atom may spell in this type's documents.
The value of one of the type's own live tags, its payload already read, or :default
when the tag or payload is not one it reads.
A value of the type's own, in its map form, or :default for the kernel's rules.
@callback keys() :: Macaw.Layers.Vocabulary.table()
The keys the type reads as atoms, by spelling.
Whether the map at path is a name map: its keys are names, never the type's keys.
path is from the document's root, as the value holds it: atom keys, string names and
list indexes. Below a tag it is {:tag, tag} followed by the path within the tag's
payload, as it is read. A fragment type answers with Macaw.Layers.Fragment.name_map?/2.
@callback tags() :: module()
The type's tag table: a module that uses Macaw.Layers.Tags.
Functions
@spec encode_term(term(), module(), [term()] | nil) :: {:ok, term()} | {:error, [Macaw.Layers.Fault.t(), ...]}
One term's map form, for a type writing its own tag's payload (spec/01 §6): {:ok, form},
or every value and key without one, by path from the term.
at is the term's place, as name_map?/1 is asked it below a tag: {:tag, tag} and
the path of the term within the payload ([{:tag, "$use"}, "local"]), so its maps are
written as the reader reads them there. nil, the default, is no place: every map in
the term is a node.
@spec from_map(map(), module(), [option()]) :: {:ok, map()} | {:error, [Macaw.Layers.Fault.t(), ...]}
The value a map form holds: its tags held to the type's table, its keys read against the
type's keys, its $atoms against the type's atoms, each with the vocabulary: given
(see read/3); {:ok, value} or every fault.
@spec gates(binary() | map(), module(), [option()]) :: {:ok, map()} | {:error, [Macaw.Layers.Fault.t(), ...]}
A document from elsewhere through the gates, in order (spec/01 §5): {:ok, map}, its
map form at the type's current version, stamp included, or every fault of the first gate
that refuses it.
- bytes: a JSON document longer than the
byteslimit is refused; - decode with the
depthanditemslimits, byMacaw.Layers.Scan, which stops at a limit before any value is built; only then is it decoded, with Elixir'sJSON, and its root must be an object. A map form given directly skips the bytes and is walked fordepthanditemsinstead (Macaw.Layers.Limits.structure/2); - version: a newer stamp is refused by name before anything else is read;
- migrate, step by step;
- recount
depthanditems, since a migration can grow a document.
type implements Macaw.Layers.Version. limits: tightens the limits, or raises one by
name, as Macaw.Layers.Limits.new/1 takes them. Nothing in the document is evaluated,
and its value is not judged: that is read/3.
@spec read(binary() | map(), module(), [option()]) :: {:ok, term()} | {:error, [Macaw.Layers.Fault.t(), ...]}
The value a document from elsewhere holds: through gates/3, then read from its map form
(from_map/2, the stamp dropped), then judged by its type
(Macaw.Layers.Document.check/2); {:ok, value}, or the faults of the first step that
refuses it.
input is a JSON document, a binary, or its decoded map form. type implements this
behavior, Macaw.Layers.Version and Macaw.Layers.Document. limits: as gates/3
takes them.
The vocabulary is the reader's. The words a document may spell as atoms are the
type's tables (keys/0, atoms/0) and, with vocabulary: %{keys: [...], atoms: [...]}, the words given there, merged over them: an adopter's registered words, given
by code. No atom is made from the document beyond those tables. A key outside them is
read as its string, and a $atom outside them is {:enum, atoms} at its path.
@spec to_json(map(), module(), [option()]) :: {:ok, binary()} | {:error, [Macaw.Layers.Fault.t(), ...]}
The value's JSON form, stamped "$version" with the type's current version where the
type has a version line (Macaw.Layers.Version.current/0): {:ok, json}, or every
value and key without a form, by path. The value is not judged here: that is
Macaw.Layers.Document.check/2. vocabulary: as to_map/3 takes it.
@spec to_map(map(), module(), [option()]) :: {:ok, map()} | {:error, [Macaw.Layers.Fault.t(), ...]}
The value's map form: {:ok, map}, or every value and key without one, by path.
vocabulary: the words the type's keys are taken with (see read/3).