# `JSV.Vocabulary`
[🔗](https://github.com/lud/jsv/blob/v0.18.3/lib/jsv/vocabulary.ex#L1)

Behaviour for vocabulary implementation.

A vocabulary module is used twice during the lifetime of a JSON schema:

* When building a schema, the vocabulary module is given key/value pairs such
  as `{"type", "integer"}` or `{"properties", map_of_schemas}` and must
  consume or ignore the given keyword, storing custom validation data in an
  accumulator for further use.
* When validating a schema, the module is called with the data to validate and
  the accumulated validation data to produce a validation result.

# `acc`

```elixir
@type acc() :: term()
```

Represents the accumulator initially returned by `c:init_validators/1` and
accepted and returned by `c:handle_keyword/4`.

This accumulator is then given to `c:finalize_validators/1` and the
`t:collection/0` type is used from there.

# `collection`

```elixir
@type collection() :: term()
```

Represents the final form of the collected keywords after the ultimate
transformation returned by `c:finalize_validators/1`.

# `data`

```elixir
@type data() ::
  %{optional(binary()) =&gt; data()}
  | [data()]
  | binary()
  | boolean()
  | number()
  | nil
```

# `pair`

```elixir
@type pair() :: {binary(), term()}
```

# `finalize_validators`

```elixir
@callback finalize_validators(acc()) :: :ignore | collection()
```

# `format_error`
*optional* 

```elixir
@callback format_error(atom(), %{optional(atom()) =&gt; term()}, data()) ::
  String.t()
  | %{
      :message =&gt; String.t(),
      optional(:kind) =&gt; atom(),
      optional(:annots) =&gt; [
        JSV.Validator.Error.t() | JSV.ErrorFormatter.error_unit()
      ]
    }
```

# `handle_keyword`

```elixir
@callback handle_keyword(pair(), acc(), JSV.Builder.t(), raw_schema :: term()) ::
  {acc(), JSV.Builder.t()} | :ignore
```

# `init_validators`

```elixir
@callback init_validators(keyword()) :: acc()
```

# `priority`

```elixir
@callback priority() :: non_neg_integer()
```

Returns the priority for applyting this module to the data.

Lower values (close to zero) will be applied first. You can think "order"
instead of "priority" but several modules can share the same priority value.

This can be useful to define vocabularies that depend on other vocabularies.
For instance, the `unevaluatedProperties` keyword needs "properties",
"patternProperties", "additionalProperties" and "allOf", "oneOf", "anyOf",
_etc._ to be ran before itself so it can lookup what has been evaluated.

Modules shipped in this library have priority of 100, 200, etc. up to 900 so
you can interleave your own vocabularies. Casting values to non-validable
terms (such as structs or dates) should be done by vocabularies with a
priority of 1000 and above.

# `validate`

```elixir
@callback validate(data(), collection(), vctx :: JSV.Validator.context()) ::
  JSV.Validator.result()
```

# `__using__`
*macro* 

By using this module you will:

* Declare that module as a behaviour
* Import all macros from the module
* Declare a `priority/0` function if the `:priority` option is provided.

# `consume_keyword`
*macro* 

Defines a `c:handle_keyword/4` callback that will return the current
accumulator without changes, but preventing other vocabulary modules with
lower priority (higher number) to be called with this keyword.

The keyword must be given in atom form.

# `ignore_any_keyword`
*macro* 

Defines a `c:handle_keyword/4` callback that will return `:ignore` for any
given value.

Generally used below `take_keyword/6`:

      take_keyword :items, items when is_map(items), acc, builder, raw_schema do
        # ...
      end

      ignore_any_keyword()

# `ignore_keyword`
*macro* 

Defines a `c:handle_keyword/4` callback that will return `:ignore` for the
given keyword. The keyword must be given in atom form.

Generally used below `take_keyword/6`:

      take_keyword :items, items when is_map(items), acc, builder, raw_schema do
        # ...
      end

      ignore_keyword(:additionalItems)
      ignore_keyword(:prefixItems)

# `take_integer`

```elixir
@spec take_integer(
  JSV.Builder.path_segment(),
  integer() | term(),
  list(),
  JSV.Builder.t()
) ::
  {list(), JSV.Builder.t()}
```

Adds the given integer to the list accumulator as a 2-tuple with the given
`key`.

Fails if the value is not an integer. Floats with zero-fractional (as `123.0`)
will be accepted and converted to integer, as the JSON Schema spec dictates.

# `take_keyword`
*macro* 

An utility macro to ease declare vocabularies with atom keys.

Defines the `c:handle_keyword/4` callback.

**Important**

- The keyword must be given in atom form.
- The original goal was to allow atom keys and values everywhere. Schemas are
  now converted to binary from before being built.
- It is still useful to use this macro to the signature of the
  `c:handle_keyword/4` callback can be changed easily without too much
  refactoring.
- Guards must be placed after the second argument:

      take_keyword :items, items when is_map(items), acc, builder, raw_schema do
        # ...
      end

# `take_number`

```elixir
@spec take_number(
  JSV.Builder.path_segment(),
  number() | term(),
  list(),
  JSV.Builder.t()
) ::
  {list(), JSV.Builder.t()}
```

Adds the given integer to the list accumulator as a 2-tuple with the given
`key`.

Fails if the value is not a number.

# `take_sub`

```elixir
@spec take_sub(
  JSV.Builder.path_segment(),
  JSV.normal_schema(),
  list(),
  JSV.Builder.t()
) ::
  {list(), JSV.Builder.t()}
```

Gives the sub raw schema to the builder and adds the build result in the list
accumulator as a 2-tuple with the given `key`.

# `take_sub`

```elixir
@spec take_sub(
  JSV.Builder.path_segment(),
  JSV.Builder.path_segment(),
  JSV.normal_schema(),
  list(),
  JSV.Builder.t()
) :: {list(), JSV.Builder.t()}
```

Same as `take_sub/4` but uses a custom `path_segment` to append to the
`schemaLocation` of the built subschema.

# `to_decimal`

```elixir
@spec to_decimal(integer() | binary()) :: Decimal.t()
```

Casts the given integer to a %Decimal{} struct using `Decimal.from_float/1`
for floats.

# `with_decimal`
*macro* 

---

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