use std::sync::Arc;
use futures::Stream;
use futures::StreamExt;
use leptoskit_query::BoxFuture;
use serde::Serialize;
use serde_json::Value;
use tokio::sync::Semaphore;
use tokio::sync::mpsc;
use super::QueryCx;
use crate::Error;
use crate::Result;
use crate::StreamOperation;
use crate::live::EndReason;
use crate::live::ServerFrame;
use crate::live::WireError;
use crate::stream::Resumable;
pub struct StreamRegistration {
pub name: &'static str,
pub resumable: bool,
#[doc(hidden)]
pub open: Open,
}
pub type Open = fn(Value, QueryCx, StreamSink) -> BoxFuture<'static, ()>;
inventory::collect!(StreamRegistration);
#[must_use]
pub fn registration(name: &str) -> Option<&'static StreamRegistration> {
inventory::iter::<StreamRegistration>().find(|registration| registration.name == name)
}
#[doc(hidden)]
pub fn open<S: StreamOperation>(
args: Value,
cx: QueryCx,
sink: StreamSink,
) -> BoxFuture<'static, ()> {
Box::pin(async move {
match serde_json::from_value::<S>(args) {
Ok(op) => op.pump(cx, sink).await,
Err(error) => {
sink.fail(&Error::invalid(format!(
"the arguments of `{}` don't fit: {error}",
S::NAME
)));
}
}
})
}
pub struct StreamSink {
stream: u32,
out: mpsc::UnboundedSender<String>,
credits: Arc<Semaphore>,
}
#[doc(hidden)]
pub type Encoded = Result<(Value, Option<Value>)>;
impl StreamSink {
#[cfg_attr(
not(feature = "live"),
allow(dead_code, reason = "only a live session opens streams")
)]
pub(crate) fn new(
stream: u32,
out: mpsc::UnboundedSender<String>,
credits: Arc<Semaphore>,
) -> Self {
Self {
stream,
out,
credits,
}
}
#[doc(hidden)]
pub async fn drive<S, I>(self, items: S, encode: fn(I) -> Encoded)
where
S: Stream<Item = I> + Send,
{
let mut items = std::pin::pin!(items);
loop {
match self.credits.acquire().await {
Ok(permit) => permit.forget(),
Err(_) => return,
}
let Some(item) = items.next().await else {
self.end(EndReason::Done);
return;
};
match encode(item) {
Ok((value, cursor)) => {
let frame = ServerFrame::Item {
stream: self.stream,
value,
cursor,
};
if !self.send(&frame) {
return;
}
}
Err(error) => {
self.fail(&error);
return;
}
}
}
}
#[doc(hidden)]
pub fn fail(&self, error: &Error) {
self.end(EndReason::Failed(WireError {
status: error.status(),
message: error.message().to_owned(),
}));
}
fn end(&self, reason: EndReason) {
self.send(&reason.frame(self.stream));
}
fn send(&self, frame: &ServerFrame) -> bool {
let Ok(text) = serde_json::to_string(frame) else {
return false;
};
self.out.send(text).is_ok()
}
}
fn encode<T: Serialize>(item: &T) -> Result<Value> {
serde_json::to_value(item).map_err(|_| Error::server("a stream's item isn't JSON"))
}
#[doc(hidden)]
pub fn plain<T: Serialize>(item: T) -> Encoded {
Ok((encode(&item)?, None))
}
#[doc(hidden)]
pub fn fallible<T: Serialize>(item: Result<T>) -> Encoded {
plain(item?)
}
#[doc(hidden)]
#[expect(
clippy::needless_pass_by_value,
reason = "every encoder takes the item the stream yields, by value"
)]
pub fn resumable<T: Serialize + Resumable>(item: T) -> Encoded {
let cursor = serde_json::to_value(item.cursor())
.map_err(|_| Error::server("a stream's cursor isn't JSON"))?;
Ok((encode(&item)?, Some(cursor)))
}
#[doc(hidden)]
pub fn resumable_fallible<T: Serialize + Resumable>(item: Result<T>) -> Encoded {
resumable(item?)
}