Skip to content

Commit

Permalink
improve readme with more examples
Browse files Browse the repository at this point in the history
  • Loading branch information
phcurado committed Nov 29, 2023
1 parent bc37bce commit ef4a2e5
Showing 1 changed file with 153 additions and 3 deletions.
156 changes: 153 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@ Parameter.dump(UserParam, params)
}}
```

Parameter offers a similar Schema model from [Ecto](https://github.com/elixir-ecto/ecto) library to deal with parameters. The main use case is to parse response from external APIs. This library provides a well structured schema model which tries to parse the external data. Check the [official documentation](https://hexdocs.pm/parameter/) for more information.

Parameter offers a similar Schema model from [Ecto](https://github.com/elixir-ecto/ecto) library for creating a schema and parsing it against external data. The main use case of this library is to parse response from external APIs but you may also use to validate parameters in Phoenix Controllers, when receiving requests to validate it's parameters. In general `Parameter` can be used to build strucutred data and deal with serialization/deserialization of data. Check the [official documentation](https://hexdocs.pm/parameter/) for more information.

## Installation

Expand All @@ -97,7 +96,158 @@ add `:parameter` on `.formatter.exs`:
import_deps: [:parameter]
```

For `Parameter` with `Ecto` integration check out the [parameter_ecto](https://github.com/phcurado/parameter_ecto) project.
## Validating parameters on Controllers

Parameter let's you define the shape of the data that it's expected to receive in Phoenix Controllers:

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

alias MyProject.Accounts

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} <- Accounts.create_user(user_params) do
render(conn, "user.json", %{user: user})
end
end
end
```

We can also use parameter for both request and response:

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

alias MyProject.Accounts
alias MyProject.Accounts.User

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

param UserCreateResponse do
# Returning the user ID created on request
field :id, :integer
field :last_name, :string
field :last_name, :string
end

def create(conn, params) do
with {:ok, user_request} <- Parameter.load(__MODULE__.UserCreateRequest, params),
{:ok, %User{} = user} <- Accounts.create_user(user_request),
{:ok, user_response} <- Parameter.dump(__MODULE__.UserCreateResponse, user) do

conn
|> put_status(:created)
|> json(%{user: user_response})
end
end
end
```

This example also shows that `Parameter` can dump the user response even if it comes from a different data strucutre. The `%User{}` struct on this example comes from `Ecto.Schema` and `Parameter` is able to convert it to params defined in `UserCreateResponse`.

## Runtime schemas

It's also possible to create schemas via runtime without relying on any macros. This gives great flexibility on schema creation as now `Parameter` schemas can be created and validated dynamically:

```elixir
schema = %{
first_name: [key: "firstName", type: :string, required: true],
address: [type: {:map, %{street: [type: :string, required: true]}}],
phones: [type: {:array, %{country: [type: :string, required: true]}}]
} |> Parameter.Schema.compile!()

Parameter.load(schema, %{"firstName" => "John"})
{:ok, %{first_name: "John"}}
```


The same API can also be evaluated on compile time by using module attributes:

```elixir
defmodule UserParams do
alias Parameter.Schema

@schema %{
first_name: [key: "firstName", type: :string, required: true],
address: [required: true, type: {:map, %{street: [type: :string, required: true]}}],
phones: [type: {:array, %{country: [type: :string, required: true]}}]
} |> Schema.compile!()

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

Or dynamically creating schemas:

```elixir
defmodule EnvParser do
alias Parameter.Schema

def fetch!(env, opts \\ []) do
atom_env = String.to_atom(env)
type = Keyword.get(opts, :type, :string)
default = Keyword.get(opts, :default)

%{
atom_env => [key: env, type: type, default: default, required: true]
}
|> Schema.compile!()
|> Parameter.load(%{env => System.get_env(env)}, ignore_nil: true)
|> case do
{:ok, %{^atom_env => parsed_env}} -> parsed_env
{:error, %{^atom_env => error}} -> raise ArgumentError, message: "#{env}: #{error}"
end
end
end
```

And now with this code we can dynamically fetch environment variables with `System.get_env/1`, define then as `required`, convert it to the correct type and use on our application's runtime:

```elixir
# runtime.ex
import Config

# ...

config :my_app,
auth_enabled?: EnvParser.fetch!("AUTH_ENABLED", default: true, type: :boolean),
api_url: EnvParser.fetch!("API_URL") # using the default type string

# ...
```

this will come in handy since you don't have to worry anymore when fetching environment variables, what will be the shape of the data and what type I will have to use or convert in the application, `Parameter` will do this automatically for you.

This small example show one of the possibilities but this can be extended depending on your use case.
A common example is to use runtime schemas when you have similar `schemas` and you want to reuse their properties across different entities:

```elixir
user_base = %{first_name: [key: "firstName", type: :string, required: true]}
admin_params = %{role: [key: "role", type: :string, required: true]}
user_admin = Map.merge(user_base, admin_params)

user_base_schema = Parameter.Schema.compile!(user_base)
user_admin_schema = Parameter.Schema.compile!(user_admin)

# Now we can use both schemas to serialize/deserialize data with `load` and `dump` parameter functions
```

For more info on how to create schemas, check the [schema documentation](https://hexdocs.pm/parameter/Parameter.Schema.html)

## License

Expand Down

0 comments on commit ef4a2e5

Please sign in to comment.