swactor/crates/datastream/src/publisher_actor.rs

102 lines
3.3 KiB
Rust
Raw Normal View History

2026-07-12 06:14:34 +00:00
//! Actor wrapper that exposes a local [`DatastreamEndpoint`] to remote
//! collectors.
//!
//! The actor owns the process-local subscription step. Transport-specific code is
//! injected by the runtime crate so the datastream core does not depend on the
//! concrete QUIC writer implementation.
use std::marker::PhantomData;
use std::sync::Arc;
use iroh::EndpointAddr;
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize as DeriveSerialize};
use swactor::Error;
use swactor::actor::ActorInterface;
use swactor::runtime::Ctx;
use swactor_transport::{Codec, CodecRegistry, NetworkMessage};
use crate::{DatastreamEndpoint, DatastreamSubscription, SubscriptionRequest};
/// Well-known actor registry name for node-side datastream subscription requests.
pub const DATASTREAM_PUBLISHER_NAME: &str = "datastream-publisher";
/// Request sent from a collector/orchestrator to a node-local datastream publisher.
#[derive(Clone, Debug, DeriveSerialize, Deserialize)]
pub struct DatastreamSubscribe {
pub collector: EndpointAddr,
pub request: SubscriptionRequest,
pub flow_id: [u8; 16],
pub token: Vec<u8>,
}
/// Messages accepted by [`DatastreamPublisherActor`].
#[derive(Clone, Debug, DeriveSerialize, Deserialize)]
pub enum DatastreamPublisherMsg {
Subscribe(DatastreamSubscribe),
}
impl NetworkMessage for DatastreamPublisherMsg {
fn type_tag() -> &'static str {
"swactor::DatastreamPublisherMsg"
}
}
/// Node-side actor that turns remote subscribe messages into local endpoint
/// subscriptions, then hands the subscription to the caller's transport writer.
pub struct DatastreamPublisherActor {
endpoint: Arc<DatastreamEndpoint>,
on_subscribe: Box<dyn FnMut(DatastreamSubscribe, DatastreamSubscription) + Send>,
}
impl DatastreamPublisherActor {
pub fn new(
endpoint: Arc<DatastreamEndpoint>,
on_subscribe: impl FnMut(DatastreamSubscribe, DatastreamSubscription) + Send + 'static,
) -> Self {
Self {
endpoint,
on_subscribe: Box::new(on_subscribe),
}
}
}
impl ActorInterface for DatastreamPublisherActor {
type Incoming = DatastreamPublisherMsg;
type Response = ();
fn handle(&mut self, _ctx: &Ctx, msg: Self::Incoming) {
match msg {
DatastreamPublisherMsg::Subscribe(subscribe) => {
let subscription = self
.endpoint
.subscribe("remote-collector", subscribe.request.clone());
(self.on_subscribe)(subscribe, subscription);
}
}
}
}
/// Register JSON encoding for remote datastream publisher messages.
pub fn register_datastream_publisher_codec(registry: &mut CodecRegistry) {
registry.register::<DatastreamPublisherMsg, JsonCodec<DatastreamPublisherMsg>>(JsonCodec(
PhantomData,
));
}
struct JsonCodec<M>(PhantomData<M>);
impl<M> Codec<M> for JsonCodec<M>
where
M: Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
{
fn encode(&self, msg: &M) -> Result<Vec<u8>, Error> {
serde_json::to_vec(msg).map_err(|e| Error::from(format!("encode: {e}")))
}
fn decode(&self, bytes: &[u8]) -> Result<M, Error> {
serde_json::from_slice(bytes).map_err(|e| Error::from(format!("decode: {e}")))
}
}