use std::pin::Pin;
use std::task::{Context, Poll};
use futures_core::Stream;
use serde::{Deserialize, Serialize};
use tokio_stream::StreamExt;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RedfishEvent {
pub event_type: Option<String>,
pub id: Option<String>,
pub data: String,
pub body: Option<serde_json::Value>,
}
pub struct EventStream {
inner: Pin<Box<dyn Stream<Item = Result<RedfishEvent, crate::error::RedfishError>> + Send>>,
}
impl Stream for EventStream {
type Item = Result<RedfishEvent, crate::error::RedfishError>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.inner.as_mut().poll_next(cx)
}
}
impl EventStream {
pub(crate) fn new(response: reqwest::Response) -> Self {
let stream = async_stream(response);
Self { inner: Box::pin(stream) }
}
}
fn async_stream(
response: reqwest::Response,
) -> impl Stream<Item = Result<RedfishEvent, crate::error::RedfishError>> {
use tokio_stream::wrappers::ReceiverStream;
let (tx, rx) = tokio::sync::mpsc::channel(64);
tokio::spawn(async move {
let mut bytes_stream = response.bytes_stream();
let mut buffer = String::new();
let mut event_type: Option<String> = None;
let mut event_id: Option<String> = None;
let mut data_lines: Vec<String> = Vec::new();
while let Some(chunk) = bytes_stream.next().await {
let chunk = match chunk {
Ok(c) => c,
Err(e) => {
let _ = tx.send(Err(crate::error::RedfishError::Http(e))).await;
break;
}
};
buffer.push_str(&String::from_utf8_lossy(&chunk));
while let Some(pos) = buffer.find('\n') {
let line = buffer[..pos].trim_end_matches('\r').to_string();
buffer = buffer[pos + 1..].to_string();
if line.is_empty() {
if !data_lines.is_empty() {
let data = data_lines.join("\n");
let body = serde_json::from_str(&data).ok();
let event = RedfishEvent {
event_type: event_type.take(),
id: event_id.take(),
data,
body,
};
if tx.send(Ok(event)).await.is_err() {
return;
}
}
data_lines.clear();
} else if let Some(value) = line.strip_prefix("data:") {
data_lines.push(value.trim_start().to_string());
} else if let Some(value) = line.strip_prefix("event:") {
event_type = Some(value.trim_start().to_string());
} else if let Some(value) = line.strip_prefix("id:") {
event_id = Some(value.trim_start().to_string());
}
}
}
});
ReceiverStream::new(rx)
}