docs: Add option schemas to public API functions and improve docs. (#99)

This commit is contained in:
James Harton 2024-03-18 14:21:50 +13:00 committed by GitHub
parent 27a01e7862
commit 7032155f97
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 168 additions and 12 deletions

View file

@ -1,5 +1,5 @@
defmodule Reactor do
alias Reactor.{Dsl, Executor, Step}
alias Reactor.{Dsl, Error.Validation.StateError, Executor, Step}
@moduledoc """
Reactor is a dynamic, concurrent, dependency resolving saga orchestrator.
@ -129,13 +129,58 @@ defmodule Reactor do
steps: [Step.t()]
}
@doc false
@doc "A guard which returns true if the value is a Reactor struct"
@spec is_reactor(any) :: Macro.t()
defguard is_reactor(reactor) when is_struct(reactor, __MODULE__)
@option_schema [
max_concurrency: [
type: :pos_integer,
required: false,
doc: "The maximum number of processes to use to run the Reactor"
],
timeout: [
type: {:or, [:pos_integer, {:literal, :infinity}]},
required: false,
default: :infinity,
doc: "How long to allow the Reactor to run for"
],
max_iterations: [
type: {:or, [:pos_integer, {:literal, :infinity}]},
required: false,
default: :infinity,
doc: "The maximum number of times to allow the Reactor to loop"
],
async_option: [
type: :boolean,
required: false,
default: true,
doc: "Whether to allow the Reactor to start processes"
],
concurrency_key_option: [
type: :reference,
required: false,
hide: true
]
]
@doc """
Run a reactor.
Attempt to run a Reactor.
## Arguments
* `reactor` - The Reactor to run, either a Reactor DSL module, or a Reactor
struct.
* `inputs` - A map of values passed in to satisfy the Reactor's expected
inputs.
* `context` - An arbitrary map that will be merged into the Reactor context
and passed into each step.
## Options
#{Spark.Options.docs(@option_schema)}
"""
@doc spark_opts: [{4, @option_schema}]
@spec run(t | module, inputs, context_arg, options) :: {:ok, any} | {:error, any} | {:halted, t}
def run(reactor, inputs \\ %{}, context \\ %{}, options \\ [])
@ -152,4 +197,24 @@ defmodule Reactor do
when is_reactor(reactor) and reactor.state in ~w[pending halted]a do
Executor.run(reactor, inputs, context, options)
end
def run(reactor, _inputs, _context, _options) do
{:error,
StateError.exception(
reactor: reactor,
state: reactor.state,
expected: ~w[pending halted]a
)}
end
@doc "Raising version of `run/4`."
@spec run!(t | module, inputs, context_arg, options) :: any | no_return
def run!(reactor, inputs \\ %{}, context \\ %{}, options \\ [])
def run!(reactor, inputs, context, options) do
case run(reactor, inputs, context, options) do
{:ok, value} -> {:ok, value}
{:error, reason} -> raise reason
end
end
end

View file

@ -22,7 +22,7 @@ defmodule Reactor.Builder do
import Reactor, only: :macros
import Reactor.Utils
@type step_options :: [async? | max_retries() | arguments_transform | context | ref]
@type step_options :: [async? | max_retries | arguments_transform | context | ref]
@typedoc "Should the step be run asynchronously?"
@type async? :: {:async?, boolean | (keyword -> boolean)}

View file

@ -9,9 +9,54 @@ defmodule Reactor.Builder.Step do
import Reactor.Argument, only: :macros
import Reactor.Utils
@option_schema [
async?: [
type: :boolean,
default: true,
required: false,
doc: "Allow the step to be run asynchronously?"
],
max_retries: [
type: {:or, [:non_neg_integer, {:literal, :infinity}]},
default: 100,
required: false,
doc: "The maximum number of times the step can ask to be retried"
],
transform: [
type: {:or, {:mfa_or_fun, 1}},
required: false,
doc: "A function which can modify all incoming arguments"
],
context: [
type: :map,
required: false,
doc: "Context which will be merged with the reactor context when calling this step"
],
ref: [
type: {:in, [:step_name, :make_ref]},
required: false,
default: :make_ref,
doc: "What sort of step reference to generate"
]
]
@doc """
Build and add a new step to a Reactor.
## Arguments
* `reactor` - An existing Reactor struct to add the step to.
* `name` - The proposed name of the new step.
* `impl` - A module implementing the `Reactor.Step` behaviour (or a tuple
containing the module and options).
* `arguments` - A list of `Reactor.Argument` structs or shorthand keyword
lists.
## Options
#{Spark.Options.docs(@option_schema)}
"""
@doc spark_opts: [{5, @option_schema}]
@spec add_step(
Reactor.t(),
any,
@ -65,7 +110,21 @@ defmodule Reactor.Builder.Step do
You're most likely to use this when dynamically returning new steps from an
existing step.
## Arguments
* `name` - The name of the new step.
* `impl` - A module implementing the `Reactor.Step` behaviour (or a tuple
containing the module and options).
* `arguments` - A list of `Reactor.Argument` structs or shorthand keyword
lists.
## Options
#{Spark.Options.docs(@option_schema)}
"""
@doc spark_opts: [{5, @option_schema}]
@spec new_step(any, Builder.impl(), [Builder.step_argument()], Builder.step_options()) ::
{:ok, Step.t()} | {:error, any}
def new_step(name, impl, arguments, options) do

View file

@ -2,8 +2,14 @@ defmodule Reactor.Step.AnonFn do
@moduledoc """
The built-in step for executing in-line DSL anonymous functions.
This step assumes that it is being called as per the
`:spark_function_behaviour` semantics.
## Options
* `run` - a one or two arity function or MFA which will be called as the run
function of the step.
* `compensate` - a one to three arity function or MFA which will be called as
the compensate function of the step. Optional.
* `undo` - a one to three arity function or MFA which will be called as the
undo function of this step. Optional.
"""
use Reactor.Step

View file

@ -5,13 +5,18 @@ defmodule Reactor.Step.ComposeWrapper do
Yes, this gets hairy, fast.
This is dynamically injected into steps by `Reactor.Step.Compose` - you
probably don't want to use this unless you're sure what you're doing.
## Options
* `original` - the original value of the Step's `impl` key.
* `prefix` - a list of values to be placed in the `name` before the original value.
* `prefix` - a list of values to be placed in the `name` before the original
value.
> #### Tip {: .tip}
>
> This is dynamically injected into steps by `Reactor.Step.Compose`.
>
> Most likely you will never need to use this step directly.
"""
use Reactor.Step

View file

@ -2,8 +2,20 @@ defmodule Reactor.Step.Transform do
@moduledoc """
The built-in step for executing input and argument transformations.
This step assumes that it is being executed as per `:spark_function_behaviour`
semantics.
Expects a single argument named `value` which contains the value to be
transformed.
## Options
* `fun` - a one or two arity function or MFA to use to modify the `value`
argument.
> #### Tip {: .tip}
>
> This step is emitted by the builder when an argument needs to be transformed
> before being passed into a step.
>
> Most likely you will never need to use this step directly.
"""
alias Reactor.{Error.Invalid.MissingArgumentError, Error.Invalid.TransformError, Step}

View file

@ -4,6 +4,14 @@ defmodule Reactor.Step.TransformAll do
The returned map is used as the arguments to the step, instead of the step's
defined arguments.
> #### Tip {: .tip}
>
> This step will be emitted by the builder when a step wants to transform all
> it's arguments.
>
> Most likely you will never need to use this step directly.
"""
use Reactor.Step

View file

@ -38,9 +38,10 @@ defmodule Reactor.MixProject do
end
end,
groups_for_modules: [
DSL: ~r/^Reactor\.Dsl$/,
Dsl: ~r/^Reactor\.Dsl.*/,
Steps: ~r/^Reactor\.Step.*/,
Middleware: ~r/^Reactor\.Middleware.*/,
Errors: ~r/^Reactor\.Error.*/,
Builder: ~r/^Reactor\.Builder.*/,
Internals: ~r/^Reactor\..*/
],