defmodule Runbox.Runtime.OutputAction do
@moduledoc group: :internal
@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 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
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 oa_body?(ScenarioOA.oa_params() | any()) :: boolean()
def oa_body?(params) do
not is_nil(OABody.impl_for(params))
end
defp valid_body?(params) do
OABody.valid?(params)
end
@doc """
Returns `true` if provided attributes access tags are valid.
"""
@spec attributes_access_tags_valid?(ScenarioOA.access_tags()) :: boolean()
def attributes_access_tags_valid?(access_tags) do
attributes_valid? =
fn access_tags ->
access_tags
|> Map.keys()
|> Enum.all?(fn attribute ->
(is_list(attribute) and Enum.all?(attribute, &is_binary/1)) or
attribute == :all or
is_binary(attribute)
end)
end
tag_definitions_valid? =
fn access_tags ->
access_tags
|> Map.values()
|> Enum.all?(&attribute_tag_definition_valid?/1)
end
is_map(access_tags) and attributes_valid?.(access_tags) and
tag_definitions_valid?.(access_tags)
end
defp attribute_tag_definition_valid?(tag_def) do
(is_list(tag_def) and Enum.all?(tag_def, &(is_binary(&1) and String.trim(&1) == &1))) or
(is_binary(tag_def) and String.trim(tag_def) == tag_def)
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,
access_tags: access_tags
}) do
is_binary(type) and
id_valid?(id) and
is_map(attributes) and
Runbox.Runtime.OutputAction.attributes_access_tags_valid?(access_tags)
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,
access_tags: access_tags
}) do
is_binary(type) and
is_binary(id) and
is_map(attributes) and
Runbox.Runtime.OutputAction.attributes_access_tags_valid?(access_tags)
end
end
defimpl Runbox.Runtime.OABody, for: Runbox.Scenario.OutputAction.DeleteAllAssetAttributes do
def valid?(%Runbox.Scenario.OutputAction.DeleteAllAssetAttributes{
type: type,
id: id,
access_tags: access_tags
}) do
is_binary(type) and is_binary(id) and
Runbox.Runtime.OutputAction.attributes_access_tags_valid?(access_tags)
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,
access_tags: access_tags
}) do
is_binary(from_type) and is_binary(from_id) and
is_binary(to_type) and is_binary(to_id) and
is_binary(type) and is_list(access_tags) and
Enum.all?(access_tags, &(is_binary(&1) and String.trim(&1) == &1))
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,
access_tags: access_tags
}) do
is_binary(from_type) and is_binary(from_id) and
is_binary(to_type) and is_binary(to_id) and
is_binary(type) and is_list(access_tags) and
Enum.all?(access_tags, &(is_binary(&1) and String.trim(&1) == &1))
end
end
defimpl Runbox.Runtime.OABody, for: Runbox.Scenario.OutputAction.Notification do
alias Runbox.Scenario.OutputAction.Notification
alias Runbox.Scenario.OutputAction.Notification.AssetActor
alias Runbox.Scenario.OutputAction.Notification.IncidentActor
def valid?(%Notification{
type: type,
primary_actor: primary_actor,
actors: actors,
aqls: aqls,
priority: priority,
metadata: metadata,
direct_subscriptions: direct_subscriptions,
attachments: attachments,
email_reply_to: reply_to
}) do
type_valid?(type) and
actors_valid?(actors) and
is_map(aqls) and
is_map(metadata) and
priority_valid?(priority) and
direct_subscriptions_valid?(direct_subscriptions) and
primary_actor_valid?(primary_actor) and
attachments_valid?(attachments) and
email_reply_to_valid?(reply_to)
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 actors_valid?(actors), do: is_list(actors) and Enum.all?(actors, &ref_valid?/1)
defp direct_subscriptions_valid?(ds), do: is_nil(ds) or is_list(ds)
defp primary_actor_valid?(pa), do: is_nil(pa) or ref_valid?(pa)
defp attachments_valid?(at), do: is_nil(at) or is_list(at)
defp email_reply_to_valid?(rt), do: is_nil(rt) or is_map(rt)
defp ref_valid?(%AssetActor{type: type, id: id}) when is_binary(type) and is_binary(id), do: true
defp ref_valid?(%IncidentActor{type: type, id: id}) when is_binary(type) and is_binary(id),
do: true
defp ref_valid?(_), do: false
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,
incident_actors: incident_actors,
params: params,
origin_messages: origin_messages
}) do
valid_type?(type) and
is_binary(template) and
is_map(actors) and
(is_nil(incident_actors) or is_map(incident_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, &actor_valid?/1)
defp actor_valid?(%IncidentActor{type: type, id: id}), do: is_binary(type) and is_binary(id)
defp actor_valid?(_actor), do: false
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