zoedsoupe

zoedsoupe

Peri - a comprehensive schema validation library for Elixir

Hello forum!

I am excited to share Peri, a schema validation library for Elixir, designed to simplify and enhance your data validation processes. Inspired by Clojure’s Plumatic Schema, Peri brings a robust and flexible solution to the Elixir ecosystem.

What is Peri?

Peri is a library that focuses on validating raw maps and supports nested schemas, optional fields, custom validations, and a variety of data types. It provides detailed error reporting to help you debug and handle invalid data effectively.

Key Features:

  • Simple and Nested Schema Validation: Easily validate both flat and deeply nested schemas. Define your data structures in a clear and concise manner.
  • Optional and Required Fields: Specify which fields are optional and which are required. Ensure your data meets the expected criteria.
  • Custom Validations: Implement custom validation functions for complex rules specific to your application.
  • Support for Various Data Types: Validate strings, integers, floats, booleans, atoms, tuples, lists, and more.
  • Detailed Error Reporting: Receive clear and informative error messages to quickly identify and resolve issues in your data.

Example Usage:

Here’s a quick example to showcase how easy it is to define and validate a schema using Peri:

defmodule MySchemas do
  import Peri

  defschema :user, %{
    name: :string,
    age: :integer,
    email: {:required, :string},
    address: %{
      street: :string,
      city: :string
    },
    tags: {:list, :string},
    role: {:enum, [:admin, :user, :guest]},
    geolocation: {:tuple, [:float, :float]},
    rating: {:custom, &validate_rating/1}
  }

  defp validate_rating(n) when n < 10, do: :ok
  defp validate_rating(_), do: {:error, "invalid rating"}
end

user_data = %{name: "John", age: 30, email: "john@example.com", address: %{street: "123 Main St", city: "Somewhere"}, tags: ["science", "funky"], role: :admin, geolocation: {12.2, 34.2}, rating: 9}
case MySchemas.user(user_data) do
  {:ok, valid_data} -> IO.puts("Data is valid!")
  {:error, errors} -> IO.inspect(errors, label: "Validation errors")
end

In this example, we define a user schema with various fields, including nested structures, required fields, and custom validations. Peri makes it straightforward to ensure that your data conforms to these definitions.

Getting Started:

To start using Peri, add it to your mix.exs dependencies:

def deps do
  [
    {:peri, "~> 0.2"}
  ]
end

Then run mix deps.get to fetch and compile the dependency.

Documentation and Resources:

Contributing:

I welcome feedback, suggestions, and contributions from the community. If you find any issues or have ideas for improvements, please check out the contributing guidelines on GitHub.

Why Use Peri?

Peri is designed to integrate seamlessly into your Elixir projects, providing a powerful and flexible tool for schema validation. Whether you are dealing with simple data structures or complex nested schemas, Peri offers a clean and efficient solution.

I hope you find Peri useful for your Elixir applications. Feel free to share your experiences, ask questions, and contribute to the project.

Happy coding!

Peri GitHub Repository | HexDocs

Most Liked

zoedsoupe

zoedsoupe

Hey, I do admire the drops library! I was very excited when you announced it! But for some use cases can be very clunky and complex to define a custom DSL for schema conformation. So i did a comparison between drops and peri:

Comparison of Peri and Drops Approaches

Overview

Peri and Drops are two libraries for defining and conforming schemas in Elixir, each with its own unique features and design philosophies. Here’s a detailed comparison between the two:

Simplicity and Flexibility

Peri:

  • Design Philosophy: Peri is designed to keep things simple while remaining powerful. It allows developers to define schemas and validate raw data structures with ease.
  • Schema Definition: In Peri, schemas are defined using simple Elixir or any other data structure. This makes it very approachable and easy to integrate with existing Elixir code.
  • Validation Types: Peri supports various types, including custom validations, conditional types, providing flexibility in schema definitions.
  • Error Handling: Peri accumulates validation errors, allowing for comprehensive error reporting.

Drops:

  • Design Philosophy: Drops offers a more structured and predefined customization approach. It leverages the concept of contracts for data coercion and validation.
  • Schema Definition: Schemas in Drops are defined using the Drops.Contract module, with clear differentiation between required and optional keys.
  • Validation Types: Drops provides a rich set of predefined types and predicates for additional checks. It also supports nested schemas, type-safe casting, and custom types.
  • Error Handling: Drops uses structured error messages, making it easier to understand validation issues and their causes.

Use Cases

Peri:

  • Dynamic and Flexible Validations: Ideal for applications where schemas need to be flexible and dynamically defined. Custom and conditional types are particularly useful for complex validation logic.
  • Raw Data Structure Validation: Peri excels in scenarios where raw data structures validation is required without relying on structs or Ecto changesets. So you can define schemas for simple strings or even tuples, lists, list of tuples and so on. Composability is the main philosophy of the library.

Drops:

  • Predefined Customizations: Suitable for projects that benefit from predefined validation rules and type safety. The rich set of types and predicates ensures thorough validation.
  • Complex Data Structures: Drops is well-suited for validating complex and nested data structures, thanks to its support for nested schemas and type-safe casting.

Examples

Peri Example:

