Normandy.Coordination.Pattern (normandy v0.2.0)

View Source

Pattern matching utilities for agent results.

Provides helper functions and macros to make working with agent results more ergonomic and expressive.

Examples

# Extract successful results
{:ok, value} = Pattern.ok(agent_result)

# Extract error reasons
{:error, reason} = Pattern.error(agent_result)

# Safe extraction with default
value = Pattern.ok!(agent_result, default: "fallback")

# Check result type
true = Pattern.ok?(agent_result)
false = Pattern.error?(agent_result)

# Extract from multiple results
successes = Pattern.filter_ok([result1, result2, result3])
errors = Pattern.filter_errors([result1, result2, result3])

# Transform results
transformed = Pattern.map_ok(result, fn value -> String.upcase(value) end)

# Chain operations
final = result
  |> Pattern.map_ok(&String.trim/1)
  |> Pattern.map_ok(&String.upcase/1)
  |> Pattern.ok!(default: "EMPTY")

Summary

Functions

Returns {:ok, list_of_values} if all results are successful.

Converts a map of results into a result of a map.

Collects all successful results into a list.

Extracts the error reason from an {:error, reason} tuple.

Extracts the error reason from an {:error, reason} tuple or returns a default.

Returns true if the result is {:error, _}.

Filters a list of results to only include errors.

Filters a list of results to only include successful ones.

Returns the first successful result from a list, or the last error if all failed.

Transforms an error result by applying a function to its reason.

Transforms a successful result by applying a function to its value.

Extracts the value from an {:ok, value} tuple.

Extracts the value from an {:ok, value} tuple or returns a default.

Returns true if the result is {:ok, _}.

Chains a function that returns a result to a successful result.

Applies a function and wraps the result in {:ok, result}.

Unwraps a result, raising an error if it's not successful.

Wraps a value in {:ok, value} if it's not already a result tuple.

Types

result()

@type result() :: {:ok, term()} | {:error, term()}

Functions

all_ok(results)

@spec all_ok([result()]) :: {:ok, [term()]} | {:error, [term()]}

Returns {:ok, list_of_values} if all results are successful.

Returns {:error, list_of_reasons} if any result is an error.

Examples

iex> results = [{:ok, 1}, {:ok, 2}, {:ok, 3}]
iex> Normandy.Coordination.Pattern.all_ok(results)
{:ok, [1, 2, 3]}

iex> results = [{:ok, 1}, {:error, "bad"}, {:ok, 3}]
iex> Normandy.Coordination.Pattern.all_ok(results)
{:error, ["bad"]}

iex> results = [{:error, "bad"}, {:error, "worse"}]
iex> Normandy.Coordination.Pattern.all_ok(results)
{:error, ["bad", "worse"]}

all_ok_map(results)

@spec all_ok_map(%{required(term()) => result()}) ::
  {:ok, %{required(term()) => term()}} | {:error, %{required(term()) => term()}}

Converts a map of results into a result of a map.

Returns {:ok, map_of_values} if all results are successful. Returns {:error, map_of_reasons} if any result is an error.

Examples

iex> results = %{a: {:ok, 1}, b: {:ok, 2}, c: {:ok, 3}}
iex> Normandy.Coordination.Pattern.all_ok_map(results)
{:ok, %{a: 1, b: 2, c: 3}}

iex> results = %{a: {:ok, 1}, b: {:error, "bad"}, c: {:ok, 3}}
iex> Normandy.Coordination.Pattern.all_ok_map(results)
{:error, %{b: "bad"}}

collect_ok(results)

@spec collect_ok([result()]) :: {:ok, [term()]} | {:error, [term()]}

Collects all successful results into a list.

Returns {:ok, list_of_values} if at least one success, otherwise {:error, list_of_reasons}.

Examples

iex> results = [{:ok, 1}, {:ok, 2}, {:ok, 3}]
iex> Normandy.Coordination.Pattern.collect_ok(results)
{:ok, [1, 2, 3]}

iex> results = [{:ok, 1}, {:error, "bad"}, {:ok, 3}]
iex> Normandy.Coordination.Pattern.collect_ok(results)
{:ok, [1, 3]}

iex> results = [{:error, "bad"}, {:error, "worse"}]
iex> Normandy.Coordination.Pattern.collect_ok(results)
{:error, ["bad", "worse"]}

error(result)

@spec error(result()) :: result()

Extracts the error reason from an {:error, reason} tuple.

Returns the original result if not an error.

Examples

iex> Normandy.Coordination.Pattern.error({:error, "reason"})
{:error, "reason"}

iex> Normandy.Coordination.Pattern.error({:ok, "value"})
{:ok, "value"}

error!(result, opts \\ [])

@spec error!(
  result(),
  keyword()
) :: term()

Extracts the error reason from an {:error, reason} tuple or returns a default.

Options

  • :default - Value to return if result is not {:error, reason} (default: nil)

Examples

iex> Normandy.Coordination.Pattern.error!({:error, "reason"}, [])
"reason"

iex> Normandy.Coordination.Pattern.error!({:ok, "value"}, default: "no error")
"no error"

iex> Normandy.Coordination.Pattern.error!({:ok, "value"}, [])
nil

error?(arg1)

@spec error?(result()) :: boolean()

Returns true if the result is {:error, _}.

Examples

iex> Normandy.Coordination.Pattern.error?({:error, "reason"})
true

iex> Normandy.Coordination.Pattern.error?({:ok, "value"})
false

filter_errors(results)

@spec filter_errors([result()]) :: [term()]

Filters a list of results to only include errors.

