Skip to main content

cloud_sdk/transport/streaming/
io.rs

1//! Caller-buffered blocking, Send-async, and local-async stream drivers.
2
3mod asynchronous;
4mod blocking;
5
6pub use asynchronous::{drive_async_stream, drive_local_stream};
7pub use blocking::drive_blocking_stream;
8
9use cloud_sdk_sanitization::sanitize_bytes;
10use core::{fmt, future::Future};
11
12use super::policy::partial_state;
13use super::{StreamPartialState, StreamPolicy, StreamProgressError, StreamReplayability};
14
15/// One source observation.
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum StreamRead {
18    /// One chunk was initialized in the supplied scratch prefix.
19    Chunk(usize),
20    /// The source was observed but produced no chunk in this turn.
21    Wait,
22    /// The finite source reached end-of-stream.
23    End,
24}
25
26/// Blocking source that initializes caller-owned scratch storage.
27pub trait BlockingStreamSource {
28    /// Source-specific payload-free failure.
29    type Error;
30
31    /// Returns explicit body replayability and exact source version.
32    fn replayability(&self) -> StreamReplayability<'_>;
33
34    /// Produces at most one chunk and never retains `output`.
35    fn read_chunk(&mut self, output: &mut [u8]) -> Result<StreamRead, Self::Error>;
36}
37
38/// Blocking sink with explicit commit and infallible abort cleanup.
39pub trait BlockingStreamSink {
40    /// Sink-specific payload-free failure.
41    type Error;
42
43    /// Accepts some or all bytes. A zero result is a bounded no-progress event.
44    fn write_chunk(&mut self, input: &[u8]) -> Result<usize, Self::Error>;
45
46    /// Commits a successfully validated stream.
47    fn commit(&mut self) -> Result<(), Self::Error>;
48
49    /// Aborts an incomplete stream according to its recorded partial state.
50    fn abort(&mut self, partial: StreamPartialState);
51}
52
53/// Local asynchronous source whose future may be `!Send`.
54pub trait LocalAsyncStreamSource {
55    /// Source-specific payload-free failure.
56    type Error;
57
58    /// Returns explicit body replayability and exact source version.
59    fn replayability(&self) -> StreamReplayability<'_>;
60
61    /// Produces at most one chunk without retaining `output` after completion.
62    fn read_chunk_local<'operation>(
63        &'operation mut self,
64        output: &'operation mut [u8],
65    ) -> impl Future<Output = Result<StreamRead, Self::Error>> + 'operation;
66}
67
68/// Cross-thread asynchronous source.
69pub trait AsyncStreamSource {
70    /// Source-specific payload-free failure.
71    type Error;
72
73    /// Returns explicit body replayability and exact source version.
74    fn replayability(&self) -> StreamReplayability<'_>;
75
76    /// Produces at most one chunk in a `Send` future.
77    fn read_chunk<'operation>(
78        &'operation mut self,
79        output: &'operation mut [u8],
80    ) -> impl Future<Output = Result<StreamRead, Self::Error>> + Send + 'operation;
81}
82
83impl<T: AsyncStreamSource> LocalAsyncStreamSource for T {
84    type Error = T::Error;
85
86    fn replayability(&self) -> StreamReplayability<'_> {
87        AsyncStreamSource::replayability(self)
88    }
89
90    async fn read_chunk_local<'operation>(
91        &'operation mut self,
92        output: &'operation mut [u8],
93    ) -> Result<StreamRead, Self::Error> {
94        AsyncStreamSource::read_chunk(self, output).await
95    }
96}
97
98/// Local asynchronous sink whose futures may be `!Send`.
99pub trait LocalAsyncStreamSink {
100    /// Sink-specific payload-free failure.
101    type Error;
102
103    /// Accepts some or all bytes without retaining `input` after completion.
104    fn write_chunk_local<'operation>(
105        &'operation mut self,
106        input: &'operation [u8],
107    ) -> impl Future<Output = Result<usize, Self::Error>> + 'operation;
108
109    /// Commits a successfully validated stream.
110    fn commit_local(&mut self) -> impl Future<Output = Result<(), Self::Error>> + '_;
111
112    /// Synchronously aborts on error or future cancellation.
113    fn abort_local(&mut self, partial: StreamPartialState);
114}
115
116/// Cross-thread asynchronous sink.
117pub trait AsyncStreamSink {
118    /// Sink-specific payload-free failure.
119    type Error;
120
121    /// Accepts some or all bytes in a `Send` future.
122    fn write_chunk<'operation>(
123        &'operation mut self,
124        input: &'operation [u8],
125    ) -> impl Future<Output = Result<usize, Self::Error>> + Send + 'operation;
126
127    /// Commits a successfully validated stream in a `Send` future.
128    fn commit(&mut self) -> impl Future<Output = Result<(), Self::Error>> + Send + '_;
129
130    /// Synchronously aborts on error or future cancellation.
131    fn abort(&mut self, partial: StreamPartialState);
132}
133
134impl<T: AsyncStreamSink> LocalAsyncStreamSink for T {
135    type Error = T::Error;
136
137    async fn write_chunk_local<'operation>(
138        &'operation mut self,
139        input: &'operation [u8],
140    ) -> Result<usize, Self::Error> {
141        AsyncStreamSink::write_chunk(self, input).await
142    }
143
144    async fn commit_local(&mut self) -> Result<(), Self::Error> {
145        AsyncStreamSink::commit(self).await
146    }
147
148    fn abort_local(&mut self, partial: StreamPartialState) {
149        AsyncStreamSink::abort(self, partial);
150    }
151}
152
153/// One bounded stream execution failure.
154#[derive(Clone, Copy, Eq, PartialEq)]
155pub enum StreamExecutionError<S, D> {
156    /// Caller scratch storage is empty.
157    EmptyScratch,
158    /// The source claimed more initialized bytes than the supplied prefix.
159    InvalidSourceLength,
160    /// SDK-owned accounting or lifecycle validation failed.
161    Progress(StreamProgressError),
162    /// The source failed.
163    Source(S),
164    /// The sink failed while writing or committing.
165    Sink(D),
166}
167
168impl<S, D> fmt::Debug for StreamExecutionError<S, D> {
169    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
170        match self {
171            Self::EmptyScratch => formatter.write_str("EmptyScratch"),
172            Self::InvalidSourceLength => formatter.write_str("InvalidSourceLength"),
173            Self::Progress(error) => formatter.debug_tuple("Progress").field(error).finish(),
174            Self::Source(_) => formatter.write_str("Source([redacted])"),
175            Self::Sink(_) => formatter.write_str("Sink([redacted])"),
176        }
177    }
178}
179
180impl<S, D> fmt::Display for StreamExecutionError<S, D> {
181    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
182        formatter.write_str(match self {
183            Self::EmptyScratch => "stream scratch storage is empty",
184            Self::InvalidSourceLength => "stream source reported an invalid length",
185            Self::Progress(_) => "stream progress policy rejected execution",
186            Self::Source(_) => "stream source failed",
187            Self::Sink(_) => "stream sink failed",
188        })
189    }
190}
191
192impl<S, D> core::error::Error for StreamExecutionError<S, D> {}
193
194struct AbortGuard<'sink, S> {
195    sink: &'sink mut S,
196    partial: StreamPartialState,
197    abort: fn(&mut S, StreamPartialState),
198    armed: bool,
199}
200
201impl<'sink, S> AbortGuard<'sink, S> {
202    fn new(sink: &'sink mut S, abort: fn(&mut S, StreamPartialState)) -> Self {
203        Self {
204            sink,
205            partial: StreamPartialState::Clean,
206            abort,
207            armed: true,
208        }
209    }
210
211    fn sink(&mut self) -> &mut S {
212        self.sink
213    }
214
215    fn record_write_attempt(&mut self, policy: StreamPolicy) {
216        self.partial = partial_state(policy.sink_mode(), true);
217    }
218
219    fn disarm(&mut self) {
220        self.armed = false;
221    }
222}
223
224impl<S> Drop for AbortGuard<'_, S> {
225    fn drop(&mut self) {
226        if self.armed {
227            (self.abort)(self.sink, self.partial);
228        }
229    }
230}
231
232struct ScratchGuard<'scratch> {
233    bytes: &'scratch mut [u8],
234}
235
236impl<'scratch> ScratchGuard<'scratch> {
237    fn new(bytes: &'scratch mut [u8]) -> Self {
238        sanitize_bytes(bytes);
239        Self { bytes }
240    }
241
242    fn bytes(&mut self) -> &mut [u8] {
243        self.bytes
244    }
245}
246
247impl Drop for ScratchGuard<'_> {
248    fn drop(&mut self) {
249        sanitize_bytes(self.bytes);
250    }
251}