Packages
jsv
0.2.0
0.21.2
0.21.1
0.21.0
0.20.0
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.3
0.18.2
0.18.1
0.18.0
0.17.1
0.17.0
0.16.0
0.15.2
0.15.1
0.15.0
0.14.0
0.13.1
0.13.0
0.12.0
0.11.5
0.11.4
0.11.3
retired
0.11.2
0.11.1
0.11.0
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.0
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
A JSON Schema Validator with complete support for the latest specifications.
Current section
Files
Jump to
Current section
Files
lib/jsv.ex
defmodule JSV do
alias JSV.AtomTools
alias JSV.BooleanSchema
alias JSV.Builder
alias JSV.ErrorFormatter
alias JSV.Root
alias JSV.ValidationError
alias JSV.Validator
alias JSV.Validator.ValidationContext
readme =
"README.md"
|> File.read!()
|> String.split("<!-- moduledoc-split -->")
|> tl()
@moduledoc """
This is the main API for the JSV library.
#{readme}
"""
@default_default_meta "https://json-schema.org/draft/2020-12/schema"
@build_opts_schema NimbleOptions.new!(
resolver: [
type: {:or, [:atom, :mod_arg]},
required: true,
doc: """
The `JSV.Resolver` behaviour implementation module to
retrieve schemas identified by an URL.
Accepts a `module` or a `{module, options}` tuple.
The options can be any term and will be given to the
`resolve/2` callback of the module.
"""
],
default_meta: [
type: :string,
doc:
~S(The meta schema to use for resolved schemas that do not define a `"$schema"` property.),
default: @default_default_meta
],
formats: [
type: {:or, [:boolean, nil, {:list, :atom}]},
doc: """
Controls the validation of strings with the `"format"` keyword.
* `nil` - Formats are validated according to the meta-schema vocabulary.
* `true` - Enforces validation with the built-in validator modules.
* `false` - Disables all format validation.
* `[Module1, Module2,...]` – set those modules as validators. Disables the built-in format validator modules.
The default validators can be included manually in the list, see `default_format_validator_modules/0`.
""",
default: nil
]
)
@doc """
Builds the schema as a `#{inspect(Root)}` schema for validation.
### Options
#{NimbleOptions.docs(@build_opts_schema)}
"""
@spec build(Builder.raw_schema(), keyword) :: {:ok, Root.t()} | {:error, Exception.t()}
def build(raw_schema, opts) when is_map(raw_schema) do
raw_schema = AtomTools.fmap_atom_to_binary(raw_schema)
case NimbleOptions.validate(opts, @build_opts_schema) do
{:ok, opts} ->
builder = Builder.new(opts)
case Builder.build(builder, raw_schema) do
{:ok, root} -> {:ok, root}
{:error, reason} -> {:error, %JSV.BuildError{reason: reason}}
end
{:error, _} = err ->
err
end
end
def build(valid?, _opts) when is_boolean(valid?) do
{:ok, %Root{raw: valid?, root_key: :root, validators: %{root: BooleanSchema.of(valid?)}}}
end
@doc """
Same as `build/2` but raises on error.
"""
@spec build!(Builder.raw_schema(), keyword) :: Root.t()
def build!(raw_schema, opts) do
case build(raw_schema, opts) do
{:ok, root} -> root
{:error, reason} -> raise reason
end
end
@doc """
Returns the default meta schema used when the `:default_meta` option is not
set in `build/2`.
Currently returns #{inspect(@default_default_meta)}.
"""
@spec default_meta :: binary
def default_meta do
@default_default_meta
end
@validate_opts_schema NimbleOptions.new!(
cast_formats: [
type: :boolean,
default: false,
doc:
"When enabled format validators will return casted values, " <>
"for instance a `Date` struct instead of the date as string. " <>
"It has no effect when the schema was not built with formats enabled."
]
)
@doc """
Validate the data with the given schema. The schema must be a `JSV.Root`
struct generated with `build/2`.
**Important**: this function returns casted data:
* If the `:cast_formats` option is enabled, string values may be transformed
in other data structures. Refer to the "Formats" section of the `JSV`
documentation for more information.
* The JSON Schema specification states that `123.0` is a valid integer. This
function will return `123` instead. This may return invalid data for floats
with very large integer parts. As always when dealing with JSON and floats,
use strings.
* Future versions of the library will allow to cast raw data into Elixir
structs.
### Options
#{NimbleOptions.docs(@validate_opts_schema)}
"""
@spec validate(term, JSV.Root.t(), keyword) :: {:ok, term} | {:error, Exception.t()}
def validate(data, root, opts \\ [])
def validate(data, %JSV.Root{} = root, opts) do
case NimbleOptions.validate(opts, @validate_opts_schema) do
{:ok, opts} ->
case validation_entrypoint(root, data, opts) do
{:ok, casted_data, _} -> {:ok, casted_data}
{:error, %ValidationContext{} = validator} -> {:error, Validator.to_error(validator)}
end
{:error, _} = err ->
err
end
end
@spec normalize_error(ValidationError.t() | Validator.context() | [Validator.Error.t()]) :: map()
def normalize_error(%ValidationError{} = error) do
ErrorFormatter.normalize_error(error)
end
def normalize_error(errors) when is_list(errors) do
normalize_error(ValidationError.of(errors))
end
# TODO provide a way to return ordered json for errors, or just provide a
# preprocess function.
def normalize_error(%ValidationContext{} = validator) do
normalize_error(Validator.to_error(validator))
end
@doc false
# direct entrypoint for tests when we want to get the returned context.
@spec validation_entrypoint(term, term, term) :: Validator.result()
def validation_entrypoint(%JSV.Root{} = schema, data, opts) do
%JSV.Root{validators: validators, root_key: root_key} = schema
root_schema_validators = Map.fetch!(validators, root_key)
context = JSV.Validator.context(validators, _scope = [root_key], opts)
JSV.Validator.validate(data, root_schema_validators, context)
end
@doc """
Returns the list of format validator modules that are used when a schema is
built with format validation enabled and the `:formats` option to `build/2` is
`true`.
"""
@spec default_format_validator_modules :: [module]
def default_format_validator_modules do
[JSV.FormatValidator.Default]
end
end