View Source Inlets, unwrapping and context

Inlets

Building a request to be passed to a pipeline can be verbose, especially when you just need to pass in a couple of arguments in the command field. Plumbery provides a helper that makes things easier, called inlet. An inlet is a generated function that stuffs arguments into a request's command struct/map and calls a pipeline.

Let's see how inlets work. If you read through other guides, you may have already noticed that we use examples that do not look like something one would realistically write but hopefully explain concepts well. I will not break this pattern and create a pipeline that does something that does not require a pipeline.

defmodule Example do
  use Plumbery
  import Plumbery.Request
  defp mul_xy(%{command: %{x: x, y: y}} = req), do: success(req, x * y)

  pipeline :multiply do
    pipe :mul_xy
  end
end

%{result: {:ok, 999}} =
  %Plumbery.Request{command: %{x: 9, y: 111}}
  |> Example.multiply()

Looks like that's too much effort to just pass in a couple of numbers. Let's create and use an inlet.

defmodule Example do
  use Plumbery
  import Plumbery.Request
  defp mul_xy(%{command: %{x: x, y: y}} = req), do: success(req, x * y)

  pipeline :multiply_pipeline do
    private true
    pipe :mul_xy
    inlet multiply(x, y \\ 2) when is_number(x) and is_number(y)
    # This requires x and y to be the same
    inlet multiply_x_x(x, y = x) when is_number(x)
  end
end

%{result: {:ok, 999}} = Example.multiply(9, 111)
%{result: {:ok, 18}} = Example.multiply(9)
%{result: {:ok, 81}} = Example.multiply_x_x(9, 9)
# raises FunctionClauseError
%{result: {:ok, 90}} = Example.multiply_x_x(9, 10)

Here we can see some interesting things about an inlet:

  • you can define the exact signature of the inlet function, including patterns, default values and guard clauses
  • the arguments are added to request's command, under the same names as the arguments

Info

In some cases when using named arguments and patterns it is not possible to distinguish variable name from the pattern (like in y = x above). In that case the lefthand side is used as variable name and the key to put inside the command map.

Unwrapping

Passing the whole request structure from pipe to pipe is necessary since we need to keep track of the pipeline state, but for a pipeline that is not meant to be included in other pipelines all we are interested in is the execution result, not command, assigns and other stuff inside the request struct. We can make a pipeline to return only the result using the unwrap option.

defmodule Example do
  use Plumbery
  import Plumbery.Request
  defp mul_xy(%{command: %{x: x, y: y}} = req), do: success(req, x * y)

  pipeline :multiply_pipeline do
    private true
    unwrap true
    pipe :mul_xy
    inlet multiply(x, y \\ 2) when is_number(x) and is_number(y)
    # This requires x and y to be the same
    inlet multiply_x_x(x, y = x) when is_number(x)
  end
end

{:ok, 999} = Example.multiply(9, 111)
{:ok, 18} = Example.multiply(9)
{:ok, 81} = Example.multiply_x_x(9, 9)
# raises FunctionClauseError
{:ok, 90} = Example.multiply_x_x(9, 10)

Inlets can be also defined outside of a pipeline, in that case you need to specify the pipeline to execute:

defmodule Example do
  use Plumbery
  import Plumbery.Request
  defp mul_xy(%{command: %{x: x, y: y}} = req), do: success(req, x * y)

  pipeline :multiply_pipeline do
    private true
    unwrap true
    pipe :mul_xy
  end

  inlet multiply(x, y \\ 2) when is_number(x) and is_number(y) do
    pipeline :multiply_pipeline
  end

  # This requires x and y to be the same
  inlet multiply_x_x(x, y = x) when is_number(x), pipeline: :multiply_pipeline
end

{:ok, 999} = Example.multiply(9, 111)
{:ok, 18} = Example.multiply(9)
{:ok, 81} = Example.multiply_x_x(9, 9)
# raises FunctionClauseError
{:ok, 90} = Example.multiply_x_x(9, 10)

Context

Oftentimes when building APIs it is common that most entrypoints require a context argument with current user and other information. For that purpose Plumbery.Request has context field, but other than that Plumbery does not provide support for context. The only exception is the ability to add context as the last argument for functions generated by inlets. If you specify use_context option, the context will be added as the last argument of the generated function.

defmodule Example do
  use Plumbery
  import Plumbery.Request
  defp act(req), do: success(req, req.context.actor)

  pipeline :example_pipeline do
    pipe :act
    unwrap true
    inlet entry(), use_context: true
  end
end

{:ok, :system} = Example.entry(%{actor: :system})