phcurado

phcurado

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

Showing Posts 1 to 4

phcurado

phcurado OP

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 OP

Parameter 0.6.1 version released.

  • Supports many flag on load/3 and dump/3 options to load and dump lists.
    Reusing the same user example in the first post:
params = %{"firstName" => "John", "lastName" => "Doe"}
Parameter.load(MyApp.UserParam, [params, params], many: true)
{:ok, 
[
  %{first_name: "John", last_name: "Doe"}, 
  %{first_name: "John", last_name: "Doe"}
]}
  • Improved error handler when dealing with list fields:
params = %{"lastName" => "Doe"}
Parameter.load(MyApp .UserParam, [params, params], many: true)
# index shown on errors
{:error,
%{
  0 => %{first_name: "is required"},
  1 => %{first_name: "is required"}
}}
  • New options for fields in the schema:
    • load_default: default value when none is passed when loading a field.
    • dump_default: default value when none is passed when dumping a field.
defmodule MyApp.UserParam do
  use Parameter.Schema

  param do
    field :name, :string, load_default: "loadName", dump_default: "dumpName"
  end
end

Parameter.load(MyApp.UserParam, %{})
{:ok, %{name: "loadName"}}

Parameter.dump(MyApp.UserParam, %{})
{:ok, %{"name" => "dumpName"}}
  • Enum api change to use key option instead of as to be more semantic to the field param definition (the older version still works with a deprecation warning).
defmodule MyApp.UserParam do
  use Parameter.Schema

  enum Status do
    value :user_online, key: "userOnline"
    value :user_offline, key: "userOffline"
  end

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

Parameter.load(MyApp.UserParam, %{"firstName" => "John", "status" => "userOnline"})
{:ok, %{first_name: "John", status: :user_online}}
# or dump
Parameter.dump(MyApp.UserParam, %{first_name: "John", status: :user_offline})
{:ok, %{"firstName" => "John", "status" => "userOffline"}}

Docs for the new version here

phcurado

phcurado OP

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 OP

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

— All posts loaded —

Where Next? Top

Trending in Announcing Top

wojtekmach
Hey everyone! Req is an HTTP client for Elixir that I’ve been working on for quite some time. There is already a lot of HTTP clients out...
New
handnot2
Samly can be used to enable SAML 2.0 Single Sign On in a Plug/Phoenix application. This library uses Erlang esaml to provide plug enabl...
New
woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
MRdotB
I needed to reuse React components from my Chrome extension in my Phoenix/LiveView backend. I noticed that for Svelte/Vue, there are live...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
fuelen
Hi all! I want to present a small library which provides a mix task for generating an Entity-Relationship Diagram for Ecto schemas. You...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
mudasobwa
I am seeing a lot of aplications of Argumentum ad Vericundiam in software discussions. They do link some piece of writing and point us to...
New
AstonJ
This showed up on my feed.. anyone heard of it? Just hype? Ox Alpha is a reasoning model designed for coding, sustained ag...
New
sergio
It’s not that it’s vocabulary is too advanced. It’s something worse. I get lost trying to follow even a paragraph written by Claude. It’...
New
sorenone
Today we’re releasing Oban for Python. Not an Oban client in Python. Not a pythonx wrapper embedded in Elixir. Nope, it’s a fully operati...
New
akoutmos
@hugobarauna, Dr. Dimitrios Koutmos (my brother) and I (Alex Koutmos) have been hard at work on writing a book on how you can use Elixir ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews