1use core::fmt;
4
5use crate::operation::RequestIdPolicy;
6use crate::rate_limit::RateLimit;
7
8use super::cleanup::sanitize_response_storage;
9use super::retained::{ProtectedRequestId, RetainedMetadataError, RetainedResponseMetadata};
10use super::{ResponseContentType, ResponseHeaders, ResponseStorageSanitizer, StatusCode};
11
12#[derive(Debug)]
14pub struct ResponseMetadata {
15 rate_limit: Option<RateLimit>,
16}
17
18impl ResponseMetadata {
19 pub const EMPTY: Self = Self { rate_limit: None };
21
22 #[must_use]
24 pub fn with_rate_limit(mut self, rate_limit: RateLimit) -> Self {
25 self.rate_limit = Some(rate_limit);
26 self
27 }
28
29 pub(crate) const fn rate_limit(&self) -> Option<RateLimit> {
30 self.rate_limit
31 }
32}
33
34#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub enum ResponseWriterError {
37 AlreadyCommitted,
39 NotCommitted,
41 InitializedLengthTooLarge,
43}
44
45impl_static_error!(ResponseWriterError,
46 Self::AlreadyCommitted => "response writer is already committed",
47 Self::NotCommitted => "response writer is not committed",
48 Self::InitializedLengthTooLarge => "response length exceeds admitted storage",
49);
50
51struct ResponseCommit {
52 status: StatusCode,
53 initialized_len: usize,
54 metadata: ResponseMetadata,
55}
56
57pub struct ResponseWriter<'buffer> {
62 storage: &'buffer mut [u8],
63 admitted_len: usize,
64 headers: ResponseHeaders<'buffer>,
65 request_id: Option<ProtectedRequestId>,
66 commit: Option<ResponseCommit>,
67}
68
69impl<'buffer> ResponseWriter<'buffer> {
70 #[must_use]
72 pub const fn body_capacity(&self) -> usize {
73 self.admitted_len
74 }
75
76 pub fn body_mut(&mut self) -> Result<&mut [u8], ResponseWriterError> {
78 if self.commit.is_some() {
79 return Err(ResponseWriterError::AlreadyCommitted);
80 }
81 self.storage
82 .get_mut(..self.admitted_len)
83 .ok_or(ResponseWriterError::InitializedLengthTooLarge)
84 }
85
86 pub fn headers_mut(&mut self) -> Result<&mut ResponseHeaders<'buffer>, ResponseWriterError> {
88 if self.commit.is_some() {
89 return Err(ResponseWriterError::AlreadyCommitted);
90 }
91 Ok(&mut self.headers)
92 }
93
94 pub fn headers(&self) -> &ResponseHeaders<'buffer> {
96 &self.headers
97 }
98
99 pub fn commit(
101 &mut self,
102 status: StatusCode,
103 initialized_len: usize,
104 metadata: ResponseMetadata,
105 ) -> Result<(), ResponseWriterError> {
106 if self.commit.is_some() {
107 return Err(ResponseWriterError::AlreadyCommitted);
108 }
109 if initialized_len > self.admitted_len {
110 return Err(ResponseWriterError::InitializedLengthTooLarge);
111 }
112 self.commit = Some(ResponseCommit {
113 status,
114 initialized_len,
115 metadata,
116 });
117 Ok(())
118 }
119
120 #[must_use]
122 pub const fn is_committed(&self) -> bool {
123 self.commit.is_some()
124 }
125
126 fn response(&self) -> Result<TransportResponse<'_, 'buffer>, ResponseWriterError> {
127 let commit = self
128 .commit
129 .as_ref()
130 .ok_or(ResponseWriterError::NotCommitted)?;
131 let body = self
132 .storage
133 .get(..commit.initialized_len)
134 .ok_or(ResponseWriterError::InitializedLengthTooLarge)?;
135 Ok(TransportResponse::from_commit(
136 commit,
137 body,
138 &self.headers,
139 self.request_id,
140 ))
141 }
142
143 fn apply_request_id_policy(
144 &mut self,
145 policy: RequestIdPolicy,
146 ) -> Result<(), RetainedMetadataError> {
147 let request_id = self.headers.hide_request_id()?;
148 match policy {
149 RequestIdPolicy::Discard => {
150 if let Some(request_id) = request_id {
151 self.headers.clear_protected(request_id);
152 }
153 }
154 RequestIdPolicy::Protected | RequestIdPolicy::Retain => {
155 self.request_id = request_id;
156 }
157 }
158 Ok(())
159 }
160
161 fn request_id(&self) -> Option<&[u8]> {
162 self.request_id
163 .and_then(|request_id| self.headers.protected_value(request_id))
164 }
165
166 fn retain_request_id<'destination>(
167 &mut self,
168 destination: &'destination mut [u8],
169 retention_limit: usize,
170 ) -> Result<RetainedResponseMetadata<'destination>, RetainedMetadataError> {
171 let mut retained = RetainedResponseMetadata::empty_for_core(destination);
172 let Some(request_id) = self.request_id.take() else {
173 return Ok(retained);
174 };
175 let result = {
176 let source = self
177 .headers
178 .protected_value(request_id)
179 .ok_or(RetainedMetadataError::RequestIdTooLong)?;
180 if source.len() > retention_limit {
181 Err(RetainedMetadataError::RetentionLimitExceeded)
182 } else {
183 retained.write_request_id(source)
184 }
185 };
186 self.headers.clear_protected(request_id);
187 result.map(|()| retained)
188 }
189
190 fn initialized_body(&self, initialized_len: usize) -> &[u8] {
191 self.storage.get(..initialized_len).unwrap_or_default()
192 }
193}
194
195impl fmt::Debug for ResponseWriter<'_> {
196 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
197 formatter
198 .debug_struct("ResponseWriter")
199 .field("storage_capacity", &self.storage.len())
200 .field("admitted_len", &self.admitted_len)
201 .field("committed", &self.commit.is_some())
202 .field("body", &"[redacted]")
203 .finish()
204 }
205}
206
207pub struct ResponseBuffer<'buffer> {
213 writer: ResponseWriter<'buffer>,
214 additive: Option<&'buffer dyn ResponseStorageSanitizer>,
215}
216
217impl<'buffer> ResponseBuffer<'buffer> {
218 #[must_use]
220 pub fn new(
221 storage: &'buffer mut [u8],
222 max_body_bytes: usize,
223 header_storage: &'buffer mut [u8],
224 ) -> Self {
225 Self::construct(storage, max_body_bytes, header_storage, None)
226 }
227
228 #[must_use]
230 pub fn with_additive_sanitizer(
231 storage: &'buffer mut [u8],
232 max_body_bytes: usize,
233 header_storage: &'buffer mut [u8],
234 additive: &'buffer dyn ResponseStorageSanitizer,
235 ) -> Self {
236 Self::construct(storage, max_body_bytes, header_storage, Some(additive))
237 }
238
239 fn construct(
240 storage: &'buffer mut [u8],
241 max_body_bytes: usize,
242 header_storage: &'buffer mut [u8],
243 additive: Option<&'buffer dyn ResponseStorageSanitizer>,
244 ) -> Self {
245 let headers = ResponseHeaders::new(header_storage);
246 sanitize_response_storage(storage, additive);
247 Self {
248 writer: ResponseWriter {
249 admitted_len: core::cmp::min(storage.len(), max_body_bytes),
250 storage,
251 headers,
252 request_id: None,
253 commit: None,
254 },
255 additive,
256 }
257 }
258
259 #[must_use]
261 pub const fn writer(&mut self) -> &mut ResponseWriter<'buffer> {
262 &mut self.writer
263 }
264
265 pub fn with_response<R>(
267 &self,
268 inspect: impl for<'response> FnOnce(TransportResponse<'response, 'buffer>) -> R,
269 ) -> Result<R, ResponseWriterError> {
270 let response = self.writer.response()?;
271 Ok(inspect(response))
272 }
273
274 pub(crate) fn response(&self) -> Result<TransportResponse<'_, 'buffer>, ResponseWriterError> {
275 self.writer.response()
276 }
277
278 pub(crate) fn apply_request_id_policy(
279 &mut self,
280 policy: RequestIdPolicy,
281 ) -> Result<(), RetainedMetadataError> {
282 self.writer.apply_request_id_policy(policy)
283 }
284
285 pub(crate) fn request_id(&self) -> Option<&[u8]> {
286 self.writer.request_id()
287 }
288
289 pub(crate) fn retain_request_id<'destination>(
290 &mut self,
291 destination: &'destination mut [u8],
292 retention_limit: usize,
293 ) -> Result<RetainedResponseMetadata<'destination>, RetainedMetadataError> {
294 self.writer.retain_request_id(destination, retention_limit)
295 }
296
297 pub(crate) fn initialized_body(&self, initialized_len: usize) -> &[u8] {
298 self.writer.initialized_body(initialized_len)
299 }
300}
301
302impl fmt::Debug for ResponseBuffer<'_> {
303 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
304 formatter
305 .debug_struct("ResponseBuffer")
306 .field("writer", &self.writer)
307 .field("additive", &self.additive.is_some())
308 .finish()
309 }
310}
311
312impl Drop for ResponseBuffer<'_> {
313 fn drop(&mut self) {
314 sanitize_response_storage(self.writer.storage, self.additive);
315 }
316}
317
318#[derive(Clone, Copy)]
333pub struct TransportResponse<'response, 'storage> {
334 status: StatusCode,
335 body: &'response [u8],
336 metadata: &'response ResponseMetadata,
337 headers: &'response ResponseHeaders<'storage>,
338 request_id: Option<ProtectedRequestId>,
339}
340
341impl<'response, 'storage> TransportResponse<'response, 'storage> {
342 fn from_commit(
343 commit: &'response ResponseCommit,
344 body: &'response [u8],
345 headers: &'response ResponseHeaders<'storage>,
346 request_id: Option<ProtectedRequestId>,
347 ) -> Self {
348 Self {
349 status: commit.status,
350 body,
351 metadata: &commit.metadata,
352 headers,
353 request_id,
354 }
355 }
356
357 #[must_use]
359 pub const fn status(&self) -> StatusCode {
360 self.status
361 }
362
363 #[must_use]
365 pub const fn body(&self) -> &'response [u8] {
366 self.body
367 }
368
369 pub fn content_type(
373 &self,
374 ) -> Result<Option<ResponseContentType<'response>>, super::ContentTypeError> {
375 let Some(header) = self.headers.get("content-type") else {
376 return Ok(None);
377 };
378 let value =
379 core::str::from_utf8(header.value()).map_err(|_| super::ContentTypeError::Invalid)?;
380 ResponseContentType::new(value).map(Some)
381 }
382
383 #[must_use]
385 pub const fn rate_limit(&self) -> Option<RateLimit> {
386 self.metadata.rate_limit()
387 }
388
389 #[must_use]
391 pub const fn headers(&self) -> &'response ResponseHeaders<'storage> {
392 self.headers
393 }
394
395 pub fn with_request_id<R>(&self, inspect: impl FnOnce(Option<&[u8]>) -> R) -> R {
397 inspect(
398 self.request_id
399 .and_then(|request_id| self.headers.protected_value(request_id)),
400 )
401 }
402}
403
404impl fmt::Debug for TransportResponse<'_, '_> {
405 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
406 formatter
407 .debug_struct("TransportResponse")
408 .field("status", &self.status)
409 .field("body_len", &self.body.len())
410 .field("body", &"[redacted]")
411 .field("metadata", &self.metadata)
412 .field("headers", &self.headers)
413 .field("request_id", &"[redacted]")
414 .finish()
415 }
416}