defmodule Runbox.Runtime.OutputAction do
@moduledoc """
Output Action for internal use by Runtime
"""
alias Runbox.Runtime.OABody
alias Runbox.Scenario.OutputAction, as: ScenarioOA
alias __MODULE__, as: OA
defstruct [:body, :timestamp, :scenario_id, :run_id]
@typedoc "Runtime Output Action"
@type t :: %__MODULE__{
body: ScenarioOA.oa_params() | ScenarioOA.BadOutputAction.t(),
timestamp: integer(),
scenario_id: String.t(),
run_id: String.t()
}
@doc """
Creates a new output action.
To produce a valid output action, the `params` have to be one of the accepted
structs. See the documentation of the particular structs for details.
If the `params` are not one of the accepted structs or some of the fields do
not pass validation, it returns an output action with body set to
`t:Runbox.Scenario.OutputAction.BadOutputAction.t/0` with original params
inside.
"""
@spec new(params :: ScenarioOA.oa_params(), integer(), String.t(), String.t()) :: t()
def new(params, timestamp, scenario_id, run_id) do
if is_oa_body?(params) and valid_body?(params) do
%OA{body: params, timestamp: timestamp, scenario_id: scenario_id, run_id: run_id}
else
body = %ScenarioOA.BadOutputAction{params: params}
%OA{body: body, timestamp: timestamp, scenario_id: scenario_id, run_id: run_id}
end
end
@doc """
Returns `true` if valid output action.
"""
@spec valid?(OA.t() | any()) :: boolean()
def valid?(%OA{body: body, timestamp: ts, scenario_id: sid, run_id: rid}) do
is_oa_body?(body) and valid_body?(body) and is_integer(ts) and is_binary(sid) and is_binary(rid)
end
def valid?(_) do
false
end
@doc """
Returns `true` if valid output action body.
"""
@spec is_oa_body?(ScenarioOA.oa_params() | any()) :: boolean()
def is_oa_body?(params) do
not is_nil(OABody.impl_for(params))
end
defp valid_body?(params) do
OABody.valid?(params)
end
end
defprotocol Runbox.Runtime.OABody do
@moduledoc false
@spec valid?(t) :: boolean()
def valid?(body)
end
defimpl Runbox.Runtime.OABody, for: Runbox.Scenario.OutputAction.UpsertAssetAttributes do
def valid?(%Runbox.Scenario.OutputAction.UpsertAssetAttributes{
type: type,
id: id,
attributes: attributes
}) do
is_binary(type) and
id_valid?(id) and
is_map(attributes)
end
defp id_valid?(id), do: is_binary(id) and not String.contains?(id, "/")
end
defimpl Runbox.Runtime.OABody, for: Runbox.Scenario.OutputAction.DeleteAssetAttributes do
def valid?(%Runbox.Scenario.OutputAction.DeleteAssetAttributes{
type: type,
id: id,
attributes: attributes
}) do
is_binary(type) and
is_binary(id) and
is_map(attributes)
end
end
defimpl Runbox.Runtime.OABody, for: Runbox.Scenario.OutputAction.DeleteAllAssetAttributes do
def valid?(%Runbox.Scenario.OutputAction.DeleteAllAssetAttributes{
type: type,
id: id
}) do
is_binary(type) and is_binary(id)
end
end
defimpl Runbox.Runtime.OABody, for: Runbox.Scenario.OutputAction.UpsertEdge do
def valid?(%Runbox.Scenario.OutputAction.UpsertEdge{
from_type: from_type,
from_id: from_id,
to_type: to_type,
to_id: to_id,
type: type
}) do
is_binary(from_type) and is_binary(from_id) and
is_binary(to_type) and is_binary(to_id) and
is_binary(type)
end
end
defimpl Runbox.Runtime.OABody, for: Runbox.Scenario.OutputAction.DeleteEdge do
def valid?(%Runbox.Scenario.OutputAction.DeleteEdge{
from_type: from_type,
from_id: from_id,
to_type: to_type,
to_id: to_id,
type: type
}) do
is_binary(from_type) and is_binary(from_id) and
is_binary(to_type) and is_binary(to_id) and
is_binary(type)
end
end
defimpl Runbox.Runtime.OABody, for: Runbox.Scenario.OutputAction.Notification do
alias Runbox.Scenario.OutputAction.Notification
def valid?(%Notification{
type: type,
primary_asset: primary_asset,
ids: ids,
aqls: aqls,
priority: priority,
metadata: metadata,
direct_subscriptions: direct_subscriptions,
attachments: attachments
}) do
type_valid?(type) and
ids_valid?(ids) and
is_map(aqls) and
is_map(metadata) and
priority_valid?(priority) and
direct_subscriptions_valid?(direct_subscriptions) and
primary_asset_valid?(primary_asset) and
attachments_valid?(attachments)
end
defp type_valid?(type), do: is_binary(type) or (is_atom(type) and not is_nil(type))
defp priority_valid?(priority), do: priority in [:low, :medium, :high, "low", "medium", "high"]
defp ids_valid?(ids), do: is_list(ids) and Enum.all?(ids, &is_binary/1)
defp direct_subscriptions_valid?(ds), do: is_nil(ds) or is_list(ds)
defp primary_asset_valid?(pa), do: is_nil(pa) or is_binary(pa)
defp attachments_valid?(at), do: is_nil(at) or is_list(at)
end
defimpl Runbox.Runtime.OABody, for: Runbox.Scenario.OutputAction.Event do
alias Runbox.Scenario.OutputAction.Event
def valid?(%Event{
type: type,
template: template,
actors: actors,
params: params,
origin_messages: origin_messages
}) do
valid_type?(type) and
is_binary(template) and
is_map(actors) and
is_map(params) and
is_list(origin_messages)
end
defp valid_type?(type), do: is_binary(type) or (is_atom(type) and not is_nil(type))
end
defimpl Runbox.Runtime.OABody, for: Runbox.Scenario.OutputAction.ExecuteSQL do
alias Runbox.Scenario.OutputAction.ExecuteSQL
def valid?(%ExecuteSQL{
db_connection: db_connection,
sql_query: sql_query,
data: data,
type: type
}) do
is_map(db_connection) and
is_binary(sql_query) and
is_list(data) and
type in [:postgresql, "postgresql"]
end
end
defimpl Runbox.Runtime.OABody, for: Runbox.Scenario.OutputAction.Incident do
alias Runbox.Scenario.OutputAction.Incident
alias Runbox.Scenario.OutputAction.IncidentActor
alias Runbox.Scenario.OutputAction.IncidentFuture
alias Runbox.Scenario.OutputAction.IncidentHistory
def valid?(%Incident{
id: id,
future: future,
history: history,
actors: actors
}) do
id_valid?(id) and
future_valid?(future) and
history_valid?(history) and
actors_valid?(actors)
end
defp id_valid?(id), do: is_binary(id) and not String.contains?(id, "/")
defp future_valid?(future),
do: is_list(future) and Enum.all?(future, &match?(%IncidentFuture{}, &1))
defp history_valid?(history),
do: is_list(history) and Enum.all?(history, &match?(%IncidentHistory{}, &1))
defp actors_valid?(nil), do: true
defp actors_valid?(actors),
do: is_list(actors) and Enum.all?(actors, &match?(%IncidentActor{}, &1))
end
defimpl Runbox.Runtime.OABody, for: Runbox.Scenario.OutputAction.IncidentPatch do
alias Runbox.Scenario.OutputAction.IncidentPatch
alias Runbox.Scenario.OutputAction.IncidentActor
alias Runbox.Scenario.OutputAction.IncidentFuture
alias Runbox.Scenario.OutputAction.IncidentHistory
def valid?(%IncidentPatch{
future: future,
history: history,
actors: actors
}) do
future_valid?(future) and
history_valid?(history) and
actors_valid?(actors[:add]) and
actors_valid?(actors[:remove])
end
defp future_valid?(nil), do: true
defp future_valid?(future),
do: is_list(future) and Enum.all?(future, &match?(%IncidentFuture{}, &1))
defp history_valid?(nil), do: true
defp history_valid?(history),
do: is_list(history) and Enum.all?(history, &match?(%IncidentHistory{}, &1))
defp actors_valid?(nil), do: true
defp actors_valid?(actors),
do: is_list(actors) and Enum.all?(actors, &match?(%IncidentActor{}, &1))
end
defimpl Runbox.Runtime.OABody, for: Runbox.Scenario.OutputAction.BadOutputAction do
def valid?(_), do: true
end