Zoi - schema validation library inspired by Zod

This is a game changer for Elixir, finally we have a better way to define composable validations with a nice syntax.

Great work @phcurado

2 Likes

Zoi is reaching 10k downloads on hex.pm :star:

I would like to thank everyone who has used and supported this library. Every feedback has been taken seriously and helped to improve this project.

These are the main features that have been added based on feedback:

  • Zoi.describe/2: function for generating documentation based on descriptions your schema.
  • Zoi.Form: module that integrates with Phoenix forms.
  • Zoi.JSONSchema: Converting Zoi schemas to JSON Schema (for API documentation, validation, etc.).
  • Zoi.Struct: A helper module to define and validate structs using Zoi schemas
  • Zoi.Schema: Utilities for traversing and transforming Zoi schemas.

And many more added into the project’s internals!

I created a blog post where I walk through some of the Zoi features and the reason behind creating this library.

Check it out here.

Don’t forget to check the git repo and if you have any feedback or suggestions, feel free to open an issue or a PR.

6 Likes

Hello :slight_smile: and congrats!

Thanks again for featuring Oaskit!

How hard was it to implement the typespec generation, like for instance for a map where values are lists of integers? I’m thinking of implementing this for the JSON schema validator but I fear it will be too complicated, or otherwise the types will be too broad (which can be good enough…).

1 Like

thank you @lud for your great libray as well!

It was not that hard but quite tedious since Zoi supports many types. A few examples:

  • Zoi.string/1 is simply binary() under the hood, check here.
  • Zoi.union/2 loops through the list of schemas and add the | operator, check here
  • Zoi.object/2, one of the most complex types, is a basic loop that calls the type_spec function recursively, here.

So if you build a map like this (map where the value is a list of integers):

@schema Zoi.object(%{numbers: Zoi.array(Zoi.integer())})

@type schema :: unquote(Zoi.type_spec(@schema))

The type would have the following AST:

{:%{}, [], [{{:required, [], [:numbers]}, [{:integer, [], []}]}]}

which translates to:

%{numbers: [integer()]}

So quite precise in terms of how the type should be according to your schema

1 Like

It’s good that you have a struct for each type, so you can directly map the struct to a typespec in many cases.

I’ll have to infer a lot more things, but I can take some inspiration from your code :slight_smile:

Thank you!

1 Like