The document-level reads: the three questions a consumer asks of a raw, decoded datamodel document rather than of an index it already holds.
StatifierDatamodel.Index is the reader; this module is the shorthand
over it, so that a consumer holding a document supplies one argument and
never re-derives an index of its own. Each function here admits its
argument through StatifierDatamodel.Index.index/1 and answers totally
for anything that admission declines - the same total-normalizer
discipline the index is written under, reaching one more caller.
declared_paths/1- ADR-0001 decision 7's projection, over a document: the declared-path set, ornilfor an input that is not a document.candidates_under/2- the completion query: the declared paths strictly under a prefix, in the document's own order.declared_values/1- theone_ofenumerations the document declares, per path, for a picker that offers values rather than paths.
What is deliberately not here: the shapes a consumer accepts beside a
document. A host that also accepts a bare list of paths, a MapSet, or
nil, and that folds a document into that same normalizer, owns the
fold - the set it ends up with is that consumer's input contract, and
this package answers only for the document half of it.
Advisory, never a gate
Decision 12, as everywhere in this package: these are facts about a document. A path a document does not declare is unknown, not wrong, and nothing here produces a finding, a severity or a verdict.
Summary
Functions
The declared paths strictly under prefix, in the document's own order -
decision 7's completion query, reached through
StatifierDatamodel.Index.under/2 rather than restated here.
ADR-0001 decision 7's projection, taken straight off a decoded document.
The value enumerations the document declares, per path: every indexed
entry whose one_of yields at least one drawable option.
Functions
The declared paths strictly under prefix, in the document's own order -
decision 7's completion query, reached through
StatifierDatamodel.Index.under/2 rather than restated here.
This is the narrowing query an expression control needs and a shipped
<datalist> does not: a datalist is handed the whole set once and the
browser filters it, while a control that re-renders per keystroke wants
only the branch the author is inside. Both read the same document,
through the same one implementation of the projection.
prefix itself is not among the results, matching under/2; an input
that is not a document has no order to query and returns [].
iex> alias StatifierDatamodel.Document
iex> Document.candidates_under(
...> %{"scopes" => [%{"scope" => "local", "entries" => [
...> %{"path" => "card", "type" => "object", "fields" => [
...> %{"path" => "card.brand"}, %{"path" => "card.last4"}]}]}]},
...> "card")
["card.brand", "card.last4"]
iex> StatifierDatamodel.Document.candidates_under(["card.brand"], "card")
[]
ADR-0001 decision 7's projection, taken straight off a decoded document.
A map carrying a list under "scopes" is a document and projects to its
declared-path set; an empty document projects to MapSet.new(), not to
nil. That distinction is decision 6's and it is the whole reason this
function has a nil: a host that supplied a document declaring nothing
has made a claim about its data universe, while nil is reserved for an
input that is not a document at all.
iex> StatifierDatamodel.Document.declared_paths(%{"scopes" => []})
MapSet.new([])
iex> StatifierDatamodel.Document.declared_paths(%{"version" => 1, "scopes" => [
...> %{"scope" => "local", "entries" => [
...> %{"name" => "step", "path" => "signup.step", "type" => "string",
...> "label" => "Step"}]}]})
MapSet.new(["signup.step"])
iex> StatifierDatamodel.Document.declared_paths(%{"scopes" => "not a list"})
nil
iex> StatifierDatamodel.Document.declared_paths(["signup.step"])
nil
The value enumerations the document declares, per path: every indexed
entry whose one_of yields at least one drawable option.
A declared value is offered when it can be drawn as an option - a string
as itself, a number or a boolean as its printed form. Anything else, a
list or an object or a null, is dropped, and a path whose whole
enumeration drops out is absent rather than present and empty. That
asymmetry is deliberate: [] is a host's spelling of offer nothing
here, and a derivation must not be able to write it by accident.
This does not promote the hint
Nothing here validates and nothing refuses. one_of is a completion hint
listing the values a host expects, and whether it is a hint or a claim is
an open question on the record; defaulting a picker from it is a use of
the hint rather than an answer to that question, and a value control fed
from it still admits anything the author types. That is the same
suggests-never-constrains posture the whole package takes.
A consumer that also lets a host supply its own per-path map merges that map over this one, in its own package: what replaces what is that consumer's decision, and this function answers only for what the document declares.
iex> alias StatifierDatamodel.Document
iex> Document.declared_values(%{"scopes" => [%{"scope" => "local", "entries" => [
...> %{"path" => "signup.step", "type" => "string",
...> "one_of" => ["details", "payment", "review"]},
...> %{"path" => "signup.email", "type" => "string"}]}]})
%{"signup.step" => ["details", "payment", "review"]}
iex> StatifierDatamodel.Document.declared_values(["signup.step"])
%{}