Current section

Files

Jump to
spectra src spectra_codec.erl
Raw

src/spectra_codec.erl

-module(spectra_codec).
-doc """
Behaviour for custom codecs that extend spectra's encoding, decoding, and schema
generation for specific types and formats.
## Params argument
The `Params` argument passed to every callback is the value of the
`type_parameters` key in the `-spectra(#{type_parameters => Value})` attribute
on the type definition, or `undefined` if that attribute is absent.
**Important**: `Params` is unrelated to Erlang type variables (generic
parameters such as the `T` in `-type wrapper(T) :: {wrapper, T}`). When a
codec is invoked for a parameterised type, the concrete bindings for those type
variables are **not** available in `Params`. Codec types with Erlang type
variables are therefore not useful in practice — the codec cannot act on the
variable bindings. Use `type_parameters` in the `-spectra` attribute to pass
static, per-type configuration to your codec instead.
""".
-callback encode(
Format :: atom(),
Module :: module(),
TypeRef :: spectra:sp_type_reference(),
Data :: dynamic(),
Params :: term()
) ->
spectra:codec_encode_result().
-callback decode(
Format :: atom(),
Module :: module(),
TypeRef :: spectra:sp_type_reference(),
Input :: dynamic(),
Params :: term()
) ->
spectra:codec_decode_result().
-callback schema(
Format :: atom(),
Module :: module(),
TypeRef :: spectra:sp_type_reference(),
Params :: term()
) ->
dynamic().
-optional_callbacks([schema/4]).
-export([
try_codec_encode/4,
try_codec_decode/4,
try_codec_schema/3
]).
-spec try_codec_encode(
Mod :: module(),
Format :: atom(),
Type :: spectra:sp_type(),
Data :: dynamic()
) -> spectra:codec_encode_result().
try_codec_encode(Mod, Format, Type, Data) ->
#{name := TypeReference} = spectra_type:get_meta(Type),
case spectra_type_info:find_codec(Mod, TypeReference) of
{ok, M} ->
M:encode(Format, Mod, TypeReference, Data, spectra_type:parameters(Type));
error ->
continue
end.
-spec try_codec_decode(
Mod :: module(),
Format :: atom(),
Type :: spectra:sp_type(),
Data :: dynamic()
) -> spectra:codec_decode_result().
try_codec_decode(Mod, Format, Type, Data) ->
#{name := TypeReference} = spectra_type:get_meta(Type),
case spectra_type_info:find_codec(Mod, TypeReference) of
{ok, M} ->
M:decode(Format, Mod, TypeReference, Data, spectra_type:parameters(Type));
error ->
continue
end.
-spec try_codec_schema(
Mod :: module(),
Format :: atom(),
Type :: spectra:sp_type()
) -> dynamic() | continue.
try_codec_schema(Mod, Format, Type) ->
#{name := TypeReference} = spectra_type:get_meta(Type),
case spectra_type_info:find_codec(Mod, TypeReference) of
{ok, M} ->
case erlang:function_exported(M, schema, 4) of
true ->
M:schema(Format, Mod, TypeReference, spectra_type:parameters(Type));
false ->
erlang:error({schema_not_implemented, M, TypeReference})
end;
error ->
continue
end.