lib/runbox/scenario/manifest.ex

defmodule Runbox.Scenario.Manifest do
  @moduledoc """
  Behaviour and struct for scenario manifest.
  """
  alias Runbox.Scenario.Type

  @doc """
  Invoked to obtain the scenario manifest.
  """
  @callback get_info :: t()

  @doc """
  Invoked once when the scenario run starts.

  Useful for doing any necessary preparations for scenario run.

  **Warning:** Do not rely on any external resources (e.g. SQL queries) which may
  change the scenario logic.

  This callback is optional. If it is not implemented, no action is performed
  when starting the scenario run.
  """
  @callback on_start :: :ok | {:error, term()}
  @optional_callbacks on_start: 0

  defmacro __using__(_) do
    quote do
      @behaviour unquote(__MODULE__)
    end
  end

  defmodule IncidentFrame do
    @moduledoc """
    Struct representing one column in incident forecast UI widget.

    ## Properties:

      * `:name` displayed column name
      * `:since` time frame start interval in ms
      * `:till` time frame end interval in ms
    """

    @derive {Jason.Encoder, only: [:name, :since, :till]}
    defstruct [:name, :since, :till]

    @type t() :: %IncidentFrame{
            name: String.t(),
            since: integer | nil,
            till: integer | nil
          }
  end

  defmodule Incident do
    @moduledoc """
    Struct representing incident manifest
    """

    @derive {Jason.Encoder, only: [:name, :actors, :frames]}
    defstruct name: "",
              actors: [],
              frames: []

    @type t() :: %Incident{
            name: String.t(),
            actors: [String.t()],
            frames: [IncidentFrame.t()]
          }
  end

  defstruct id: "",
            name: "",
            description: "",
            version: nil,
            change_log: %{},
            type: "",
            incidents: %{},
            events: %{},
            groups: %{},
            topics: [],
            required_raw_topics: [],
            notifications: %{}

  @type t() :: %Runbox.Scenario.Manifest{
          id: String.t(),
          name: String.t(),
          description: String.t(),
          version: String.t() | nil,
          type: Type.t(),
          incidents: %{(type :: String.t()) => Incident.t()},
          events: %{(type :: String.t()) => %{name: String.t(), description: String.t()}},
          groups: %{(id :: String.t()) => group_of_assets},
          topics: [String.t()],
          required_raw_topics: [String.t()],
          notifications: %{notification_type() => notification_metadata()}
        }

  @typedoc """
  Metadata (template) of assets group.

  The scenario should be able to carry a list of groups it uses. These groups
  can then be easily created in the UI by the user without having to fill in
  some information manually. Metadata defines these helpers for the UI.

  ## Properties

    * `:name` - short name of group template for UI.
    * `:description` - description of assets group.
    * `:asset_type` - asset type of asset created by the group.
    * `:edge_type` - edge type for connecting assets in the group.
    * `:edge_direction` - edge direction (true = from group asset to assets in group).
    * `:attributes` - description of group attributes.

  """
  @type group_of_assets :: %{
          name: String.t(),
          description: String.t(),
          asset_type: String.t(),
          edge_type: String.t(),
          edge_direction: boolean,
          attributes: [attribute_detail]
        }

  @typedoc """
  Assets group attributes metadata.

  Metadata helps the UI to render form for the group attributes definition.
  Attributes of the group are saved in asset attributes in Reality Network
  if group is materialized as asset in Reality Network.

  ## Properties

    * `:name` - user-friendly attribute name - what is set.
    * `:description` - longer attribute description - what the attribute does, the unit, ...
    * `:path` - path to attribute in attributes JSON.
    * `:type` - type of attribute value (helps the UI to render form input).

  """
  @type attribute_detail :: %{
          name: String.t(),
          description: String.t(),
          path: [String.t()],
          type: :string | :number
        }

  @typedoc """
  Notification type is an atom uniquely identifying the notification in the scenario
  """
  @type notification_type :: atom()

  @typedoc """
  Notification specification metadata.

  ## Properties

    * `:name` - user-friendly name for the notification type
    * `:metadata_types` - describes metadata the notification caries, this information can be used for
      additional filtering in subscriptions. It can be empty map if no metadata is needed.
    * `type_name` - is a binary specifying the name of the notification metadata type
    * `:type` - type of notification metadata, can be either a `:number`, `:string` or `:boolean`.

  """

  @type notification_metadata :: %{
          name: String.t(),
          metadata_types: %{(type_name :: String.t()) => %{type: :string | :number | :boolean}}
        }
end