use axum::extract::{Query, State};
use axum::http::StatusCode;
use axum::response::sse::{Event, KeepAlive, Sse};
use axum::routing::{get, post};
use axum::{Json, Router};
use futures::Stream;
use nstreams_core::event::DeliveredEvent;
use nstreams_core::filter::SubscribeRequest;
use nstreams_core::namespace::{Namespace, StreamNamespace};
use nstreams_core::Error;
use serde::Deserialize;
use crate::service::NStreams;
use crate::transport::{delivered_event_json, registered_event_json};
#[derive(Debug, Deserialize)]
pub struct PublishRequest {
pub namespace: String,
pub payload: serde_json::Value,
#[serde(default)]
pub filter_value: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct SubscribeQuery {
pub namespace: String,
#[serde(default = "default_history")]
pub history: u64,
}
fn default_history() -> u64 {
1_000
}
pub fn routes() -> Router<NStreams> {
Router::new()
.route("/subscribe", get(subscribe_get).post(subscribe_post))
.route("/publish", post(publish))
}
pub fn router(nstreams: NStreams) -> Router<NStreams> {
routes().with_state(nstreams)
}
pub async fn subscribe_get(
State(nstreams): State<NStreams>,
Query(query): Query<SubscribeQuery>,
) -> Result<Sse<impl Stream<Item = Result<Event, std::convert::Infallible>>>, StatusCode> {
let request = SubscribeRequest {
namespace: query.namespace,
history: query.history,
filter: None,
};
subscribe_sse(nstreams, request).await
}
pub async fn subscribe_post(
State(nstreams): State<NStreams>,
Json(request): Json<SubscribeRequest>,
) -> Result<Sse<impl Stream<Item = Result<Event, std::convert::Infallible>>>, StatusCode> {
subscribe_sse(nstreams, request).await
}
async fn subscribe_sse(
nstreams: NStreams,
request: SubscribeRequest,
) -> Result<Sse<impl Stream<Item = Result<Event, std::convert::Infallible>>>, StatusCode> {
StreamNamespace::validate(request.namespace.as_str()).map_err(|_| StatusCode::BAD_REQUEST)?;
let mut subscription = nstreams
.subscribe_request(request)
.await
.map_err(map_subscribe_error)?;
let request_id = subscription.request_id().to_string();
let stream = async_stream::stream! {
yield Ok(Event::default()
.event("registered")
.data(registered_event_json(&request_id).to_string()));
while let Some(item) = subscription.next().await {
match item {
Ok(event) => match encode_message_event(&event) {
Ok(event) => yield Ok(event),
Err(error) => yield Ok(Event::default().event("error").data(error.to_string())),
},
Err(error) => yield Ok(Event::default().event("error").data(error.to_string())),
}
}
};
Ok(Sse::new(stream).keep_alive(KeepAlive::default()))
}
pub async fn publish(
State(nstreams): State<NStreams>,
Json(body): Json<PublishRequest>,
) -> Result<StatusCode, StatusCode> {
let namespace = StreamNamespace::new(body.namespace);
StreamNamespace::validate(namespace.as_str()).map_err(|_| StatusCode::BAD_REQUEST)?;
let payload = serde_json::to_vec(&body.payload).map_err(|_| StatusCode::BAD_REQUEST)?;
nstreams
.publish(
&namespace,
&payload,
body.filter_value.as_deref(),
)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(StatusCode::ACCEPTED)
}
fn encode_message_event(event: &DeliveredEvent) -> Result<Event, nstreams_core::Error> {
let data = delivered_event_json(event)?.to_string();
Ok(Event::default().event("message").data(data))
}
fn map_subscribe_error(error: Error) -> StatusCode {
match error {
Error::InvalidNamespace(_, _) | Error::InvalidFilter(_) => StatusCode::BAD_REQUEST,
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}