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

Copy Markdown View Source

A document type's validity, judged on its value (spec/01 §5).

A document type implements this behavior: validate/1, which judges the type's value, the terms Elixir holds (atom keys where the type has a key, string names elsewhere, atoms, times, the type's own structs), never a representation of it. check/2 is how any value is judged, whether built in Elixir or read from an encoding:

{:ok, value} = Macaw.Layers.Document.check(value, MyApp.Page)

The kernel knows no encoding here. Reading one is an adapter's: Macaw.Layers.Form is the JSON adapter, whose read/3 takes a JSON document through the gates (the bytes, the scan, the version, the migration, the recount), reads its value from the map form, and then judges the value by check/2. The limits bound what reading an encoding costs, so they are the adapter's; a value is judged with none.

Summary

Callbacks

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

Functions

The value judged by its type's validate/1: {:ok, value}, or every fault, ordered by path. No encoding is involved: a value holding a term no adapter can write is judged as the type's own rules say, and fails only at that adapter.

The canonical digest of a value (spec/01 §7): 64 lowercase hex characters, the SHA-256 of the value's canonical encoding. The encoding is of the term itself, never of a representation of it, so a value has one digest however it is written or read back.

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

Callbacks

validate(value)

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

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

Functions

check(value, type)

@spec check(term(), module()) ::
  {:ok, term()} | {:error, [Macaw.Layers.Fault.t(), ...]}

The value judged by its type's validate/1: {:ok, value}, or every fault, ordered by path. No encoding is involved: a value holding a term no adapter can write is judged as the type's own rules say, and fails only at that adapter.

digest(value)

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

The canonical digest of a value (spec/01 §7): 64 lowercase hex characters, the SHA-256 of the value's canonical encoding. The encoding is of the term itself, never of a representation of it, so a value has one digest however it is written or read back.

The encoding is a version byte, then the term, each part tagged by its type and self-delimiting:

TermEncoding
an atom (nil, true and false too)a, its name's byte size, its name in UTF-8
a binaryb, its byte size, its bytes
a bitstring of another sizes, its bit size, its bits padded with zeros to a byte
an integeri, the byte size of its decimal spelling, that spelling
a floatf, its IEEE 754 64-bit form
a listl, its length, each element
a tuplet, its size, each element
a mapm, its size, each key's encoding then its value's, in the bytewise order of the keys' encodings

Sizes are 64-bit, and every number is big-endian. So key order never matters, and different terms have different encodings: 1 and 1.0, 0.0 and -0.0 (as under ===), :a and "a", {:a} and [:a] all differ. A struct is its map, __struct__ and all, so a DateTime carries its zone and precision, and a struct differs from its bare map.

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 data: a function, a PID, a reference, a port or an improper list.

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

iex> Macaw.Layers.Document.digest(%{v: 1}) == Macaw.Layers.Document.digest(%{v: 1.0})
false

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

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

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

known is the type's keys as its value holds them, atoms in a value. A key the type knows stays the type's to read; one it does not is refused by name, never dropped. A key a reader kept at its own spelling, a string, is named as it was written.

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