JSV.Schema.Helpers (jsv v0.19.0)

Copy Markdown View Source

Helpers to define schemas in plain Elixir code.

Summary

Schema Presets

Returns a JSON Schema with allOf: schemas.

Returns a JSON Schema with anyOf: schemas.

Object properties with atom keys.

Required object properties with atom keys.

Returns a JSON Schema with type: :array and items: item_schema.

Returns a JSON Schema with type: :boolean.

Returns a JSON Schema with const: const.

Returns a JSON Schema with type: :string and format: :date.

Returns a JSON Schema with type: :string and format: :"date-time".

Returns a JSON Schema with type: :string and format: :email.

Note that in the JSON Schema specification, if the enum contains 1 then 1.0 is a valid value.

Does not set the type: :string on the schema. Use string_of/2 for a shortcut.

Returns a JSON Schema with type: :integer.

Returns a JSON Schema with type: :integer and maximum: -1.

Returns a JSON Schema with type: :string and minLength: 1.

Returns a JSON Schema with type: :integer and minimum: 0.

Returns a JSON Schema with type: :number.

Returns a JSON Schema with type: :object.

Returns a JSON Schema with oneOf: schemas.

Returns a JSON Schema with type: :integer and minimum: 1.

Does not set the type: :object on the schema. Use props/2 for a shortcut.

Note that any preexisting schema properties are replaced.

Returns a schema referencing the given ref.

Returns a JSON Schema with type: :string.

Accepts a list of atoms and returns a schema that validates a string representation of one of the given atoms.

Like string_enum_to_atom/2 but also validates null JSON values. The nil atom should not be given in the atom list, except if you want to accept the "nil" JSON string and cast it to nil.

Returns a JSON Schema with type: :string and format: format.

Returns a schema with type: :string that casts strings to atoms.

Returns a schema with type: :string that casts "true" and "false" to booleans.

Returns a schema with type: :string that casts strings to existing atoms.

Returns a schema with type: :string that casts strings to floats.

Returns a schema with type: :string that casts strings to integers.

Returns a schema with type: :string that casts strings to numbers (integers or floats).

Returns a JSON Schema with type: :string and format: :uri.

Returns a JSON Schema with type: :string and format: :uuid.

Functions

Makes a schema nullable by adding :null to the allowed types.

Marks a schema as optional when using the keyword list syntax with JSV.defschema/1 or JSV.defschema/3.

The Schema Description sigil.

Schema Presets

Schema presets are functions that take zero or more arguments and return predefined schemas. Those predefined schemas are not JSV.Schema structs but raw maps.

Each function has a second version with an additional extra argument that will be combined with the predefined schema using JSV.Schema.combine/2.

Note that the extra attributes cannot override what is defined in the preset.

Example

%{
  properties: %{
    foo: integer(),
    bar: integer(description: "An actual bar", minimum: 10),
    baz: any_of([MyApp.Baz,MyApp.OldBaz], description: "Baz baz baz")
  }
}

all_of(schemas, extra \\ nil)

@spec all_of([JSV.Schema.schema()], extra()) :: schema()

Returns a JSON Schema with allOf: schemas.

any_of(schemas, extra \\ nil)

@spec any_of([JSV.Schema.schema()], extra()) :: schema()

Returns a JSON Schema with anyOf: schemas.

aprops(properties, extra \\ nil)

@spec aprops(properties(), extra()) :: schema()

Object properties with atom keys.

Like props/2, but on validation the defined properties are returned with atom keys instead of string keys. Additional properties (not listed in the schema) keep their string keys.

Note that any preexisting schema properties are replaced.

Example

iex> schema = aprops(name: string(), age: integer())
iex> root = JSV.build!(schema, atoms: true)
iex> JSV.validate!(%{"name" => "Alice", "age" => 123}, root)
%{name: "Alice", age: 123}

arprops(properties, extra \\ nil)

@spec arprops(properties(), extra()) :: schema()

Required object properties with atom keys.

Like props/2, but on validation the defined properties are returned with atom keys instead of string keys. Additional properties (not listed in the schema) keep their string keys.

Note that any preexisting schema properties are replaced.

Example

iex> schema = aprops(name: string(), age: integer())
iex> root = JSV.build!(schema, atoms: true)
iex> JSV.validate!(%{"name" => "Alice", "age" => 123}, root)
%{name: "Alice", age: 123}

array_of(item_schema, extra \\ nil)

@spec array_of(JSV.Schema.schema(), extra()) :: schema()

Returns a JSON Schema with type: :array and items: item_schema.

boolean(extra \\ nil)

@spec boolean(JSV.Schema.attributes() | nil) :: JSV.Schema.schema()

Returns a JSON Schema with type: :boolean.

const(const, extra \\ nil)

