# `StatifierBlocks.Schema`
[🔗](https://github.com/riddler/statifier_blocks/blob/v0.44.1/lib/statifier_blocks/schema.ex#L1)

The block document's JSON Schema (draft-07), shipped in this package at
`priv/schemas/block-document.schema.json`.

A host that stores or receives block documents outside Elixir can check
them against this file with a validator of its own choosing. This package
ships no validator and runs none: `path/0` names the installed file and
`json/0` hands over its content.

## What the root admits

The root describes every check `StatifierBlocks.Document.from_json/1`
makes that JSON Schema can express: the envelope, the generic block shape
every type satisfies, the `datamodel` and `accepts` keys, and the
canonical value grammar (integers, never floats, in `config` and
`metadata`). It never refuses a document `from_json/1` accepts. Where it
admits more, the section below says so.

The typed `config` and `slots` descriptions for the `core.*` types sit
under `definitions/core`, keyed by type name, and the root does **not**
apply them. Validation is registry-free, so a block of an unknown host type
validates, and so does a `core.*` block whose config its own definition
would refuse. A definition is meant to be applied together with
`#/definitions/block`, and it is never stricter than the type's
`validate_config/1`.

## What the schema cannot say

These stay the decoder's and the type's alone:

  * that block ids are unique across the whole document;
  * that datamodel entry ids are unique;
  * key order, whitespace, or anything else about canonical bytes;
  * a slot's arity (a definition names it in words, never enforces it);
  * whether a type's config is valid, which the type's
    `validate_config/1` owns.

## Where the root admits more than the package

The root admits two kinds of document that `from_json/1` refuses:

  * a document that repeats a block id or a datamodel entry id, since
    uniqueness is one of the things above the schema cannot say;
  * a whole number spelled with a fraction or an exponent, such as `1.0`
    or `1e2`, wherever an integer is read: `schema_version`, `revision`,
    `type_version`, and inside `config` or `metadata`. Draft-07 counts a
    number with a zero fractional part as an integer, while this package
    decodes such a spelling as a float and refuses it. The canonical
    encoder never writes one.

In the other direction there is no exception: the root refuses no
document the package accepts.

## A schema for a palette

`for_palette/1` answers a schema for one palette: the root above, with
every block judged by its own type's definition as well as by the
generic block shape. A `core.*` entry takes its definition from
`definitions/core`, verbatim. Any other entry - a host type, or a host
module mounted under a `core.` name - takes one generated from its
`config_schema/1` and `slots/1`: `config_schema(%{})`, as
`StatifierBlocks.Palette.new_block/2` asks it, then `slots/1` of the config
built from those defaults. Each declared field is
named with its label as `title` and its default as `default`, and typed
from its declared field type, with `null` admitted beside it; each
declared slot named as a list of blocks. A type whose declared field types
vary with its config is typed from the `config_schema(%{})` answer alone.
A name the palette does not carry takes the generic block shape alone, as
it decodes.

A generated definition never carries `required?` into `required`, never
closes `config` or `slots` to keys it does not name, and never names a
field whose `value_path` carries a list position. A `{:type_expr, opts}`
field is typed as a string or an array, with no constraint on its
members. An entry whose `config_schema/1` or `slots/1` raises, or
answers something other than a list of declarations, gets the generic
definition rather than a raise.

# `for_palette`

```elixir
@spec for_palette(StatifierBlocks.Palette.t()) :: map()
```

A draft-07 schema for `palette`'s block types, as a map with string keys
that `JSON.encode!/1` serializes.

It is the shipped root with every block, the root block and every slot's
children alike, also judged by a definition keyed on its `type`: a core
entry's from `definitions/core`, any other entry's generated from its
declared `config_schema/1` and `slots/1` (see "A schema for a palette"
above). A block whose type the palette does not carry is judged by the
generic block shape alone, and a palette with no entries answers a schema
that admits exactly what the shipped root admits. The answer carries no
`$id`: it is not the shipped file.

It consults no validator, network or clock, and two calls on equal
palettes answer equal maps. Every document the package admits validates
against it when every block whose type the palette carries answers `:ok`
from its `validate_config/1` and holds, in each field its
`config_schema/1` declares for `config_schema(%{})`, as
`Palette.new_block/2` asks it, a value the field's type admits.

    iex> schema = StatifierBlocks.Schema.for_palette(StatifierBlocks.Palette.new())
    iex> {schema["$schema"], Map.has_key?(schema, "$id")}
    {"http://json-schema.org/draft-07/schema#", false}

# `json`

```elixir
@spec json() :: String.t()
```

The schema's content, embedded when this package is compiled.

    iex> StatifierBlocks.Schema.json() |> JSON.decode!() |> Map.fetch!("$schema")
    "http://json-schema.org/draft-07/schema#"

# `path`

```elixir
@spec path() :: Path.t()
```

The installed schema file's absolute path.

    iex> File.read!(StatifierBlocks.Schema.path()) == StatifierBlocks.Schema.json()
    true

---

*Consult [api-reference.md](api-reference.md) for complete listing*
