Spectral (Spectral v0.9.2)

View Source

Elixir 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

decode_option()

@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; false gives the default behaviour.

encode_option()

@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; false gives the default behaviour.

schema_option()

@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; false gives the default behaviour.

sp_type_or_ref()

@type sp_type_or_ref() :: :spectra.sp_type_or_ref()

A spectra type structure or type reference. sp_type() is an opaque Erlang record.

sp_type_reference()

@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_info()

@type type_info() :: :spectra.type_info()

Spectra type information for a module. Alias for :spectra.type_info().

Functions

__using__(opts)

(macro)

Sets up the Spectral macros and injects __spectra_type_info__/0 function.

When you use Spectral, the following happens:

  • The spectral/1 macro is imported for documenting types and functions
  • A __spectra_type_info__/0 function is injected that returns type information
  • The @spectral attribute is registered (used internally by the spectral/1 macro)

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()}
end

Types 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 the params argument to Spectral.Codec callbacks 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: %{}
end

The __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 to sp_type records. Each sp_type contains:

    • Type structure information (e.g., sp_map, sp_simple_type, sp_union, etc.)
    • A meta field containing optional documentation
  • records - Map of record names (atoms) to sp_rec records containing record field information

  • functions - Map of {function_name, arity} tuples to lists of sp_function_spec records. When annotated with spectral/1, each spec's meta.doc field 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

decode(data, module, type_ref, format \\ :json, opts \\ [])

@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_decoded option is set)
  • module - Module containing the type definition
  • type_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}}

decode!(data, module, type_ref, format \\ :json, opts \\ [])

@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_decoded option is set)
  • module - Module containing the type definition
  • type_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

Examples

iex> ~s({"name":"Alice","age":30})
...> |> Spectral.decode!(Person, :t)
%Person{age: 30, name: "Alice", address: nil}

encode(data, module, type_ref, format \\ :json, opts \\ [])

@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 encode
  • module - Module containing the type definition
  • type_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_encoded option 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"

encode!(data, module, type_ref, format \\ :json, opts \\ [])

@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 encode
  • module - Module containing the type definition
  • type_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_encoded option is set

Raises

Examples

iex> %Person{name: "Alice", age: 30}
...> |> Spectral.encode!(Person, :t)
...> |> IO.iodata_to_binary()
~s({"age":30,"name":"Alice"})

schema(module, type_ref, format \\ :json_schema)

@spec schema(module() | type_info(), atom() | sp_type_or_ref(), atom()) :: iodata()

Generates a schema for the specified type.

Parameters

  • module - Module containing the type definition
  • type_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

schema(module, type_ref, format, opts)

@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 definition
  • type_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_encoded option is set

Examples

iex> schema = Spectral.schema(Person, :t, :json_schema, [:pre_encoded])
iex> is_map(schema)
true

sp_function_spec(args \\ [])

(macro)

sp_function_spec(record, args)

(macro)

spectral(metadata)

(macro)

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()}
end

Fields — before a @type

  • title - A short title for the type
  • description - A detailed description
  • 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 forwarded as the params argument to Spectral.Codec callbacks for this type (any term)

Fields — before a @spec

  • summary - Short one-line summary of the function / endpoint
  • description - A detailed description
  • deprecated - Whether the function is deprecated (boolean)