= 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:../../apidocs/io/vertx/core/json/pointer/JsonPointer.html[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>4.0.3</version>
</dependency>
----

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

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

== Concepts

=== Schema

Each parsed schema is represented by a `link:../../apidocs/io/vertx/json/schema/Schema.html[Schema]` instance. A schema is a tree of `link:../../apidocs/io/vertx/json/schema/common/Validator.html[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:../../apidocs/io/vertx/json/schema/SchemaParser.html[SchemaParser]` is the component that parses the schemas from Json data structures to `link:../../apidocs/io/vertx/json/schema/Schema.html[Schema]` instances.
The `link:../../apidocs/io/vertx/json/schema/SchemaRouter.html[SchemaRouter]` is the component able to cache parsed schemas and resolve `$ref`.
Every time a new `$ref` is solved or a `link:../../apidocs/io/vertx/json/schema/SchemaParser.html[SchemaParser]` parses a new schema, the new schema will be cached inside the corresponding `link:../../apidocs/io/vertx/json/schema/SchemaRouter.html[SchemaRouter]`.
The `link:../../apidocs/io/vertx/json/schema/SchemaParser.html[SchemaParser]` can be extended to support custom keywords and formats.

The available `link:../../apidocs/io/vertx/json/schema/SchemaParser.html[SchemaParser]` are:

* `link:../../apidocs/io/vertx/json/schema/draft201909/Draft201909SchemaParser.html[Draft201909SchemaParser]` for Json Schema Draft 2019-09
* `link:../../apidocs/io/vertx/json/schema/draft7/Draft7SchemaParser.html[Draft7SchemaParser]` for Json Schema Draft 7
* `link:../../apidocs/io/vertx/json/schema/openapi3/OpenAPI3SchemaParser.html[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,java]
----
SchemaRouter schemaRouter = SchemaRouter.create(vertx, new SchemaRouterOptions());
SchemaParser schemaParser = SchemaParser.createDraft201909SchemaParser(schemaRouter);
----

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

Now you can parse the schema:

[source,java]
----
Schema schema = parser.parse(object, schemaPointer);
----

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,java]
----
Schema schema = parser.parse(object);
schema.getScope(); // Get generated scope of schema (schema pointer)
----

[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:../../apidocs/io/vertx/json/schema/Schema.html#validateSync-java.lang.Object-[validateSync]` and `link:../../apidocs/io/vertx/json/schema/Schema.html#validateAsync-java.lang.Object-[validateAsync]`
* Asynchronous: One or more branches of the validator tree requires an asynchronous validation. You must use `link:../../apidocs/io/vertx/json/schema/Schema.html#validateAsync-java.lang.Object-[validateAsync]` to validate your json. If you use `link:../../apidocs/io/vertx/json/schema/Schema.html#validateSync-java.lang.Object-[validateSync]` it will throw a `link:../../apidocs/io/vertx/json/schema/NoSyncValidationException.html[NoSyncValidationException]`

To validate a schema in an asynchronous state:

[source,java]
----
schema.validateAsync(json).onComplete(ar -> {
  if (ar.succeeded()) {
    // Validation succeeded
  } else {
    // Validation failed
    ar.cause(); // Contains ValidationException
  }
});
----

To validate a schema in a synchronous state:

[source,java]
----
try {
  schema.validateSync(json);
  // Successful validation
} catch (ValidationException e) {
  // Failed validation
} catch (NoSyncValidationException e) {
  // Cannot validate synchronously. You must validate using validateAsync
}
----

To check the schema state you can use method `link:../../apidocs/io/vertx/json/schema/Schema.html#isSync--[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:../../apidocs/io/vertx/json/schema/Schema.html#validateAsync-java.lang.Object-[validateAsync]` 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,java]
----
parser.withStringFormatValidator("firstUppercase", str -> Character.isUpperCase(str.charAt(0)));

JsonObject mySchema = new JsonObject().put("format", "firstUppercase");
Schema schema = parser.parse(mySchema);
----

== Adding custom keywords

