Fild (fild v0.1.1)

Utilities for extracting and validating typed fields from maps or keyword lists.

This module provides functions to fetch required (get_mandatory/3) and optional (get_optional/3) fields from a map or keyword list, validating their presence and type.

Examples

Fild.get_mandatory(%{age: 20}, :age, :integer)
#=> {:ok, 20}

Fild.get_mandatory(%{}, :age, :integer)
#=> {:error, "expected :age to be given"}

Fild.get_mandatory(%{age: "20"}, :age, :integer)
#=> {:error, "expected :age to be integer, got: \"20\""}

Fild.get_optional(%{}, :mode, :atom, :default)
#=> {:ok, :default}

Bang Variants

All functions also have a ! variant (e.g. get_mandatory!/3) that raises an error on validation failure instead of returning an {:error, reason} tuple.

Summary

Functions

Fetches a mandatory key from the given args, validates its type, and returns {:ok, value} on success, or {:error, reason} if the key is missing or invalid.

Fetches a mandatory key from the given args, validates its type against the provided type specification, and returns the value directly.

Fetches an optional key from args, validates its type if present, and returns {:ok, value} or {:ok, default} if the key is missing.

Fetches an optional key from args, validates its type if present, and returns the value or the provided default.

Validates the given value against the specified type.

Types

field_type()

@type field_type() ::
  simple_type()
  | {:one_of, [term()]}
  | {:list_of, simple_type()}
  | {:struct, module()}

simple_type()

@type simple_type() ::
  :boolean
  | :integer
  | :float
  | :number
  | :binary
  | :map
  | :list
  | :atom
  | :non_empty_list
  | :any

Functions

get_mandatory(args, key, type \\ :any)

@spec get_mandatory(Access.t(), key :: term(), field_type()) ::
  {:ok, term()} | {:error, String.t()}

Fetches a mandatory key from the given args, validates its type, and returns {:ok, value} on success, or {:error, reason} if the key is missing or invalid.

Examples

iex> Fild.get_mandatory(%{foo: 42}, :foo, :integer)
{:ok, 42}

iex> Fild.get_mandatory(%{}, :foo, :integer)
{:error, "expected :foo to be given"}

iex> Fild.get_mandatory(%{foo: "bar"}, :foo, :integer)
{:error, "expected :foo to be integer, got: "bar""}

get_mandatory!(args, key, type \\ :any)

@spec get_mandatory!(Access.t(), key :: term(), field_type()) :: term() | no_return()

Fetches a mandatory key from the given args, validates its type against the provided type specification, and returns the value directly.

Raises a RuntimeError if the key is missing or if the value does not conform to the expected type.

Examples

iex> Fild.get_mandatory!(%{foo: 42}, :foo, :integer)
42

iex> Fild.get_mandatory!(%{}, :foo, :integer)
** (RuntimeError) expected :foo to be given

iex> Fild.get_mandatory!(%{foo: "bar"}, :foo, :integer)
** (RuntimeError) expected :foo to be integer, got: "bar"

get_optional(args, key, type \\ :any, default \\ nil)

@spec get_optional(Access.t(), key :: term(), field_type(), term()) ::
  {:ok, term()} | {:error, String.t()}

Fetches an optional key from args, validates its type if present, and returns {:ok, value} or {:ok, default} if the key is missing.

Returns {:error, reason} if the key exists but the value is invalid.

Examples

iex> Fild.get_optional(%{foo: 42}, :foo, :integer, 0)
{:ok, 42}

iex> Fild.get_optional(%{}, :foo, :integer, 0)
{:ok, 0}

iex> Fild.get_optional(%{foo: "bar"}, :foo, :integer, 0)
{:error, "expected :foo to be integer, got: "bar""}

get_optional!(args, key, type \\ :any, default \\ nil)

@spec get_optional!(Access.t(), key :: term(), field_type(), term()) ::
  term() | no_return()

Fetches an optional key from args, validates its type if present, and returns the value or the provided default.

Raises a RuntimeError if the key exists but the value is invalid.

Examples

iex> Fild.get_optional!(%{foo: 42}, :foo, :integer, 0)
42

iex> Fild.get_optional!(%{}, :foo, :integer, 0)
0

iex> Fild.get_optional!(%{foo: "bar"}, :foo, :integer, 0)
** (RuntimeError) expected :foo to be integer, got: "bar"

validate_field(key, value, type)

@spec validate_field(key :: atom(), value :: term(), field_type()) ::
  :ok | {:error, binary()}

Validates the given value against the specified type.

Returns :ok if the value matches the type, or {:error, reason} otherwise.

Examples

iex> Fild.validate_field(:foo, 42, :integer)
:ok

iex> Fild.validate_field(:foo, "bar", :integer)
{:error, "expected :foo to be integer, got: "bar""}

iex> Fild.validate_field(:foo, :a, {:one_of, [:a, :b]})
:ok

iex> Fild.validate_field(:foo, :c, {:one_of, [:a, :b]})
{:error, "expected :foo to be one of [:a, :b], got: :c"}

iex> Fild.validate_field(:foo, [1, 2], {:list_of, :integer})
:ok

iex> Fild.validate_field(:foo, [1, "no"], {:list_of, :integer})
{:error, "expected :foo to be a list of :integer, got: [1, "no"]"}

iex> Fild.validate_field(:foo, %URI{}, {:struct, URI})
:ok

iex> Fild.validate_field(:foo, %{}, {:struct, URI})
{:error, "expected :foo to be URI struct, got: %{}"}