/edantic

JSON casting and validation library based on Elixir type specifications

Primary LanguageElixirMIT LicenseMIT

Build Status Coverage Status

Edantic

Edantic is a library for casting «plain» JSON-originated data into Elixir data structures with necessary validations.

Example

Assume there is a module with a corresponding struct:

defmodule Person do
  defstruct [
    :age, :name, :department
  ]

  @type first_name() :: String.t
  @type second_name() :: String.t

  @type t() :: %__MODULE__{
    age: non_neg_integer(),
    name: {first_name(), second_name()},
    department: :finance | :it
  }
end

And there is some JSON-originated data:

data = %{
  "age" => 23,
  "name" => ["girolamo", "savonarola"],
  "department" => "it"
}

With Edantic we can simultaneously validate this data and convert it into Elixir structures:

import Edantic

{:ok, person} = Edantic.cast(Person.t(), data)

person == %Person{
  age: 23,
  name: {"girolamo", "savonarola"},
  department: :it
}
data_bad_department = %{
  "age" => 23,
  "name" => ["girolamo", "savonarola"],
  "department" => "unknown"
}

{:error, error} = Edantic.cast(Person.t(), data_bad_department)

error
|> Edantic.CastError.format()
|> IO.puts()
key-value pair does not match to any of the specified for the map
  data: %{"department" => "unknown"}
  type: %Edantic.Support.Types.Person{age: non_neg_integer(), department: :finance | :it, name: {first_name(), second_name()}}}

JSON

By «JSON-originated data» is denoted all the data matching the following type t():

@type key() :: String.t()
@type value() ::
        String.t() | nil | boolean | integer() | float() | %{optional(key()) => value()} | [value()]
@type t() :: value()

Primitive conversions

Since plain data structures are rather poor, there are some automatic enrichments allowed while casting:

  • Strings can be cast to corresponding atoms "a" -> :a.
  • Lists of suitable size can be cast to tuples [1, "a"] -> {1, :a}.
  • Maps can be cast to an arbitrary struct with the same set of fields %{a: 123} -> %SomeSt{a: 123} if fields pass validations.

Usage in releases

Since type info is located in separate beam chunks which are stripped by default, be sure your releases do not strip them.

For example, by setting strip_beams option to false.

  def project do
    [
      ...
      deps: deps(),
      releases: [
        release_name: [
          strip_beams: false,
          ...
        ]
      ]
    ]
  end

Installation

If available in Hex, the package can be installed by adding edantic to your list of dependencies in mix.exs:

def deps do
  [
    {:edantic, "~> 0.1.0"}
  ]
end

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/edantic.

License

This software is licensed under MIT License.