Current section

Files

Jump to
jido lib jido sensors bus_sensor.ex
Raw

lib/jido/sensors/bus_sensor.ex

# defmodule Jido.Sensors.Bus do
# @moduledoc """
# A sensor that monitors signals on a bus and emits them as signals.
# This sensor subscribes to a bus and forwards signals received on that bus to its configured target.
# It filters signals based on the required stream ID.
# ## Options
# * `:id` - Required. The unique identifier for this sensor instance
# * `:target` - Required. The target to dispatch signals to
# * `:bus_name` - Required. The name of the bus to monitor
# * `:stream_id` - Required. The specific stream ID to monitor.
# """
# use Jido.Sensor,
# name: "bus_sensor",
# description: "Monitors signals on a bus",
# category: :system,
# tags: [:bus, :monitoring],
# vsn: "1.0.0",
# schema: [
# bus_name: [
# type: :atom,
# required: true,
# doc: "Name of the bus to monitor"
# ],
# stream_id: [
# type: :string,
# required: true,
# doc: "Stream ID to monitor"
# ],
# filter_source: [
# type: :string,
# required: false,
# doc: "Optional source ID to filter out"
# ]
# ]
# require Logger
# @impl true
# def mount(opts) do
# target =
# case opts.target do
# {:pid, target_opts} when is_list(target_opts) ->
# {:pid,
# Keyword.merge(target_opts,
# message_format: fn signal -> {:signal, {:ok, signal}} end
# )}
# {:pid, pid} when is_pid(pid) ->
# {:pid,
# [
# target: pid,
# delivery_mode: :async,
# message_format: fn signal -> {:signal, {:ok, signal}} end
# ]}
# _ ->
# opts.target
# end
# state = %{
# id: opts.id,
# target: target,
# sensor: %{name: "bus_sensor"},
# config: %{
# bus_name: opts.bus_name,
# stream_id: opts.stream_id,
# filter_source: Map.get(opts, :filter_source)
# }
# }
# # Subscribe to the bus
# case subscribe_to_bus(state) do
# {:ok, subscription} ->
# {:ok, Map.put(state, :subscription, subscription)}
# {:error, reason} = error ->
# Logger.error("Failed to subscribe to bus: #{inspect(reason)}")
# error
# end
# end
# @impl true
# def handle_info({:subscribed, subscription}, state) do
# case Jido.Signal.Dispatch.dispatch(
# {:subscribed, subscription},
# {:pid,
# [
# target: extract_target_pid(state.target),
# delivery_mode: :async,
# message_format: fn _ -> {:subscribed, subscription} end
# ]}
# ) do
# :ok ->
# {:noreply, %{state | subscription: subscription}}
# {:error, reason} ->
# Logger.error("Failed to forward subscription message: #{inspect(reason)}")
# {:noreply, state}
# end
# end
# def handle_info({:signals, signals}, state) when is_list(signals) do
# handle_signals(signals, state)
# end
# def handle_info({:signals, _subscription_ref, signals}, state) when is_list(signals) do
# handle_signals(signals, state)
# end
# def handle_info(msg, state) do
# Logger.warning("#{__MODULE__} received unexpected message: #{inspect(msg)}")
# {:noreply, state}
# end
# @impl true
# def deliver_signal(_state) do
# {:ok, nil}
# end
# @impl true
# def shutdown(state) do
# if subscription = Map.get(state, :subscription) do
# case Jido.Bus.whereis(state.config.bus_name) do
# {:ok, bus} ->
# Jido.Bus.unsubscribe(bus, subscription)
# _error ->
# :ok
# end
# end
# :ok
# end
# # Private helpers
# defp subscribe_to_bus(%{config: config, id: id}) do
# stream = Map.get(config, :stream_id, :all)
# case Jido.Bus.whereis(config.bus_name) do
# {:ok, bus} ->
# Jido.Bus.subscribe(
# bus,
# stream,
# "#{id}_subscription",
# self(),
# start_from: :origin
# )
# error ->
# error
# end
# end
# defp handle_signals(signals, state) do
# Logger.debug("Received signals: #{inspect(signals)}")
# filtered = Enum.filter(signals, &matches_stream?(&1, Map.get(state.config, :stream_id)))
# Logger.debug("Filtered signals: #{inspect(filtered)}")
# case filtered do
# [] ->
# {:noreply, state}
# signals ->
# Enum.each(signals, fn signal ->
# Logger.debug("Converting signal: #{inspect(signal)}")
# case convert_signal(signal, state) do
# {:ok, converted} ->
# Logger.debug("Converted signal: #{inspect(converted)}")
# Logger.debug("Target: #{inspect(state.target)}")
# case state.target do
# {:pid, target_opts} when is_list(target_opts) ->
# message_format =
# Keyword.get(target_opts, :message_format, fn s -> {:signal, {:ok, s}} end)
# target_pid = Keyword.get(target_opts, :target)
# send(target_pid, message_format.(converted))
# {:pid, pid} when is_pid(pid) ->
# send(pid, {:signal, {:ok, converted}})
# _ ->
# case Jido.Signal.Dispatch.dispatch(
# converted,
# state.target
# ) do
# :ok ->
# Logger.debug("Successfully dispatched signal")
# error ->
# Logger.error("Failed to dispatch signal: #{inspect(error)}")
# end
# end
# error ->
# Logger.error("Failed to convert signal: #{inspect(error)}")
# end
# end)
# {:noreply, state}
# end
# end
# defp matches_stream?(_signal, nil), do: true
# defp matches_stream?(%Jido.Bus.RecordedSignal{} = signal, stream_id) do
# signal.stream_id == stream_id
# end
# defp matches_stream?(%Jido.Signal{} = signal, stream_id) do
# get_in(signal.jido_metadata || %{}, ["stream_id"]) == stream_id
# end
# defp convert_signal(%Jido.Bus.RecordedSignal{} = recorded, state) do
# case Jido.Signal.new(%{
# source: "#{state.sensor.name}:#{state.id}",
# type: recorded.type,
# data: recorded.data,
# jido_metadata:
# Map.merge(recorded.jido_metadata || %{}, %{
# "original_id" => recorded.signal_id,
# "original_stream" => recorded.stream_id,
# "original_version" => recorded.stream_version,
# "original_source" => recorded.causation_id,
# "original_correlation" => recorded.correlation_id
# })
# }) do
# {:ok, signal} -> {:ok, signal}
# error -> error
# end
# end
# defp convert_signal(%Jido.Signal{} = signal, state) do
# Logger.debug("Converting Jido.Signal: #{inspect(signal)}")
# {:ok,
# %{
# signal
# | source: "#{state.sensor.name}:#{state.id}",
# jido_metadata:
# Map.merge(signal.jido_metadata || %{}, %{
# "original_id" => signal.id,
# "original_stream" => get_in(signal.jido_metadata || %{}, ["stream_id"]),
# "original_source" => signal.source
# })
# }}
# end
# defp extract_target_pid({:pid, opts}) when is_list(opts), do: Keyword.get(opts, :target)
# defp extract_target_pid(pid) when is_pid(pid), do: pid
# defp extract_target_pid(_), do: nil
# end