A feed's schema: what each of its fields means (spec/08 §10.1, D-136).
A schema maps a field — named as the feed's rows name it, an atom or a string — to a
description: its type, and as much of unit, quantity, label, order,
status, polarity, target, continuity, window, metric_kind, bounds and
identity as the feed knows, and for a :category field the values it takes, every
one (#643, D-194). 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 (#533). A declared
value may be an entry, %{value: v, name: "Display", color: c}, whose color is a
host's hint a design's own range outranks (D-224); values/2, value_names/2 and
value_colors/2 read the bare values, the names and the colors. A :time
field holds instants; a feed whose clock is a number describes it :number and
window: true, the field its windows are over (D-154). A numeric field may say how it
was measured, metric_kind: :gauge | :counter | :rate, which refines what its
quantity's additivity allows, and where its values lie, bounds: [min, max], a display
hint over its quantity's (D-155). A unit is a UCUM code Visualize.Unit reads and a
quantity one of Visualize.Quantity.all/0, and when both are given the unit measures
the quantity (D-157). A field whose value names the thing a row is about says
identity: true, several such fields together naming it (D-167); identity/2 gives a
row's identity under the schema.
A schema is identified by its version, a content hash (spec/08 §10.2): the same
schema has the same version on every node and under every release, and any change of
content is a new version. A Visualize.Data.Delivery names the version its rows were
produced under; Visualize.Data.Schema.Cache holds schemas by {feed, version}.
Schemas of different versions may still describe the same data in different words,
and compatibility/2 says when one's rows read as the other's (D-159).
Examples
iex> {:ok, schema} =
...> Visualize.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> Visualize.Data.Schema.fetch(schema, "latency", :unit)
{:ok, "s"}
iex> String.length(Visualize.Data.Schema.version(schema))
64
iex> Visualize.Data.Schema.new(%{state: %{type: :category, colors: %{"ok" => "#0a0"}}})
{:error, [{[:state, :colors], {:color_key, :colors}}]}
Summary
Types
What one field means (spec/08 §10.1).
Why a field of one schema cannot read another's rows (spec/08 §10.1, 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/08 §10.1, D-224): its display
name, and a color hint — a literal string or a typed color (spec/08 §1.9).
Functions
The fourteen description keys, :type first (spec/08 §10.1).
Whether other's rows read as schema's (spec/08 §10.1, D-159): for each field of
schema, other has a field of the same name compared as strings, 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 spelled as schema spells it, ordered by its name as a string. Labels and facts
are not compared, and other's extra fields are not faults — except identity (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 field's description, by either spelling of its name (spec/08 §6.2).
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 fields the schema declares identity: true, as it spells them, ordered by name as a
string; [] when it declares none (spec/08 §10.1, D-167).
A row's identity under the schema (spec/08 §10.1, D-167): the bare value of the one
identity field, a map from field to value, keyed as the schema spells them, 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/08 §10.1).
The schema of a map of field descriptions, or ArgumentError naming every fault.
A map from each declared value with a color to its color, as written — a literal string
or a typed color — (spec/08 §10.1, D-224); %{} when none has one, or the field declares
no values. It is a :color_map as it stands; a design's own range outranks it.
A map from each declared value with a name to its name (spec/08 §10.1, D-224); %{}
when none has one, or the field declares no values.
A field's declared values, bare, in their declared order (spec/08 §10.1, 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/08 §10.2), computed once by new/1.
Types
@type description() :: %{ :type => :time | :number | :category | :text, optional(:unit) => String.t(), optional(:quantity) => atom(), optional(:label) => String.t(), optional(:order) => [String.t() | atom() | number(), ...], optional(:status) => %{required(term()) => atom()}, 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/08 §10.1).
@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/08 §10.1, D-159).
A field name, as the feed's rows spell it.
@type t() :: %Visualize.Data.Schema{ fields: %{required(name()) => description()}, identity: [name()] | 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) => Visualize.Color.value() }
A declared value with what a person reads of it (spec/08 §10.1, D-224): its display
name, and a color hint — a literal string or a typed color (spec/08 §1.9).
Functions
@spec attributes() :: [atom(), ...]
The fourteen description keys, :type first (spec/08 §10.1).
@spec compatibility(t(), t()) :: :ok | {:error, [{name(), incompatibility()}, ...]}
Whether other's rows read as schema's (spec/08 §10.1, D-159): for each field of
schema, other has a field of the same name compared as strings, 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 spelled as schema spells it, ordered by its name as a string. Labels and facts
are not compared, and other's extra fields are not faults — except identity (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 = Visualize.Data.Schema.new!(%{t: %{type: :time}, value: %{type: :number, unit: "ms"}})
iex> history = Visualize.Data.Schema.new!(%{"t" => %{type: :time}, "value" => %{type: :number, unit: "s", label: "Load"}})
iex> Visualize.Data.Schema.compatibility(live, history)
:ok
iex> bytes = Visualize.Data.Schema.new!(%{"value" => %{type: :number, unit: "By"}})
iex> Visualize.Data.Schema.compatibility(live, bytes)
{:error, [{:t, :missing}, {:value, {:unit, "ms", "By"}}]}
iex> hosts = Visualize.Data.Schema.new!(%{host: %{type: :category, identity: true}})
iex> ifaces = Visualize.Data.Schema.new!(%{host: %{type: :category, identity: true},
...> iface: %{type: :category, identity: true}})
iex> Visualize.Data.Schema.compatibility(hosts, ifaces)
{:error, [{:iface, {:identity, false, true}}]}
@spec fetch(t(), name()) :: {:ok, description()} | :error
A field's description, by either spelling of its name (spec/08 §6.2).
iex> schema = Visualize.Data.Schema.new!(%{"v" => %{type: :number}})
iex> Visualize.Data.Schema.fetch(schema, :v)
{:ok, %{type: :number}}
iex> Visualize.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> Visualize.Data.Schema.format_error({[:latency, :unit], {:invalid_for, :category}})
"latency.unit: is not a key a :category field takes"
The fields the schema declares identity: true, as it spells them, ordered by name as a
string; [] when it declares none (spec/08 §10.1, D-167).
iex> schema = Visualize.Data.Schema.new!(%{
...> iface: %{type: :category, identity: true},
...> host: %{type: :category, identity: true},
...> bytes: %{type: :number}
...> })
iex> Visualize.Data.Schema.identity(schema)
[:host, :iface]
A row's identity under the schema (spec/08 §10.1, D-167): the bare value of the one
identity field, a map from field to value, keyed as the schema spells them, 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 = Visualize.Data.Schema.new!(%{id: %{type: :category, identity: true}, x: %{type: :number}})
iex> Visualize.Data.Schema.identity(one, %{"id" => "frog-1", "x" => 3})
"frog-1"
iex> two = Visualize.Data.Schema.new!(%{host: %{type: :category, identity: true},
...> iface: %{type: :category, identity: true}})
iex> Visualize.Data.Schema.identity(two, %{host: "mx1", iface: "eth0", bytes: 9})
%{host: "mx1", iface: "eth0"}
iex> Visualize.Data.Schema.identity(two, %{host: "mx1"})
nil
The schema of a map of field descriptions, or every fault of it by path (spec/08 §10.1).
iex> Visualize.Data.Schema.new(%{v: %{type: :category, unit: "s"}})
{:error, [{[:v, :unit], {:invalid_for, :category}}]}
iex> Visualize.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.
@spec value_colors(t(), name()) :: %{required(value()) => Visualize.Color.value()}
A map from each declared value with a color to its color, as written — a literal string
or a typed color — (spec/08 §10.1, D-224); %{} when none has one, or the field declares
no values. It is a :color_map as it stands; a design's own range outranks it.
iex> schema = Visualize.Data.Schema.new!(%{state: %{type: :category, values: [
...> %{value: "ok", color: "#2e7d32"}, %{value: "warn", color: {:ramp, :status, 0.5}}]}})
iex> Visualize.Data.Schema.value_colors(schema, :state)
%{"ok" => "#2e7d32", "warn" => {:ramp, :status, 0.5}}
A map from each declared value with a name to its name (spec/08 §10.1, D-224); %{}
when none has one, or the field declares no values.
iex> schema = Visualize.Data.Schema.new!(%{state: %{type: :category, values: [
...> %{value: "ok", name: "OK"}, %{value: "down", color: "#c62828"}, "unknown"]}})
iex> Visualize.Data.Schema.value_names(schema, "state")
%{"ok" => "OK"}
A field's declared values, bare, in their declared order (spec/08 §10.1, 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 = Visualize.Data.Schema.new!(%{state: %{type: :category, values: [
...> %{value: "ok", name: "OK", color: "#2e7d32"}, "unknown"]}})
iex> Visualize.Data.Schema.values(schema, :state)
["ok", "unknown"]
iex> Visualize.Data.Schema.values(schema, :other)
nil
The schema's version: the SHA-256 of its canonical encoding as 64 lowercase hex digits
(spec/08 §10.2), computed once by new/1.