JSONSchex (jsonschex v0.7.0)

Copy Markdown View Source

JSON Schema Draft 2020-12 validator for Elixir.

Compile a schema once, then validate data against it repeatedly:

iex> schema = %{"type" => "integer", "minimum" => 10}
iex> {:ok, compiled} = JSONSchex.compile(schema)
iex> JSONSchex.validate(compiled, 15)
:ok
iex> {:error, [error]} = JSONSchex.validate(compiled, 5)
iex> error.rule
:minimum
iex> JSONSchex.format_error(error)
"Value 5 is less than minimum 10"

With format assertion:

iex> schema = %{"type" => "string", "format" => "email"}
iex> {:ok, compiled} = JSONSchex.compile(schema, format_assertion: true)
iex> JSONSchex.validate(compiled, "user@example.com")
:ok
iex> {:error, [error]} = JSONSchex.validate(compiled, "not-an-email")
iex> error.rule
:format
iex> JSONSchex.format_error(error)
~s(Invalid email format: "not-an-email")

For compile-time schema embedding, see:

use JSONSchex imports the ~X sigil as a convenience.

Summary

Functions

Imports the ~X sigil for compile-time schema literals.

Bundles a JSON Schema fragment from a containing document into a standalone raw schema.

Compiles a raw JSON Schema into a reusable Schema struct.

Compiles a JSON Schema fragment from a containing document.

Formats a validation error into a human-readable string.

Validates data against a compiled schema.

Functions

__using__(opts)

(macro)

Imports the ~X sigil for compile-time schema literals.

For the explicit API, use JSONSchex.Sigil directly.

bundle_fragment(document, opts)

@spec bundle_fragment(
  map() | boolean(),
  keyword()
) :: {:ok, map() | boolean()} | {:error, JSONSchex.Types.Error.t()}

Bundles a JSON Schema fragment from a containing document into a standalone raw schema.

This uses the same entrypoint and reference-context options as compile_fragment/2, mounts reachable external resources under $defs, and returns a raw JSON Schema map or boolean that can be compiled later with compile/2.

Examples

iex> document = %{
...>   "components" => %{"schemas" => %{"Name" => %{"type" => "string"}}},
...>   "schema" => %{"$ref" => "#/components/schemas/Name"}
...> }
iex> {:ok, bundled} = JSONSchex.bundle_fragment(document, entry_pointer: "#/schema")
iex> {:ok, schema} = JSONSchex.compile(bundled)
iex> JSONSchex.validate(schema, "Ada")
:ok

compile(schema, opts \\ [])

Compiles a raw JSON Schema into a reusable Schema struct.

Options

  • :loader(uri -> {:ok, map()} | {:error, term()}) for remote $ref schemas

  • :base_uri — Starting base URI for resolving relative references
  • :format_assertion — Enable strict format validation (default: false)
  • :content_assertion — Enable strict content vocabulary validation (default: false)

See the Loader guide and Content and Format guide for details.

Examples

iex> {:ok, schema} = JSONSchex.compile(%{"type" => "string"})
iex> is_struct(schema, JSONSchex.Types.Schema)
true

iex> {:ok, schema} = JSONSchex.compile(%{"type" => "string", "format" => "email"}, format_assertion: true)
iex> JSONSchex.validate(schema, "test@example.com")
:ok

compile_fragment(document, opts)

@spec compile_fragment(
  map() | boolean(),
  keyword()
) :: {:ok, JSONSchex.Types.Schema.t()} | {:error, JSONSchex.Types.Error.t()}

Compiles a JSON Schema fragment from a containing document.

This is useful when a schema lives inside a larger resource, such as an OpenAPI 3.1 document, and local references like #/components/schemas/User must resolve against that containing document rather than against the fragment map in isolation.

Options

  • :entry_pointer — JSON Pointer to the schema fragment (#/... or /...). Provide exactly one of :entry_pointer or :entry_ref.
  • :entry_ref — URI-reference style entrypoint alternative (#/... or path-or-uri#/...). If it includes a base URI/path and :base_uri is omitted, that base is used for relative reference resolution.
  • :base_uri — optional starting base URI/path for resolving relative references when the entrypoint is provided as :entry_pointer
  • :loader — optional loader for external resources. It may return {:ok, schema} or {:ok, %{document: schema, base_uri: base_uri}}; wrapper metadata uses atom keys only.
  • :format_assertion — Enable strict format validation (default: false)
  • :content_assertion — Enable strict content vocabulary validation (default: false)

:entry_pointer is the simplest form when the containing document's base is supplied separately with :base_uri or when no relative external refs are reachable. :entry_ref is useful when the entrypoint and base URI/path can be represented as one reference, such as "/api/openapi.yaml#/components/schemas/User".

Examples

iex> document = %{
...>   "components" => %{"schemas" => %{"Name" => %{"type" => "string"}}},
...>   "schema" => %{"$ref" => "#/components/schemas/Name"}
...> }
iex> {:ok, schema} = JSONSchex.compile_fragment(document, entry_pointer: "#/schema")
iex> JSONSchex.validate(schema, "Ada")
:ok
iex> {:error, [%{rule: :type}]} = JSONSchex.validate(schema, 123)

iex> document = %{"schema" => %{"type" => "integer"}}
iex> {:ok, schema} = JSONSchex.compile_fragment(document, entry_ref: "/api/openapi.yaml#/schema")
iex> JSONSchex.validate(schema, 42)
:ok

format_error(error)

@spec format_error(JSONSchex.Types.Error.t()) :: String.t()

Formats a validation error into a human-readable string.

Examples

iex> error = %JSONSchex.Types.Error{path: ["age", "user"], rule: :minimum, context: %JSONSchex.Types.ErrorContext{contrast: 0, input: -5}}
iex> JSONSchex.format_error(error)
"At /user/age: Value -5 is less than minimum 0"

iex> {:error, error} = JSONSchex.compile(%{"type" => "1"})
iex> JSONSchex.format_error(error)
~s(Keyword 'type' must be one of [string, integer, number, boolean, object, array, null], got: "1")

iex> {:error, error} = JSONSchex.compile(%{"minimum" => "five"})
iex> JSONSchex.format_error(error)
~s(Keyword 'minimum' must be a number, got: "five")

iex> {:error, error} = JSONSchex.compile(%{"multipleOf" => -3})
iex> JSONSchex.format_error(error)
~s(Keyword 'multipleOf' must be a strictly positive number, got: -3)

iex> {:error, error} = JSONSchex.compile(%{"minLength" => -1})
iex> JSONSchex.format_error(error)
~s(Keyword 'minLength' must be a non-negative integer, got: -1)

iex> {:error, error} = JSONSchex.compile(%{"uniqueItems" => "yes"})
iex> JSONSchex.format_error(error)
~s(Keyword 'uniqueItems' must be a boolean, got: "yes")

validate(schema, data)

@spec validate(JSONSchex.Types.Schema.t(), term()) ::
  :ok | {:error, [JSONSchex.Types.Error.t()]}

Validates data against a compiled schema.

Returns :ok or {:error, [Error.t()]}. Use JSONSchex.format_error/1 on individual errors to produce human-readable messages.

Examples

iex> {:ok, schema} = JSONSchex.compile(%{"type" => "integer"})
iex> JSONSchex.validate(schema, 42)
:ok

iex> {:ok, schema} = JSONSchex.compile(%{"type" => "integer"})
iex> {:error, errors} = JSONSchex.validate(schema, "not an integer")
iex> [error] = errors
iex> error.rule
:type
iex> error.path
[]
iex> JSONSchex.format_error(error)
"Expected type integer, got string"