Visualize.Data.Schema (Visualize v0.5.1)

Copy Markdown View Source

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 and bounds as the feed knows. It says what the numbers are, whoever draws them, and never how they are drawn: a description has no colour key, since colour is the theme's to choose from the meaning (#533). 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 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], {:colour_key, :colors}}]}

Summary

Types

What one field means (spec/08 §10.1).

A fault of new/1: the path from the schema to the value, and why.

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.

t()

Functions

The twelve 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 spelt 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.

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

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

description()

@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]
}

What one field means (spec/08 §10.1).

error()

@type error() :: {[name() | atom() | non_neg_integer() | term()], term()}

A fault of new/1: the path from the schema to the value, and why.

incompatibility()

@type incompatibility() ::
  :missing | {:type, atom(), atom()} | {:unit, term(), term()}

Why a field of one schema cannot read another's rows (spec/08 §10.1, D-159).

name()

@type name() :: atom() | String.t()

A field name, as the feed's rows spell it.

t()

@type t() :: %Visualize.Data.Schema{
  fields: %{required(name()) => description()},
  version: String.t()
}

Functions

attributes()

@spec attributes() :: [atom(), ...]

The twelve description keys, :type first (spec/08 §10.1).

compatibility(schema, other)

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

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"}}]}

fetch(schema, name)

@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

fetch(schema, name, key)

@spec fetch(t(), name(), atom()) :: {:ok, term()} | :error

One attribute of a field's description; :error when the field or the attribute is absent.

format_error(arg)

@spec format_error(error()) :: String.t()

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"

new(fields)

@spec new(term()) :: {:ok, t()} | {:error, [error(), ...]}

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"}}]}

new!(fields)

@spec new!(term()) :: t()

The schema of a map of field descriptions, or ArgumentError naming every fault.

version(schema)

@spec version(t()) :: String.t()

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.