GUIDES
GUIDESDOCS

Parameters

params Block

You should do ALL things you want with input parameters by params block and never do it within do_block.
Make sure to declare params within params and all undeclared params will be dropped.
There's a simple example:

params do
  requires :age,    type: Integer, values: 18..65
  requires :sex,    type: Atom, values: [:male, :female], default: :female
  requires :name,   type: Map do
    requires :first_name
    requires :last_name
  end
  optional :intro,  type: String, regexp: ~r/^[a-z]+$/
  optional :avatar, type: File
  optional :avatar_url, type: String
  exactly_one_of [:avatar, :avatar_url]
end

params Variable within do_block

params variable will be given when params dsl defined.
The initiative value of params is conn.private[:maru_params]

Supported Parameter Types

There are a number of build-in Types:

  • String
  • Integer
  • Float
  • Boolean
  • CharList
  • Atom
  • File
  • Json
  • Base64

You can also use them as :string :integer :float :boolean :char_list :atom :file :json and :base64.

An Maru.Exceptions.InvalidFormat[reason: :illegal] exception will be raised on type change error.

🚧

Atom Parser

Atom type parameter is parsed by String.to_existing_atom, so a values validator is recommended.

πŸ“˜

Multipart file uploads

If you are using the File type parameter, make sure you include the :multipart parser in your routes

Read more at https://hexdocs.pm/plug/Plug.Parsers.html#module-examples

  plug Plug.Parsers, parsers: [:urlencoded, :multipart, :json],
                    json_decoder: Poison

Custom Parameter Type

You can custom type like this:

defmodule MyColorStruct do
  defstruct r: 0, g: 0, b: 0
  
  def from_hex(<<r::binary-size(2), g::binary-size(2), b::binary-size(2)>>) do
    %__MODULE__{
      r: String.to_integer(r, 16),
      g: String.to_integer(g, 16),
      b: String.to_integer(b, 16),
    }
  end
end

defmodule Maru.Types.Color do
  use Maru.Type

  def parse("blue", _), do: "2196F3" |> MyColorStruct.from_hex
  def parse("red", _),  do: "F44336" |> MyColorStruct.from_hex
  def parse(<<"#", s::binary-size(6)>>, _), do: s |> MyColorStruct.from_hex
end
    
pramams do
  optional :color, type: Color
end

And you can define custom type with arguments:

defmodule Maru.Types.Time do
  use Maru.Type

  def arguments do
    [:format]
  end
  
  def parse(input, %{format: "m/d/Y"}) do
    ...
  end
  
  def parse(input, %{format: "Y-m-d"}) do
    ...
  end
end
    
requires :time, type: Time, format: "m/d/Y"
requires :time, type: Time, format: "Y-m-d"

Pipe Types

Pipe Types is useful for multiple encoded parameters. For example when your parameter value is a json encoded string, you can define your params block like this:

params
  group :payload, type: Json |> List do
    group :alert, type: Map do
      requires :id, type: Integer
      requires :name, type: String
    end
  end
end
post do
  xxx
end
curl -d 'payload=[{"alert":{"id":4399031,"name":"foo"}}]'

You can also use functions as pipe, for example:

defmodule Test do
  def f(s) do
    s |> String.split
  end
end

requires :foo, type: fn s -> s end |> (&Test.f/1) |> List

Rename params

You can rename params like this:

params do
  requires :a, type: String, source: "b"
end

And then, you will get %{a: 1} within endpoint by params when client send b = 1.

Validators

There're three build-in validators: regexp values and allow_blank, you can use them like this:

params do
  requires :id,  type: :integer, regexp: ~r/^[0-9]+$/
  requires :sex, type: :atom, values: [:female, :male], default: :female
  optional :age, type: :integer, values: 18..65
end

An Maru.Exceptions.UndefinedValidator exception will be raised if validator not defined.
An Maru.Exception.Validation exception will be raised on validators check error.

mutually_exclusive, exactly_one_of and at_least_one_of can also be used in the same way as grape except that we should use an explicit List.

params do
  requires :food do
    optional :meat
    optional :fish
    optional :rice
    at_least_one_of [:meat, :fish, :rice]
  end
  group :drink do
    optional :beer
    optional :wine
    optional :juice
    exactly_one_of [:beer, :wine, :juice]
  end
  optional :dessert do
    optional :cake
    optional :icecream
    mutually_exclusive :above_all
  end
end

Custom validators

defmodule Maru.Validations.Length do
  def validate_param!(attr_name, value, option) do
    byte(value) in option ||
      Maru.Exceptions.Validation |> raise [param: attr_name, validator: :length, value: value, option: option]
  end
end
params do
  requires :text, length: 2..6
end

Nested Params

In general, Nested Params can be used in the same way as grape except that we should use List and Map in Elixir instead of Array and Hash in Ruby.

params do
  optional :preferences, type: List do
    requires :key
    requires :value
  end

  requires :name, type: Map do
    requires :first_name
    requires :last_name
  end
end

One Line List Params

If the type of list value is not a map, you can use List[] to parse it. For this params parser

params do
  requires :foo, type: List[Integer]
  requires :bar, type: &String.split(&1, ",") |> List[Integer]
end

and this request body,

{
  "foo": ["1", "2", "3"],
  "bar": "1,2,3"
}

Both params[:foo] and params[:bar] got [1, 2, 3].

Reusable Params

You can define reusable params using helpers.

defmodule API do
  helpers do
    params :name do
	    optional :first_name
  	  optional :last_name
    end
  end

  params do
    use :name
  end
  get do
    params[:first_name]
    params[:last_name]
    ...
  end
end

You can also define reusable params using shared helpers.

defmodule SharedParams do
  use Maru.Helper

  params :period do
    optional :start_date
    optional :end_date
  end

  params :pagination do
    optional :page, type: Integer
    optional :per_page, type: Integer
  end
end

defmodule API do
  helpers SharedParams

  params do
    use [:period, :pagination]
  end
  get do
    ...
  end
end