Spectral (Spectral v0.9.2)
View SourceElixir wrapper for the Erlang spectra library.
Provides idiomatic Elixir interfaces for encoding, decoding, and schema generation based on type specifications.
API
All functions are designed to work well with Elixir's pipe and with operators:
%Person{name: "Alice", age: 30}
|> Spectral.encode!(Person, :t)
|> send_response()
with {:ok, json} <- Spectral.encode(%Person{name: "Alice"}, Person, :t) do
send_response(json)
end
Summary
Types
Options for schema/4.
A spectra type structure or type reference. sp_type() is an opaque Erlang record.
A reference to a named type {:type, name, arity} or record {:record, name}.
Spectra type information for a module. Alias for :spectra.type_info().
Functions
Sets up the Spectral macros and injects __spectra_type_info__/0 function.
Decodes data from the specified format.
Decodes data from the specified format, raising on error.
Encodes data to the specified format.
Encodes data to the specified format, raising on error.
Generates a schema for the specified type.
Generates a schema for the specified type, with options.
Adds documentation metadata for a type.
Types
@type decode_option() :: :pre_decoded | {:pre_decoded, boolean()}
Options for decode/5 and decode!/5.
:pre_decoded- Accept an already-decoded JSON term as input, skipping the JSON parsing step. Equivalent to{:pre_decoded, true}.{:pre_decoded, boolean()}- Explicit boolean form;falsegives the default behaviour.
@type encode_option() :: :pre_encoded | {:pre_encoded, boolean()}
Options for encode/5 and encode!/5.
:pre_encoded- Skip the final JSON serialization step and return the intermediate JSON term (a map/list) instead of iodata. Equivalent to{:pre_encoded, true}.{:pre_encoded, boolean()}- Explicit boolean form;falsegives the default behaviour.
@type schema_option() :: :pre_encoded | {:pre_encoded, boolean()}
Options for schema/4.
:pre_encoded- Skip the final JSON serialization step and return a map instead of iodata. Equivalent to{:pre_encoded, true}.{:pre_encoded, boolean()}- Explicit boolean form;falsegives the default behaviour.
@type sp_type_or_ref() :: :spectra.sp_type_or_ref()
A spectra type structure or type reference. sp_type() is an opaque Erlang record.
@type sp_type_reference() :: {:type, atom(), non_neg_integer()} | {:record, atom()}
A reference to a named type {:type, name, arity} or record {:record, name}.
@type type_info() :: :spectra.type_info()
Spectra type information for a module. Alias for :spectra.type_info().
Functions
Sets up the Spectral macros and injects __spectra_type_info__/0 function.
When you use Spectral, the following happens:
- The
spectral/1macro is imported for documenting types and functions - A
__spectra_type_info__/0function is injected that returns type information - The
@spectralattribute is registered (used internally by thespectral/1macro)
Annotating Types
Place spectral/1 immediately before a @type definition to attach documentation
that will appear in generated JSON schemas and OpenAPI component schemas:
defmodule Person do
use Spectral
defstruct [:name, :age]
spectral title: "Person", description: "A person record"
@type t :: %Person{name: String.t(), age: non_neg_integer()}
endTypes without a spectral call will not have title/description in their JSON schemas.
Type Documentation Fields
title- A short title for the type (string)description- A detailed description (string)deprecated- Whether the type is deprecated (boolean)examples- Example values (list)examples_function-{module, function_name, args}tuple; called at schema generation time to produce examples. The function must be exported.type_parameters- Static configuration passed as theparamsargument toSpectral.Codeccallbacks for this type (any term)
Annotating Functions (Endpoint Documentation)
Place spectral/1 immediately before a @spec definition to attach endpoint
documentation. This metadata is used by Spectral.OpenAPI.endpoint/5 to automatically
populate the OpenAPI operation fields:
defmodule MyController do
use Spectral
spectral summary: "Get user", description: "Returns a user by ID"
@spec show(map(), map()) :: map()
def show(_conn, _params), do: %{}
end
# Build the endpoint — docs are read automatically from the function's metadata
endpoint = Spectral.OpenAPI.endpoint(:get, "/users/{id}", MyController, :show, 2)Function Documentation Fields
summary- Short summary of the endpoint operation (string)description- Longer description of the operation (string)deprecated- Whether the endpoint is deprecated (boolean)
Multiple Annotations
A module can mix type and function annotations freely:
defmodule MyModule do
use Spectral
spectral title: "Public API", description: "The public interface"
@type public_api :: map()
spectral summary: "List items", description: "Returns all items"
@spec index(map(), map()) :: map()
def index(_conn, _params), do: %{}
endThe __spectra_type_info__/0 Function
The injected __spectra_type_info__/0 function returns detailed type information for the module.
It extracts type definitions from the module's compiled BEAM file and enriches them with
documentation from spectral attributes.
Return Value Structure
Returns a type_info record (Erlang record from spectra library):
{:type_info, types, records, functions}Fields:
types- Map of{type_name, arity}tuples tosp_typerecords. Eachsp_typecontains:- Type structure information (e.g.,
sp_map,sp_simple_type,sp_union, etc.) - A
metafield containing optional documentation
- Type structure information (e.g.,
records- Map of record names (atoms) tosp_recrecords containing record field informationfunctions- Map of{function_name, arity}tuples to lists ofsp_function_specrecords. When annotated withspectral/1, each spec'smeta.docfield holds the endpoint documentation.
Type Documentation (meta field)
When you use the spectral macro to document a type, the documentation is stored in that
type's meta field as:
%{doc: %{title: "...", description: "...", examples: [...]}}Function Documentation (sp_function_spec meta field)
When you use the spectral macro to document a function, the documentation is stored in
each matching sp_function_spec's meta field as:
%{doc: %{summary: "...", description: "..."}}Use Spectral.TypeInfo.get_function_doc/3 to retrieve it.
Example Usage
defmodule Person do
use Spectral
spectral title: "Person", description: "A person record"
@type t :: %Person{name: String.t()}
end
# Access type information
{:type_info, types, records, functions} = Person.__spectra_type_info__()
# Get the type definition for Person.t/0
person_type = types[{:t, 0}]
# Extract documentation from the type's meta field
meta = :spectra_type.get_meta(person_type)
# => %{doc: %{title: "Person", description: "A person record"}}Use Cases
This function is primarily used internally by Spectral's encoding, decoding, and schema generation functions, but can be called directly for:
- Introspection and debugging
- Custom tooling that needs access to type information
- Documentation generation
- Type analysis and validation tools
@spec decode(dynamic(), module() | type_info(), atom() | sp_type_or_ref(), atom(), [ decode_option() ]) :: {:ok, dynamic()} | {:error, [Spectral.Error.t()]}
Decodes data from the specified format.
Parameters
data- The data to decode (binary for JSON, string for string format; or a JSON term when:pre_decodedoption is set)module- Module containing the type definitiontype_ref- Type reference (typically an atom like:t)format- Format to decode from (default::json)opts- Options list (default:[]). Supported options::pre_decoded- Accept an already-decoded JSON term as input, skipping JSON parsing.
Returns
{:ok, dynamic()}- Decoded data on success{:error, [%Spectral.Error{}]}- List of errors on failure
Examples
iex> ~s({"name":"Alice","age":30,"address":{"street":"Ystader Straße", "city": "Berlin"}})
...> |> Spectral.decode(Person, :t)
{:ok, %Person{age: 30, name: "Alice", address: %Person.Address{street: "Ystader Straße", city: "Berlin"}}}
iex> ~s({"name":"Alice"})
...> |> Spectral.decode(Person, :t)
{:ok, %Person{age: nil, name: "Alice", address: nil}}
iex> ~s({"name":"Alice","age":30,"extra_field":"ignored"})
...> |> Spectral.decode(Person, :t)
{:ok, %Person{age: 30, name: "Alice", address: nil}}
iex> Spectral.decode(%{"name" => "Alice", "age" => 30}, Person, :t, :json, [:pre_decoded])
{:ok, %Person{age: 30, name: "Alice", address: nil}}
@spec decode!(dynamic(), module() | type_info(), atom() | sp_type_or_ref(), atom(), [ decode_option() ]) :: dynamic()
Decodes data from the specified format, raising on error.
Like decode/5 but raises Spectral.Error instead of returning an error tuple.
Parameters
data- The data to decode (binary for JSON, string for string format; or a JSON term when:pre_decodedoption is set)module- Module containing the type definitiontype_ref- Type reference (typically an atom like:t)format- Format to decode from (default::json)opts- Options list (default:[]). Supported options::pre_decoded- Accept an already-decoded JSON term as input, skipping JSON parsing.
Returns
dynamic()- Decoded data on success
Raises
Spectral.Error- If decoding fails
Examples
iex> ~s({"name":"Alice","age":30})
...> |> Spectral.decode!(Person, :t)
%Person{age: 30, name: "Alice", address: nil}
@spec encode(dynamic(), module() | type_info(), atom() | sp_type_or_ref(), atom(), [ encode_option() ]) :: {:ok, iodata() | dynamic()} | {:error, [Spectral.Error.t()]}
Encodes data to the specified format.
Parameters
data- The data to encodemodule- Module containing the type definitiontype_ref- Type reference (typically an atom like:t)format- Format to encode to (default::json)opts- Options list (default:[]). Supported options::pre_encoded- Return the intermediate JSON term (map/list) instead of iodata.
Returns
{:ok, iodata()}- Encoded data on success (default){:ok, dynamic()}- Encoded data as a JSON term when:pre_encodedoption is set{:error, [%Spectral.Error{}]}- List of errors on failure
Examples
iex> person = %Person{name: "Alice", age: 30, address: %Person.Address{street: "Ystader Straße", city: "Berlin"}}
...> with {:ok, json} <- Spectral.encode(person, Person, :t) do
...> IO.iodata_to_binary(json)
...> end
~s({"address":{"city":"Berlin","street":"Ystader Straße"},"age":30,"name":"Alice"})
iex> {:ok, json} = %Person{name: "Alice"} |> Spectral.encode(Person, :t)
iex> IO.iodata_to_binary(json)
~s({"name":"Alice"})
iex> {:ok, term} = %Person{name: "Alice", age: 30} |> Spectral.encode(Person, :t, :json, [:pre_encoded])
iex> term["name"]
"Alice"
@spec encode!(dynamic(), module() | type_info(), atom() | sp_type_or_ref(), atom(), [ encode_option() ]) :: iodata() | dynamic()
Encodes data to the specified format, raising on error.
Like encode/5 but raises Spectral.Error instead of returning an error tuple.
Parameters
data- The data to encodemodule- Module containing the type definitiontype_ref- Type reference (typically an atom like:t)format- Format to encode to (default::json)opts- Options list (default:[]). Supported options::pre_encoded- Return the intermediate JSON term (map/list) instead of iodata.
Returns
iodata()- Encoded data on success (default)dynamic()- Encoded data as a JSON term when:pre_encodedoption is set
Raises
Spectral.Error- If encoding fails
Examples
iex> %Person{name: "Alice", age: 30}
...> |> Spectral.encode!(Person, :t)
...> |> IO.iodata_to_binary()
~s({"age":30,"name":"Alice"})
Generates a schema for the specified type.
Parameters
module- Module containing the type definitiontype_ref- Type reference (typically an atom like:t)format- Schema format (default::json_schema)
Returns
iodata()- Generated schema
Examples
iex> schemadata = Spectral.schema(Person, :t)
iex> is_binary(IO.iodata_to_binary(schemadata))
true
@spec schema(module() | type_info(), atom() | sp_type_or_ref(), atom(), [ schema_option() ]) :: iodata() | dynamic()
Generates a schema for the specified type, with options.
Like schema/3 but accepts an options list.
Parameters
module- Module containing the type definitiontype_ref- Type reference (typically an atom like:t)format- Schema format (default::json_schema)opts- Options list. Supported options::pre_encoded- Return a map instead of iodata, skipping JSON encoding.
Returns
iodata()- Generated schema (default)dynamic()- Schema as a map when:pre_encodedoption is set
Examples
iex> schema = Spectral.schema(Person, :t, :json_schema, [:pre_encoded])
iex> is_map(schema)
true
Adds documentation metadata for a type.
Use this macro immediately before a @type definition to document it.
The line number is automatically captured to pair the documentation
with the correct type.
Example
defmodule Person do
use Spectral
spectral title: "Person", description: "A person record"
@type t :: %Person{name: String.t()}
endFields — before a @type
title- A short title for the typedescription- A detailed descriptiondeprecated- Whether the type is deprecated (boolean)examples- Example values (list)examples_function-{module, function_name, args}tuple; called at schema generation time to produce examples. The function must be exported.type_parameters- Static configuration forwarded as theparamsargument toSpectral.Codeccallbacks for this type (any term)
Fields — before a @spec
summary- Short one-line summary of the function / endpointdescription- A detailed descriptiondeprecated- Whether the function is deprecated (boolean)