Parsing and Validating Data in Elixir
Zoey de Souza Pessanha
Posted on June 18, 2024
In the enchanting world of Elixir programming, data validation is a quest every developer embarks on. It's a journey through the land of schemas, types, and constraints, ensuring data integrity and correctness. Today, we'll explore four powerful artifacts: Ecto, Norm, Drops, and Peri. Each of these tools offers unique powers for taming your data. We'll delve into their strengths, use cases, and compare them to help you choose the right one for your quest.
The Quest: Parse, Don't Validate
Before we embark on our journey, let's discuss a guiding principle in functional programming: Parse, Don't Validate. This pattern emphasizes transforming data into a well-defined structure as early as possible. By doing so, you avoid scattered, ad-hoc validation throughout your codebase, leading to clearer, more maintainable code. It's like casting a spell to organize the chaos of raw data into a neat, structured form.
The Artifacts
1. Ecto
Ecto is a robust toolkit primarily designed for interacting with databases. However, it also offers powerful capabilities for embedded schemas and schemaless changesets, making it versatile for data validation.
Embedded Schemas
Ecto allows defining schemas that don't map to a database table, ideal for validating nested data structures.
defmodule User do
use Ecto.Schema
embedded_schema do
field :name, :string
field :email, :string
end
end
def changeset(data) do
%User{}
|> Ecto.Changeset.cast(data, [:name, :email])
|> Ecto.Changeset.validate_required([:name, :email])
end
Schemaless Changesets
For dynamic data, Ecto provides schemaless changesets, offering flexibility at the cost of increased complexity.
def changeset(data) do
Ecto.Changeset.cast({%{}, %{name: :string, email: :string}}, data, [:name, :email])
|> Ecto.Changeset.validate_required([:name, :email])
end
2. Norm
Norm focuses on defining and conforming to data structures with custom predicates, offering a clean syntax and powerful validation.
defmodule User do
import Norm
defschema do
schema(%{
name: spec(is_binary()),
age: spec(is_integer() and &(&1 > 18))
})
end
end
Norm.conform(%{name: "Jane", age: 25}, User.schema())
# => {:ok, %{name: "Jane", age: 25}}
3. Drops
Drops is a newer library that provides a rich set of tools for defining and validating schemas, leveraging Elixir's type system.
defmodule UserContract do
use Drops.Contract
schema do
%{
required(:name) => string(:filled?),
required(:age) => integer(gt?: 18)
}
end
end
UserContract.conform(%{name: "Jane", age: 21})
# => {:ok, %{name: "Jane", age: 21}}
4. Peri
Peri is inspired by Clojure's Plumatic Schema, focusing on validating raw maps with nested schemas and optional fields. It's designed to be powerful yet simple, embracing the "Parse, Don't Validate" pattern.
defmodule MySchemas do
import Peri
defschema :user, %{
name: :string,
age: :integer,
email: {:required, :string},
role: {:enum, [:admin, :user]}
}
defschema :profile, %{
user: {:custom, &MySchemas.user/1},
bio: :string
}
end
MySchemas.user(%{name: "John", age: 30, email: "john@example.com", role: :admin})
# => {:ok, %{name: "John", age: 30, email: "john@example.com", role: :admin}}
MySchemas.user(%{name: "John", age: "thirty", email: "john@example.com"})
# => {:error, [%Peri.Error{path: [:age], message: "expected integer received \"thirty\""}]}
Conditional and Composable Types in Peri
Peri shines with its support for conditional and composable types, making it a powerful tool for complex validation scenarios.
defmodule AdvancedSchemas do
import Peri
defschema :user, %{
name: :string,
age: {:cond, &(&1 >= 18), :integer, :nil},
email: {:either, {:string, :nil}},
preferences: {:list, {:oneof, [:string, :atom]}}
}
end
AdvancedSchemas.user(%{name: "Alice", age: 25, email: nil, preferences: ["coding", :reading]})
# => {:ok, %{name: "Alice", age: 25, email: nil, preferences: ["coding", :reading]}}
AdvancedSchemas.user(%{name: "Bob", age: 17})
# => {:ok, %{name: "Bob", age: 17, email: nil, preferences: nil}}
Conclusion
Each of these tools offers unique advantages and caters to different needs:
- Ecto is great for data associated with databases but can handle schemaless and embedded data structures too.
- Norm provides a clean and powerful way to define and validate data structures.
- Drops leverages Elixir's type system and offers rich schema definitions and validations.
- Peri emphasizes simplicity and power, supporting complex types and conditional validations.
By understanding the strengths and weaknesses of each, you can choose the right tool for your data validation needs in Elixir. Happy coding, fellow sorcerers of Elixiria!
References
Feel free to dive into the source code and contribute to these projects to make Elixiria an even more magical place!
Posted on June 18, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.