= Vert.x Json Schema
:toc: left

Vert.x Json Schema provides an extendable and asynchronous implementation for https://json-schema.org/[Json Schema] specification.
You can use Json Schemas to validate every json structure. This module provides:

* Implementation of https://tools.ietf.org/html/draft-handrews-json-schema-validation-02[Json Schema draft2019-09]
* Implementation of https://tools.ietf.org/html/draft-handrews-json-schema-validation-01[Json Schema draft-7]
* Implementation of https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#schemaObject[OpenAPI 3 dialect].
* Non blocking `$ref` resolution and caching
* Lookup into the schema cache using {@link io.vertx.core.json.pointer.JsonPointer}
* Synchronous and asynchronous validation
* Ability to extend the validation tree adding new keywords and new format predicates
* DSL to build schemas programmatically

== Using Vert.x Json Schema

To use Vert.x Json Schema, add the following dependency to the _dependencies_ section of your build descriptor:

* Maven (in your `pom.xml`):

[source,xml,subs="+attributes"]
----
<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-json-schema</artifactId>
  <version>${maven.version}</version>
</dependency>
----

* Gradle (in your `build.gradle` file):

[source,groovy,subs="+attributes"]
----
dependencies {
  compile 'io.vertx:vertx-json-schema:${maven.version}'
}
----

== Concepts

=== Schema

Each parsed schema is represented by a {@link io.vertx.json.schema.Schema} instance. A schema is a tree of {@link io.vertx.json.schema.common.Validator} objects,
where each one contains the necessary logic to perform the validation. The performed validation is _fail-fast_: as soon as a validation error is encountered, the validation fails without going further

=== SchemaParser & SchemaRouter

The {@link io.vertx.json.schema.SchemaParser} is the component that parses the schemas from Json data structures to {@link io.vertx.json.schema.Schema} instances.
The {@link io.vertx.json.schema.SchemaRouter} is the component able to cache parsed schemas and resolve `$ref`.
Every time a new `$ref` is solved or a {@link io.vertx.json.schema.SchemaParser} parses a new schema, the new schema will be cached inside the corresponding {@link io.vertx.json.schema.SchemaRouter}.
The {@link io.vertx.json.schema.SchemaParser} can be extended to support custom keywords and formats.

The available {@link io.vertx.json.schema.SchemaParser} are:

* {@link io.vertx.json.schema.draft201909.Draft201909SchemaParser} for Json Schema Draft 2019-09
* {@link io.vertx.json.schema.draft7.Draft7SchemaParser} for Json Schema Draft 7
* {@link io.vertx.json.schema.openapi3.OpenAPI3SchemaParser} for OpenAPI 3 dialect

== Parse a schema

To parse a schema you first need a schema router and a schema parser matching your schema _dialect_.
For example to instantiate a _draft 2019-09_ schema parser:

