1pub mod sse;
4pub use sse::{SseEvent, SseStream, TerminalPolicy};
5
6#[cfg(feature = "gateway")]
8pub struct ContractStream<T> {
9 stream: SseStream,
10 contract: EventContract,
11 options: RequestOptions,
12 session: Session,
13 failure: Option<Error>,
14 lifecycle: crate::observability::Lifecycle,
15 marker: std::marker::PhantomData<T>,
16}
17#[cfg(feature = "gateway")]
18pub(crate) struct EventContract {
19 pub schema: &'static str,
20 pub operation: &'static str,
21 pub status: u16,
22 pub request_id: Option<String>,
23 pub limits: crate::validation::ValidationLimits,
24}
25#[cfg(feature = "gateway")]
26impl<T: serde::de::DeserializeOwned> ContractStream<T> {
27 pub(crate) fn new(
28 stream: SseStream,
29 contract: EventContract,
30 options: RequestOptions,
31 session: Session,
32 ) -> Self {
33 let lifecycle = crate::observability::Lifecycle::received(
34 crate::observability::Operation::TypedStream,
35 contract.status,
36 contract.request_id.is_some(),
37 );
38 Self {
39 lifecycle,
40 stream,
41 contract,
42 options,
43 session,
44 failure: None,
45 marker: std::marker::PhantomData,
46 }
47 }
48 pub fn close(&mut self) {
49 self.lifecycle.closed();
50 self.stream.close();
51 }
52 pub fn into_stream(self) -> impl futures_util::Stream<Item = Result<T>> + Send
54 where
55 T: Send,
56 {
57 futures_util::stream::try_unfold(self, |mut stream| async move {
58 stream
59 .next()
60 .await
61 .map(|value| value.map(|event| (event, stream)))
62 })
63 }
64 pub async fn next(&mut self) -> Result<Option<T>> {
65 if let Some(error) = &self.failure {
66 return Err(error.clone());
67 }
68 let event = loop {
69 match self.stream.next().await {
70 Ok(Some(event)) if event.data().is_empty() => {
71 let active = self
74 .options
75 .run(&self.session, async {
76 tokio::task::yield_now().await;
77 Ok(())
78 })
79 .await;
80 if let Err(error) = active {
81 let error = error
82 .with_response_metadata(
83 self.contract.status,
84 self.contract.request_id.clone(),
85 )
86 .with_operation(self.contract.operation)
87 .with_outcome(RemoteOutcome::Unknown);
88 self.lifecycle.failed(&error);
89 self.stream.close();
90 self.failure = Some(error.clone());
91 return Err(error);
92 }
93 }
94 Ok(Some(event)) => break event,
95 Ok(None) => {
96 self.lifecycle.success();
97 return Ok(None);
98 }
99 Err(error) => {
100 self.lifecycle.failed(&error);
101 self.failure = Some(error.clone());
102 return Err(error);
103 }
104 }
105 };
106 let result = self
107 .options
108 .run(&self.session, async {
109 let value = event.json::<serde_json::Value>()?;
110 let value = crate::validation::parse_with_limits(
111 self.contract.schema,
112 value,
113 self.options.response_mode(),
114 &self.contract.limits,
115 )
116 .map_err(|error| {
117 Error::new(if error.kind() == ErrorKind::Limit {
118 ErrorKind::Limit
119 } else {
120 ErrorKind::Decode
121 })
122 })?;
123 crate::json_decode::from_value(value).map_err(|_| Error::new(ErrorKind::Decode))
124 })
125 .await;
126 match result {
127 Ok(value) => Ok(Some(value)),
128 Err(error) => {
129 let error = error
130 .with_response_metadata(self.contract.status, self.contract.request_id.clone())
131 .with_operation(self.contract.operation)
132 .with_outcome(RemoteOutcome::Unknown);
133 self.lifecycle.failed(&error);
134 self.stream.close();
135 self.failure = Some(error.clone());
136 Err(error)
137 }
138 }
139 }
140}
141#[cfg(feature = "gateway")]
142impl<T> fmt::Debug for ContractStream<T> {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 f.debug_struct("ContractStream")
145 .field("operation", &self.contract.operation)
146 .field("stream", &self.stream)
147 .finish_non_exhaustive()
148 }
149}
150
151use crate::{
152 Error, ErrorKind, Limits, RemoteOutcome, RequestOptions, Result, Session, transport::BodyStream,
153};
154use bytes::Bytes;
155use futures_util::StreamExt;
156use http::{HeaderMap, StatusCode};
157use std::{fmt, time::Duration};
158use tokio::{sync::OwnedSemaphorePermit, time::Instant};
159use tokio_util::sync::CancellationToken;
160
161pub struct ResponseStream {
162 status: StatusCode,
163 headers: HeaderMap,
164 request_id: Option<String>,
165 body: Option<BodyStream>,
166 permit: Option<OwnedSemaphorePermit>,
167 session: Session,
168 cancellation: CancellationToken,
169 deadline: Option<Instant>,
170 idle: Duration,
171 operation: &'static str,
172 max_bytes: usize,
173 consumed: usize,
174 lifecycle: crate::observability::Lifecycle,
175}
176
177impl ResponseStream {
178 pub(crate) fn new(
179 response: crate::transport::TransportResponse,
180 permit: OwnedSemaphorePermit,
181 session: Session,
182 options: &RequestOptions,
183 explicit_deadline: bool,
184 limits: &Limits,
185 operation: &'static str,
186 ) -> Self {
187 let deadline = match (
188 explicit_deadline.then_some(options.deadline()),
189 limits
190 .stream_session_timeout
191 .and_then(|v| Instant::now().checked_add(v)),
192 ) {
193 (Some(a), Some(b)) => Some(a.min(b)),
194 (a, b) => a.or(b),
195 };
196 let request_id = crate::engine::error_metadata(&response.headers, None).0;
197 let mut headers = response.headers;
198 for value in headers.values_mut() {
199 value.set_sensitive(true);
200 }
201 let lifecycle = crate::observability::Lifecycle::received(
202 crate::observability::Operation::BodyStream,
203 response.status.as_u16(),
204 request_id.is_some(),
205 );
206 Self {
207 lifecycle,
208 status: response.status,
209 headers,
210 request_id,
211 body: Some(response.body),
212 permit: Some(permit),
213 session,
214 cancellation: options.cancellation_token(),
215 deadline,
216 idle: limits.stream_idle_timeout,
217 operation,
218 max_bytes: limits.max_response_bytes,
219 consumed: 0,
220 }
221 }
222 pub fn status(&self) -> StatusCode {
223 self.status
224 }
225 pub fn headers(&self) -> &HeaderMap {
227 &self.headers
228 }
229 pub fn request_id(&self) -> Option<&str> {
231 self.request_id.as_deref()
232 }
233 pub fn bytes_consumed(&self) -> usize {
234 self.consumed
235 }
236 pub fn close(&mut self) {
237 self.lifecycle.closed();
238 self.body = None;
239 self.permit = None;
240 }
241 pub(crate) fn check_active(&self) -> Result<()> {
242 let kind = if self.cancellation.is_cancelled() || self.session.is_cancelled() {
243 Some(ErrorKind::Cancelled)
244 } else if self.deadline.is_some_and(|value| Instant::now() >= value) {
245 Some(ErrorKind::Deadline)
246 } else {
247 None
248 };
249 match kind {
250 Some(kind) => Err(Error::new(kind)
251 .with_response_metadata(self.status.as_u16(), self.request_id.clone())
252 .with_operation(self.operation)
253 .with_outcome(RemoteOutcome::Unknown)),
254 None => Ok(()),
255 }
256 }
257 pub async fn next(&mut self) -> Result<Option<Bytes>> {
260 if self.body.is_none() {
261 return Ok(None);
262 }
263 let result = async {
264 self.check_active()?;
265 let now = Instant::now();
266 let deadline = now
267 .checked_add(self.idle)
268 .ok_or_else(|| Error::new(ErrorKind::Configuration))?;
269 let deadline = self.deadline.map_or(deadline, |value| value.min(deadline));
270 if now >= deadline {
271 return Err(Error::new(ErrorKind::Deadline));
272 }
273 let body = self
274 .body
275 .as_mut()
276 .ok_or_else(|| Error::new(ErrorKind::Protocol))?;
277 let value = tokio::select! {biased;
278 _=self.cancellation.cancelled()=>Err(Error::new(ErrorKind::Cancelled)),
279 _=self.session.cancelled()=>Err(Error::new(ErrorKind::Cancelled)),
280 _=tokio::time::sleep_until(deadline)=>Err(Error::new(ErrorKind::Deadline)),
281 value=body.next()=>value.transpose(),
282 }?;
283 if self.cancellation.is_cancelled() || self.session.is_cancelled() {
284 return Err(Error::new(ErrorKind::Cancelled));
285 }
286 if Instant::now() >= deadline {
287 return Err(Error::new(ErrorKind::Deadline));
288 }
289 if let Some(bytes) = &value {
290 self.consumed = self
291 .consumed
292 .checked_add(bytes.len())
293 .ok_or_else(|| Error::new(ErrorKind::Limit))?;
294 if self.consumed > self.max_bytes {
295 return Err(Error::new(ErrorKind::Limit));
296 }
297 }
298 Ok(value)
299 }
300 .await;
301 match result {
302 Ok(None) => {
303 self.lifecycle.success();
304 self.close();
305 Ok(None)
306 }
307 Ok(value) => Ok(value),
308 Err(error) => {
309 let error = crate::engine::body_failure(error)
310 .with_response_metadata(self.status.as_u16(), self.request_id.clone())
311 .with_operation(self.operation)
312 .with_outcome(RemoteOutcome::Unknown);
313 self.lifecycle.failed(&error);
314 self.close();
315 Err(error)
316 }
317 }
318 }
319}
320impl fmt::Debug for ResponseStream {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322 f.debug_struct("ResponseStream")
323 .field("operation", &self.operation)
324 .field("status", &self.status)
325 .field("bytes_consumed", &self.consumed)
326 .field("closed", &self.body.is_none())
327 .finish_non_exhaustive()
328 }
329}