View Source Exop.Operation behaviour (Exop v1.4.5)
Provides macros for an operation's contract definition and process/1 function.
Example
defmodule SomeOperation do
use Exop.Operation
parameter :param1, type: :integer, required: true
parameter :param2, type: :string, length: %{max: 3}, format: ~r/foo/
def process(params) do
"This is the operation's result with one of the params = " <> params[:param1]
end
end
Summary
Callbacks
Operation's entry point. Takes defined contract as the single parameter.
Contract itself is a list of maps: [%{name: atom(), opts: keyword()}]
Functions
Authorizes an action with predefined policy (see Policy check
macro docs).
If authorization fails, any code after (below) auth check will be postponed (an error {:error, {:auth, _reason}}
will be returned immediately)
Defines a callback module that will be used for a successful operation as side effect.
Returns policy that was defined in an operation.
Defines a fallback module that will be used for an operation's non-ok-tuple (fail) result handling.
Defines a parameter with name
and opts
in an operation contract.
Options could include the parameter value checks and transformations (like coercion).
Defines a policy that will be used for authorizing the possibility of a user to invoke an operation.
Callbacks
Functions
Authorizes an action with predefined policy (see Policy check
macro docs).
If authorization fails, any code after (below) auth check will be postponed (an error {:error, {:auth, _reason}}
will be returned immediately)
Defines a callback module that will be used for a successful operation as side effect.
defmodule MultiplyByTenOperation do
use Exop.Operation
callback LiveProdcast, topic: "math", type: "mutliplication"
parameter :a, type: :integer, required: true
def process(%{a: a}), do: a * 10
end
A callback module itself might be:
defmodule LiveProdcast do
use Exop.Callback
def process(operation_module, params_passed_to_the_operation, successful_result, opts) do
Phoenix.PubSub.broadcast(MyApp.PubSub, opts[:topic], %{type: opts[:type], payload: successful_result})
end
end
opts
is an open keyword list to pass needed options and metadata to the successful call.
@spec current_policy() :: any()
Returns policy that was defined in an operation.
Defines a fallback module that will be used for an operation's non-ok-tuple (fail) result handling.
defmodule MultiplyByTenOperation do
use Exop.Operation
fallback LoggerFallback
parameter :a, type: :integer, required: true
def process(%{a: a}), do: a * 10
end
A fallback module itself might be:
defmodule LoggerFallback do
use Exop.Fallback
require Logger
def process(operation_module, params_passed_to_the_operation, operation_error_result) do
Logger.error("Oops")
end
end
If return: true
option is provided then failed operation's run/1
will return the
fallback's process/3
result.
Defines a parameter with name
and opts
in an operation contract.
Options could include the parameter value checks and transformations (like coercion).
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.
Example
parameter :some_param, type: :map, required: true
parameter "my parameter", type: :map, required: true
Available checks are:
type
Checks whether a parameter's value is of declared type.
parameter :some_param, type: :map
required
Checks the presence of a parameter in passed params collection.
parameter :some_param, required: true
default
Checks if the parameter is missed and assigns default value to it if so.
parameter :some_param, default: "default value"
numericality
Checks whether a parameter's value is a number and passes constraints (if constraints were defined).
parameter :some_param, numericality: %{equal_to: 10, greater_than: 0,
greater_than_or_equal_to: 10,
less_than: 20,
less_than_or_equal_to: 10}
equals
Checks whether a parameter's value exactly equals given value (with type equality).
parameter :some_param, equals: 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
Checks wether parameter's value matches given regex.
parameter :some_param, format: ~r/foo/
length
Checks the length of a parameter's value.
parameter :some_param, length: %{min: 5, max: 10, is: 7, in: 5..8}
inner
Checks the inner of either Map or Keyword parameter.
parameter :some_param, type: :map, inner: %{
a: [type: :integer, required: true],
b: [type: :string, length: %{min: 1, max: 6}]
}
struct
Checks whether the given parameter is expected structure.
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:
parameter :list_param, list_item: %{type: :string, length: %{min: 7}}
func
Checks whether an item is valid over custom validation function.
parameter :some_param, func: &__MODULE__.your_validation/2
def your_validation({param_name, param_value}, all_received_params_map) do
# your validation logic based on given arguments is here
end
allow_nil
It is not a parameter check itself, because it doesn't return any validation errors.
It is a parameter attribute which allow you to have other checks for a parameter whilst have
a possibility to pass nil
as the parameter's value.
If nil
is passed all the parameter's checks are ignored during validation.
from
This option allows you to pass a parameter to run/1
and run!/1
functions with one name and
work with this parameter within an operation under another name.
parameter :a, type: :integer, from: "a"
subset_of
Checks whether a parameter's value (list) is a subset of a defined check-list. To pass this check, all items within given into an operation parameter should be included into check-list, otherwise the check is failed.
parameter :some_param, subset_of: [1, 2, :a, "b", C]
Interrupt
In some cases you might want to make an 'early return' from process/1
function.
For this purpose you can call interrupt/1
function within process/1
and pass an interruption reason to it.
An operation will be interrupted and return {:interrupt, your_reason}
def process(_params) do
interrupt(%{fail: "oops"})
:ok # will not return it
end
Coercion
It is possible to coerce a parameter before the contract validation, all validation checks
will be invoked on coerced parameter value.
Since coercion changes a parameter before any validation has been invoked,
default values are resolved (with :default
option) before the coercion.
The flow looks like: Resolve param default value -> Coerce -> Validate coerced
parameter :a, type: :string, coerce_with: &__MODULE__.to_string/2
def to_string({:a, value}, %{} = _received_params) when is_integer(value) do
Integer.to_string(value)
end
def to_string({:a, value}, %{} = _received_params) when is_binary(value) do
value
end
For more information and examples check out general Exop docs.
Defines a policy that will be used for authorizing the possibility of a user to invoke an operation.
defmodule ReadOperation do
use Exop.Operation
policy MonthlyReportPolicy, :can_read?
parameter :user, required: true, struct: User
def process(params) do
authorize(params.user)
# make some reading...
end
end
A policy itself might be:
defmodule MonthlyReportPolicy do
# not only Keyword or Map as an argument since 1.1.1
def can_read?(%User{role: "manager"}), do: true
def can_read?(_opts), do: false
def can_write?(%User{role: "manager"}), do: true
def can_write?(_opts), do: false
end