andreashasse

andreashasse

Spectral - Type-driven JSON encoding/decoding, validation, and OpenAPI generation

I’m happy to announce Spectral, a library that lets your Elixir structs and @type specs become the single source of truth for validation, encoding/decoding (primarily JSON), and OpenAPI schema generation. If you’re familiar with Pydantic in the Python world, the idea is similar.

Who is this for?

Spectral is aimed at developers building and consuming JSON who want to avoid keeping multiple representations of the same information in sync — a type definition here, validation logic there, a JSON schema somewhere else. If your types already express the shape of your data, Spectral lets them do more of the work.

Example

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

  @type role :: :user | :admin

  @type t :: %Person{
    name: String.t(),
    age: non_neg_integer(),
    role: role()
  }

  @spec from_json(binary()) :: {:ok, t()} | {:error, [Spectral.Error.t()]}
  def from_json(json), do: Spectral.decode(json, __MODULE__, :t, :json)

  @spec to_json(t()) :: {:ok, iodata()} | {:error, [Spectral.Error.t()]}
  def to_json(person), do: Spectral.encode(person, __MODULE__, :t, :json)
end

{:ok, person} = Person.from_json(~s({"name": "Alice", "age": 30, "role": "admin"}))
#=> {:ok, %Person{name: "Alice", age: 30, role: :admin}}

Person.to_json(person)
#=> {:ok, ...}

Person.from_json(~s({"name": "Alice", "age": -1, "role": "admin"}))
#=> {:error, [%Spectral.Error{location: ["age"], type: :type_mismatch, ...}]}

# Generate OpenAPI schema
Spectral.schema(Person, :t)

:package: Hex: spectral | Hex
:open_book: Docs: Spectral v0.13.0 — Documentation

https://github.com/andreashasse/spectral

Most Liked Responses

DaAnalyst

DaAnalyst

Maybe combine the struct type and defstruct into one (with a macro)?

It’s been a while since I developed my deftypestruct and I’ve seen someone posting a library doing something very similar here like a week or two ago.

ex (my lib creates {module_name}.t(), but can be made to use a different type name too):

defmodule Person do
  deftypestruct %{
    name: String.t(),
    age: non_neg_integer() | nil,
    role: role()
  }
end

I also check against nil so unless explicitly permitted (like the age field above) the lib raises.

mudasobwa

mudasobwa

Creator of Cure

You might be interested in taking a look at estructura allowing transparent nesting, coercion, validation, and (!) generation for stream_data property-based testing out of the box.

andreashasse

andreashasse

The deftypestruct approach is elegant. As your library generates the type and put it in the beam it works well with spectral :slight_smile:

Thanks for pointing out the nil handling in spectral, you can find more info in the spectral docs nil section.

Where Next?

Popular in Announcing Top

Crowdhailer
The latest release of Ace (0.10.0) includes serving content over HTTP/2. I have started writing a webserver to teach my self more about...
New
pkrawat1
Presenting Aviacommerce, open source e-commerce platform in Elixir Aviacommerce is an open source e-commerce platform in Elixir. We at...
New
devonestes
Introducing assertions, the library that helps you write really great test assertions! GitHub: GitHub - devonestes/assertions: Helpful a...
New
sasajuric
I’d like to announce a small library called boundaries. This is an experimental project which explores the idea of enforcing boundaries ...
New
kelvinst
Hey everyone! Well, we made this lib a while ago and now we decided to finally go out and public with it! It’s a tool for creating and m...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 52341 488
New
Crowdhailer
Experimenting with this code. OK.try do user <- fetch_user(1) cart <- fetch_cart(1) order = checkout(cart, user) save_orde...
New
ahamez
Hi everyone, I’ve been working on this protobuf library for 3 years. We use it in the company I work for, EasyMile, to communicate with ...
New
aditya7iyengar
Rummage.Ecto and Rummage.Phoenix provide ways to perform Searching, Sorting and Pagination over Ecto queries and Phoenix collections. Fo...
New
woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New

Other popular topics Top

9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
Lily
In templates/appointment/index.html.eex: <%= for appointment <- @appointments do %> <tr> <td><%= appoi...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39297 209
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New

We're in Beta

About us Mission Statement