View Source JSV.Vocabulary behaviour (jsv v0.3.0)

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.

Summary

Types

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

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

Functions

By using this module you will

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

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

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

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

An utility macro to ease declare vocabularies with atom keys.

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

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.

Types

acc()

@type acc() :: term()

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

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

collection()

@type collection() :: term()

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

data()

@type data() ::
  %{optional(binary()) => data()}
  | [data()]
  | binary()
  | boolean()
  | number()
  | nil

pair()

@type pair() :: {binary() | atom(), term()}

Callbacks

finalize_validators(acc)

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

format_error(atom, map, data)

(optional)
@callback format_error(atom(), %{optional(atom()) => term()}, data()) ::
  String.t()
  | {String.t(), [JSV.Validator.Error.t() | JSV.ErrorFormatter.annotation()]}
  | {atom(), String.t(),
     [JSV.Validator.Error.t() | JSV.ErrorFormatter.annotation()]}

handle_keyword(pair, acc, t, raw_schema)

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

init_validators(keyword)

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

priority()

@callback priority() :: pos_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(data, collection, context)

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

Functions

__using__(opts)

(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(atom_form)

(macro)

Defines a 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 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(atom_form)

(macro)

Defines a 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(key, n, acc, builder)

@spec take_integer(
  JSV.Validator.path_segment(),
  integer() | term(),
  list(),
  JSV.Builder.t()
) ::
  {:ok, list(), JSV.Builder.t()} | {:error, binary()}

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(atom_form, bind_value, bind_acc, bind_builder, bind_raw_schema, list)

(macro)

An utility macro to ease declare vocabularies with atom keys.

Defines the 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 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(key, n, acc, builder)

@spec take_number(
  JSV.Validator.path_segment(),
  number() | term(),
  list(),
  JSV.Builder.t()
) ::
  {:ok, list(), JSV.Builder.t()} | {:error, binary()}

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(key, sub_raw_schema, acc, builder)

@spec take_sub(
  JSV.Validator.path_segment(),
  JSV.Builder.raw_schema() | term(),
  list(),
  JSV.Builder.t()
) ::
  {:ok, list(), JSV.Builder.t()} | {:error, term()}

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.