@spec const(term(), extra()) :: schema()

Returns a JSON Schema with const: const.

date(extra \\ nil)

@spec date(JSV.Schema.attributes() | nil) :: JSV.Schema.schema()

Returns a JSON Schema with type: :string and format: :date.

datetime(extra \\ nil)

@spec datetime(JSV.Schema.attributes() | nil) :: JSV.Schema.schema()

Returns a JSON Schema with type: :string and format: :"date-time".

email(extra \\ nil)

@spec email(JSV.Schema.attributes() | nil) :: JSV.Schema.schema()

Returns a JSON Schema with type: :string and format: :email.

enum(enum, extra \\ nil)

@spec enum(list(), extra()) :: schema()

Note that in the JSON Schema specification, if the enum contains 1 then 1.0 is a valid value.

format(format, extra \\ nil)

@spec format(atom() | binary(), extra()) :: schema()

Does not set the type: :string on the schema. Use string_of/2 for a shortcut.

integer(extra \\ nil)

@spec integer(JSV.Schema.attributes() | nil) :: JSV.Schema.schema()

Returns a JSON Schema with type: :integer.

neg_integer(extra \\ nil)

@spec neg_integer(JSV.Schema.attributes() | nil) :: JSV.Schema.schema()

Returns a JSON Schema with type: :integer and maximum: -1.

non_empty_string(extra \\ nil)

@spec non_empty_string(JSV.Schema.attributes() | nil) :: JSV.Schema.schema()

Returns a JSON Schema with type: :string and minLength: 1.

non_neg_integer(extra \\ nil)

@spec non_neg_integer(JSV.Schema.attributes() | nil) :: JSV.Schema.schema()

Returns a JSON Schema with type: :integer and minimum: 0.

number(extra \\ nil)

@spec number(JSV.Schema.attributes() | nil) :: JSV.Schema.schema()

Returns a JSON Schema with type: :number.

object(extra \\ nil)

@spec object(JSV.Schema.attributes() | nil) :: JSV.Schema.schema()

Returns a JSON Schema with type: :object.

See the props/2 function that accepts properties as a first argument.

one_of(schemas, extra \\ nil)

@spec one_of([JSV.Schema.schema()], extra()) :: schema()

Returns a JSON Schema with oneOf: schemas.

pos_integer(extra \\ nil)

@spec pos_integer(JSV.Schema.attributes() | nil) :: JSV.Schema.schema()

Returns a JSON Schema with type: :integer and minimum: 1.

properties(properties, extra \\ nil)

@spec properties(properties(), extra()) :: schema()

Does not set the type: :object on the schema. Use props/2 for a shortcut.

Note that any preexisting schema properties are replaced.

props(properties, extra \\ nil)

@spec props(properties(), extra()) :: schema()

Note that any preexisting schema properties are replaced.

ref(ref, extra \\ nil)

@spec ref(String.t(), extra()) :: schema()

Returns a schema referencing the given ref.

A struct-based schema module name is not a valid reference. Modules should be passed directly where a schema (and not a $ref) is expected.

Example

For instance to define a user property, this is valid:

props(user: UserSchema)

The following is invalid:

# Do not do this
props(user: ref(UserSchema))

string(extra \\ nil)

@spec string(JSV.Schema.attributes() | nil) :: JSV.Schema.schema()

Returns a JSON Schema with type: :string.

string_enum_to_atom(enum, extra \\ nil)

@spec string_enum_to_atom([atom()], extra()) :: schema()

Accepts a list of atoms and returns a schema that validates a string representation of one of the given atoms.

On validation, a cast will be made to return the original atom value.

This is useful when dealing with enums that are represented as atoms in the codebase, such as Oban job statuses or other Ecto enum types.

iex> schema = props(status: string_enum_to_atom([:executing, :pending]))
iex> root = JSV.build!(schema, atoms: true)
iex> JSV.validate(%{"status" => "pending"}, root)
{:ok, %{"status" => :pending}}

Does not support nil

See string_enum_to_atom_or_nil/2 for nil support.

This function sets the string type on the schema. If nil is given in the enum, the corresponding valid JSON value will be the "nil" string rather than null.

string_enum_to_atom_or_nil(enum, extra \\ nil)

@spec string_enum_to_atom_or_nil([atom()], extra()) :: schema()

Like string_enum_to_atom/2 but also validates null JSON values. The nil atom should not be given in the atom list, except if you want to accept the "nil" JSON string and cast it to nil.

string_of(format, extra \\ nil)

@spec string_of(atom() | binary(), extra()) :: schema()

Returns a JSON Schema with type: :string and format: format.

string_to_atom(extra \\ nil)

@spec string_to_atom(extra()) :: schema()

Returns a schema with type: :string that casts strings to atoms.

