Skip to main content

ollama_sdk/
parser.rs

1//! Provides a generic parser for parsing and handling streaming responses
2//! from the Ollama API.
3//!
4//! This module contains parsers for different types of streaming events,
5//! such as chat completions and text generation.
6
7use std::marker::PhantomData;
8use std::pin::Pin;
9use std::task::{Context, Poll};
10
11use crate::types::OllamaError;
12use crate::Result;
13use bytes::Bytes;
14use futures::Stream;
15use serde::de::DeserializeOwned;
16
17/// Small conversion trait so endpoint-specific event enums can be constructed
18/// from a successful message `M`, an error string, or a partial payload.
19pub trait StreamEventExt<M>: Sized {
20    /// Create an event from a successfully deserialized message.
21    fn from_message(msg: M) -> Self;
22
23    /// Create an event from an error string.
24    fn from_error(err: String) -> Self;
25
26    /// Create a partial event (with optional error text).
27    fn partial(partial: String, error: Option<String>) -> Self;
28}
29
30/// Generic newline-delimited JSON streaming parser.
31///
32/// - `S` is the underlying stream that yields `Result<Bytes>`
33/// - `M` is the concrete message struct you expect per line (DeserializeOwned)
34/// - `E` is the endpoint event enum type that implements `StreamEventExt<M>`
35pub struct GenericStreamParser<S, M, E>
36where
37    S: Stream<Item = Result<Bytes>> + Send + Unpin,
38    M: DeserializeOwned,
39    E: StreamEventExt<M>,
40{
41    inner: S,
42    buffer: Vec<u8>,
43    _marker: PhantomData<(M, E)>,
44}
45
46impl<S, M, E> GenericStreamParser<S, M, E>
47where
48    S: Stream<Item = Result<Bytes>> + Send + Unpin,
49    M: DeserializeOwned,
50    E: StreamEventExt<M>,
51{
52    pub fn new(stream: S) -> Self {
53        Self {
54            inner: stream,
55            buffer: Vec::new(),
56            _marker: PhantomData,
57        }
58    }
59
60    /// Try to parse one complete newline-terminated line from the buffer.
61    /// Returns `Some(Ok(E))` when we parsed one event; `Some(Err(e))` for a transport/error;
62    /// `None` when no full line is available yet.
63    fn parse_lines(&mut self) -> Option<Result<E>> {
64        loop {
65            // find newline
66            let newline_pos = self.buffer.iter().position(|&b| b == b'\n')?;
67            // take inclusive newline bytes
68            let line_bytes = self.buffer.drain(..=newline_pos).collect::<Vec<u8>>();
69            let line_str = String::from_utf8_lossy(&line_bytes);
70            let line_str = line_str.trim();
71
72            if line_str.is_empty() {
73                continue; // skip blank lines
74            }
75
76            // First try to parse the expected message type M
77            match serde_json::from_str::<M>(line_str) {
78                Ok(msg) => return Some(Ok(E::from_message(msg))),
79                Err(e_msg) => {
80                    // If not M, try to parse an OllamaError
81                    match serde_json::from_str::<OllamaError>(line_str) {
82                        Ok(err) => return Some(Ok(E::from_error(err.error))),
83                        Err(_) => {
84                            // fallback: treat as partial with parse error string
85                            return Some(Ok(E::partial(
86                                line_str.to_string(),
87                                Some(e_msg.to_string()),
88                            )));
89                        }
90                    }
91                }
92            }
93        }
94    }
95}
96
97impl<S, M, E> Stream for GenericStreamParser<S, M, E>
98where
99    S: Stream<Item = Result<Bytes>> + Send + Unpin,
100    M: DeserializeOwned + Unpin,
101    E: StreamEventExt<M> + Unpin,
102{
103    type Item = Result<E>;
104
105    // remove `mut` from the `self` binding; we'll call `get_mut()` to get &mut Self.
106    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
107        // SAFETY / rationale:
108        // We have exclusive access to the pinned reference in this poll method,
109        // so calling `get_mut()` to obtain `&mut Self` for internal field mutation
110        // is correct here.
111        let this = self.get_mut();
112
113        loop {
114            // 1. Try to parse any complete lines in buffer
115            if let Some(event) = this.parse_lines() {
116                return Poll::Ready(Some(event));
117            }
118
119            // 2. If no complete line, check if stream is done
120            if this.buffer.is_empty() {
121                // Only poll inner if buffer is empty
122                match Pin::new(&mut this.inner).poll_next(cx) {
123                    Poll::Ready(Some(Ok(bytes))) => {
124                        this.buffer.extend_from_slice(&bytes);
125                        continue; // loop: try parse again
126                    }
127                    Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))),
128                    Poll::Ready(None) => return Poll::Ready(None), // stream ended, buffer empty
129                    Poll::Pending => return Poll::Pending,
130                }
131            } else {
132                // Buffer has data, but no newline → need more
133                // Poll inner stream
134                match Pin::new(&mut this.inner).poll_next(cx) {
135                    Poll::Ready(Some(Ok(bytes))) => {
136                        this.buffer.extend_from_slice(&bytes);
137                        continue;
138                    }
139                    Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))),
140                    Poll::Ready(None) => {
141                        // Stream ended with partial data
142                        let content = String::from_utf8_lossy(&this.buffer).to_string();
143                        this.buffer.clear();
144                        if !content.trim().is_empty() {
145                            return Poll::Ready(Some(Ok(E::partial(content, None))));
146                        }
147                        return Poll::Ready(None);
148                    }
149                    Poll::Pending => return Poll::Pending,
150                }
151            }
152        }
153    }
154}