Macaw.Layers.Document behaviour (macaw_layers v0.2.2)

Copy Markdown View Source

Reading a document through the gates, in order (spec/01 §5).

A document type implements this behavior and Macaw.Layers.Version: its version line and steps, and validate/1, which judges a document at the current version. Whatever the document came from (a file, a database, an object store), it is read the same way, by read/3, and nothing in it is evaluated:

  1. bytes: an encoded 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;
  3. version: a newer stamp is refused by name before anything else is read;
  4. migrate, step by step;
  5. validate, and count depth and items again, since a migration can grow a document.

Faults from one gate stop the gates after it. Within the last, the type's faults and the recount's are all reported, ordered by path.

Summary

Types

read/3's options.

Callbacks

:ok, or every fault of a document at the type's current version. Unknown keys are refused by name (see unknown_keys/3).

Functions

The canonical digest of a document's map form (Macaw.Layers.Form.to_map/2): 64 lowercase hex characters, the SHA-256 of its canonical encoding (spec/01 §7).

The document, read through every gate, at the type's current version: {:ok, map}, or every fault of the first gate that refuses it.

The faults of map's keys that are not among known, each at its own spelling under path: {path ++ [key], :unknown_key}, ordered by path.

Types

option()

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

read/3's options.

Callbacks

validate(document)

@callback validate(document :: map()) :: :ok | {:error, [Macaw.Layers.Fault.t(), ...]}

:ok, or every fault of a document at the type's current version. Unknown keys are refused by name (see unknown_keys/3).

Functions

digest(map)

@spec digest(map()) :: String.t()

The canonical digest of a document's map form (Macaw.Layers.Form.to_map/2): 64 lowercase hex characters, the SHA-256 of its canonical encoding (spec/01 §7).

The encoding is JSON with objects' keys sorted bytewise, no insignificant whitespace, each string as JSON encodes it, an integer in decimal, and a float in its shortest spelling that reads back as the same float. Reordering keys, at any depth, does not change the digest; a document and its JSON round trip have the same one. The encoding is part of the kernel's contract: changing it changes every digest, so it changes only with a new major version.

Raises ArgumentError for a term that is not a map form: an atom key, a tuple, any value JSON has no form for. Write the value's map form first.

iex> Macaw.Layers.Document.digest(%{"b" => 1, "a" => [true, nil]}) ==
...>   Macaw.Layers.Document.digest(%{"a" => [true, nil], "b" => 1})
true

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

@spec read(binary() | map(), module(), [option()]) ::
  {:ok, map()} | {:error, [Macaw.Layers.Fault.t(), ...]}

The document, read through every gate, at the type's current version: {:ok, map}, or every fault of the first gate that refuses it.

input is the JSON form, a binary, or the map form, which skips the bytes gate and is walked for depth and items instead of scanned. type implements this behavior and Macaw.Layers.Version. limits: tightens the limits, as Macaw.Layers.Limits.new/1 takes them.

unknown_keys(map, known, path \\ [])

@spec unknown_keys(map(), [String.t()], Macaw.Layers.Fault.path()) :: [
  Macaw.Layers.Fault.t()
]

The faults of map's keys that are not among known, each at its own spelling under path: {path ++ [key], :unknown_key}, ordered by path.

known is the type's keys as the document spells them, strings. A key the type knows stays the type's to read; one it does not is refused by name, never dropped.

iex> Macaw.Layers.Document.unknown_keys(%{"title" => "t", "ttle" => "x"}, ["title"], [:page])
[{[:page, "ttle"], :unknown_key}]