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 defmodule Reactor do
alias Reactor.{Dsl, Executor, Step} alias Reactor.{Dsl, Error.Validation.StateError, Executor, Step}
@moduledoc """ @moduledoc """
Reactor is a dynamic, concurrent, dependency resolving saga orchestrator. Reactor is a dynamic, concurrent, dependency resolving saga orchestrator.
@ -129,13 +129,58 @@ defmodule Reactor do
steps: [Step.t()] steps: [Step.t()]
} }
@doc false @doc "A guard which returns true if the value is a Reactor struct"
@spec is_reactor(any) :: Macro.t() @spec is_reactor(any) :: Macro.t()
defguard is_reactor(reactor) when is_struct(reactor, __MODULE__) 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 """ @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} @spec run(t | module, inputs, context_arg, options) :: {:ok, any} | {:error, any} | {:halted, t}
def run(reactor, inputs \\ %{}, context \\ %{}, options \\ []) 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 when is_reactor(reactor) and reactor.state in ~w[pending halted]a do
Executor.run(reactor, inputs, context, options) Executor.run(reactor, inputs, context, options)
end 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 end

View file

@ -22,7 +22,7 @@ defmodule Reactor.Builder do
import Reactor, only: :macros import Reactor, only: :macros
import Reactor.Utils 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?" @typedoc "Should the step be run asynchronously?"
@type async? :: {:async?, boolean | (keyword -> boolean)} @type async? :: {:async?, boolean | (keyword -> boolean)}

View file

@ -9,9 +9,54 @@ defmodule Reactor.Builder.Step do
import Reactor.Argument, only: :macros import Reactor.Argument, only: :macros
import Reactor.Utils 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 """ @doc """
Build and add a new step to a Reactor. 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( @spec add_step(
Reactor.t(), Reactor.t(),
any, any,
@ -65,7 +110,21 @@ defmodule Reactor.Builder.Step do
You're most likely to use this when dynamically returning new steps from an You're most likely to use this when dynamically returning new steps from an
existing step. 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()) :: @spec new_step(any, Builder.impl(), [Builder.step_argument()], Builder.step_options()) ::
{:ok, Step.t()} | {:error, any} {:ok, Step.t()} | {:error, any}
def new_step(name, impl, arguments, options) do def new_step(name, impl, arguments, options) do

View file

@ -2,8 +2,14 @@ defmodule Reactor.Step.AnonFn do
@moduledoc """ @moduledoc """
The built-in step for executing in-line DSL anonymous functions. The built-in step for executing in-line DSL anonymous functions.
This step assumes that it is being called as per the ## Options
`:spark_function_behaviour` semantics.
* `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 use Reactor.Step

View file

@ -5,13 +5,18 @@ defmodule Reactor.Step.ComposeWrapper do
Yes, this gets hairy, fast. 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 ## Options
* `original` - the original value of the Step's `impl` key. * `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 use Reactor.Step

View file

@ -2,8 +2,20 @@ defmodule Reactor.Step.Transform do
@moduledoc """ @moduledoc """
The built-in step for executing input and argument transformations. The built-in step for executing input and argument transformations.
This step assumes that it is being executed as per `:spark_function_behaviour` Expects a single argument named `value` which contains the value to be
semantics. 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} 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 The returned map is used as the arguments to the step, instead of the step's
defined arguments. 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 use Reactor.Step

View file

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