use std::convert::Infallible;
use std::fmt;
use std::pin::Pin;
use std::str::Utf8Error;
use std::task::{Context, Poll, ready};
use futures_core::Stream;
#[derive(Debug)]
pub struct SseStream<S> {
body: Option<S>,
decoder: SseDecoder,
}
impl<S> SseStream<S> {
#[must_use]
pub fn new(body: S) -> Self {
Self {
body: Some(body),
decoder: SseDecoder::new(),
}
}
#[must_use]
pub fn max_event_size(mut self, limit: usize) -> Self {
self.decoder = self.decoder.max_event_size(limit);
self
}
fn end(&mut self) {
self.body = None;
self.decoder = SseDecoder::new();
}
}
impl<S, B, E> Stream for SseStream<S>
where
S: Stream<Item = Result<B, E>> + Unpin,
B: AsRef<[u8]>,
{
type Item = Result<SseEvent, SseError<E>>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
loop {
match this.decoder.next_event() {
Ok(Some(event)) => return Poll::Ready(Some(Ok(event))),
Ok(None) => {}
Err(error) => {
this.end();
return Poll::Ready(Some(Err(error.widen())));
}
}
let Some(body) = this.body.as_mut() else {
return Poll::Ready(None);
};
match ready!(Pin::new(body).poll_next(cx)) {
Some(Ok(chunk)) => this.decoder.push(chunk.as_ref()),
Some(Err(error)) => {
this.end();
return Poll::Ready(Some(Err(SseError::Transport(error))));
}
None => {
this.end();
return Poll::Ready(None);
}
}
}
}
}
#[derive(Debug)]
pub struct SseDecoder {
buffer: Vec<u8>,
read: usize,
scanned: usize,
unterminated: usize,
started: bool,
after_cr: bool,
max_event_size: usize,
overflowing: bool,
overflowed: bool,
event: PendingEvent,
#[cfg(test)]
examined: usize,
}
impl Default for SseDecoder {
fn default() -> Self {
Self::new()
}
}
impl SseDecoder {
pub const DEFAULT_MAX_EVENT_SIZE: usize = 64 * 1024 * 1024;
#[must_use]
pub fn new() -> Self {
Self {
buffer: Vec::new(),
read: 0,
scanned: 0,
unterminated: 0,
started: false,
after_cr: false,
max_event_size: Self::DEFAULT_MAX_EVENT_SIZE,
overflowing: false,
overflowed: false,
event: PendingEvent::default(),
#[cfg(test)]
examined: 0,
}
}
#[must_use]
pub fn max_event_size(mut self, limit: usize) -> Self {
self.max_event_size = limit;
self
}
pub fn push(&mut self, bytes: &[u8]) {
if self.overflowing || self.overflowed {
return;
}
if self.read > 0 {
self.buffer.drain(..self.read);
self.scanned = self.scanned.saturating_sub(self.read);
self.unterminated = self.unterminated.saturating_sub(self.read);
self.read = 0;
}
let last_ending = bytes.iter().rposition(|byte| is_line_ending(*byte));
#[cfg(test)]
{
self.examined += last_ending.map_or(bytes.len(), |end| bytes.len() - end);
}
let (unterminated, waiting) = match last_ending {
Some(end) => (self.buffer.len() + end + 1, bytes.len() - end - 1),
None => (
self.unterminated,
self.buffer.len() - self.unterminated + bytes.len(),
),
};
let bytes = if waiting > self.max_event_size {
self.overflowing = true;
let Some(end) = last_ending else {
return;
};
&bytes[..=end]
} else {
bytes
};
let additional = room(
self.buffer.len(),
self.buffer.capacity(),
bytes.len(),
self.max_event_size,
);
self.buffer.reserve_exact(additional);
if last_ending.is_none() && self.scanned == self.buffer.len() {
self.scanned += bytes.len();
}
self.buffer.extend_from_slice(bytes);
self.unterminated = unterminated.min(self.buffer.len());
}
pub fn next_event(&mut self) -> Result<Option<SseEvent>, SseError<Infallible>> {
if self.overflowed {
return Err(self.too_large());
}
if !self.started {
let pending = &self.buffer[self.read..];
if !self.overflowing && pending.len() < BOM.len() && BOM.starts_with(pending) {
return Ok(None);
}
if pending.starts_with(BOM) {
self.read += BOM.len();
self.scanned = self.scanned.max(self.read);
}
self.started = true;
}
loop {
let pending = &self.buffer[self.read..];
if self.after_cr && !pending.is_empty() {
self.after_cr = false;
if pending[0] == b'\n' {
self.read += 1;
self.scanned = self.scanned.max(self.read);
continue;
}
}
let from = self.scanned.max(self.read);
let scan = &self.buffer[from..self.unterminated.max(from)];
let found = scan.iter().position(|byte| is_line_ending(*byte));
#[cfg(test)]
{
self.examined += found.map_or(scan.len(), |offset| offset + 1);
}
let Some(offset) = found else {
self.scanned = self.buffer.len();
if self.overflowing {
self.overflow();
return Err(self.too_large());
}
return Ok(None);
};
let (start, end) = (self.read, from + offset);
if end - start > self.max_event_size {
self.overflow();
return Err(self.too_large());
}
self.read = end + 1;
self.scanned = self.read;
self.after_cr = self.buffer[end] == b'\r';
let line =
std::str::from_utf8(&self.buffer[start..end]).map_err(SseError::InvalidUtf8)?;
match self.event.line(line, self.max_event_size) {
Ok(Some(event)) => return Ok(Some(event)),
Ok(None) => {}
Err(TooLarge) => {
self.overflow();
return Err(self.too_large());
}
}
}
}
fn overflow(&mut self) {
self.overflowed = true;
self.buffer = Vec::new();
self.read = 0;
self.scanned = 0;
self.unterminated = 0;
self.event = PendingEvent::default();
}
fn too_large(&self) -> SseError<Infallible> {
SseError::EventTooLarge {
limit: self.max_event_size,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct SseEvent {
pub event: String,
pub data: String,
pub id: Option<String>,
}
impl SseEvent {
#[must_use]
pub fn new(event: impl Into<String>, data: impl Into<String>) -> Self {
Self {
event: event.into(),
data: data.into(),
id: None,
}
}
}
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SseError<E> {
Transport(E),
InvalidUtf8(Utf8Error),
#[non_exhaustive]
EventTooLarge {
limit: usize,
},
}
impl<E: Into<crate::TransportError>> SseError<E> {
#[must_use]
pub fn into_error(self) -> allama::Error {
match self {
Self::Transport(error) => error.into().into_stream_error(),
Self::InvalidUtf8(error) => {
allama::Error::stream_decode(SseError::<Infallible>::InvalidUtf8(error).to_string())
.authored()
}
Self::EventTooLarge { limit } => allama::Error::stream_protocol(
SseError::<Infallible>::EventTooLarge { limit }.to_string(),
)
.authored(),
}
}
}
impl SseError<Infallible> {
fn widen<E>(self) -> SseError<E> {
match self {
Self::Transport(never) => match never {},
Self::InvalidUtf8(error) => SseError::InvalidUtf8(error),
Self::EventTooLarge { limit } => SseError::EventTooLarge { limit },
}
}
}
impl<E: fmt::Display> fmt::Display for SseError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Transport(error) => error.fmt(f),
Self::InvalidUtf8(error) => write!(f, "invalid UTF-8 in the SSE stream: {error}"),
Self::EventTooLarge { limit } => {
write!(f, "an SSE event passed the size cap of {limit} bytes")
}
}
}
}
impl<E: std::error::Error + 'static> std::error::Error for SseError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Transport(error) => error.source(),
Self::InvalidUtf8(error) => Some(error),
Self::EventTooLarge { .. } => None,
}
}
}
impl<E: Into<crate::TransportError>> From<SseError<E>> for allama::Error {
fn from(error: SseError<E>) -> Self {
error.into_error()
}
}
const BOM: &[u8] = b"\xEF\xBB\xBF";
fn is_line_ending(byte: u8) -> bool {
matches!(byte, b'\n' | b'\r')
}
fn room(len: usize, capacity: usize, additional: usize, limit: usize) -> usize {
let needed = len + additional;
if needed <= capacity {
return 0;
}
capacity.saturating_mul(2).min(limit).max(needed) - len
}
struct TooLarge;
#[derive(Debug, Default)]
struct PendingEvent {
event: String,
data: String,
last_id: String,
}
impl PendingEvent {
fn line(&mut self, line: &str, max_data: usize) -> Result<Option<SseEvent>, TooLarge> {
if line.is_empty() {
return Ok(self.dispatch());
}
if line.starts_with(':') {
return Ok(None);
}
let (field, value) = match line.split_once(':') {
Some((field, value)) => (field, value.strip_prefix(' ').unwrap_or(value)),
None => (line, ""),
};
match field {
"event" => value.clone_into(&mut self.event),
"data" => {
if self.data.len() + value.len() > max_data {
return Err(TooLarge);
}
let additional = room(
self.data.len(),
self.data.capacity(),
value.len() + 1,
max_data.saturating_add(1),
);
self.data.reserve_exact(additional);
self.data.push_str(value);
self.data.push('\n');
}
"id" if !value.contains('\0') => value.clone_into(&mut self.last_id),
_ => {}
}
Ok(None)
}
fn dispatch(&mut self) -> Option<SseEvent> {
let event = std::mem::take(&mut self.event);
let mut data = std::mem::take(&mut self.data);
data.pop()?;
Some(SseEvent {
event: if event.is_empty() {
"message".to_owned()
} else {
event
},
data,
id: (!self.last_id.is_empty()).then(|| self.last_id.clone()),
})
}
}
mod chat;
pub use crate::framing::VendorFrames;
pub use chat::SseChatStream;
#[cfg(test)]
mod tests;