string_to_boolean(extra \\ nil)

@spec string_to_boolean(extra()) :: schema()

Returns a schema with type: :string that casts "true" and "false" to booleans.

string_to_existing_atom(extra \\ nil)

@spec string_to_existing_atom(extra()) :: schema()

Returns a schema with type: :string that casts strings to existing atoms.

string_to_float(extra \\ nil)

@spec string_to_float(extra()) :: schema()

Returns a schema with type: :string that casts strings to floats.

string_to_integer(extra \\ nil)

@spec string_to_integer(extra()) :: schema()

Returns a schema with type: :string that casts strings to integers.

string_to_number(extra \\ nil)

@spec string_to_number(extra()) :: schema()

Returns a schema with type: :string that casts strings to numbers (integers or floats).

uri(extra \\ nil)

@spec uri(JSV.Schema.attributes() | nil) :: JSV.Schema.schema()

Returns a JSON Schema with type: :string and format: :uri.

uuid(extra \\ nil)

@spec uuid(JSV.Schema.attributes() | nil) :: JSV.Schema.schema()

Returns a JSON Schema with type: :string and format: :uuid.

Types

extra()

@type extra() :: JSV.Schema.attributes() | nil

properties()

@type properties() ::
  [{property_key(), JSV.Schema.schema()}]
  | %{optional(property_key()) => JSV.Schema.schema()}

property_key()

@type property_key() :: atom() | binary()

schema()

@type schema() :: JSV.Schema.schema()

Functions

nullable(schema)

@spec nullable(map() | module()) :: map()

Makes a schema nullable by adding :null to the allowed types.

Example

iex> nullable(integer())
%{type: [:integer, :null]}

iex> nullable(%{type: :integer, anyOf: [%{minimum: 1}, %{maximum: -1}]})
%{
  type: [:integer, :null],
  anyOf: [%{type: :null}, %{minimum: 1}, %{maximum: -1}]
}

iex> nullable(%{type: :integer, oneOf: [%{minimum: 1}, %{maximum: -1}]})
%{
  type: [:integer, :null],
  oneOf: [%{type: :null}, %{minimum: 1}, %{maximum: -1}]
}

When given a schema module, wraps it in an anyOf that allows either the module's schema or null:

iex> defmodule Position do
...>   use JSV.Schema
...>   defschema x: integer(), y: integer()
...> end
iex> nullable(Position)
%{anyOf: [%{type: :null}, Position]}

iex> defmodule Point do
...>   def json_schema do
...>     %{
...>       "properties" => %{
...>         "x" => %{"type" => "integer"},
...>         "y" => %{"type" => "integer"}
...>       }
...>     }
...>   end
...> end
iex> nullable(Point)
%{anyOf: [%{type: :null}, Point]}

optional(schema, opts \\ [])

@spec optional(
  term(),
  keyword()
) :: {:__optional__, term(), keyword()}

Marks a schema as optional when using the keyword list syntax with JSV.defschema/1 or JSV.defschema/3.

This is useful for recursive module references where you want to avoid infinite nesting requirements. When used in property list syntax with defschema, the property will not be marked as required.

defschema name: string(),
          parent: optional(MySelfReferencingModule)

Skipping optional keys during JSON serialization

This is only applicable to schema defined with JSV.defschema/3. The more generic macro JSV.defschema/1 let you implement a full module so you must implement the protocols yourself, or use anyOf: null/sub schema for some properties.

When encoding a struct to JSON, optional value (set as nil in the struct) are still rendered, which may be invalid if someone needs to validate the serialized value with the original schema. As the optional properties are not required, the :nskip option (for "normalization skip") with a constant value can be given. The value will not be serialized if it matches the value.

defschema name: string(),
          parent: optional(MySelfReferencingModule, nskip: nil)

sigil_SD(arg, list)

(macro)

The Schema Description sigil.

A sigil used to embed long texts in schemas descriptions. Replaces all combinations of whitespace by a single whitespace and trims the string.

It does not support any modifier.

Note that newlines are perfectly fine in schema descriptions, as they are simply encoded as "\n". This sigil is intended for schemas that need to be compressed because they are sent over the wire repeatedly (like in HTTP APIs or when working with LLMs).

Example

iex> ~SD"""
...> This schema represents an elixir.
...>
...> An elixir is a potion with positive outcomes!
...> """
"This schema represents an elixir. An elixir is a potion with positive outcomes!"

left ~> right

An alias for JSV.Schema.combine/2.

Example

iex> object(description: "a user")
...> ~> any_of([AdminSchema, CustomerSchema])
...> ~> properties(foo: integer())
%{
  type: :object,
  description: "a user",
  properties: %{foo: %{type: :integer}},
  anyOf: [AdminSchema, CustomerSchema]
}