defmodule MySchemas do
  import Peri

  defschema :user, %{
    name: :string,
    age: :integer,
    email: {:required, :string},
    address: %{
      street: :string,
      city: :string
    }
  end

  user_data = %{name: "John", age: 30, email: "john@example.com", address: %{street: "123 Main St", city: "Somewhere"}}
  Peri.validate(MySchemas.user, user_data)

Drops Example:

defmodule UserContract do
  use Drops.Contract

  schema do
    %{
      required(:name) => string(),
      required(:email) => string(),
      optional(:age) => integer(gt?: 18)
    }
  end
end

UserContract.conform(%{name: "Jane", email: "jane@doe.org"})

Conclusion

Peri keeps schema definitions simple and flexible, making it ideal for projects requiring dynamic and extensible validation logic. Drops, on the other hand, provides a more structured approach with rich predefined types and validation rules, making it suitable for projects where type safety and predefined validation logic are paramount.

Both libraries offer powerful validation capabilities, and the choice between them depends on the specific needs of the project and the preferences of the development team.

Also, given a more complex example of schema in peri, I would like to translate it to drops, what do you think?

defmodule MySchemas do
  import Peri

  defschema :user, %{
    name: :string,
    age: {:required, :integer},
    email: {:required, :string},
    address: %{
      street: :string,
      city: :string,
      country: {:either, {:string, :atom}}
    },
    role: {:required, {:enum, [:admin, :user, :guest]}},
    settings: {:oneof, [:map, {:list, :any}]},
    score: {:cond, &(&1 > 100), :integer, :float},
    preferences: %{
      theme: {:enum, [:light, :dark]},
      notifications: :boolean
    }
  }

  user_data = %{
    name: "John",
    age: 30,
    email: "john@example.com",
    address: %{street: "123 Main St", city: "Somewhere", country: :usa},
    role: :admin,
    settings: %{volume: 10},
    score: 105,
    preferences: %{theme: :dark, notifications: true}
  }

  case MySchemas.user(user_data) do
    {:ok, valid_data} -> IO.puts("Data is valid!")
    {:error, errors} -> IO.inspect(errors, label: "Validation errors")
  end

This schema demonstrates:

Conditional Type: score is validated as an integer if it’s greater than 100, otherwise as a float.

Either Type: country can be either a string or an atom.

One-of Type: settings can be either a map or a list.

Enum Type: role and preferences.theme must be one of the specified values.

zoedsoupe

zoedsoupe

Thanks for the comment! I’m following the Elixir Scribe project, so it would be a pleasure to contribute it, even indirectly.

So Peri and Norm want to tackle the same problem, but the way each one achieve this is very different.

Norm focus in DSL based on macros for type safe guarantee, and go beyond that, provided native custom validations and generation for your already defined schema.

Peri on the other hand, os very recent and the main focus is to define schemas based on raw elixir data structures. No macros, no structs, no magical stuff, only the old good way to recurse into a schema and enforce structure, type casting or custom validations.

I was thought to be less complex in comparison of Ecto schemaless changesets and the cost is that the library is so simple but also very powerful.

When comparing directly with Norm i can see some pros:

  • do not depend on macros, just raw data structures and recursion for schema validations
  • it’s very flexible and allows you to build complex schemas structures very easy, with few LoC
  • provide a tree structure of errors, s you can traverse into the error structure easily and provide your own error messages (for now this need to be manual, but i will provide an API to define custom error messages on the schema)

But there are some cons as the Peri just was released 4 days ago haha.

  • Peri does not provide generation of data for your schemas, but i’ll surely tackle this issue in the near future
  • Norm is very mature and maybe handle some corner cases of complex schemas more “gracefully”. I do massive testing on Peri.

In general these are my thinkings. Peri is ideal for flexible and direct schema structure and enforcement while Norm will shine on data generation.

zoedsoupe

zoedsoupe

Peri can be very similar to nimble_options, the difference though is that nimble_options only work with keyword lists while Peri works with ANY Elixir data structure or type.

Peri doesn’t have a native good handler for keyword lists though, this is exactly what I’m implementing right now :slight_smile:

I really like the way nimble_options validates the structure recursively, so I borrowed to provide meaningful and descriptive error trees.

zoedsoupe

zoedsoupe

Hey @here, I want to share with you the new release of Peri, changes are:

  • support for schema definition with literal keyword lists, like nimble_options
  • add conforms?/1 function to know if a data matches some schema
solnic

solnic

YAY! More schema validations in Elixir - great to see this! I’m also working on a similar lib called Drops right here :point_right: solnic/drops. Would be great to compare our approaches. I also thought about not using macros and keep it simple but in my experience defining large schemas w/o DSLs gets messy quickly, that’s why I built a DSL.

Where Next?

Popular in Libraries Top

deadtrickster
I’ve just released stable versions of my Prometheus Elixir libs: Elixir client [docs]; Ecto collector [docs]; Plugs instrumenter/Export...
New
oltarasenko
Dear Elixir community, After a year of development, bug fixes, and improvements, we are proudly ready to share the release of Crawly 0.1...
New
seancribbs
Today I released a new dialyzer Mix task as the dialyzex package! At the time we started writing this task, the existing dialyzer integra...
New
dbern
I’m excited to announce that TaxJar has developed and open-sourced DateTimeParser. We developed it because we found a need to parse user ...
New
pkrawat1
Presenting Aviacommerce, open source e-commerce platform in Elixir Aviacommerce is an open source e-commerce platform in Elixir. We at...
New
New
riverrun
I’ve just released version 3 of Comeonin, a password hashing library. The following small changes have been made: changes to the NIF c...
New
benlime
LiveMotion enables high performance animations declared on the server and run on the client. As a follow up to my previous thread A libr...
New
Qqwy
While not as prevalent as in imperative languages, arrays (collections with efficient random element access) are still very useful in Eli...
New
mattludwigs
Grizzly is a library for working with Z-Wave devices. Z-Wave is a low-frequency radio protocol for controlling smart home devices on a me...
New

Other popular topics Top

chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30048 115
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
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
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

Sub Categories:

We're in Beta

About us Mission Statement