Tutorial View Source
Your first pipeline
Mix.install([{:opus, "~> 0.8"}, {:kino, "~> 0.5"}])Below you'll see a simple pipeline with two steps. It takes a number, adds 1 then multiplies by 2.
When a module is made a pipeline with use Opus.Pipeline it can be called with a call/1 function.
So our module below can be called with:
ArithmeticPipeline.call(number)defmodule ArithmeticPipeline do
use Opus.Pipeline
step(:add_one, with: &(&1 + 1))
step(:multiply_by_two)
def multiply_by_two(n), do: n * 2
endinput = Kino.Input.number("number", default: 0)number = Kino.Input.read(input)
ArithmeticPipeline.call(number)Error Handling
So far we've only defined step stages.
This stage processes the input value and with a success value the next stage is called with that value.
With an error value the pipeline is halted and an {:error, any} is returned.
defmodule ArithmeticPipelineWithErrors do
use Opus.Pipeline
step(:add_one, with: &(&1 + 1))
step(:multiply_by_two)
step(:add_three, with: &(&1 + 3))
def multiply_by_two(n) when n < 42, do: n * 2
def multiply_by_two(_), do: {:error, "I only handle numbers < 42"}
endTry this out with a number > 42 and you should see an Opus.PipelineError.
As you can see when a step function returns an error tuple
like {:error, "I only handle numbers < 42"} the pipeline
is halted an the next stages are not executed.
input2 = Kino.Input.number("input2", default: 43)number = Kino.Input.read(input2)
ArithmeticPipelineWithErrors.call(number)Validating Input
defmodule Validator do
use Opus.Pipeline
check(:valid_user, with: &match?(%{user: %{id: id}} when is_integer(id), &1))
check(:even_user, with: &(rem(&1.user.id, 2) == 0), error_message: "User should have an even id")
endinput3 = Kino.Input.number("input3", default: 3)number = Kino.Input.read(input3)
Validator.call(%{user: %{id: number}})The error message to return when a check fails is configurable. It can be an atom, string or a function.
defmodule ValidatorWithError do
use Opus.Pipeline
check(:valid_user, with: &match?(%{user: %{id: id}} when is_integer(id), &1))
check(:even_user,
with: &(rem(&1.user.id, 2) == 0),
error_message: fn %{user: %{id: id}} ->
"Oh the user should have an even id, #{id} is not even"
end
)
endValidatorWithError.call(%{user: %{id: 5}})Side-effects with the tee stage
You can use the tee macro for side-effects. The return value in such stages is ignored.
defmodule ArithmeticSideEffectsPipeline do
use Opus.Pipeline
step(:add_one, with: &(&1 + 1))
tee(:print_number,
with: fn n ->
IO.puts("The given number is.. #{n}")
# The following error will be ignored
raise "error"
end
)
step(:multiply_by_two)
def multiply_by_two(n), do: n * 2
endNotice how raising an error does not halt the pipeline with tee.
input4 = Kino.Input.number("input4", default: 5)number = Kino.Input.read(input4)
ArithmeticSideEffectsPipeline.call(number)Linking Pipelines
Pipelines can call other pipelines and there's the link macro to make that easier.
link is essentially a step where the linked pipeline is called with the step function argument
and the step returns the return value of the linked pipeline.
input5 = Kino.Input.text("input4", default: "5")input6 = Kino.Input.text("input4", default: "6")defmodule ReadFirstInput do
use Opus.Pipeline
step(:read, with: &put_in(&1[:a], Kino.Input.read(&1[:input_a])))
end
defmodule ReadSecondInput do
use Opus.Pipeline
step(:read, with: &put_in(&1[:b], Kino.Input.read(&1[:input_b])))
end
defmodule Calculator do
use Opus.Pipeline
link(ReadFirstInput)
link(ReadSecondInput)
step(:parse)
step(:add, with: &(&1.a + &1.b), if: &match?(%{operation: :add}, &1))
step(:multiply, with: &(&1.a * &1.b), if: &match?(%{operation: :multiply}, &1))
def parse(%{a: a, b: b} = calculation) do
{a, _} = Integer.parse(a)
{b, _} = Integer.parse(b)
%{calculation | a: a, b: b}
end
endNotice how we leverage the if option to calculate based on the given operation.
Both if and unless can be used to make a stage optional.
Calculator.call(%{operation: :add, input_a: input5, input_b: input6})Calculator.call(%{operation: :multiply, input_a: input5, input_b: input6})