Returns a list of error reasons (without the :error wrapper).

Examples

iex> results = [{:ok, 1}, {:error, "bad"}, {:ok, 2}, {:error, "worse"}]
iex> Normandy.Coordination.Pattern.filter_errors(results)
["bad", "worse"]

iex> Normandy.Coordination.Pattern.filter_errors([{:ok, 1}, {:ok, 2}])
[]

filter_ok(results)

@spec filter_ok([result()]) :: [term()]

Filters a list of results to only include successful ones.

Returns a list of values (without the :ok wrapper).

Examples

iex> results = [{:ok, 1}, {:error, "bad"}, {:ok, 2}, {:ok, 3}]
iex> Normandy.Coordination.Pattern.filter_ok(results)
[1, 2, 3]

iex> Normandy.Coordination.Pattern.filter_ok([{:error, "bad"}, {:error, "worse"}])
[]

find_ok(results)

@spec find_ok([result()]) :: result()

Returns the first successful result from a list, or the last error if all failed.

Examples

iex> results = [{:error, "bad"}, {:ok, "good"}, {:ok, "also good"}]
iex> Normandy.Coordination.Pattern.find_ok(results)
{:ok, "good"}

iex> results = [{:error, "bad"}, {:error, "worse"}, {:error, "worst"}]
iex> Normandy.Coordination.Pattern.find_ok(results)
{:error, "worst"}

iex> Normandy.Coordination.Pattern.find_ok([])
{:error, :no_results}

map_error(result, fun)

@spec map_error(result(), (term() -> term())) :: result()

Transforms an error result by applying a function to its reason.

Leaves successful results unchanged.

Examples

iex> Normandy.Coordination.Pattern.map_error({:error, "reason"}, &String.upcase/1)
{:error, "REASON"}

iex> Normandy.Coordination.Pattern.map_error({:ok, "value"}, &String.upcase/1)
{:ok, "value"}

map_ok(result, fun)

@spec map_ok(result(), (term() -> term())) :: result()

Transforms a successful result by applying a function to its value.

Leaves error results unchanged.

Examples

iex> Normandy.Coordination.Pattern.map_ok({:ok, "hello"}, &String.upcase/1)
{:ok, "HELLO"}

iex> Normandy.Coordination.Pattern.map_ok({:error, "reason"}, &String.upcase/1)
{:error, "reason"}

ok(result)

@spec ok(result()) :: result()

Extracts the value from an {:ok, value} tuple.

Returns the original result if not successful.

Examples

iex> Normandy.Coordination.Pattern.ok({:ok, "value"})
{:ok, "value"}

iex> Normandy.Coordination.Pattern.ok({:error, "reason"})
{:error, "reason"}

ok!(result, opts \\ [])

@spec ok!(
  result(),
  keyword()
) :: term()

Extracts the value from an {:ok, value} tuple or returns a default.

Options

  • :default - Value to return if result is not {:ok, value} (default: nil)

Examples

iex> Normandy.Coordination.Pattern.ok!({:ok, "value"}, [])
"value"

iex> Normandy.Coordination.Pattern.ok!({:error, "reason"}, default: "fallback")
"fallback"

iex> Normandy.Coordination.Pattern.ok!({:error, "reason"}, [])
nil

ok?(arg1)

@spec ok?(result()) :: boolean()

Returns true if the result is {:ok, _}.

Examples

iex> Normandy.Coordination.Pattern.ok?({:ok, "value"})
true

iex> Normandy.Coordination.Pattern.ok?({:error, "reason"})
false

then(result, fun)

@spec then(result(), (term() -> result())) :: result()

Chains a function that returns a result to a successful result.

If the input is {:ok, value}, applies the function to the value. If the input is an error, returns the error unchanged.

This is similar to Elixir's with statement but for single results.

Examples

iex> alias Normandy.Coordination.Pattern
iex> result = {:ok, "  hello  "}
iex> result
...> |> Pattern.then(&{:ok, String.trim(&1)})
...> |> Pattern.then(&{:ok, String.upcase(&1)})
{:ok, "HELLO"}

iex> alias Normandy.Coordination.Pattern
iex> result = {:error, "bad input"}
iex> result
...> |> Pattern.then(&{:ok, String.trim(&1)})
...> |> Pattern.then(&{:ok, String.upcase(&1)})
{:error, "bad input"}

try_wrap(fun)

@spec try_wrap((-> term())) :: result()

Applies a function and wraps the result in {:ok, result}.

If the function raises an exception, returns {:error, exception}.

Examples

iex> Normandy.Coordination.Pattern.try_wrap(fn -> 1 + 1 end)
{:ok, 2}

iex> Normandy.Coordination.Pattern.try_wrap(fn -> raise "boom" end)
{:error, %RuntimeError{message: "boom"}}

unwrap!(arg)

@spec unwrap!(result()) :: term() | no_return()

Unwraps a result, raising an error if it's not successful.

Examples

iex> Normandy.Coordination.Pattern.unwrap!({:ok, "value"})
"value"

iex> Normandy.Coordination.Pattern.unwrap!({:error, "reason"})
** (RuntimeError) Unwrap failed: "reason"

wrap(result)

@spec wrap(term()) :: result()

Wraps a value in {:ok, value} if it's not already a result tuple.

Examples

iex> Normandy.Coordination.Pattern.wrap("value")
{:ok, "value"}

iex> Normandy.Coordination.Pattern.wrap({:ok, "value"})
{:ok, "value"}

iex> Normandy.Coordination.Pattern.wrap({:error, "reason"})
{:error, "reason"}