Extep (Extep v0.1.1)
View SourceA tiny and friendly step runner for Elixir pipelines.
Extep is a simple and dependency-free utility that helps you compose Elixir pipelines using a shared context. It's useful for building multi-step workflows that can gracefully halt or error along the way. Extep is an implementation of the Railway-oriented programming and was inspired by Ecto.Multi and Sage.
Example
iex> params = %{user_id: 1, plan: "super-power-plus"}
iex> Extep.new(%{params: params})
...> |> Extep.run(:params, &validate_params/1)
...> |> Extep.run(:user, &fetch_user/1)
...> |> Extep.run(:items, &fetch_items/1)
...> |> Extep.return(&create_subscription/1)
{:ok, %{id: 123, object: "subscription", user_id: 1, items: [%{code: "item1"}, %{code: "item2"}]}}
Summary
Types
Functions
@spec new() :: t()
Creates a new Extep
struct with an empty context.
Examples
iex> Extep.new()
%Extep{status: :ok, context: %{}, message: nil}
Creates a new Extep
struct with an initial context map.
Examples
iex> Extep.new(%{foo: "bar"})
%Extep{status: :ok, context: %{foo: "bar"}, message: nil}
@spec return(t(), context_mutator_fun() | context_key()) :: any()
Returns a final result from your pipeline.
If your Extep
status is :ok
and you pass a function, it calls the function
with the context and returns whatever it returns. If you pass a context key,
it fetches the value for that key and returns it in an ok tuple.
If the pipeline was halted, it returns the content of the :message
field of the Extep struct.
If the pipeline errored, it returns {:error, message}
.
Examples
iex> Extep.new(%{foo: 1})
...> |> Extep.run(:bar, fn ctx -> {:ok, ctx.foo + 1} end)
...> |> Extep.return(fn ctx -> {:ok, ctx.bar + 2} end)
{:ok, 4}
iex> Extep.new(%{foo: 1})
...> |> Extep.run(:bar, fn ctx -> {:ok, ctx.foo + 1} end)
...> |> Extep.run(:baz, fn ctx -> {:ok, ctx.bar + 2} end)
...> |> Extep.return(:bar)
{:ok, 2}
iex> Extep.new(%{foo: 1})
...> |> Extep.run(:bar, fn ctx -> {:ok, ctx.foo + 1} end)
...> |> Extep.return(&return_error_tuple/1)
{:error, %{return_error_tuple: "error message"}}
iex> Extep.new(%{foo: 1})
...> |> Extep.run(:bar, fn ctx -> {:ok, ctx.foo + 1} end)
...> |> Extep.run(:baz, &return_error_tuple/1)
...> |> Extep.return(:bar)
{:error, %{return_error_tuple: "error message"}}
iex> Extep.new(%{foo: 1})
...> |> Extep.run(:bar, fn ctx -> {:ok, ctx.foo + 1} end)
...> |> Extep.run(:baz, fn _ctx -> {:error, "error message"} end)
...> |> Extep.return(:bar)
{:error, %{baz: "error message"}}
iex> Extep.new(%{foo: 1})
...> |> Extep.run(:bar, fn ctx -> {:ok, ctx.foo + 1} end)
...> |> Extep.run(fn _ctx -> {:error, "error message"} end)
...> |> Extep.return(:bar)
{:error, %{no_context_key: "error message"}}
iex> Extep.new(%{foo: 1})
...> |> Extep.run(:bar, fn ctx -> {:ok, ctx.foo + 1} end)
...> |> Extep.return(fn _ctx -> {:halt, {:ok, "halt message"}} end)
{:ok, "halt message"}
iex> Extep.new(%{foo: 1})
...> |> Extep.run(:bar, fn ctx -> {:ok, ctx.foo + 1} end)
...> |> Extep.return(fn _ctx -> {:halt, {:cancel, "cancel message"}} end)
{:cancel, "cancel message"}
iex> Extep.new(%{foo: 1})
...> |> Extep.run(:bar, fn ctx -> {:ok, ctx.foo + 1} end)
...> |> Extep.run(:baz, fn _ctx -> {:halt, {:ok, "halt message"}} end)
...> |> Extep.return(:bar)
{:ok, "halt message"}
@spec run(t(), context_checker_fun()) :: t()
Runs a checker function on the context without changing it.
If the function returns :ok
or {:ok, "value"}
, the pipeline continues.
If it returns {:halt, "reason"}
or {:error, "reason"}
, the pipeline stops and
the reason is set in the :message
field of the Extep struct.
Examples
iex> Extep.new(%{foo: 1})
...> |> Extep.run(fn _ctx -> :ok end)
%Extep{status: :ok, context: %{foo: 1}, message: nil}
iex> Extep.new(%{foo: 1})
...> |> Extep.run(fn _ctx -> {:ok, "is valid"} end)
%Extep{status: :ok, context: %{foo: 1}, message: nil}
iex> Extep.new(%{foo: 1})
...> |> Extep.run(&return_error_tuple/1)
%Extep{status: :error, context: %{foo: 1}, message: %{return_error_tuple: "error message"}}
iex> Extep.new(%{foo: 1})
...> |> Extep.run(fn _ctx -> {:error, "error message"} end)
%Extep{status: :error, context: %{foo: 1}, message: %{no_context_key: "error message"}}
iex> Extep.new(%{foo: 1})
...> |> Extep.run(:foo, fn _ctx -> {:halt, "halt message"} end)
%Extep{status: :halted, context: %{foo: 1}, message: "halt message"}
@spec run(t(), context_key(), context_mutator_fun()) :: t()
Runs a mutator function that updates the context with the result under the given key.
If the function returns {:ok, "value"}
, "value" is set to the given key and the
pipeline continues.
If it returns {:halt, "reason"}
or {:error, "reason"}
, the pipeline stops and
the reason is set in the :message
field of the Extep struct.
Examples
iex> Extep.new(%{foo: 1})
...> |> Extep.run(:foo, fn ctx -> {:ok, ctx.foo + 1} end)
...> |> Extep.run(:bar, fn ctx -> {:ok, ctx.foo + 2} end)
%Extep{status: :ok, context: %{foo: 2, bar: 4}, message: nil}
iex> Extep.new(%{foo: 1})
...> |> Extep.run(:foo, &return_error_tuple/1)
...> |> Extep.run(:bar, fn ctx -> {:ok, ctx.foo + 2} end)
%Extep{status: :error, context: %{foo: 1}, message: %{return_error_tuple: "error message"}}
iex> Extep.new(%{foo: 1})
...> |> Extep.run(:foo, fn _ctx -> {:error, "error message"} end)
...> |> Extep.run(:bar, fn ctx -> {:ok, ctx.foo + 2} end)
%Extep{status: :error, context: %{foo: 1}, message: %{foo: "error message"}}
iex> Extep.new(%{foo: 1})
...> |> Extep.run(:foo, fn _ctx -> {:halt, {:ok, "halt message"}} end)
...> |> Extep.run(:bar, fn ctx -> {:ok, ctx.foo + 2} end)
%Extep{status: :halted, context: %{foo: 1}, message: {:ok, "halt message"}}