[source,$lang]
----
{@link examples.JsonSchemaExamples#instantiate}
----

You can reuse `SchemaRouter` instance for different `SchemaParser` and you can parse different `Schema` with same `SchemaParser`.

Now you can parse the schema:

[source,$lang]
----
{@link examples.JsonSchemaExamples#parse}
----

When you parse a schema you must provide the **schema pointer**, a pointer that identifies the location of the schema.
If you don't have any schema pointer `SchemaParser` will generate one for you:

[source,$lang]
----
{@link examples.JsonSchemaExamples#parseNoId}
----

[IMPORTANT]
====
Remember that the schema pointer is required to reference this schema later using Json Schema `$ref`
and to resolve relative references. If you load a schema from filesystem and you use relative references, **provide the correct pointer** or the
`SchemaRouter` won't be able to resolve the local filesystem `$ref`.
====

== Validate

A schema could have two states:

* Synchronous: The validators tree can provide a synchronous validation. You can validate your json both using {@link io.vertx.json.schema.Schema#validateSync(Object)} and {@link io.vertx.json.schema.Schema#validateAsync(Object)}
* Asynchronous: One or more branches of the validator tree requires an asynchronous validation. You must use {@link io.vertx.json.schema.Schema#validateAsync(Object)} to validate your json. If you use {@link io.vertx.json.schema.Schema#validateSync(Object)} it will throw a {@link io.vertx.json.schema.NoSyncValidationException}

To validate a schema in an asynchronous state:

[source,$lang]
----
{@link examples.JsonSchemaExamples#validateAsync}
----

To validate a schema in a synchronous state:

[source,$lang]
----
{@link examples.JsonSchemaExamples#validateSync}
----

To check the schema state you can use method {@link io.vertx.json.schema.Schema#isSync()}.
The schema can mutate the state in time, e.g. if you have a schema that is asynchronous because of a `$ref`,
after the first validation the external schema is cached and the schema will switch to synchronous state.

[NOTE]
====
If you use {@link io.vertx.json.schema.Schema#validateAsync(Object)} while the schema is in a synchronous state,
the schema will validate synchronously wrapping the result in the returned `Future`, avoiding unnecessary async computations and memory usage
====

== Adding custom formats

You can add custom formats to use with validation keyword `format` before parsing the schemas:

[source,$lang]
----
{@link examples.JsonSchemaExamples#customFormat}
----

== Adding custom keywords

For every new keyword type you want to provide, you must implement {@link io.vertx.json.schema.common.ValidatorFactory}
and provide an instance to `SchemaParser` using {@link io.vertx.json.schema.SchemaParser#withValidatorFactory(ValidatorFactory)}.
When parsing happens, the `SchemaParser` calls {@link io.vertx.json.schema.common.ValidatorFactory#canConsumeSchema(JsonObject)} for each registered factory.
If the factory can consume the schema, then the method {@link io.vertx.json.schema.common.ValidatorFactory#createValidator(JsonObject, JsonPointer, SchemaParserInternal, MutableStateValidator)}
is called. This method returns an instance of {@link io.vertx.json.schema.common.Validator}, that represents the object that will perform the validation.
If something goes wrong during `Validator` creation, a {@link io.vertx.json.schema.SchemaException} should be thrown

You can add custom keywords of three types:

* Keywords that always validate the input synchronously
* Keywords that always validate the input asynchronously
* Keywords with mutable state

=== Synchronous keywords

Synchronous validators must implement the interface {@link io.vertx.json.schema.common.SyncValidator}.
In the example below I add a keyword that checks if the number of properties in a json object is a multiple of a provided number:

[source,$lang]
----
{@link examples.PropertiesMultipleOfValidator}
----

After we defined the keyword validator we can define the factory:

[source,$lang]
----
{@link examples.PropertiesMultipleOfValidatorFactory}
----

Now we can mount the new validator factory:

[source,$lang]
----
{@link examples.JsonSchemaExamples#mountSyncKeyword}
----

=== Asynchronous keywords

Synchronous validators must implement the interface {@link io.vertx.json.schema.common.AsyncValidator}.
In this example I add a keyword that retrieves from the Vert.x Event bus an enum of values:

[source,$lang]
----
{@link examples.AsyncEnumValidator}
----

After we defined the keyword validator we can define the factory:

[source,$lang]
----
{@link examples.AsyncEnumValidatorFactory}
----

Now we can mount the new validator factory:

[source,$lang]
----
{@link examples.JsonSchemaExamples#mountAsyncKeyword}
----

== Building your schemas from code

If you want to build schemas from code, you can use the included DSL. Only Draft-7 is supported for this feature.

To start, add static imports for {@link io.vertx.json.schema.draft7.dsl.Schemas} and {@link io.vertx.json.schema.draft7.dsl.Keywords}

=== Creating the schema

Inside {@link io.vertx.json.schema.draft7.dsl.Schemas} there are static methods to create the schema:

[source,$lang]
----
{@link examples.JsonSchemaDslExamples#createSchema}
----

=== Using the keywords

For every schema you can add keywords built with {@link io.vertx.json.schema.draft7.dsl.Keywords} methods,
depending on the type of the schema:

[source,$lang]
----
{@link examples.JsonSchemaDslExamples#keywords}
----

=== Defining the schema structure

Depending on the schema you create, you can define a structure.

To create an object schema with some properties schemas and additional properties schema:

[source,$lang]
----
{@link examples.JsonSchemaDslExamples#createObject}
----

To create an array schema:

[source,$lang]
----
{@link examples.JsonSchemaDslExamples#createArray}
----

To create a tuple schema:

[source,$lang]
----
{@link examples.JsonSchemaDslExamples#createTuple}
----

=== `$ref` and aliases

To add a `$ref` schema you can use the {@link io.vertx.json.schema.common.dsl.Schemas#ref(JsonPointer)} method.
To assign an `$id` keyword to a schema, use {@link io.vertx.json.schema.common.dsl.SchemaBuilder#id(JsonPointer)}

You can also refer to schemas defined with this dsl using aliases. You can use {@link io.vertx.json.schema.common.dsl.SchemaBuilder#alias(String)} to assign an alias to
a schema. Then you can refer to a schema with an alias using {@link io.vertx.json.schema.common.dsl.Schemas#refToAlias(String)}:

[source,$lang]
----
{@link examples.JsonSchemaDslExamples#alias}
----

=== Using the schema

After you defined the schema, you can call {@link io.vertx.json.schema.common.dsl.SchemaBuilder#build(SchemaParser)} to parse and use the schema:

[source,$lang]
----
{@link examples.JsonSchemaDslExamples#parse}
----
