Open-source project
elixir-toniq/norm avatar
elixir-toniq/norm

Norm specifies data in Elixir and reuses the predicates you already have

Data specification and generation

701 stars29 forksElixirMIT

At a glance

What is it?
Norm is an Elixir library for describing the structure of data, used both to validate incoming values and to generate new ones. It deliberately ships no predicates of its own, wrapping the guards you already write with spec/1, and it adds contract annotations for function definitions plus a permissive schema model designed to let data grow over time.
Who is it for?
Norm suits an Elixir codebase that already validates data in one place and wants the same specification to drive generation and contract checks, and it does not suit a team that needs a schema to reject unknown fields or to cast string keys onto atom keys, since both are deliberate design choices here.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Elixir, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A specification library that ships no predicates of its own

Norm is a system for specifying the structure of data, and it can be used for validation and for generation. The unusual decision is stated at the top of the documentation: Norm does not provide any set of predicates and instead allows you to re-use any of your existing validations.

In practice that means a specification is built from ordinary Elixir guards and predicates, wrapped in a macro.

elixir
spec(is_binary())
spec(is_integer() and &(&1 > 0))
spec(is_binary() and fn str -> String.length(str) > 0 end)

The data is always passed as the first argument to the predicate, which is what lets you reuse a predicate with more than one parameter.

elixir
iex> defmodule Predicate do
...>   def greater?(x, y), do: x > y
...> end
iex> conform!(10, spec(Predicate.greater?(5)))
10

This is a different philosophy from a validation library that ships its own vocabulary of types. There is nothing to learn and nothing to translate, and the specification of an integer being positive is the same expression you would write in a guard. The cost is that the resulting specification is Elixir code, so it cannot be read by something that does not evaluate Elixir, such as a generated schema document or a form validator in another language.

conform! raises, conform returns a list of errors

Norm validates data by conforming the value to a specification, and if the value does not conform then a list of errors is returned. There are two functions for this, conform/2 and conform!/2. The documentation is direct about which to use: if you need to return a list of well defined errors then use conform/2, and otherwise conform!/2 is generally more useful.

The bang version raises Norm.MismatchError, and the message names the value that failed and the predicate that rejected it, in the form val: -50 fails: &(&1 > 0).

One design decision in the argument order is worth calling out, because it changes how the library reads in a pipeline. The input data is always passed as the first argument to conform, so that calls to conform are easily chainable. Specs read as the second argument and data flows first, which means a spec can be built once and applied to many values, or a value can be threaded through several conform calls in a pipe.

There is a second half to the library that the validation API is only a gateway to. Because a specification describes the shape of a value, the same description can be used to produce values of that shape, which is where the property-based-testing topic on the repository comes from. The documentation shows a schema being handed to gen/1 and the result sampled.

elixir
iex> generated_users =
...>   user_schema
...>   |> gen()
...>   |> Enum.take(3)

The generated values are then checked against the same conditions that the schema was built from, so a generator and a validator share one definition.

Schemas are permissive on purpose, which cuts both ways

The schema/1 function specifies maps and structs, and the way it handles missing and extra data is the most consequential design choice in the library.

The documentation states that schemas are designed to allow systems to grow over time, and provides this in two ways. The first is that any unspecified fields in the input are passed through when conforming the input. The second is that all keys in a schema are optional.

elixir
iex> conform!(%{foo: :foo, bar: :bar}, user_schema)
%{foo: :foo, bar: :bar}

The reasoning is given explicitly. This allows callers to start sending new data over time without coordination with the consuming function. A producer can add a field, and the consumer will carry it rather than reject the whole payload for containing something it has never heard of.

The documentation also acknowledges the reaction this invites. If you are used to more restrictive systems for managing data these might seem like odd choices, and the answer is deferred to a later section on selections, where required keys can be expressed. The trade is real: a permissive schema will not catch a typo in a key name, and it will not tell you that a field you thought was mandatory is simply missing. Teams that want the opposite default have to opt into it deliberately.

For structs the same model applies, and the convention is to provide an s/0 function in the module that defines the struct so that schemas can be shared throughout the system. Only the specced keys are conformed, and the remaining keys are checked for presence.

Atom and string keys are matched exactly, with no casting

Key semantics are worth reading carefully because they determine whether a payload from an external source will conform at all. Atom and string keys are matched explicitly, and there is no casting that occurs when conforming values. If you need to match on string keys you should specify your schema with string keys.

That is a simple rule with a large practical effect. A JSON payload decoded with string keys will not satisfy a schema written with atom keys, and Norm will not bridge the gap for you. The library's answer is that you decide which key type your domain uses and write the schema to match, which keeps the behaviour of the conform function predictable at the cost of a conversion step somewhere in your pipeline.

