phcurado

phcurado

Parameter - Simplified definition, serialization, deserialization and validation of params

Parameter is a library for dealing with parameter serialization, deserialization and validation following a structure very similar to Ecto schema.

The main motivation for creating this is to have more options when loading external parameters and convert it to plain elixir maps or structure. It can load the data to a map or struct, transform external data keys to elixir keys, perform validations and dump the data.

The idea is to create a schema that models the data you need to load on your elixir application. The “key” field on the schema is useful to map keys that comes with a different format of your schema keys. For example when external data have camelCase keys and you want to use snake_case.

As an example imagine you are making and http request to a rest API to fetch a single user, this is how you can model the User schema:

defmodule User do
  use Parameter.Schema
  alias Parameter.Validators

  param do
    field :first_name, :string, key: "firstName", required: true
    field :last_name, :string, key: "lastName"
    field :email, :string, validator: &Validators.email(&1)
    has_one :address, Address do
      field :city, :string, required: true
      field :street, :string
      field :number, :integer
    end
  end
end

And this is how the external data could look like:

params = %{
  "firstName" => "John",
  "lastName" => "Doe",
  "email" => "john@email.com",
  "address" => %{"city" => "New York", "street" => "Broadway"}
}

Now this params can be deserialized using the load/2 function

iex> Parameter.load(User, params)
{:ok, %{
  first_name: "John",
  last_name: "Doe",
  email: "john@email.com",
  address: %{city: "New York", street: "Broadway"}
}}

the camelCase keys were converted to snake_case keys as per schema definition. If the external data already have the same key type as the schema definition this param can be ignored.
It also possible to load the schema as structure:

iex> Parameter.load(User, params, struct: true)
{:ok, %User{
  first_name: "John",
  last_name: "Doe",
  email: "john@email.com",
  address: %User.Address{city: "New York", street: "Broadway", number: nil}
}}

Also can be used on Phoenix APIs that needs to validate parameters before sending it to the context module.

defmodule MyProjectWeb.UserController do
  use MyProjectWeb, :controller
  import Parameter.Schema

  alias MyProject.Users

  param UserParams do
    field :first_name, :string, required: true
    field :last_name, :string, required: true
  end

  def create(conn, params) do
    with {:ok, user_params} <- Parameter.load(__MODULE__.UserParams, params),
         {:ok, user} <- Users.create_user(user_params) do
      render(conn, "user.json", %{user: user})
    end
  end
end

There are plenty of more options available as built in validators (or possibility for creating custom ones), custom types, error handling, unknown options that comes from external data and serializing (dump) the data.

Check it out

Most Liked

phcurado

phcurado

Parameter 0.8.0 version released :tada:

Now supports creating schemas without relying on any macro API.

Example on how it works:

## Create your runtime schema
schema = %{
  first_name: [key: "firstName", type: :string, required: true],
  address: [required: true, type: {:has_one, %{street: [type: :string, required: true]}}],
  phones: [type: {:has_many, %{number: [type: :string, required: true], country_code: [key: "countryCode", type: :integer, required: true]}}]
}
# Compile the schema (it also performs validation)
compiled_schema = Parameter.Schema.compile!(schema)

# Now suppose these are the parameters coming from an external data
params = %{
  "firstName" => "John", 
  "address" => %{"street" => "some street"},
  "phones" => [
     %{"number" => "123456789", "countryCode" => 123}
  ]
}

# Load the parameters against the schema
Parameter.load(compiled_schema, params)
{:ok,
 %{
   address: %{street: "some street"},
   first_name: "John",
   phones: [%{country_code: 123, number: "123456789"}]
 }}

It’s also possible to have the same compile time guarantees from the Macro API by using module attributes:

defmodule UserParams do
  alias Parameter.Schema

  @schema %{
    first_name: [key: "firstName", type: :string, required: true],
    last_name: [key: "lastName", type: :string, required: true]
  } |> Schema.compile!()

  def load(params) do
    Parameter.load(@schema, params)
  end
end

params = %{"firstName" => "John", "lastName" => "Doe"}
UserParams.load(params)
{:ok, %{first_name: "John", last_name: "Doe"}}
phcurado

phcurado

Parameter 0.5.0 version released.

New types introduced to offer more options when loading/dumping data:

0.5.0 - Enum type

Enum type is now supported. This type is useful for translating external data enum definitions to internal values.

Example of having a UserParam schema:

defmodule MyApp.UserParam do
  use Parameter.Schema

  enum Status do
    value "active", as: :active
    value "pendingRequest", as: :pending_request
  end

  param do
    field :first_name, :string, key: "firstName"
    field :status, MyApp.UserParam.Status
  end
end

We can load or dump the data with the enum value, automatically converted to it’s definition:

iex> Parameter.load(MyApp.UserParam, %{"firstName" => "John", "status" => "active"})
{:ok, %{first_name: "John", status: :active}}
# or dumping data the atom value will be converted:
...> Parameter.dump(MyApp.UserParam, %{first_name: "John", status: :pending_request})
{:ok, %{"firstName" => "John", "status" => "pendingRequest"}}

It can work also with integer values since some APIs use numbers for enum representation:

enum Status do
  value 1, as: :active
  value 2, as: :pending_request
end

and load/dump will give the following results:

iex> Parameter.load(MyApp.UserParam, %{"firstName" => "John", "status" => 1})
{:ok, %{first_name: "John", status: :active}}
# or dumping data the atom value will be converted:
...> Parameter.dump(MyApp.UserParam, %{first_name: "John", status: :pending_request})
{:ok, %{"firstName" => "John", "status" => 2}}

check all the available options for declaring and using enums here.

0.4.0 - Decimal type

decimal type supported, it’s very simple to add on your schema.
For example having a Payment schema with amount field that should be parsed as decimal:

defmodule MyApp.Payment do
  use Parameter.Schema

  param do
    field :amount, :decimal
  end
end

load the data using the schema definition:

iex> Parameter.load(Payment, %{"amount" => 10.50})
{:ok, %{amount: #Decimal<10.5>}}

And add the optional decimal dependency to be able to work with this type.

phcurado

phcurado

Parameter 0.11.0 version released :tada:

Some small improvements:

  • Support for on_load/2 and on_dump/2 functions in field definition which gives the possibility to customize load and dump behaviour.
  • ignore_nil and ignore_empty option added for customizing the behaviour when receiving nil or empty "" fields.
  • Supports for deep nested types when using map or array composite types.
  • Test coverage is now 100% :tada:

Changelog here

Where Next?

Popular in Announcing Top

bryanjos
Hi, I just published version 0.23.0 of Elixirscript. https://github.com/bryanjos/elixirscript/blob/master/CHANGELOG.md Most of the chan...
New
Crowdhailer
Experimenting with this code. OK.try do user &lt;- fetch_user(1) cart &lt;- fetch_cart(1) order = checkout(cart, user) save_orde...
New
wmnnd
Hi there, for my project DBLSQD, I needed a file storage solution that is a bit more flexible than Arc. Because I thought others might f...
New
wfgilman
I’ve cleaned up and open sourced three financial libraries I was using for my company. They are bindings for the APIs of these three comp...
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
kip
Image is an image processing library for Elixir. It is based upon the fabulous vix library that provides a libvips wrapper for Elixir. I...
622 19610 194
New
zachdaniel
Ash Framework What is Ash? Ash Framework is a declarative, resource-oriented application development framework for Elixir. A resource can...
New

Other popular topics Top

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
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 44778 311
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
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

We're in Beta

About us Mission Statement