Starchoice.Decoder behaviour (starchoice v0.3.0) View Source

This module can be used from two different ways:

As a macro

To be defining decoders as macro, you need to use the Starchoice.Decoder module in your struct. It's declaration is highly (like totally) inspired by Ecto's schemas.

To see available options, take a look at put_field/3's documentation

Examples

defmodule User do
  use Starchoice.Decoder
  defstruct first_name: nil, age: nil

  defdecoder do
    field(:first_name)
    field(:age)
  end
end

User.__decoder__() # %Decoder{}
User.__decoder__(:default) # %Decoder{}

When you don't pass a name to the defdecoder/2 function, it defaults to default. So calling defdecoder do and defdecoder :default do is identical. This is because you might be interested in creating multiple decoders for the same struct like below:

defmodule User do
  use Starchoice.Decoder
  defstruct first_name: nil, last_name: nil, email: nil, age: nil

  defdecoder do
    field(:first_name)
    field(:age)
  end

  defdecoder :full do
    field(:first_name)
    field(:last_name)
    field(:email)
    field(:age)
  end
end

User.__decoder__() # %Decoder{fields: [{:first_name, _}, {:age, _}]}
User.__decoder__(:default) # %Decoder{fields: [{:first_name, _}, {:age, _}]}
User.__decoder__(:full) # %Decoder{fields: [{:first_name, _}, {:last_name, _}, {:email, _}, {:age, _}]}

And you can now use the module directly when calling Starchoice.decode/3 like:

iex> Starchoice.decode(input, User)
iex> Starchoice.decode(input, {User, :full})

Source mapper

While each field supports a :source option to specify what key to use in the parsed map, you can provide a module to perform the casting. If, as an example, the map is using camelCaseKeys, you can provide a source mapper that perform the appropriate conversion.

defmodule CamelCase do
  def map_source(field) do
    splitted =
      field
      |> to_string()
      |> String.split("_", parts: 2)

    case splitted do
      [key] -> key
      [head, rest] -> head <> Macro.camelize(rest)
    end
  end
end

defmodule User do
  defstruct [:first_name, :last_name]

  use Starchoice.Decoder, source_mapper: CamelCase

  defdecoder do
    field(:first_name)
    field(:last_name)
  end
end

iex> map = %{"firstName" => "Bobby", "lastName" => "Hill"}
iex> Starchoice.decode(map, User)
%User{first_name: "Bobby", last_name: "Hill"}

iex> map = %{"first_name" => "Bobby", "last_name" => "Hill"}
iex> Starchoice.decode(map, User)
%User{first_name: nil, last_name: nil}

Doing so is the same as adding individual :source option to each field.

Manually

You could also build decoder manually like the following:

defmodule User do
  defstruct email: nil, password: nil

  def mask_password(_), do: "MASKED"
end

User
|> Decoder.new()
|> Decoder.put_field(:email)
|> Decoder.put_field(:password, with: &User.mask_password/1)

# or

Decoder.new(User, [
  {:email, []},
  {:password, with: &User.mask_password/1}
])

To see available options, take a look at put_field/3's documentation

Link to this section Summary

Functions

Initiates a new Decoder, you can pass a list of fields with options.

Puts a field decoding in the decoder. Available options are

Puts source option casted from source mapper

Link to this section Types

Specs

decoder_struct() :: module() | :map

Specs

field_definition() :: {atom(), [field_option()]}

Specs

field_option() ::
  {:required, boolean()}
  | {:with, Starchoice.decoder()}
  | {:default, any()}
  | {:sanitize, function()}
  | {:source, String.t()}

Specs

t() :: %Starchoice.Decoder{fields: [field_definition()], struct: module()}

Specs

using_option() :: {:source_mapper, module()}

Link to this section Functions

Link to this macro

defdecoder(name \\ :default, list)

View Source (macro)

Specs

defdecoder(atom(), [{:do, Macro.t()}]) :: Macro.t()
Link to this macro

field(name, opts \\ [])

View Source (macro)

Specs

field(atom(), [field_option()]) :: Macro.t()
Link to this function

new(struct, fields \\ [])

View Source

Specs

new(decoder_struct(), [field_definition()]) :: t()

Initiates a new Decoder, you can pass a list of fields with options.

To see available options, take a look at put_field/3's documentation

Link to this function

put_field(decoder, field, options \\ [])

View Source

Specs

put_field(t(), atom(), [field_option()]) :: t()

Puts a field decoding in the decoder. Available options are:

  • :required: Defines if a field is required, will caused a raise (or {:error, _} tuple) when the required field isn't present
  • :default: Specifies a fallback value in case the field is missing (can't be used with required: true)
  • :with: Specifies a decoder the decode the given field. Like the Starchoice.decode/3 call, it can support any valid decoder in Module, {Module, :decoder} and a function.
  • :sanitize: Specifies a sanitizer. By default, the value is sanitized by trimming the value and casting to nil if the value is "". Can either be a boolean or a function.
  • :source: Specifies the source key in the decoding item
Link to this function

put_source_option(name, options, source_mapper)

View Source

Specs

put_source_option(atom(), [field_option()], module() | nil) ::
  String.t() | atom()

Puts source option casted from source mapper

Link to this section Callbacks

Specs

__decoder__(atom()) :: t()