Tuples and atoms can be matched without needing to wrap them in a function, which is what makes the ok and error tuple pattern from Elixir idiomatic code directly expressible.

elixir
iex> {1, "hello"} = conform!({1, "hello"}, {spec(is_integer()), spec(is_binary())})
{1, "hello"}

Because bare tuples are supported, functions returning {:ok, term()} and {:error, term()} can be validated directly, and the documentation notes that these specifications can be combined with one_of/1 to create union types. The example builds a result spec that accepts an ok tuple carrying a binary and an error tuple carrying anything, then conforms a function's return value against it.

coll_of covers collections, with options for count, uniqueness and output type

Collections are described with coll_of, and the options list is where the interesting behaviour sits.

The kind option takes a predicate function describing the kind of collection being conformed. The distinct option is a boolean specifying whether the collection should have distinct elements. min_count and max_count set the minimum and maximum element count, and into sets the output collection the input will be conformed into, where the input type is used if it is not specified.

elixir
iex> conform!([:a, :b, :c], coll_of(spec(is_atom), into: MapSet.new()))

The into option is the one with the most reach, because it means conforming is not purely a check. A list can be conformed into a MapSet, and a struct-shaped input can be conformed into a different shape, which makes conform usable as a normalisation step rather than only as a guard. Combined with distinct and the count bounds, a single collection specification can express a fairly wide range of rules, and each option is a knob you either set or leave at the default.

For functions, the library also offers contract annotations. A module can use Norm and define specs for its functions, then declare a contract tying them together.

elixir
defmodule Colors do
  use Norm

  def rgb(), do: spec(is_integer() and &(&1 in 0..255))

  def hex(), do: spec(is_binary() and &String.starts_with?(&1, "#"))

The contract line ties the three arguments to the rgb specification and the return value to the hex specification, so the same specs that validate data can also describe a function's interface.

A small library with a slow release history

Norm is an Elixir library with a Mix project layout, so the top level carries mix.exs, mix.lock, a lib/ directory and a test/ directory, alongside the usual tooling files.credo.exs for linting and.formatter.exs for formatting. The project description is simply Data specification and generation, and the topics list Elixir, property-based-testing and specification.

The licence is MIT, with the details in the LICENSE.md file at the top level of the repository.

The release history is the part that deserves attention. The most recent published tag is v0.13.1, from 2025-01-10, and before that sit v0.10.0 from 2019-12-30 and v0.9.0 from 2019-12-02. The last push to the master branch was on 2026-04-13, which means the branch is ahead of the last tag and that the changelog in the repository is the record of what changed.

For a library this small, the version numbering carries less weight than it would in a larger framework, but a dependency pinned to a tag older than the branch you are reading is a common source of confusion when documentation and behaviour disagree. There is also no project homepage, so the repository, its changelog and its tests are the whole surface.

Editorial conclusion

Norm suits an Elixir codebase that already validates data in one place and wants the same specification to drive generation and contract checks, and it does not suit a team that needs a schema to reject unknown fields or to cast string keys onto atom keys, since both are deliberate design choices here. Before adopting it, check the CHANGELOG.md against the version you depend on, since the last published tag is v0.13.1 from 2025-01-10 while the master branch has moved since, and decide deliberately how permissive you want schemas to be before they become the default across a service.

Frequently asked questions

What is Norm in Elixir and what does it do?

Norm is a system for specifying the structure of data, used for validation and for generation of data. It provides no predicates of its own and instead lets you re-use existing validations, wrapping any predicate with the spec/1 macro and composing them with and and or.

When should I use conform/2 instead of conform!/2?

Use conform/2 when you need to return a list of well defined errors, and conform!/2 otherwise, since the documentation describes the bang version as generally more useful. The bang version raises Norm.MismatchError with the failing value and the predicate that rejected it.

Does Norm cast string keys to atom keys when conforming a map?

No. Atom and string keys are matched explicitly and there is no casting that occurs when conforming values, so a schema written with atom keys will not accept a payload with string keys. The documentation says to specify the schema with string keys if that is what you need to match.

How do I generate data with Norm?

The same schema used for validation can be handed to gen/1, and the result piped into Enum.take to sample values, as the documentation example does with a user schema. Because generation and validation share one definition, generated values can be checked against the conditions the schema was built from.

Official sources

  1. elixir-toniq/norm on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/elixir-toniq-norm.svg)](https://hysenlabs.com/projects/elixir-toniq-norm)

Was this useful?