Formata (formata v0.1.0)

Copy Markdown View Source

Composable data codecs, schemas, and validation for Elixir.

Formata decodes and encodes JSON, TOML, and YAML, validates data against native (Formata.Backplane.Schema) schemas or JSON Schema documents (Formata.Backplane.JsonSchema), and applies explicit transformations.

iex> {:ok, schema} = Formata.Schema.native(%{"port" => {:required, {:integer, gte: 1}}})
iex> {:ok, data} = Formata.decode(~s({"port": 8080}), :json)
iex> Formata.validate(data, schema)
:ok
iex> Formata.encode(data, :toml)
{:ok, "port = 8080\n"}

Conventions

  • Data-first arguments: decode(input, format), encode(data, format), convert(input, from, to), validate(data, schema), cast(data, schema).
  • Producing operations return {:ok, result}; validate/3 returns :ok.
  • Expected failures return {:error, errors} with a non-empty list of Formata.Error structs.

Summary

Functions

Validates and transforms data with a native schema.

Converts a document from one format to another.

Decodes a document.

Encodes data as a document.

Validates data against a compiled schema without changing it.

Types

errors()

@type errors() :: [Formata.Error.t(), ...]

format()

@type format() :: Formata.Codec.format()

Functions

cast(data, schema, opts \\ [])

@spec cast(term(), Formata.Schema.t(), keyword()) ::
  {:ok, term()} | {:error, errors()}

Validates and transforms data with a native schema.

Applies the schema's defaults, coercions, and transforms, and handles unknown fields per unknown: :strip | :allow | :reject (default :strip). See Formata.Schema.Native for the order of operations. Returns {:ok, data}.

JSON Schemas define no transformations, so casting with one returns an :unsupported error.

iex> {:ok, schema} = Formata.Schema.native(%{"page" => {:coerce, :integer}})
iex> Formata.cast(%{"page" => "2"}, schema)
{:ok, %{"page" => 2}}

convert(input, from, to, opts \\ [])

@spec convert(binary(), format(), format(), keyword()) ::
  {:ok, binary()} | {:error, errors()}

Converts a document from one format to another.

The document is decoded into Elixir values, the optional :transforms are applied in order (see Formata.Transform.apply_all/2), and the result is encoded. No intermediate format is used.

Options

  • :transforms - explicit transformations, for example [:drop_nils]
  • :decode - options for the source codec
  • :encode - options for the target codec

Examples

iex> Formata.convert(~s({"name": "Ada", "nick": null}), :json, :toml, transforms: [:drop_nils])
{:ok, ~s(name = "Ada"\n)}

iex> {:error, [%Formata.Error{kind: :encode, code: :unrepresentable}]} =
...>   Formata.convert(~s({"nick": null}), :json, :toml)

decode(input, format, opts \\ [])

@spec decode(binary(), format(), keyword()) :: {:ok, term()} | {:error, errors()}

Decodes a document.

format is :json, :toml, :yaml, or a Formata.Codec module; opts are passed to the codec.

iex> Formata.decode("name: Ada", :yaml)
{:ok, %{"name" => "Ada"}}

iex> {:error, [%Formata.Error{kind: :parse, code: :syntax}]} = Formata.decode("{", :json)

encode(data, format, opts \\ [])

@spec encode(term(), format(), keyword()) :: {:ok, binary()} | {:error, errors()}

Encodes data as a document.

Values the target format cannot represent are rejected with :encode errors; nothing is dropped or converted implicitly.

iex> Formata.encode(%{"a" => [1, 2]}, :json)
{:ok, ~s({"a":[1,2]})}

iex> {:error, [%Formata.Error{kind: :encode, code: :unrepresentable, path: ["a"]}]} =
...>   Formata.encode(%{"a" => nil}, :toml)

validate(data, schema, opts \\ [])

@spec validate(term(), Formata.Schema.t(), keyword()) :: :ok | {:error, errors()}

Validates data against a compiled schema without changing it.

No coercion, defaults, or field stripping happen; the data is checked as given. Returns :ok or {:error, errors}.

Options

  • unknown: :allow | :reject - native schemas only (default :allow). JSON Schemas accept no options; their own additionalProperties / unevaluatedProperties apply.

Examples

iex> {:ok, schema} = Formata.Schema.native(%{age: :integer})
iex> Formata.validate(%{"age" => 30}, schema)
:ok
iex> {:error, [%Formata.Error{kind: :validation, code: :type, path: ["age"]}]} =
...>   Formata.validate(%{"age" => "30"}, schema)