spectra (spectra v0.7.0)

View Source

Summary

Functions

Decodes data from the specified format into an Erlang term based on type information.

Decodes data from the specified format into an Erlang term based on type information.

Encodes an Erlang term to the specified format based on type information.

Encodes an Erlang term to the specified format based on type information.

Generates a schema for the specified type in the given format.

Types

decode_option()

-type decode_option() :: pre_decoded | {pre_decoded, boolean()}.

encode_option()

-type encode_option() :: pre_encoded | {pre_encoded, boolean()}.

error()

-type error() ::
          #sp_error{location :: [string() | atom()],
                    type :: decode_error | type_mismatch | no_match | missing_data | not_matched_fields,
                    ctx ::
                        #{type =>
                              spectra:sp_type_or_ref() | spectra:map_field() | spectra:record_field(),
                          value => dynamic(),
                          errors => [{spectra:sp_type(), [#sp_error{}]}],
                          message => string(),
                          type_args => [{atom(), spectra:sp_type()}],
                          err_type => atom(),
                          err_reason => term()}}.

function_doc()

-type function_doc() :: #{summary => binary(), description => binary(), deprecated => boolean()}.

literal_value()

-type literal_value() :: integer() | atom() | [].

map_field()

-type map_field() ::
          #literal_map_field{kind :: assoc | exact,
                             name :: atom() | integer(),
                             binary_name :: binary(),
                             val_type :: spectra:sp_type()} |
          #typed_map_field{kind :: assoc | exact,
                           key_type :: spectra:sp_type(),
                           val_type :: spectra:sp_type()}.

missing_value()

-type missing_value() :: undefined | nil.

record_field()

-type record_field() ::
          #sp_rec_field{name :: atom(), binary_name :: binary(), type :: spectra:sp_type()}.

record_field_arg()

-type record_field_arg() :: {FieldName :: atom(), sp_type()}.

simple_types()

-type simple_types() ::
          string | nonempty_string | integer | non_neg_integer | neg_integer | pos_integer | float |
          number | boolean | binary | nonempty_binary | bitstring | nonempty_bitstring | atom | term |
          reference | pid | port | iolist | iodata | none | map.

sp_function_spec()

-type sp_function_spec() ::
          #sp_function_spec{args :: [spectra:sp_type()],
                            return :: spectra:sp_type(),
                            meta :: spectra:sp_function_spec_meta()}.

sp_function_spec_meta()

-type sp_function_spec_meta() :: #{doc => function_doc()}.

sp_type()

-type sp_type() ::
          #sp_simple_type{type :: spectra:simple_types(), meta :: spectra:sp_type_meta()} |
          #sp_rec_ref{record_name :: spectra:user_type_name(),
                      field_types :: [spectra:record_field_arg()],
                      meta :: spectra:sp_type_meta()} |
          #sp_user_type_ref{type_name :: spectra:user_type_name(),
                            variables :: [spectra:sp_type()],
                            meta :: spectra:sp_type_meta()} |
          #sp_var{name :: atom(), meta :: spectra:sp_type_meta()} |
          #sp_map{fields :: [spectra:map_field()],
                  struct_name :: undefined | atom(),
                  meta :: spectra:sp_type_meta()} |
          #sp_rec{name :: atom(),
                  fields ::
                      [#sp_rec_field{name :: atom(), binary_name :: binary(), type :: spectra:sp_type()}],
                  arity :: pos_integer(),
                  meta :: spectra:sp_type_meta()} |
          #sp_tuple{fields :: any | [spectra:sp_type()], meta :: spectra:sp_type_meta()} |
          #sp_type_with_variables{type :: spectra:sp_type(),
                                  vars :: [atom()],
                                  meta :: spectra:sp_type_meta()} |
          #sp_function{args :: any | [spectra:sp_type()],
                       return :: spectra:sp_type(),
                       meta :: spectra:sp_type_meta()} |
          #sp_union{types :: [spectra:sp_type()], meta :: spectra:sp_type_meta()} |
          #sp_literal{value :: spectra:literal_value(),
                      binary_value :: binary(),
                      meta :: spectra:sp_type_meta()} |
          #sp_range{type :: integer,
                    lower_bound :: integer(),
                    upper_bound :: integer(),
                    meta :: spectra:sp_type_meta()} |
          #sp_list{type :: spectra:sp_type(), meta :: spectra:sp_type_meta()} |
          #sp_nonempty_list{type :: spectra:sp_type(), meta :: spectra:sp_type_meta()} |
          #sp_maybe_improper_list{elements :: spectra:sp_type(),
                                  tail :: spectra:sp_type(),
                                  meta :: spectra:sp_type_meta()} |
          #sp_nonempty_improper_list{elements :: spectra:sp_type(),
                                     tail :: spectra:sp_type(),
                                     meta :: spectra:sp_type_meta()} |
          #sp_remote_type{mfargs :: {module(), atom(), [spectra:sp_type()]},
                          meta :: spectra:sp_type_meta()}.

sp_type_meta()

-type sp_type_meta() :: #{doc => type_doc()}.

sp_type_or_ref()

-type sp_type_or_ref() :: sp_type() | sp_type_reference().

sp_type_reference()

-type sp_type_reference() :: {type, Name :: atom(), Arity :: arity()} | {record, Name :: atom()}.

type_doc()

