JSV.Schema (jsv v0.8.1)
View SourceThis module defines a struct where all the supported keywords of the JSON schema specification are defined as keys. Text editors that can predict the struct keys will make autocompletion available when writing schemas.
Using in build
The %JSV.Schema{}
struct can be given to JSV.build/2
:
schema = %JSV.Schema{type: :integer}
JSV.build(schema, options())
Because Elixir structs always contain all their defined keys, writing a schema
as %JSV.Schema{type: :integer}
is actually defining the following:
%JSV.Schema{
type: :integer,
"$id": nil
additionalItems: nil,
additionalProperties: nil,
allOf: nil,
anyOf: nil,
contains: nil,
# etc...
}
For that reason, when giving a %JSV.Schema{}
struct to JSV.build/2
, any nil
value is
ignored. The same behaviour can be defined for other struct by implementing
the JSV.Normalizer.Normalize
protocol. Mere maps will keep their nil
values.
Note that JSV.build/2
does not require %JSV.Schema{}
structs, any map with binary or
atom keys is accepted.
This is also why the %JSV.Schema{}
struct does not define the const
keyword, because
nil
is a valid value for that keyword but there is no way to know if the
value was omitted or explicitly defined as nil
. To circumvent that you may
use the enum
keyword or just use a regular map instead of this module's
struct:
%JSV.Schema{enum: [nil]}
# OR
%{const: nil}
Functional helpers
This module also exports a small range of utility functions to ease writing schemas in a functional way.
This is mostly useful when generating schemas dynamically, or for shorthands.
For instance, instead of writing the following:
%Schema{
type: :object,
properties: %{
name: %Schema{type: :string, description: "the name of the user", minLength: 1},
age: %Schema{type: :integer, description: "the age of the user"}
},
required: [:name, :age]
}
One can write:
%Schema{
type: :object,
properties: %{
name: string(description: "the name of the user", minLength: 1),
age: integer(description: "the age of the user")
},
required: [:name, :age]
}
This is also useful when building schemas dynamically, as the helpers are pipe-able one into another:
new()
|> props(
name: string(description: "the name of the user", minLength: 1),
age: integer(description: "the age of the user")
)
|> required([:name, :age])
Summary
Schema Definition Utilities
Defines or merges into a JSON Schema with
allOf: schemas
..
Defines or merges into a JSON Schema with
anyOf: schemas
..
Defines or merges into a JSON Schema with
type: :array
and items: item_schema
..
Defines or merges into a JSON Schema with
type: :boolean
..
Defines or merges into a JSON Schema with
type: :string
and format: :date
..
Defines or merges into a JSON Schema with
type: :string
and format: :"date-time"
..
Defines or merges into a JSON Schema with
type: :string
and format: :email
..
Defines or merges into a JSON Schema with
format: format
.
Defines or merges into a JSON Schema with
type: :integer
..
Defines or merges into a JSON Schema with
items: item_schema
.
Merges the given key/values into the base schema. The merge is shallow and will overwrite any pre-existing key.
Defines or merges into a JSON Schema with
type: :integer
and maximum: -1
..
Defines or merges into a JSON Schema with
type: :string
and minLength: 1
..
Defines or merges into a JSON Schema with
type: :integer
and minimum: 0
..
Defines or merges into a JSON Schema with
type: :number
..
Defines or merges into a JSON Schema with
type: :object
.
Defines or merges into a JSON Schema with
oneOf: schemas
..
Defines or merges into a JSON Schema with
type: :integer
and minimum: 1
..
Defines or merges into a JSON Schema with
properties: properties
.
Defines or merges into a JSON Schema with
type: :object
and properties: properties
..
Defines or merges into a JSON Schema with
$ref: ref
.
Defines a JSON Schema with required: keys
or adds the given keys
if the
base schema already has a :required
definition.
Defines or merges into a JSON Schema with
type: :string
..
Defines or merges into a JSON Schema with
type: :string
and format: format
..
Defines or merges into a JSON Schema with
type: :string
and format: :uri
..
Defines or merges into a JSON Schema with
type: :string
and format: :uuid
..
Schema Casts
Includes the cast function in a schema. The cast function must be given as a list with two items
Defines or merges into a JSON Schema with
type: :string
and jsv-cast: ["Elixir.JSV.Cast", "string_to_atom"]
..
Defines or merges into a JSON Schema with
type: :string
, enum: enum
and jsv-cast: ["Elixir.JSV.Cast", "string_to_atom"]
.
Defines or merges into a JSON Schema with
type: :string
and jsv-cast: ["Elixir.JSV.Cast", "string_to_boolean"]
..
Defines or merges into a JSON Schema with
type: :string
and jsv-cast: ["Elixir.JSV.Cast", "string_to_existing_atom"]
..
Defines or merges into a JSON Schema with
type: :string
and jsv-cast: ["Elixir.JSV.Cast", "string_to_float"]
..
Defines or merges into a JSON Schema with
type: :string
and jsv-cast: ["Elixir.JSV.Cast", "string_to_integer"]
..
Defines or merges into a JSON Schema with
type: :string
and jsv-cast: ["Elixir.JSV.Cast", "string_to_number"]
..
Functions
Returns a new empty schema.
Returns a new schema with the given key/values.
Normalizes a JSON schema with the help of JSV.Normalizer.normalize/3
with
the following customizations
Alias for merge/2
.
Returns whether the given atom is a module with a schema/0
exported
function.
Returns the given %JSV.Schema{}
as a map without keys containing
a nil
value.
Schema Definition Utilities
Defines or merges into a JSON Schema with
allOf: schemas
..
Defines or merges into a JSON Schema with
anyOf: schemas
..
Defines or merges into a JSON Schema with
type: :array
and items: item_schema
..
Defines or merges into a JSON Schema with
type: :boolean
..
Defines or merges into a JSON Schema with
type: :string
and format: :date
..
Defines or merges into a JSON Schema with
type: :string
and format: :"date-time"
..
Defines or merges into a JSON Schema with
type: :string
and format: :email
..
Defines or merges into a JSON Schema with
format: format
.
Does not set the type: :string
on the schema. Use string_of/2
for a
shortcut.
.
Defines or merges into a JSON Schema with
type: :integer
..
Defines or merges into a JSON Schema with
items: item_schema
.
Does not set the type: :array
on the schema. Use array_of/2
for a
shortcut.
.
Merges the given key/values into the base schema. The merge is shallow and will overwrite any pre-existing key.
The resulting schema is always a map or a struct but the actual type depends on the given base. It follows the followng rules:
When the base type is a map or a struct, it is preserved
- If the base is a
%JSV.Schema{}
struct, thevalues
are merged in. - If the base is another struct, the
values
a merged in. It will fail if the struct does not define the overriden keys. No invalid struct is generated. - If the base is a mere map, it is not turned into a
%JSV.Schema{}
struct and thevalues
are merged in.
- If the base is a
Otherwise the base is cast to a
%JSV.Schema{}
struct- If the base is
nil
, the function returns a%JSV.Schema{}
struct with the givenvalues
. - If the base is a keyword list, the list will be turned into a
%JSV.Schema{}
struct and then thevalues
are merged in.
- If the base is
Examples
iex> JSV.Schema.merge(%JSV.Schema{description: "base"}, %{type: :integer})
%JSV.Schema{description: "base", type: :integer}
defmodule CustomSchemaStruct do
defstruct [:type, :description]
end
iex> JSV.Schema.merge(%CustomSchemaStruct{description: "base"}, %{type: :integer})
%CustomSchemaStruct{description: "base", type: :integer}
iex> JSV.Schema.merge(%CustomSchemaStruct{description: "base"}, %{format: :date})
** (KeyError) struct CustomSchemaStruct does not accept key :format
iex> JSV.Schema.merge(%{description: "base"}, %{type: :integer})
%{description: "base", type: :integer}
iex> JSV.Schema.merge(nil, %{type: :integer})
%JSV.Schema{type: :integer}
iex> JSV.Schema.merge([description: "base"], %{type: :integer})
%JSV.Schema{description: "base", type: :integer}
Defines or merges into a JSON Schema with
type: :integer
and maximum: -1
..
Defines or merges into a JSON Schema with
type: :string
and minLength: 1
..
Defines or merges into a JSON Schema with
type: :integer
and minimum: 0
..
Defines or merges into a JSON Schema with
type: :number
..
Defines or merges into a JSON Schema with
type: :object
.
See props/2
to define the properties as well.
.
Defines or merges into a JSON Schema with
oneOf: schemas
..
Defines or merges into a JSON Schema with
type: :integer
and minimum: 1
..
@spec properties( base(), properties() ) :: schema()
Defines or merges into a JSON Schema with
properties: properties
.
Does not set the type: :object
on the schema. Use props/2
for a
shortcut.
.
@spec props( base(), properties() ) :: schema()
Defines or merges into a JSON Schema with
type: :object
and properties: properties
..
Defines or merges into a JSON Schema with
$ref: 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))
.
Defines a JSON Schema with required: keys
or adds the given keys
if the
base schema already has a :required
definition.
Existing required keys are preserved.
Examples
iex> JSV.Schema.required(%{}, [:a, :b])
%{required: [:a, :b]}
iex> JSV.Schema.required(%{required: nil}, [:a, :b])
%{required: [:a, :b]}
iex> JSV.Schema.required(%{required: [:c]}, [:a, :b])
%{required: [:a, :b, :c]}
iex> JSV.Schema.required(%{required: [:a]}, [:a])
%{required: [:a, :a]}
Use merge/2
to replace existing required keys.
iex> JSV.Schema.merge(%{required: [:a, :b, :c]}, required: [:x, :y, :z])
%{required: [:x, :y, :z]}
Defines or merges into a JSON Schema with
type: :string
..
Defines or merges into a JSON Schema with
type: :string
and format: format
..
Defines or merges into a JSON Schema with
type: :string
and format: :uri
..
Defines or merges into a JSON Schema with
type: :string
and format: :uuid
..
Schema Casts
Includes the cast function in a schema. The cast function must be given as a list with two items:
- A module, as atom or string
- A tag, as atom, string or integer.
Atom arguments will be converted to string.
Examples
iex> JSV.Schema.cast([MyApp.Cast, :a_cast_function])
%JSV.Schema{"jsv-cast": ["Elixir.MyApp.Cast", "a_cast_function"]}
iex> JSV.Schema.cast([MyApp.Cast, 1234])
%JSV.Schema{"jsv-cast": ["Elixir.MyApp.Cast", 1234]}
iex> JSV.Schema.cast(["some_erlang_module", "custom_tag"])
%JSV.Schema{"jsv-cast": ["some_erlang_module", "custom_tag"]}
Defines or merges into a JSON Schema with
type: :string
and jsv-cast: ["Elixir.JSV.Cast", "string_to_atom"]
..
Defines or merges into a JSON Schema with
type: :string
, enum: enum
and jsv-cast: ["Elixir.JSV.Cast", "string_to_atom"]
.
Accepts a list of atoms and validates that a given value is 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 = JSV.Schema.props(status: JSV.Schema.string_to_atom_enum([:executing, :pending]))
iex> root = JSV.build!(schema)
iex> JSV.validate(%{"status" => "pending"}, root)
{:ok, %{"status" => :pending}}
Does not support nil
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
.
Defines or merges into a JSON Schema with
type: :string
and jsv-cast: ["Elixir.JSV.Cast", "string_to_boolean"]
..
Defines or merges into a JSON Schema with
type: :string
and jsv-cast: ["Elixir.JSV.Cast", "string_to_existing_atom"]
..
Defines or merges into a JSON Schema with
type: :string
and jsv-cast: ["Elixir.JSV.Cast", "string_to_float"]
..
Defines or merges into a JSON Schema with
type: :string
and jsv-cast: ["Elixir.JSV.Cast", "string_to_integer"]
..
Defines or merges into a JSON Schema with
type: :string
and jsv-cast: ["Elixir.JSV.Cast", "string_to_number"]
..
Types
@type properties() :: [{property_key(), schema()}] | %{optional(property_key()) => schema()}
@type schema() :: true | false | map()
@type schema_data() :: %{optional(binary()) => schema_data()} | [schema_data()] | number() | binary() | boolean() | nil
@type t() :: %JSV.Schema{ "$anchor": term(), "$comment": term(), "$defs": term(), "$dynamicAnchor": term(), "$dynamicRef": term(), "$id": term(), "$ref": term(), "$schema": term(), additionalItems: term(), additionalProperties: term(), allOf: term(), anyOf: term(), contains: term(), contentEncoding: term(), contentMediaType: term(), contentSchema: term(), default: term(), dependencies: term(), dependentRequired: term(), dependentSchemas: term(), deprecated: term(), description: term(), else: term(), enum: term(), examples: term(), exclusiveMaximum: term(), exclusiveMinimum: term(), format: term(), if: term(), items: term(), "jsv-cast": term(), maxContains: term(), maxItems: term(), maxLength: term(), maxProperties: term(), maximum: term(), minContains: term(), minItems: term(), minLength: term(), minProperties: term(), minimum: term(), multipleOf: term(), not: term(), oneOf: term(), pattern: term(), patternProperties: term(), prefixItems: term(), properties: term(), propertyNames: term(), readOnly: term(), required: term(), then: term(), title: term(), type: term(), unevaluatedItems: term(), unevaluatedProperties: term(), uniqueItems: term(), writeOnly: term() }
Functions
@spec new() :: t()
Returns a new empty schema.
Returns a new schema with the given key/values.
@spec normalize(term()) :: %{optional(binary()) => schema_data()} | [schema_data()] | number() | binary() | boolean() | nil
Normalizes a JSON schema with the help of JSV.Normalizer.normalize/3
with
the following customizations:
JSV.Schema
structs pairs where the value isnil
will be removed.%JSV.Schema{type: :object, properties: nil, allOf: nil, ...}
becomes%{"type" => "object"}
.- Modules names that export a schema will be converted to a raw schema with a
reference to that module that can be resolved automatically by
JSV.Resolver.Internal
. - Other atoms will be checked to see if they correspond to a module name that
exports a
schema/0
function.
Examples
defmodule Elixir.ASchemaExportingModule do
def schema, do: %{}
end
iex> JSV.Schema.normalize(ASchemaExportingModule)
%{"$ref" => "jsv:module:Elixir.ASchemaExportingModule"}
defmodule AModuleWithoutExportedSchema do
def hello, do: "world"
end
iex> JSV.Schema.normalize(AModuleWithoutExportedSchema)
"Elixir.AModuleWithoutExportedSchema"
Alias for merge/2
.
Returns whether the given atom is a module with a schema/0
exported
function.
Returns the given %JSV.Schema{}
as a map without keys containing
a nil
value.