Resolving schemas
Every schema is uniquely identified by its URI. It is common to refer to schema definitions ($defs) in the scope of a single schema document by using JSON pointers (e.g. #/$defs/foo). But sometimes referencing schemas from different documents is necessary, and this can be achieved by:
Preregistering (preloading) all schema documents that might get referenced. This approach is recommended when the number of referenced schemas is either small or known in advance. It will also ensure the validity of all registered schemas early on.
Implementing SchemaResolver interface. Recommended when schemas need to be loaded dynamically and are not known in advance. Common use cases would be fetching from the web or from a file system.
There is no built-in mechanism to fetch schemas from the web — due to safety reasons, it is discouraged by specification. The only "external" schemas that will be loaded by default are the official draft meta-schemas. They are packaged with a jar and are directly read as a classpath resource.
SchemaResolver interface
It can be a simple lambda:
Please note this example is oversimplified and will cause every validation against external schema successful.
A special Result object needs to be returned from the resolver: it is just a wrapper for a value to allow returning different types.
- Result.empty()
Means that the resolver was not able to resolve the schema (or does not support given URI). Similar to
Optional.empty().- Result.fromString(String rawSchema)
A result which contains a raw JSON/YAML string representing the schema.
Example:
SchemaResolver.Result.fromString("{}");- Result.fromJsonNode(JsonNode schemaNode)
A result which contains already parsed instance of
dev.harrel.jsonschemaJsonNoderepresenting the schema.Example:
JsonNode schemaNode = new JacksonNode.Factory().create("{}"); SchemaResolver.Result.fromJsonNode(schemaNode);- Result.fromProviderNode(Object schemaProviderNode)
A result which contains already parsed instance of provider node representing the schema. Please ensure that you are using a correct type according to your JSON/YAML provider.
Example:
Object schemaProviderNode = new ObjectMapper().readTree("{}"); SchemaResolver.Result.fromProviderNode(schemaProviderNode);
Using multiple resolvers
Let's say you have multiple ways of fetching external schemas. Instead of putting all logic combined into a single resolver, you can split the logic between multiple resolvers and then combine them:
When resolving an external schema, it will:
Invoke
ftpResolver, ifResult.empty()returned, go to the next step.Invoke
fileResolver, ifResult.empty()returned, go to the next step.Invoke
httpResolver, ifResult.empty()returned, go to the next step.Invoke the internal resolver which only resolves official meta-schemas. if
Result.empty()returned, go to the next step.If it was a direct validation call reference, it will throw SchemaNotFoundException. If it was referenced via
$refkeyword (or similar), the validation against this keyword will fail.
Details of ID resolution
Every schema has its own retrieval URI:
If parsed via
SchemaResolver- it's equal to the URI for which the resolution is performed.If registered via
Validatorand registration URI was provided - it's the same as the registration URI.If registered via
Validatorand registration URI was NOT provided - a unique retrieval URI is generated in the form ofhttps://harrel.dev/{randomSeed}.
Additionally, every schema can have its own $id (id in older drafts) defined. This causes the schema to be registered under additional URI:
If
$idis an absolute URI - it's registered as it is.If
$idis a relative URI - it's resolved against retrieval URI. For example:https://harrel.dev/12345678+/my-schema->https://harrel.dev/my-schema.If
$idis a relative URI and registration (retrieval) URI is also relative - first, retrieval URI is resolved against a generated URI, and then the$idis resolved against the result. For example:https://harrel.dev/12345678+/schemas/registration+/my-schema->https://harrel.dev/schemas/my-schema.