Spect (Spect v0.4.1)

Elixir typespec enhancements

Link to this section Summary

Functions

Typespec-driven object decoding

Decodes an object from a typespec, raising ArgumentError if the type is not found or Spect.ConvertError for a value error during conversion.

Link to this section Functions

Link to this function

to_spec(data, module, name \\ :t)

Specs

to_spec(data :: any(), module :: atom(), name :: atom()) ::
  {:ok, any()} | {:error, any()}

Typespec-driven object decoding

This function converts a data structure into a new one derived from a type specification. This provides for the effective decoding of (nested) data structures from serialization formats that do not support Elixir's rich set of types (JSON, etc.). Atoms can be decoded from strings, tuples from lists, structs from maps, etc.

data is the data structure to decode, module is the name of the module containing the type specification, and name is the name of the @type definition within the module (defaults to :t).

Examples

As mentioned above, a common use case is to decode a JSON document into an Elixir struct, for example using the Poison parser:

    "test.json"
    |> File.read!()
    |> Poison.Parser.parse!()
    |> Spect.to_spec!(Filmography)

where the Filmography module might contain the following structs:

    defmodule Filmography do

      defmodule Person do
        @type t :: %__MODULE__{
          name: String.t(),
          birth_year: pos_integer()
        }

        defstruct [:name, :birth_year]
      end

      @type acting_credit :: %{
          film: String.t(),
          lead?: boolean()
      }

      @type t :: %__MODULE__{
          subject: Person.t(),
          acting_credits: [acting_credit()]
      }

      defstruct [:subject, acting_credits: []]
    end

The conventional name for a module's primary type is t, so that is the default value for to_spec's third argument. However, that name is not mandatory, and modules can expose more than one type, so to_spec will accept any atom as a third argument and attempt to find a type with that name. Continuing with the above example:

  iex> data = %{"film" => "Amadeus", "lead?" => true}
  %{"film" => "Amadeus", "lead?" => true}

  iex> Spect.to_spec(data, Filmography, :acting_credit)
  {:ok, %{film: "Amadeus", lead?: true}}

If any of the nested fields in the typespec is declared as a DateTime.t(), to_spec will convert the value only if it is an ISO 8601 string or already a DateTime struct.

Link to this function

to_spec!(data, module, name \\ :t, args \\ [])

Specs

to_spec!(data :: any(), module :: atom(), name :: atom(), args :: list()) ::
  any()

Decodes an object from a typespec, raising ArgumentError if the type is not found or Spect.ConvertError for a value error during conversion.