Packages
altworx_runbox
21.1.2
25.0.0
24.0.0
23.1.0
23.0.0
22.2.0
22.1.0
22.0.0
21.2.0
21.1.2
21.1.1
21.1.0
21.0.0
20.0.0
19.0.0
18.0.0
17.2.0
17.1.0
17.0.1
17.0.0
16.2.0
16.1.0
16.0.0
15.0.0
14.1.0
14.0.1
14.0.0
13.0.3
13.0.2
13.0.1
13.0.0
12.1.0
12.0.0
11.0.1
11.0.0
10.0.0
9.0.0
8.0.0
7.0.1
7.0.0
6.0.0
5.0.0
4.0.0
3.0.0
2.1.0
2.0.0
1.4.1
1.4.0
1.3.0
1.2.0
1.1.0
1.0.0
0.1.3
0.1.2
0.1.1
0.1.0
Runbox is a library for running Altworx scenarios.
Current section
Files
Jump to
Current section
Files
lib/runbox/runtime/sandbox.ex
defmodule Runbox.Runtime.Sandbox do
@moduledoc group: :dev_tools
@moduledoc """
Sandbox is a helper for executing runs without started Altworx application.
Sandbox can be used for scenarios unit testing and also for interactive development, e.g. in
LiveBook.
This is a scenario-type agnostic version of `Runbox.Runtime.Stage.Sandbox` and
`Runbox.Runtime.Simple.Sandbox`. This module will identify the type of the scenario and will route
the request to the appropriate Sandbox implementation.
For interactive development of scenarios, run `iex --erl "-runbox mode slave" -S mix` from command
line in your scenarios application root directory and then use Sandbox functions to execute your
runs (of course use `recompile` before run execution if you changed scenario code).
"""
alias Mix.Project
alias Runbox.Message
alias Runbox.Scenario
alias Runbox.Scenario.OutputAction
alias Runbox.Scenario.Type
alias Runbox.Scenario.UserAction
alias Runbox.ScenarioRelease.SlaveFunc
@type topics :: %{topic_name => [Message.t()]}
@type topic_name :: String.t()
@type opts :: [opt()]
@type opt :: {:start_from, non_neg_integer()} | {:modules, [module()]}
@callback execute_run(topics(), scenario_id :: String.t(), [opt() | {:scenario, Scenario.t()}]) ::
{:ok, [OutputAction.t()]} | {:error, any()}
@sandboxes %{
Type.simple() => Runbox.Runtime.Simple.Sandbox,
Type.stage() => Runbox.Runtime.Stage.Sandbox
}
@doc """
Starts a new run for `scenario_id` and processes all input messages.
Returns all output actions generated by run execution. Input messages for each runtime topic,
referenced by its logical name, are passed in the `topics` argument. Undefined topics are
considered empty.
There are few options that can be specified.
- `:start_from` - specify the start from value for the run. The value is used to filter out
messages and is passed to the scenraio's `init` callback.
- `:modules` - list of modules containing the scenario (manifest and template, which can be just
one module). If not specified, modules are auto-discovered from the current Mix app. This opens
up the possibility to write ad-hoc scenarios with only Runbox as dependency, e.g. in
[Livebook](https://livebook.dev/).
"""
@spec execute_run(topics(), scenario_id :: String.t(), opts()) ::
{:ok, [OutputAction.t()]} | {:error, any()}
def execute_run(topics, scenario_id, opts \\ []) do
modules = Keyword.get(opts, :modules) || modules_from_mix_app()
with {:ok, scenario} <- find_scenario(scenario_id, modules),
{:ok, module} <- find_sandbox(scenario.manifest.type) do
module.execute_run(topics, scenario_id, Keyword.put(opts, :scenario, scenario))
end
end
# For internal uses in specific Sandbox implementations.
# Sets up user-action signer in the Sandbox ENV.
@doc false
def setup_signer do
Application.put_env(
:runbox,
UserAction.get_signer_env_key(),
{Node.self(), Runbox.Runtime.Sandbox.UserActionSigner, :sign}
)
end
# for internal use between Sandboxes
@doc false
def modules_from_mix_app do
if Code.ensure_loaded?(Project) and function_exported?(Project, :config, 0) do
case Keyword.fetch(Project.config(), :app) do
{:ok, app} -> Application.spec(app, :modules)
:error -> nil
end
end
end
@doc false
def find_scenario(scenario_id, modules) do
case Enum.find(SlaveFunc.release_info(modules), &(&1.manifest.id == scenario_id)) do
nil -> {:error, :not_found}
scenario -> {:ok, scenario}
end
end
defp find_sandbox(type) do
with :error <- Map.fetch(@sandboxes, type) do
{:error, :sandbox_not_available}
end
end
end