For every new keyword type you want to provide, you must implement `link:../../apidocs/io/vertx/json/schema/common/ValidatorFactory.html[ValidatorFactory]`
and provide an instance to `SchemaParser` using `link:../../apidocs/io/vertx/json/schema/SchemaParser.html#withValidatorFactory-io.vertx.json.schema.common.ValidatorFactory-[withValidatorFactory]`.
When parsing happens, the `SchemaParser` calls `link:../../apidocs/io/vertx/json/schema/common/ValidatorFactory.html#canConsumeSchema-io.vertx.core.json.JsonObject-[canConsumeSchema]` for each registered factory.
If the factory can consume the schema, then the method `link:../../apidocs/io/vertx/json/schema/common/ValidatorFactory.html#createValidator-io.vertx.core.json.JsonObject-io.vertx.core.json.pointer.JsonPointer-io.vertx.json.schema.common.SchemaParserInternal-io.vertx.json.schema.common.MutableStateValidator-[createValidator]`
is called. This method returns an instance of `link:../../apidocs/io/vertx/json/schema/common/Validator.html[Validator]`, that represents the object that will perform the validation.
If something goes wrong during `Validator` creation, a `link:../../apidocs/io/vertx/json/schema/SchemaException.html[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:../../apidocs/io/vertx/json/schema/common/SyncValidator.html[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,java]
----
`link:../../apidocs/examples/PropertiesMultipleOfValidator.html[PropertiesMultipleOfValidator]`
----

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

[source,java]
----
`link:../../apidocs/examples/PropertiesMultipleOfValidatorFactory.html[PropertiesMultipleOfValidatorFactory]`
----

Now we can mount the new validator factory:

[source,java]
----
parser.withValidatorFactory(new PropertiesMultipleOfValidatorFactory());

JsonObject mySchema = new JsonObject().put("propertiesMultipleOf", 2);
Schema schema = parser.parse(mySchema);
----

=== Asynchronous keywords

Synchronous validators must implement the interface `link:../../apidocs/io/vertx/json/schema/common/AsyncValidator.html[AsyncValidator]`.
In this example I add a keyword that retrieves from the Vert.x Event bus an enum of values:

[source,java]
----
`link:../../apidocs/examples/AsyncEnumValidator.html[AsyncEnumValidator]`
----

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

[source,java]
----
`link:../../apidocs/examples/AsyncEnumValidatorFactory.html[AsyncEnumValidatorFactory]`
----

Now we can mount the new validator factory:

[source,java]
----
parser.withValidatorFactory(new AsyncEnumValidatorFactory(vertx));

JsonObject mySchema = new JsonObject().put("asyncEnum", "enums.myapplication");
Schema schema = parser.parse(mySchema);
----

== 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:../../apidocs/io/vertx/json/schema/draft7/dsl/Schemas.html[Schemas]` and `link:../../apidocs/io/vertx/json/schema/draft7/dsl/Keywords.html[Keywords]`

=== Creating the schema

Inside `link:../../apidocs/io/vertx/json/schema/draft7/dsl/Schemas.html[Schemas]` there are static methods to create the schema:

[source,java]
----
SchemaBuilder intSchemaBuilder = intSchema();
SchemaBuilder objectSchemaBuilder = objectSchema();
----

=== Using the keywords

For every schema you can add keywords built with `link:../../apidocs/io/vertx/json/schema/draft7/dsl/Keywords.html[Keywords]` methods,
depending on the type of the schema:

[source,java]
----
stringSchema()
  .with(format(StringFormat.DATETIME));
arraySchema()
  .with(maxItems(10));
schema() // Generic schema that accepts both arrays and integers
  .with(type(SchemaType.ARRAY, SchemaType.INT));
----

=== 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,java]
----
objectSchema()
  .requiredProperty("name", stringSchema())
  .requiredProperty("age", intSchema())
  .additionalProperties(stringSchema());
----

To create an array schema:

[source,java]
----
arraySchema()
  .items(stringSchema());
----

To create a tuple schema:

[source,java]
----
tupleSchema()
  .item(stringSchema()) // First item
  .item(intSchema()) // Second item
  .item(booleanSchema()); // Third item
----

=== `$ref` and aliases

To add a `$ref` schema you can use the `link:../../apidocs/io/vertx/json/schema/common/dsl/Schemas.html#ref-io.vertx.core.json.pointer.JsonPointer-[Schemas.ref]` method.
To assign an `$id` keyword to a schema, use `link:../../apidocs/io/vertx/json/schema/common/dsl/SchemaBuilder.html#id-io.vertx.core.json.pointer.JsonPointer-[id]`

You can also refer to schemas defined with this dsl using aliases. You can use `link:../../apidocs/io/vertx/json/schema/common/dsl/SchemaBuilder.html#alias-java.lang.String-[alias]` to assign an alias to
a schema. Then you can refer to a schema with an alias using `link:../../apidocs/io/vertx/json/schema/common/dsl/Schemas.html#refToAlias-java.lang.String-[Schemas.refToAlias]`:

[source,java]
----
intSchema()
  .alias("myInt");

objectSchema()
  .requiredProperty("anInteger", refToAlias("myInt"));
----

=== Using the schema

After you defined the schema, you can call `link:../../apidocs/io/vertx/json/schema/common/dsl/SchemaBuilder.html#build-io.vertx.json.schema.SchemaParser-[build]` to parse and use the schema:

[source,java]
----
Schema schema = objectSchema()
  .requiredProperty("name", stringSchema())
  .requiredProperty("age", intSchema())
  .additionalProperties(stringSchema())
  .build(parser);
----