martinthenth

martinthenth

Goal - A parameter validation library based on Ecto

Hi fellow Elixirists! :wave:

About 8 months ago, I wrote a blog post on validating Phoenix controller parameters using Ecto Changesets in combination with Ecto Embedded Schemas (Using Ecto changesets for API request parameter validation).

Since then, I found that writing embedded schemas for validating parameters to be quite tedious. It required setting up new modules, a lot of boilerplate code, and complex file organisation.

So I decided to write a library that replaces the boilerplate code with a simple syntax, that can be written inside controllers, and is still based on Ecto Changesets to leverage its validation capabilities.

It’s called Goal, and it offers those capabilities with a nice syntax:

defmodule MyApp.SomeController do
  import Goal
  import Goal.Syntax

  def create(conn, params) do
    with {:ok, attrs} <- validate_params(params, schema()) do
      ...
    end
  end

  defp schema do
    defschema do
      required :uuid, :string, format: :uuid
      required :name, :string, min: 3, max: 3
      optional :age, :integer, min: 0, max: 120
      optional :gender, :enum, values: ["female", "male", "non-binary"]

      optional :data, :map do
        required :color, :string
        optional :money, :decimal
        optional :height, :float
      end
    end
  end
end

It features:

  1. Compatibility with all primitive types from Ecto.Schema
  2. Customisable regexes for email, password, and url formats, so you can maintain compatibility with your production system
  3. Unlimited nested maps and lists of maps
  4. trim and squish to trim and collapse whitespaces
  5. optional and required arguments for defining optional and required fields
  6. An extended version of Ecto.Changeset.traverse_errors/2 (aptly called Goal.Changeset.traverse_errors/2) that works with Goal and Ecto’s existing functionality

You can use Goal for validating Phoenix controller action parameters, but it will work with any data source as long as it’s represented in a map.

I aim for Goal to cover all parameter validations that Ecto.Changeset offers for database fields. I think I covered most with the 0.1.1 version; but if you’d like another validation, then please contribute :pray: or open an issue on GitHub.

It was inspired by Ruby’s dry-schema, and I had to borrow code from Ecto. Thank you for making such awesome libraries. :bow:

Feedback and suggestions are very welcome :pray:. For example, I was thinking about generating the chain of Ecto.Changeset functions at compile-time, to crunch out some additional usecs :racing_car:

Most Liked

stefanchrobot

stefanchrobot

This looks great! How about supporting this syntax:

defschema schema do
  required ...
end

I’d love to see the functionality of Goal incorporated into Ecto. Did you consider that? If so, there’s one thing to think about: the Goal schema seems to be controller-specific so it makes sense to have required, min, max, etc. as options. On the other hand Ecto schemas are reused across the changeset functions (each function has its own set of required/optional/validations). I still think it would make sense to support what Goal is using, maybe with a different macro (there’s schema and embedded_schema, so maybe validation_schema?).

fuelen

fuelen

required :uuid, :string, format: :uuid
optional :gender, :enum, values: ["female", "male", "non-binary"]

if the library is based on Ecto, then why not to use built-in types? I mean this

required :uuid, Ecto.UUID
optional :gender, Ecto.Enum, values: ["female", "male", "non-binary"]
martinthenth

martinthenth

Goal 0.1.3 released :tada:

I recently switched to LiveViews and found that I have the same need to validate incoming parameters as with JSON APIs. Since LiveViews depend on changesets for validation, I decided to expose the Goal.build_changeset(params, schema) function in the library.

I find using database Ecto.Schemas in LiveViews, like the examples in the LiveView docs do, a bit problematic because I often need fields that aren’t defined in the Ecto.Schema. Embedded schemas are great, but it’s a lot of boilerplate…

Plus, often my changesets have required fields like foreign keys that I cannot validate in the LiveView so the changeset is never valid; a state that I use in my forms to enable/disable form submit buttons. :thinking:

Goal solves all that with a sweet syntax.

Using Goal for LiveViews is super easy:

defmodule MyApp.SomeLiveView do
  import Goal.Syntax

  def handle_event("validate", %{"some" => some_params}, socket) do
    changeset =
      some_params
      |> Goal.build_changeset(schema())
      |> Map.put(:action, :validate)

    {:noreply, assign(socket, :changeset, changeset)}
  end

  defp schema do
    defschema do
      required :id, :uuid
      required :name, :string, max: 20
      optional :age, :integer, min: 0, max: 120
      optional :gender, :enum, values: ["female", "male", "non-binary"]
      optional :address, :string

      optional :data, :map do
        required :color, :string
        optional :money, :decimal
        optional :height, :float
      end
    end
  end
end

Where Next?

Popular in Announcing Top

dominicletz
Hi, I thought I had posted my library before but seems I hadn’t. The project is still in early stages but it’s growing and so I think it...
New
asiniy
Hey there! I wrote a download elixir package which does exactly what its name about - an easy way to download files. I saw solutions ...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29305 241
New
OvermindDL1
I created a new library (rather I pulled out a couple files from my big project), it manages an operating system PID file for the BEAM. ...
New
mathieuprog
Hello :wave: Allow me to introduce you to Tz, an alternative time zone database support to Tzdata. Why another library? First and fore...
New
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
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
jakub-zawislak
Hi everyone, I’m coming from the Symfony (PHP) framework. I like Phoenix, but it has a one thing that was build much better in the Symfo...
New
mplatts
With HEEX released we decided to start a components library using Tailwind CSS - check it out here: Petal Components. We also have a boi...
New
marcuslankenau
I feel kind of stuck with the absence of a proper xml library for Elixir. Currently I use SweetXML which was ok for me more or less to pa...
New

Other popular topics Top

Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
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
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

We're in Beta

About us Mission Statement