A source's schema: what each of its fields means (spec/01 §1.2).
A schema maps a field, named as the source's rows name it (an atom or a string), to a
description: its type (:time, :number, :category, :text, :color or
:point, a geographic position), and as much of unit, quantity, label, order,
status, polarity, target, continuity, window, metric_kind, bounds and
identity as the source knows, and for a :category field the values it takes,
every one. It says what the numbers are, whoever draws them, and never how they are
drawn: a description has no color key, since color is the theme's to choose from the
meaning. A declared value may be an entry, %{value: v, name: "Display", color: c},
whose color is a data color: a color term, a literal %Macaw.Layers.Color{} or a
theme-relative typed color, and never a string (color?/1, spec/01 §1.7).
A unit is a UCUM code Macaw.Data.Unit reads and a quantity one of
Macaw.Data.Quantity.all/0, and when both are given the unit measures the quantity
(spec/01 §1.6). A field whose value names the thing a row is about says
identity: true, several such fields together naming it (spec/01 §1.4). The window
field is window_field/1's (spec/01 §1.5).
Names, states and roles count by their spelling: new/1 writes each as a string, once,
so :latency and "latency" are one field, and the schema holds it as "latency".
Every function that takes a name takes either spelling.
A schema is identified by its version: the kernel's digest of its term, the normalized
fields (Macaw.Layers.Document.digest/1, spec/01 §1.10), the same on every node and
under every release, and new with any change of content. A Macaw.Data.Delivery names
the version its rows were produced under; Macaw.Data.Schema.Cache holds schemas by
{source, version}. compatibility/2 says when one schema's rows read as another's
(spec/01 §1.9). to_json/1 and from_json/2 are its public JSON form, an adapter at the
edge that the version never goes through (spec/01 §1.11).
Ported from visualize's Visualize.Data.Schema, with the :point type, data colors as
color terms, the window field, the kernel's digest and a public JSON form.
Examples
iex> {:ok, schema} =
...> Macaw.Data.Schema.new(%{
...> t: %{type: :time},
...> latency: %{type: :number, unit: "s", quantity: :duration, label: "Request time"},
...> state: %{type: :category, order: ["ok", "warn", "fail"],
...> status: %{"ok" => :good, "warn" => :warning, "fail" => :bad}}
...> })
iex> Macaw.Data.Schema.fetch(schema, "latency", :unit)
{:ok, "s"}
iex> String.length(Macaw.Data.Schema.version(schema))
64
iex> Macaw.Data.Schema.new(%{state: %{type: :category, colors: %{"ok" => "#0a0"}}})
{:error, [{[:state, :colors], {:color_key, :colors}}]}
Summary
Types
What one field means (spec/01 §1.2).
Why a field of one schema cannot read another's rows (spec/01 §1.2, visualize D-159).
A field name, as the feed's rows spell it.
A category field's value: a string, an atom or a number.
A declared value with what a person reads of it (spec/01 §1.2, visualize D-224): its display
name, and a color hint, a data color: a literal or a typed color (spec/01 §1.7).
Functions
The fourteen description keys, :type first (spec/01 §1.2).
Whether a value is a data color (spec/01 §1.7): a color term, checked as a term. It is a
literal %Macaw.Layers.Color{} (Macaw.Layers.Color.literal?/1), the color itself as the
datum, or a theme-relative typed color ({:color, "good"}, {:alpha, c, 0.5}, …) whose
literal operands are colors (Macaw.Layers.Color.value?/1) and whose names are in a
theme's name grammar (Macaw.Layers.Color.name?/1). A string is never a color: CSS text is
read at the input edge with Macaw.Layers.Color.parse!/1, or written ~RGB"#2e7d32".
Whether other's rows read as schema's (spec/01 §1.2, visualize D-159): for each field of
schema, other has a field of the same name, of the same type, and for a :number
field a unit that converts into schema's — two absent units agree, one absent facing a
present one does not. {:error, faults} lists every fault, field the field's string,
ordered by it. Labels and facts are not compared, and other's extra fields are not
faults — except identity (visualize D-167):
a field one schema declares identity: true and the other does not is {:identity, a, b}, a and b whether each declares it, even a field only other has, since
other's rows read as schema's would merge the things other tells apart.
A value entry's three keys, :value first (spec/01 §1.3): what a codec reads an entry's
keys as, never making an atom of another. Public, so a host that encodes schemas itself
(metresis) reads it rather than a hidden function.
A field's description, by either spelling of its name (spec/01 §1.1).
One attribute of a field's description; :error when the field or the attribute is absent.
One fault of new/1 as a line a person reads.
The schema a JSON document holds: read through the kernel's limits and scan first
(Macaw.Layers.Limits, Macaw.Layers.Scan), so a document over a limit is refused
before it is decoded, then decoded and read by from_map/1. limits: tightens them.
The schema a map form holds (spec/01 §1.11): new/1 of what it reads. Each word is read
through its own closed vocabulary, every name, state and role is a string, and an entry's
color is read by the colors' own reader (Macaw.Layers.Color.Form), so no atom is made.
A key no description or value entry has stays its string, for new/1 to refuse by name,
and a color the reader does not read stays as written, for new/1 to refuse as no color.
The fields the schema declares identity: true, as strings, in order; [] when it
declares none (spec/01 §1.2, visualize D-167).
A row's identity under the schema (spec/01 §1.2, visualize D-167): the bare value of the one
identity field, a map from field to value, keyed by the fields' strings, for several,
and nil when the schema declares none or the row has no value for one of them. Each
field is read by either spelling of its name.
The schema of a map of field descriptions, or every fault of it by path (spec/01 §1.2). A fault names the field as given; the schema holds every name, state and role as its string (spec/01 §1.10).
The schema of a map of field descriptions, or ArgumentError naming every fault.
The schema's JSON form: to_map/1, encoded.
The schema's map form (spec/01 §1.11): the JSON-ready list of [name, description]
pairs, sorted by name, every name, state and role written by its spelling, every word
of the vocabulary (type, quantity, polarity, metric_kind) as its string, and a
value entry's color in the colors' JSON form (Macaw.Layers.Color.Form: a literal as
{"$rgb": "#2e7d32"}, a typed color by its tags, {"$color": "good"}).
A map from each declared value with a color to its color, the color term the schema
holds, a literal or a typed color (spec/01 §1.3, §1.7); %{} when none has one, or the
field declares no values. A drawing library may use it as a color map, under the theme's
data-color guard; a design's own colors outrank it.
A map from each declared value with a name to its name (spec/01 §1.2, visualize D-224); %{}
when none has one, or the field declares no values.
A field's declared values, bare, in their declared order (spec/01 §1.2, visualize D-224): each
value entry's value, and each bare value as it is. nil when the schema has no such
field or the field declares no values — an open set, where [] would say a closed one
holding nothing. Read by either spelling of the field's name.
The schema's version: the SHA-256 of its canonical encoding as 64 lowercase hex digits
(spec/01 §1.10), computed once by new/1.
The schema's window field (spec/01 §1.5): the field a request's window applies to, by
which rows are ordered, trimmed and routed. It is the field described window: true,
else the schema's one :time field when it does not say window: false; nil when
there is neither, and a schema with several :time fields and none marked has none.
The window that applies to the schema's window field (spec/01 §1.5): :range over a
:time or a :number field, from one instant or value to another; :box over a
:point field, a region of positions; nil when the schema has no window field.
Types
@type description() :: %{ :type => :time | :number | :category | :text | :color | :point, optional(:unit) => String.t(), optional(:quantity) => atom(), optional(:label) => String.t(), optional(:order) => [String.t() | atom() | number(), ...], optional(:status) => %{required(value()) => atom() | String.t()}, optional(:polarity) => :higher_is_better | :lower_is_better | :neutral, optional(:target) => number(), optional(:continuity) => number(), optional(:window) => boolean(), optional(:metric_kind) => :gauge | :counter | :rate, optional(:bounds) => [number() | nil], optional(:identity) => boolean(), optional(:values) => [value() | value_entry(), ...] }
What one field means (spec/01 §1.2).
@type error() :: {[name() | atom() | non_neg_integer() | term()], term()}
A fault of new/1: the path from the schema to the value, and why.
@type incompatibility() :: :missing | {:type, atom(), atom()} | {:unit, term(), term()} | {:identity, boolean(), boolean()}
Why a field of one schema cannot read another's rows (spec/01 §1.2, visualize D-159).
A field name, as the feed's rows spell it.
@type t() :: %Macaw.Data.Schema{ fields: %{required(String.t()) => description()}, identity: [String.t()] | nil, version: String.t() }
A category field's value: a string, an atom or a number.
@type value_entry() :: %{ :value => value(), optional(:name) => String.t(), optional(:color) => Macaw.Layers.Color.value() }
A declared value with what a person reads of it (spec/01 §1.2, visualize D-224): its display
name, and a color hint, a data color: a literal or a typed color (spec/01 §1.7).
Functions
@spec attributes() :: [atom(), ...]
The fourteen description keys, :type first (spec/01 §1.2).
Whether a value is a data color (spec/01 §1.7): a color term, checked as a term. It is a
literal %Macaw.Layers.Color{} (Macaw.Layers.Color.literal?/1), the color itself as the
datum, or a theme-relative typed color ({:color, "good"}, {:alpha, c, 0.5}, …) whose
literal operands are colors (Macaw.Layers.Color.value?/1) and whose names are in a
theme's name grammar (Macaw.Layers.Color.name?/1). A string is never a color: CSS text is
read at the input edge with Macaw.Layers.Color.parse!/1, or written ~RGB"#2e7d32".
iex> import Macaw.Layers.Color, only: [sigil_RGB: 2]
iex> Macaw.Data.Schema.color?(~RGB"#2e7d32")
true
iex> Macaw.Data.Schema.color?({:alpha, {:color, :bad}, 0.74})
true
iex> Macaw.Data.Schema.color?("#2e7d32")
false
iex> Macaw.Data.Schema.color?(:good)
false
@spec compatibility(t(), t()) :: :ok | {:error, [{String.t(), incompatibility()}, ...]}
Whether other's rows read as schema's (spec/01 §1.2, visualize D-159): for each field of
schema, other has a field of the same name, of the same type, and for a :number
field a unit that converts into schema's — two absent units agree, one absent facing a
present one does not. {:error, faults} lists every fault, field the field's string,
ordered by it. Labels and facts are not compared, and other's extra fields are not
faults — except identity (visualize D-167):
a field one schema declares identity: true and the other does not is {:identity, a, b}, a and b whether each declares it, even a field only other has, since
other's rows read as schema's would merge the things other tells apart.
iex> live = Macaw.Data.Schema.new!(%{t: %{type: :time}, value: %{type: :number, unit: "ms"}})
iex> history = Macaw.Data.Schema.new!(%{"t" => %{type: :time}, "value" => %{type: :number, unit: "s", label: "Load"}})
iex> Macaw.Data.Schema.compatibility(live, history)
:ok
iex> bytes = Macaw.Data.Schema.new!(%{"value" => %{type: :number, unit: "By"}})
iex> Macaw.Data.Schema.compatibility(live, bytes)
{:error, [{"t", :missing}, {"value", {:unit, "ms", "By"}}]}
iex> hosts = Macaw.Data.Schema.new!(%{host: %{type: :category, identity: true}})
iex> ifaces = Macaw.Data.Schema.new!(%{host: %{type: :category, identity: true},
...> iface: %{type: :category, identity: true}})
iex> Macaw.Data.Schema.compatibility(hosts, ifaces)
{:error, [{"iface", {:identity, false, true}}]}
@spec entry_keys() :: [atom(), ...]
A value entry's three keys, :value first (spec/01 §1.3): what a codec reads an entry's
keys as, never making an atom of another. Public, so a host that encodes schemas itself
(metresis) reads it rather than a hidden function.
iex> Macaw.Data.Schema.entry_keys()
[:value, :name, :color]
@spec fetch(t(), name()) :: {:ok, description()} | :error
A field's description, by either spelling of its name (spec/01 §1.1).
iex> schema = Macaw.Data.Schema.new!(%{"v" => %{type: :number}})
iex> Macaw.Data.Schema.fetch(schema, :v)
{:ok, %{type: :number}}
iex> Macaw.Data.Schema.fetch(schema, :w)
:error
One attribute of a field's description; :error when the field or the attribute is absent.
One fault of new/1 as a line a person reads.
iex> Macaw.Data.Schema.format_error({[:latency, :unit], {:invalid_for, :category}})
"latency.unit: is not a key a :category field takes"
The schema a JSON document holds: read through the kernel's limits and scan first
(Macaw.Layers.Limits, Macaw.Layers.Scan), so a document over a limit is refused
before it is decoded, then decoded and read by from_map/1. limits: tightens them.
The schema a map form holds (spec/01 §1.11): new/1 of what it reads. Each word is read
through its own closed vocabulary, every name, state and role is a string, and an entry's
color is read by the colors' own reader (Macaw.Layers.Color.Form), so no atom is made.
A key no description or value entry has stays its string, for new/1 to refuse by name,
and a color the reader does not read stays as written, for new/1 to refuse as no color.
The fields the schema declares identity: true, as strings, in order; [] when it
declares none (spec/01 §1.2, visualize D-167).
iex> schema = Macaw.Data.Schema.new!(%{
...> iface: %{type: :category, identity: true},
...> host: %{type: :category, identity: true},
...> bytes: %{type: :number}
...> })
iex> Macaw.Data.Schema.identity(schema)
["host", "iface"]
A row's identity under the schema (spec/01 §1.2, visualize D-167): the bare value of the one
identity field, a map from field to value, keyed by the fields' strings, for several,
and nil when the schema declares none or the row has no value for one of them. Each
field is read by either spelling of its name.
iex> one = Macaw.Data.Schema.new!(%{id: %{type: :category, identity: true}, x: %{type: :number}})
iex> Macaw.Data.Schema.identity(one, %{"id" => "frog-1", "x" => 3})
"frog-1"
iex> two = Macaw.Data.Schema.new!(%{host: %{type: :category, identity: true},
...> iface: %{type: :category, identity: true}})
iex> Macaw.Data.Schema.identity(two, %{host: "mx1", iface: "eth0", bytes: 9})
%{"host" => "mx1", "iface" => "eth0"}
iex> Macaw.Data.Schema.identity(two, %{host: "mx1"})
nil
The schema of a map of field descriptions, or every fault of it by path (spec/01 §1.2). A fault names the field as given; the schema holds every name, state and role as its string (spec/01 §1.10).
iex> Macaw.Data.Schema.new(%{v: %{type: :category, unit: "s"}})
{:error, [{[:v, :unit], {:invalid_for, :category}}]}
iex> Macaw.Data.Schema.new(%{v: %{type: :number, unit: "By", quantity: :duration}})
{:error, [{[:v, :quantity], {:quantity, :duration, "By"}}]}
The schema of a map of field descriptions, or ArgumentError naming every fault.
The schema's JSON form: to_map/1, encoded.
The schema's map form (spec/01 §1.11): the JSON-ready list of [name, description]
pairs, sorted by name, every name, state and role written by its spelling, every word
of the vocabulary (type, quantity, polarity, metric_kind) as its string, and a
value entry's color in the colors' JSON form (Macaw.Layers.Color.Form: a literal as
{"$rgb": "#2e7d32"}, a typed color by its tags, {"$color": "good"}).
@spec value_colors(t(), name()) :: %{required(value()) => Macaw.Layers.Color.value()}
A map from each declared value with a color to its color, the color term the schema
holds, a literal or a typed color (spec/01 §1.3, §1.7); %{} when none has one, or the
field declares no values. A drawing library may use it as a color map, under the theme's
data-color guard; a design's own colors outrank it.
iex> import Macaw.Layers.Color, only: [sigil_RGB: 2]
iex> schema = Macaw.Data.Schema.new!(%{state: %{type: :category, values: [
...> %{value: "ok", color: ~RGB"#2e7d32"}, %{value: "warn", color: {:color, :warning}}]}})
iex> Macaw.Data.Schema.value_colors(schema, :state)
%{"ok" => ~RGB"#2e7d32", "warn" => {:color, "warning"}}
A map from each declared value with a name to its name (spec/01 §1.2, visualize D-224); %{}
when none has one, or the field declares no values.
iex> schema = Macaw.Data.Schema.new!(%{state: %{type: :category, values: [
...> %{value: "ok", name: "OK"}, %{value: "down", color: {:color, "bad"}}, "unknown"]}})
iex> Macaw.Data.Schema.value_names(schema, "state")
%{"ok" => "OK"}
A field's declared values, bare, in their declared order (spec/01 §1.2, visualize D-224): each
value entry's value, and each bare value as it is. nil when the schema has no such
field or the field declares no values — an open set, where [] would say a closed one
holding nothing. Read by either spelling of the field's name.
What compares or lists a field's values reads them here, never through fetch/3, whose
:values is the list as declared, entries and all.
iex> schema = Macaw.Data.Schema.new!(%{state: %{type: :category, values: [
...> %{value: "ok", name: "OK", color: {:color, "good"}}, "unknown"]}})
iex> Macaw.Data.Schema.values(schema, :state)
["ok", "unknown"]
iex> Macaw.Data.Schema.values(schema, :other)
nil
The schema's version: the SHA-256 of its canonical encoding as 64 lowercase hex digits
(spec/01 §1.10), computed once by new/1.
The schema's window field (spec/01 §1.5): the field a request's window applies to, by
which rows are ordered, trimmed and routed. It is the field described window: true,
else the schema's one :time field when it does not say window: false; nil when
there is neither, and a schema with several :time fields and none marked has none.
iex> alias Macaw.Data.Schema
iex> Schema.window_field(Schema.new!(%{t: %{type: :time}, v: %{type: :number}}))
"t"
iex> Schema.window_field(Schema.new!(%{tick: %{type: :number, window: true}, v: %{type: :number}}))
"tick"
iex> Schema.window_field(Schema.new!(%{a: %{type: :time}, b: %{type: :time}}))
nil
@spec window_kind(t()) :: :range | :box | nil
The window that applies to the schema's window field (spec/01 §1.5): :range over a
:time or a :number field, from one instant or value to another; :box over a
:point field, a region of positions; nil when the schema has no window field.