Macaw.Layers.Form behaviour (macaw_layers v0.3.0)

Copy Markdown View Source

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 wrote

The 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:

ValueMap form
nil, a boolean, a number, a stringas it is
another atom{"$atom": "spelling"}, read back through atoms/0
a DateTime in Etc/UTC{"$time": "ISO 8601"}
a lista list
a mapan 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

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

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

option()

@type option() :: {:limits, keyword() | map()} | {:vocabulary, vocabulary()}

gates/3's and read/3's options; the codec takes vocabulary:.

vocabulary()

@type vocabulary() :: %{optional(:keys) => [atom()], optional(:atoms) => [atom()]}

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

atoms()

@callback atoms() :: Macaw.Layers.Vocabulary.table()

The atoms a $atom may spell in this type's documents.

decode_value(tag, payload)

(optional)
@callback decode_value(tag :: String.t(), payload :: term()) :: {:ok, term()} | :default

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.

encode_value(term)

(optional)
@callback encode_value(term()) :: {:ok, term()} | :default

A value of the type's own, in its map form, or :default for the kernel's rules.

keys()

@callback keys() :: Macaw.Layers.Vocabulary.table()

The keys the type reads as atoms, by spelling.

name_map?(path)

(optional)
@callback name_map?(path :: [term()]) :: boolean()

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.

tags()

@callback tags() :: module()

The type's tag table: a module that uses Macaw.Layers.Tags.

Functions

encode_term(term, type, at \\ nil)

@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.

from_map(map, type, opts \\ [])

@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.

gates(input, type, opts \\ [])

@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.

  1. bytes: a JSON document longer than the bytes limit is refused;
  2. decode with the depth and items limits, by Macaw.Layers.Scan, which stops at a limit before any value is built; only then is it decoded, with Elixir's JSON, and its root must be an object. A map form given directly skips the bytes and is walked for depth and items instead (Macaw.Layers.Limits.structure/2);
  3. version: a newer stamp is refused by name before anything else is read;
  4. migrate, step by step;
  5. recount depth and items, 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.

read(input, type, opts \\ [])

@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.

to_json(value, type, opts \\ [])

@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.

to_map(value, type, opts \\ [])

@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).