-type type_doc() ::
          #{title => binary(),
            description => binary(),
            examples => [dynamic()],
            examples_function => {module(), atom(), [term()]}}.

type_info()

-type type_info() :: spectra_type_info:type_info().

user_type_name()

-type user_type_name() :: atom().

var_type()

-type var_type() :: {VarName :: atom(), sp_type()}.

Functions

decode(Format, ModuleOrTypeinfo, TypeOrRef, Data)

-spec decode(Format :: atom(),
             ModuleOrTypeinfo :: module() | type_info(),
             TypeOrRef :: atom() | sp_type_or_ref(),
             Data :: dynamic()) ->
                {ok, dynamic()} | {error, [error()]}.

Decodes data from the specified format into an Erlang term based on type information.

The function validates the decoded data against the type specification and returns an error if the data doesn't match the expected type.

Example:

-module(my_module).
-type user_id() :: pos_integer().
-type status() :: active | inactive | pending.
-record(user, {id :: user_id(), name :: binary(), age :: integer(), status :: status()}).

1> spectra:decode(json, my_module, user_id, <<"123">>).
{ok, 123}

2> spectra:decode(json, my_module, user, <<"{\"id\":42,\"name\":\"Bob\",\"age\":25, \"status\":\"active\"}">>).
{ok, #user{id = 42, name = <<"Bob">>, age = 25, status = active}}

3> spectra:decode(binary_string, my_module, status, <<"active">>).
{ok, active}

4> spectra:decode(json, my_module, user_id, <<"\"not_a_number\"">>).
{error, [#sp_error{type = type_mismatch, ...}]}

decode(Format, ModuleOrTypeinfo, TypeOrRef, Data, Options)

-spec decode(Format :: atom(),
             ModuleOrTypeinfo :: module() | type_info(),
             TypeOrRef :: atom() | sp_type_or_ref(),
             Data :: dynamic(),
             Options :: [decode_option()]) ->
                {ok, dynamic()} | {error, [error()]}.

Decodes data from the specified format into an Erlang term based on type information.

Accepts an options list. Supported options:

  • pre_decoded: The input is already a decoded term (e.g. a JSON map from a web framework). Skips the deserialization step and passes the value directly to the type decoder.

Example:

1> DecodedJson = #{<<"id">> => 42, <<"name">> => <<"Bob">>}.
2> spectra:decode(json, my_module, user, DecodedJson, [pre_decoded]).
{ok, #user{id = 42, name = <<"Bob">>}}

encode(Format, ModuleOrTypeinfo, TypeOrRef, Data)

-spec encode(Format :: atom(),
             ModuleOrTypeinfo :: module() | type_info(),
             TypeOrRef :: atom() | sp_type_or_ref(),
             Data :: dynamic()) ->
                {ok, dynamic()} | {error, [error()]}.

Encodes an Erlang term to the specified format based on type information.

The function validates the Erlang term against the type specification before encoding and returns an error if the data doesn't match the expected type.

Example:

-module(my_module).
-type user_id() :: pos_integer().
-type status() :: active | inactive | pending.
-record(user, {id :: user_id(), name :: binary(), age :: integer(), status :: status()}).

1> spectra:encode(json, my_module, user_id, 123).
{ok, <<"123">>}

2> User = #user{id = 42, name = <<"Bob">>, age = 25, status = active}.
3> spectra:encode(json, my_module, user, User).
{ok, <<"{\"id\":42,\"name\":\"Bob\",\"age\":25, \"status\":\"active\"}">>}

4> spectra:encode(json, my_module, user_id, -5).
{error, [#sp_error{type = type_mismatch, ...}]}

encode(Format, ModuleOrTypeinfo, TypeOrRef, Data, Options)

-spec encode(Format :: atom(),
             ModuleOrTypeinfo :: module() | type_info(),
             TypeOrRef :: atom() | sp_type_or_ref(),
             Data :: dynamic(),
             Options :: [encode_option()]) ->
                {ok, dynamic()} | {error, [error()]}.

Encodes an Erlang term to the specified format based on type information.

Accepts an options list. Supported options:

  • pre_encoded: Skip the final serialization step and return the intermediate term instead of bytes. For JSON, this returns a map/list/scalar instead of a binary.

Example:

1> spectra:encode(json, my_module, user, #user{id = 42, name = <<"Bob">>}, [pre_encoded]).
{ok, #{<<"id">> => 42, <<"name">> => <<"Bob">>}}

schema(Format, ModuleOrTypeinfo, TypeOrRef)

-spec schema(Format :: atom(),
             ModuleOrTypeinfo :: module() | type_info(),
             TypeOrRef :: atom() | sp_type_or_ref()) ->
                iodata().

Generates a schema for the specified type in the given format.

Example:

-module(my_module).
-type user_id() :: pos_integer().
-type status() :: active | inactive | pending.
-record(user, {id :: user_id(), name :: binary(), age :: integer(), status :: status()}).

1> spectra:schema(json_schema, my_module, user).
<<"{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"integer\"},...}}">>

2> spectra:schema(json_schema, my_module, status).
<<"{\"oneOf\":[{\"enum\":[\"active\"]},{\"enum\":[\"inactive\"]},{\"enum\":[\"pending\"]}]}">>

3> spectra:schema(json_schema, my_module, {type, user_id, 0}).
<<"{\"type\":\"integer\",\"minimum\":1}">>