SkillAgentSearch skills...

Exop

Elixir library that provides macros which allow you to encapsulate business logic and validate incoming parameters with predefined contract.

Install / Use

/learn @implicitly-awesome/Exop
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Hex.pm API Docs Build Status

Exop

A library that helps you to organize your Elixir code in more domain-driven way. Exop provides macros which helps you to encapsulate business logic and offers you additionally: incoming params validation (with predefined contract), params coercion, policy check, fallback behavior, operations chaining and more.


Exop family:

ExopData

Interested in property-based testing? Check out new Exop family member - ExopData. If you use Exop to organize your code with ExopData you can get property generators in the most easiest way.

ExopPlug

The new ExopPlug library provides a convenient way to validate incoming parameters of your Phoenix application's controllers by offering you small but useful DSL.


Table of Contents

Here is the CHANGELOG that was started from ver. 0.4.1 ¯\_(ツ)_/¯

Installation

def deps do
  [{:exop, "~> 1.4"}]
end

Operation definition

defmodule IntegersDivision do
  use Exop.Operation

  parameter :a, type: :integer, default: 1
  parameter :b, type: :integer, required: false,
                numericality: %{greater_than: 0}

  def process(params) do
    result = params[:a] / params[:b]
    IO.inspect "The division result is: #{result}"
  end
end

Exop.Operation provides parameter macro, which is responsible for the contract definition. Its spec is @spec parameter(atom | String.t, Keyword.t) :: none, we define parameter name as the first argument and parameter options as the second Keyword argument.

A parameter name could be either an atom or a string. You could even mix atom-named and string-named parameters in an operation's contract.

Parameter options determine a contract of a parameter, a set of parameters contracts is an operation contract.

Business logic of an operation is defined in process/1 function, which is required by the Exop.Operation module behaviour.

After the contract and business logic were defined, you can invoke the operation simply by calling run/1 function:

iex> IntegersDivision.run(a: 50, b: 5)
{:ok, "The division result is: 10"}

Return type will be either {:ok, any()} (where the second item in the tuple is process/1 function's result) or {:error, {:validation, map()}} (where the map() is validation errors map).

for more information see Operation results section

Parameter checks

A parameter options could have various checks. Here the list of available checks:

  • type
  • required
  • default
  • numericality
  • equals (exactly)
  • in
  • not_in
  • format (regex)
  • length
  • inner
  • struct
  • list_item
  • func
  • allow_nil
  • from
  • subset_of

type

Checks whether a parameter's value is of declared type.

parameter :some_param, type: :map

Exop handle almost all Elixir types and some additional:

  • :boolean
  • :integer
  • :float
  • :string
  • :tuple
  • :map
  • :keyword
  • :list
  • :atom
  • :module
  • :function
  • :uuid

Unknown type always generates ArgumentError exception on compile time.

module 'type' means Exop expects a parameter's value to be an atom (a module name) and this module should be already loaded (ready to call it's functions)

uuid is not actually a "type" but I placed this under :type check because there is no reason to have dedicated :uuid check.

required

Checks the presence/absence of a parameter in passed to run/1 params collection. Given parameters collection fails the validation only if required parameter is missed, if required parameter's value is nil this parameter will pass this check.

parameter :param_a                   # the same as required: true, required by default
parameter :param_b, required: false  # this parameter is not required

By default, a parameter is required (since version 1.2.0, required: true). If you want to specify a parameter is not required, provide required: false. Why? Because you might find that you repetitively type required: true for almost every parameter in a contract. I think if you provide a parameter to an operation (define it in a contract) you expect to get it. Cases, when you need a parameter passed into an operation (and don't really care whether it is present or not), are pretty rare.

Since version 1.1.0 the behavior of this check has been changed. Check out CHANGELOG for more info.

default

Checks the presence of a parameter in passed to run/1 params collection, and if the parameter is missed - assign default value to it.

parameter :some_param, default: "default value"

# default value can be also a 1-arity function output
parameter :a, type: :integer, default: &__MODULE__.default_a/1
parameter :b, type: :integer

# this function takes params given to `run/1`
def default_a(params), do: params.b + 1

#iex> YourOperation.run(b: 1)
#iex> %{a: 2, b: 1}

numericality

Checks whether a parameter's value is a number and other numeric constraints. All possible constraints are listed in the example below.

parameter :some_param, numericality: %{equal_to: 10, # (aliases: `equals`, `is`, `eq`)
                                       greater_than: 0, # (alias: `gt`)
                                       greater_than_or_equal_to: 10 # (aliases: `min`, `gte`),
                                       less_than: 20, # (alias: `lt`)
                                       less_than_or_equal_to: 10 # (aliases: `max`, `lte`)}

equals

(alias: exactly)

Checks whether a parameter's value exactly equals given value (with type equality).

parameter :some_param, equals: 100.5
parameter :some_param, exactly: 100.5

in

Checks whether a parameter's value is within a given list.

parameter :some_param, in: ~w(a b c)

not_in

Checks whether a parameter's value is not within a given list.

parameter :some_param, not_in: ~w(a b c)

format

(alias: regex)

Checks wether parameter's value matches given regex.

parameter :some_param, format: ~r/foo/
parameter :some_param, regex: ~r/foo/

length

Checks the length of a parameter's value. The value should be one of handled types:

  • list (items count)
  • string (chars count)
  • atom (treated as string)
  • map (key-value pairs count)
  • tuple (items count)

length check is complex as numericality (should define map of inner checks). All possible checks are listed in the example below.

parameter :some_param, length: %{gte: 5, gt: 4, min: 5, lte: 10, lt: 9, max: 10, is: 7, in: 5..8}

inner

Checks the inner of either Map or Keyword parameter. It applies checks described in inner map to related inner items.

# some_param = %{a: 3, b: "inner_b_attr"}

parameter :some_param, type: :map, inner: %{
  a: [type: :integer],
  b: [type: :string, length: %{min: 1, max: 6}]
}

# you can omit `type` and `inner` checks keywords in order to check inner of your parameter,
# when `type` hasn't been specified explicitly, both keyword and map types pass the `type` validation
parameter :some_param, %{
  a: [type: :integer],
  b: [type: :string, length: %{min: 1, max: 6}]
}

And, of course, all checks on a parent parameter (:some_param in the example) are still applied.

struct

Checks whether the given parameter is expected structure.

parameter :some_param, struct: %SomeStruct{}
# or
parameter :some_param, struct: SomeStruct

list_item

Checks whether each of list items conforms defined checks. An item's checks could be any that Exop offers:

# list_param = ["1234567", "7chars"]

# you can omit `type` check while you're passing a list to an operation
parameter :list_param, list_item: %{type: :string, length: %{min: 7}}

Even more complex like inner:

# list_param = [
#   %TestStruct{a: 3, b: "6chars"},
#   %TestStruct{a: nil, b: "7charss"}
# ]

parameter :list_param, list_item: %{inner: %{
                                              a: %{type: :integer},
                                              b: %{type: :string, length: %{min: 7}}
                                            }}

Moreover, coerce_with and default options are available too.

`fu

View on GitHub
GitHub Stars216
CategoryDevelopment
Updated5mo ago
Forks15

Languages

Elixir

Security Score

97/100

Audited on Oct 12, 2025

No findings