use std::convert::Infallible;
use std::sync::Arc;
use axum::{
Json, Router,
extract::{Path, State},
http::StatusCode,
response::sse::{Event, KeepAlive, Sse},
routing::{delete, get, post},
};
use futures::Stream;
use serde::{Deserialize, Serialize};
use tokio_stream::StreamExt;
use tokio_stream::wrappers::BroadcastStream;
use crate::daemon::state::DaemonState;
use super::envelope::{BusEnvelope, BusPayload, CallerIdentity};
use super::error::BusError;
use super::registry::{InstanceMeta, PeerTarget};
pub fn bus_router() -> Router<Arc<DaemonState>> {
Router::new()
.route(
"/api/v1/bus/instances",
get(list_instances).post(register_instance),
)
.route(
"/api/v1/bus/instances/{instance_id}",
delete(deregister_instance),
)
.route("/api/v1/bus/publish", post(publish_message))
.route(
"/api/v1/bus/subscribe/{instance_id}",
get(subscribe_instance),
)
}
#[derive(Debug, Deserialize)]
pub struct RegisterInstanceRequest {
pub definition_id: String,
#[serde(default)]
pub project: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct ListInstancesResponse {
pub instances: Vec<InstanceMeta>,
}
#[derive(Debug, Deserialize)]
pub struct PublishTarget {
#[serde(default)]
pub instance_id: Option<String>,
#[serde(default)]
pub definition_id: Option<String>,
}
impl PublishTarget {
pub fn to_peer_target(&self) -> Result<PeerTarget, BusError> {
match (&self.instance_id, &self.definition_id) {
(Some(i), _) => Ok(PeerTarget::Instance(i.clone())),
(None, Some(d)) => Ok(PeerTarget::Definition(d.clone())),
(None, None) => Err(BusError::InvalidTarget(
"supply instance_id (bypass) or definition_id (DOC-60 §5.3)".into(),
)),
}
}
}
#[derive(Debug, Deserialize)]
pub struct PublishRequest {
pub from: CallerIdentity,
pub to: PublishTarget,
pub payload: BusPayload,
#[serde(default)]
pub in_reply_to: Option<String>,
}
pub async fn register_instance(
State(state): State<Arc<DaemonState>>,
Json(req): Json<RegisterInstanceRequest>,
) -> Result<(StatusCode, Json<InstanceMeta>), BusError> {
let meta = state
.bus()
.registry()
.register(&req.definition_id, req.project)?;
Ok((StatusCode::CREATED, Json(meta)))
}
pub async fn deregister_instance(
State(state): State<Arc<DaemonState>>,
Path(instance_id): Path<String>,
) -> StatusCode {
if state.bus().registry().deregister(&instance_id) {
StatusCode::NO_CONTENT
} else {
StatusCode::NOT_FOUND
}
}
pub async fn list_instances(State(state): State<Arc<DaemonState>>) -> Json<ListInstancesResponse> {
Json(ListInstancesResponse {
instances: state.bus().registry().live(),
})
}
pub async fn publish_message(
State(state): State<Arc<DaemonState>>,
Json(req): Json<PublishRequest>,
) -> Result<(StatusCode, Json<BusEnvelope>), BusError> {
let target = req.to.to_peer_target()?;
let envelope = state
.bus()
.publish(req.from, &target, req.payload, req.in_reply_to)?;
Ok((StatusCode::ACCEPTED, Json(envelope)))
}
pub async fn subscribe_instance(
State(state): State<Arc<DaemonState>>,
Path(instance_id): Path<String>,
) -> Result<Sse<impl Stream<Item = Result<Event, Infallible>>>, BusError> {
let rx = state.bus().subscribe(&instance_id)?;
let stream = BroadcastStream::new(rx).filter_map(|msg| match msg {
Ok(envelope) => Event::default().json_data(envelope).ok().map(Ok),
Err(_) => None,
});
Ok(Sse::new(stream).keep_alive(KeepAlive::default()))
}