use crate::envelope::EventEnvelope;
use crate::function::AppError;
use crate::platform::Platform;
use crate::post_office::PostOffice;
pub const X_EVENT_STREAM: &str = "x-event-stream";
pub const X_EVENT_NAME: &str = "x-event-name";
pub const DATA: &str = "data";
pub const EOF: &str = "eof";
pub const EXCEPTION: &str = "exception";
pub const ENVELOPE: &str = "envelope";
pub struct EventStreamWriter {
po: PostOffice,
reply_to: String,
correlation_id: Option<String>,
first_status: i32,
first_content_type: Option<String>,
first_ttl_seconds: u64,
head_sent: bool,
closed: bool,
}
impl EventStreamWriter {
pub fn new(
platform: &Platform,
reply_to: &str,
correlation_id: Option<&str>,
) -> Result<Self, AppError> {
if reply_to.is_empty() {
return Err(AppError::new(
400,
"Streaming producer requires a reply_to address",
));
}
Ok(Self {
po: PostOffice::new(platform),
reply_to: reply_to.to_string(),
correlation_id: correlation_id.map(str::to_string),
first_status: 200,
first_content_type: None,
first_ttl_seconds: 0,
head_sent: false,
closed: false,
})
}
pub fn from_request(platform: &Platform, request: &EventEnvelope) -> Result<Self, AppError> {
Self::new(
platform,
request.reply_to().unwrap_or(""),
request.correlation_id(),
)
}
pub fn first(&mut self, status: i32, content_type: &str) -> &mut Self {
self.first_status = status;
self.first_content_type = Some(content_type.to_string());
self
}
pub fn first_with_ttl(
&mut self,
status: i32,
content_type: &str,
ttl_seconds: u64,
) -> &mut Self {
self.first_ttl_seconds = ttl_seconds;
self.first(status, content_type)
}
pub async fn write<T: serde::Serialize>(&mut self, segment: T) -> Result<(), AppError> {
self.send(segment, None).await
}
pub async fn write_named<T: serde::Serialize>(
&mut self,
event_name: &str,
segment: T,
) -> Result<(), AppError> {
self.send(segment, Some(event_name)).await
}
pub async fn close(&mut self) -> Result<(), AppError> {
self.close_with(serde_json::Value::Null).await
}
pub async fn close_with<T: serde::Serialize>(&mut self, metadata: T) -> Result<(), AppError> {
if self.closed {
return Ok(());
}
self.closed = true;
let event = self.envelope(EOF, metadata, None)?;
self.po.send(event).await
}
pub async fn fail(&mut self, error: &AppError) -> Result<(), AppError> {
if self.closed {
return Ok(());
}
self.closed = true;
let status = if error.status() >= 400 {
error.status()
} else {
500
};
let body =
serde_json::json!({"type": "error", "status": status, "message": error.message()});
let event = self.envelope(EXCEPTION, body, None)?.set_status(status);
self.po.send(event).await
}
pub fn is_closed(&self) -> bool {
self.closed
}
async fn send<T: serde::Serialize>(
&mut self,
body: T,
event_name: Option<&str>,
) -> Result<(), AppError> {
if self.closed {
log::debug!(
"Segment to {} dropped - stream already closed",
self.reply_to
);
return Ok(());
}
let carries_head = !self.head_sent;
let event = self.envelope(DATA, body, event_name)?;
if carries_head {
self.po.send(event).await
} else {
self.po.send_untraced(event).await
}
}
fn envelope<T: serde::Serialize>(
&mut self,
marker: &str,
body: T,
event_name: Option<&str>,
) -> Result<EventEnvelope, AppError> {
let mut event = EventEnvelope::new()
.set_to(&self.reply_to)
.set_header(X_EVENT_STREAM, marker)
.set_body(body)?;
if let Some(cid) = &self.correlation_id {
event = event.set_correlation_id(cid);
}
if let Some(name) = event_name.filter(|n| !n.is_empty()) {
event = event.set_header(X_EVENT_NAME, name);
}
if !self.head_sent {
self.head_sent = true;
event = event.set_status(self.first_status);
if let Some(content_type) = &self.first_content_type {
event = event.set_header("content-type", content_type);
}
if self.first_ttl_seconds > 0 {
event = event.set_header("x-ttl", &self.first_ttl_seconds.to_string());
}
}
Ok(event)
}
}