rubedo/http.rs
1//! This module provides extensions to the [HTTP](https://crates.io/crates/http),
2//! [Hyper](https://crates.io/crates/hyper), and [Axum](https://crates.io/crates/axum)
3//! crates.
4//!
5//! Hyper and Axum are built on top of the HTTP crate, and Axum uses parts of
6//! Hyper, so it makes sense to combine all of these in one module.
7
8
9
10// Modules
11
12#[cfg(test)]
13#[path = "tests/http.rs"]
14mod tests;
15
16
17
18// Packages
19
20use base64::{DecodeError, engine::{Engine as _, general_purpose::STANDARD as BASE64}};
21use bytes::Bytes;
22use core::{
23 cmp::Ordering,
24 convert::Infallible,
25 error::Error,
26 fmt::{Debug, Display, Write, self},
27 ops::{Add, AddAssign},
28 str::FromStr,
29};
30use futures::executor;
31use futures_util::FutureExt as _;
32use http::{Response, StatusCode};
33use http_body_util::{BodyExt as _, Collected, Full};
34use hyper::{
35 body::Incoming,
36 HeaderMap,
37 header::HeaderValue,
38};
39use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError};
40use serde_json::Value as Json;
41use std::borrow::Cow;
42use thiserror::Error as ThisError;
43
44#[cfg(feature = "axum")]
45use ::{
46 axum::body::{Body as AxumBody, to_bytes},
47 core::mem,
48};
49
50
51
52// Enums
53
54// ContentType
55/// The content type of an HTTP response, for use by [`UnpackedResponseBody`].
56///
57/// The content type is used to determine how to represent and interpret the
58/// response body when performing serialisation and deserialisation, including
59/// for display.
60///
61/// The default content type is [`Text`](ContentType::Text).
62///
63/// This enum is exhaustive and will never have any additional variants added
64/// to it, as all possibilities are already covered.
65///
66#[expect(clippy::exhaustive_enums, reason = "Exhaustive")]
67#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
68pub enum ContentType {
69 /// The response body is text. It will be represented as an ordinary
70 /// [`String`] when serialised.
71 #[default]
72 Text,
73
74 /// The response body is binary. It will be represented as a [`String`]
75 /// in base64 format when serialised.
76 Binary,
77}
78
79// ResponseError
80/// The possible errors that can occur when working with an HTTP response.
81#[derive(Debug, ThisError)]
82#[non_exhaustive]
83pub enum ResponseError {
84 /// An error encountered while converting the response body to bytes.
85 #[error("Error encountered while converting response body to bytes: {0}")]
86 ConversionError(Box<dyn Error>),
87}
88
89
90
91// Structs
92
93// UnpackedResponse
94/// An HTTP response in comparison-friendly form for interrogation.
95///
96/// Data in [`hyper::Response`] (and indeed [`http::Response`] as well) is
97/// stored in a specific form, made up of a header map object and a generic body
98/// type, which can be empty, a [`String`], or a streaming body future. This
99/// struct provides a way to use the data in a more accessible form, to allow it
100/// to be checked and compared. This is useful for testing, as the entire set of
101/// headers plus body can be checked all at once, and also for printing/logging.
102///
103/// If specific headers or body content needs to be checked, it is recommended
104/// to use the standard functions as they will be more efficient and performant.
105///
106/// Note that the [`body`](UnpackedResponse::body) property, which is stored as
107/// a vector of bytes, will get converted to a [`String`] if it is run through
108/// the standard [`Debug`] or [`Display`] formatters. This is because
109/// human-readable output is the intuitively-expected outcome in this situation.
110/// The behaviour can be controlled with the [`ContentType`] enum, which is used
111/// to determine whether the data is binary or text. If [`Text`](ContentType::Text),
112/// then the conversion uses [`from_utf8_lossy()`](String::from_utf8_lossy()),
113/// so no errors will occur, but if the body is not valid UTF8 then the
114/// resulting [`String`] will not be exactly the same. If an accurate
115/// representation of the body is required then it should be set to [`Binary`](ContentType::Binary),
116/// or else it should be extracted and converted to a `Vec<u8>` and then run
117/// through the [`Debug`] or [`Display`] formatters directly.
118///
119/// # See also
120///
121/// * [`axum::response`](https://docs.rs/axum/latest/axum/response/index.html)
122/// * [`axum::response::Response`](https://docs.rs/axum/latest/axum/response/type.Response.html)
123/// * [`http::Response`]
124/// * [`hyper::Response`]
125/// * [`ResponseExt`]
126/// * [`ResponseExt::unpack()`]
127/// * [`UnpackedResponseHeader`]
128///
129#[derive(Debug, Deserialize, Serialize)]
130#[non_exhaustive]
131pub struct UnpackedResponse {
132 // Public properties
133 /// The response status code. This is an enum, so is not directly comparable
134 /// to a number. The standard [`Display`] formatter will convert it to a
135 /// string in the format `"200 OK"`, but the standard [`FromStr`]
136 /// implementation will error if this is given back to it, as it expects
137 /// only `"200"`. Because this round-trip is basically broken, this struct
138 /// provides custom serialisation and deserialisation functions to convert
139 /// the status code to and from an actual number (a [`u16`]). This allows
140 /// the struct to be serialised and deserialised in a round-trip without
141 /// error, and is also the more intuitive representation of the status code
142 /// in serialised form such as JSON.
143 #[serde(serialize_with = "serialize_status_code", deserialize_with = "deserialize_status_code")]
144 pub status: StatusCode,
145
146 /// The response headers. These are in a vector rather than a hashmap
147 /// because there may be multiple headers with the same name. They are
148 /// sorted by name, and then by value, allowing for reliable comparison.
149 /// Sorting does break the original order of the headers, but this should
150 /// only very rarely matter, even when logging, and sorting allows
151 /// duplicates to be spotted by eye more easily in logs.
152 pub headers: Vec<UnpackedResponseHeader>,
153
154 /// The response body. This originates from the response body as a [`Bytes`]
155 /// container, but gets stored here as a vector of bytes for convenience.
156 /// This may not be valid UTF8, so is not converted to a [`String`]. That
157 /// step is left as optional for the caller, if required (and happens when
158 /// running the [`UnpackedResponse`] struct through the [`Debug`] or
159 /// [`Display`] formatters).
160 pub body: UnpackedResponseBody,
161}
162
163// UnpackedResponse
164impl UnpackedResponse {
165 // new
166 /// Creates a new unpacked response instance.
167 ///
168 /// This constructor builds a new [`UnpackedResponse`] instance from the
169 /// response status code, header data, and body data. This is useful when
170 /// the parts
171 ///
172 /// # Parameters
173 ///
174 /// * `status` - The response status code. See [`status`](UnpackedResponse::status).
175 /// * `headers` - The response headers. See [`headers`](UnpackedResponse::headers).
176 /// * `body` - The response body. See [`body`](UnpackedResponse::body).
177 ///
178 #[must_use]
179 pub fn new<T: Into<UnpackedResponseBody>>(
180 status: StatusCode,
181 headers: Vec<(String, String)>,
182 body: T
183 ) -> Self {
184 Self::new_from_parts(
185 status,
186 headers.into_iter().map(|(name, value)| UnpackedResponseHeader::new(name, value)).collect(),
187 body.into(),
188 )
189 }
190
191 // new_from_parts
192 /// Creates a new unpacked response instance from existing parts.
193 ///
194 /// This constructor builds a new [`UnpackedResponse`] instance from
195 /// constituent part instances that are already in the correct form. This is
196 /// useful when the parts are already available.
197 ///
198 /// # Parameters
199 ///
200 /// * `status` - The response status code. See [`status`](UnpackedResponse::status).
201 /// * `headers` - The response headers. See [`headers`](UnpackedResponse::headers).
202 /// * `body` - The response body. See [`body`](UnpackedResponse::body).
203 ///
204 #[must_use]
205 pub const fn new_from_parts(
206 status: StatusCode,
207 headers: Vec<UnpackedResponseHeader>,
208 body: UnpackedResponseBody
209 ) -> Self {
210 Self {
211 status,
212 headers,
213 body,
214 }
215 }
216}
217
218// PartialEq
219impl PartialEq for UnpackedResponse {
220 // eq
221 fn eq(&self, other: &Self) -> bool {
222 self.status == other.status && self.headers == other.headers && self.body == other.body
223 }
224}
225
226// UnpackedResponseHeader
227/// An HTTP response header.
228///
229/// A simple representation of an HTTP response header as a key-value pair. The
230/// purpose of this struct is to formalise the data structure used by
231/// [`UnpackedResponse`] for storing headers.
232///
233/// No other properties are planned or logically considerable at present, and so
234/// this struct is seen as being exhaustive.
235///
236/// # See also
237///
238/// * [`UnpackedResponse`]
239///
240#[expect(clippy::exhaustive_structs, reason = "Exhaustive")]
241#[derive(Debug, Deserialize, Serialize)]
242pub struct UnpackedResponseHeader {
243 // Public properties
244 /// The response header name.
245 pub name: String,
246
247 /// The response header value.
248 pub value: String,
249}
250
251// UnpackedResponseHeader
252impl UnpackedResponseHeader {
253 // new
254 /// Creates a new response header instance.
255 ///
256 /// # Parameters
257 ///
258 /// * `name` - The response header name.
259 /// * `value` - The response header value.
260 ///
261 #[must_use]
262 pub const fn new(name: String, value: String) -> Self {
263 Self {
264 name,
265 value,
266 }
267 }
268}
269
270// PartialEq
271impl PartialEq for UnpackedResponseHeader {
272 // eq
273 fn eq(&self, other: &Self) -> bool {
274 self.name == other.name && self.value == other.value
275 }
276}
277
278// UnpackedResponseBody
279/// An HTTP response body.
280///
281/// A simple representation of an HTTP response body as a vector of bytes. The
282/// purpose of this struct is to formalise the data structure used by
283/// [`UnpackedResponse`] for storing the body.
284///
285/// The data originates from the response body as a [`Bytes`] container, but
286/// gets stored here as a vector of bytes for convenience. This may not be valid
287/// UTF8, so is not converted to a [`String`]. That step is left as optional for
288/// the caller, if required (and happens when running through the [`Debug`] or
289/// [`Display`] formatters).
290///
291/// The conversion to a [`String`] when run through the [`Debug`] and
292/// [`Display`] formatters is because human-readable output is the
293/// intuitively-expected outcome in this situation. The behaviour can be
294/// controlled with the [`ContentType`] enum, which is used to determine whether
295/// the data is binary or text. If [`Text`](ContentType::Text), then the
296/// conversion uses [`from_utf8_lossy()`](String::from_utf8_lossy()), so no
297/// errors will occur, but if the body is not valid UTF8 then the resulting
298/// [`String`] will not be exactly the same. If an accurate representation of
299/// the body is required then it should be set to [`Binary`](ContentType::Binary),
300/// or else it should be extracted and converted to a `Vec<u8>` and then run
301/// through the [`Debug`] or [`Display`] formatters directly.
302///
303/// This struct is very similar in nature to the standard Rust [`String`]
304/// struct, in that it is a wrapper around a vector of bytes, and so its design
305/// and function names are modelled after it. The main difference is that it
306/// does not require its contents to be valid UTF8, and also that it is a tuple
307/// struct rather than a regular struct.
308///
309/// Note that serialisation/deserialisation of this struct directly will produce
310/// and expect a [`String`], not a vector of bytes. This is because this is the
311/// most useful and fitting behaviour for the intended purpose, as with the
312/// implementations of [`Display`] and [`FromStr`]. As noted above, this is
313/// lossy if the [`ContentType`] is [`Text`](ContentType::Text) and the data is
314/// not valid UTF8, but not if set to [`Binary`](ContentType::Binary).
315///
316/// # See also
317///
318/// * [`UnpackedResponse`]
319///
320#[derive(Default)]
321#[non_exhaustive]
322pub struct UnpackedResponseBody {
323 // Private properties
324 /// The response body as a vector of bytes. The data originates from the
325 /// response body as a [`Bytes`] container, but gets stored here as a vector
326 /// of bytes for convenience. This may not be valid UTF8, so is not
327 /// converted to a [`String`]. That step is left as optional for the caller,
328 /// if required (and happens when running through the [`Debug`] or
329 /// [`Display`] formatters).
330 body: Vec<u8>,
331
332 /// The content type of the response body. This is used to determine how to
333 /// represent and interpret the response body when performing serialisation
334 /// and deserialisation, including for display. The default content type is
335 /// [`Text`](ContentType::Text).
336 content_type: ContentType,
337}
338
339// UnpackedResponseBody
340impl UnpackedResponseBody {
341 // new
342 /// Creates a new response body instance.
343 ///
344 /// # Parameters
345 ///
346 /// * `data` - The response body as any type for which there is a [`From`]
347 /// implementation.
348 ///
349 pub fn new<T: Into<Self>>(data: T) -> Self {
350 data.into()
351 }
352
353 // content_type
354 /// Returns the content type of the response body.
355 ///
356 /// # See also
357 ///
358 /// * [`ContentType`]
359 /// * [`UnpackedResponseBody::is_binary()`]
360 /// * [`UnpackedResponseBody::is_text()`]
361 /// * [`UnpackedResponseBody::set_content_type()`]
362 ///
363 #[must_use]
364 pub const fn content_type(&self) -> ContentType {
365 self.content_type
366 }
367
368 // set_content_type
369 /// Sets the content type of the response body.
370 ///
371 /// This method is chainable, as it returns a mutable reference to the
372 /// response body instance.
373 ///
374 /// # See also
375 ///
376 /// * [`ContentType`]
377 /// * [`UnpackedResponseBody::content_type()`]
378 ///
379 pub const fn set_content_type(&mut self, content_type: ContentType) -> &mut Self {
380 self.content_type = content_type;
381 self
382 }
383
384 // is_binary
385 /// Returns whether the response body is binary.
386 ///
387 /// # See also
388 ///
389 /// * [`ContentType`]
390 /// * [`UnpackedResponseBody::content_type()`]
391 /// * [`UnpackedResponseBody::is_text()`]
392 ///
393 #[must_use]
394 pub fn is_binary(&self) -> bool {
395 self.content_type == ContentType::Binary
396 }
397
398 // is_text
399 /// Returns whether the response body is text.
400 ///
401 /// # See also
402 ///
403 /// * [`ContentType`]
404 /// * [`UnpackedResponseBody::content_type()`]
405 /// * [`UnpackedResponseBody::is_binary()`]
406 ///
407 #[must_use]
408 pub fn is_text(&self) -> bool {
409 self.content_type == ContentType::Text
410 }
411
412 // as_bytes
413 /// Returns a byte slice of the response body's contents.
414 ///
415 /// Provides a read-only view of the byte data within the response body,
416 /// without consuming the data. The returned slice is a reference to the
417 /// actual data stored in the response body, not a copy. Because of this, it
418 /// is not possible to mutate the contents of the response body through the
419 /// returned slice. It does not allocate new memory or change the ownership
420 /// of the byte data. This method is useful when you need to work with the
421 /// bytes of the response body in a read-only fashion, or when you want to
422 /// avoid copying the data.
423 ///
424 /// - This method returns a slice (`&[u8]`) referencing the bytes of the
425 /// response body contents.
426 /// - The original response body value remains intact, and can still be
427 /// used afterward.
428 /// - No reallocation or copying of data occurs since it's just providing
429 /// a view into the original memory.
430 ///
431 /// Use this method when you need to work with the byte data in a
432 /// non-destructive, read-only manner while keeping the original response
433 /// body intact.
434 ///
435 /// # See also
436 ///
437 /// * [`UnpackedResponseBody::as_mut_bytes()`]
438 /// * [`UnpackedResponseBody::into_bytes()`]
439 /// * [`UnpackedResponseBody::to_bytes()`]
440 ///
441 #[must_use]
442 pub fn as_bytes(&self) -> &[u8] {
443 &self.body
444 }
445
446 // as_mut_bytes
447 /// Returns a mutable referenced to the response body's contents.
448 ///
449 /// Provides a mutable view of the byte data within the response body,
450 /// without consuming the data. The returned vector is a reference to the
451 /// actual data stored in the response body, not a copy. This method is
452 /// useful when you need to work with, and modify, the bytes of the response
453 /// body directly, without copying the data.
454 ///
455 /// - This method returns a mutable vector (`&mut Vec<u8>`) referencing
456 /// the bytes of the response body contents.
457 /// - The original response body value remains intact, and can still be
458 /// used afterward.
459 /// - No reallocation or copying of data occurs since it's just providing
460 /// a reference to the original memory.
461 ///
462 /// Use this method when you need to work directly with the byte data in a
463 /// mutable manner.
464 ///
465 /// Note that unlike the function's [`String::as_mut_vec()`] counterpart,
466 /// this method is not unsafe. This is because the response body is not
467 /// required to be valid UTF8, so there is no risk of invalid UTF8 being
468 /// created.
469 ///
470 /// Note also that a better name for this method could be `as_mut_vec()`,
471 /// which would be consistent with the standard library's
472 /// [`String::as_mut_vec()`] method, which this method is modelled after,
473 /// but that would break consistency with the other methods on this struct.
474 /// In addition, there is another method called [`str::as_bytes_mut()`],
475 /// which appears to be named quite inconsistently with other comparable
476 /// methods, and so calling this method `as_mut_bytes()` might cause
477 /// confusion, but is at least self-consistent.
478 ///
479 /// # See also
480 ///
481 /// * [`UnpackedResponseBody::as_bytes()`]
482 /// * [`UnpackedResponseBody::into_bytes()`]
483 /// * [`UnpackedResponseBody::to_bytes()`]
484 ///
485 pub const fn as_mut_bytes(&mut self) -> &mut Vec<u8> {
486 &mut self.body
487 }
488
489 // into_bytes
490 /// Returns the response body as a vector of bytes.
491 ///
492 /// This consumes the response body, without cloning or copying, and returns
493 /// a new vector containing the bytes of the response body. It transfers
494 /// ownership of the byte data from the response body to the new vector.
495 /// This method is useful when you need to move the byte data out of the
496 /// response body, for example to pass it to a function that expects a
497 /// `Vec<u8>`, or when you want to modify the byte data in-place without
498 /// affecting the original response body.
499 ///
500 /// - This method consumes the response body contents and returns a
501 /// `Vec<u8>` containing its bytes.
502 /// - After calling this method, the original response body value is no
503 /// longer available for use, because it has been moved.
504 /// - Transforms the response body into a vector of bytes without any
505 /// copying.
506 ///
507 /// Use this method when you want to consume the response body and obtain
508 /// ownership of its byte data in the form of a `Vec<u8>`. This is useful
509 /// when you need to modify or move the byte data, or when you want to pass
510 /// it to functions that expect a `Vec<u8>`.
511 ///
512 /// Note that a better name for this method might be `into_vec()`, but that
513 /// would be inconsistent with the standard library's
514 /// [`String::into_bytes()`] method, which this method is modelled after.
515 ///
516 /// # See also
517 ///
518 /// * [`UnpackedResponseBody::as_bytes()`]
519 /// * [`UnpackedResponseBody::as_mut_bytes()`]
520 /// * [`UnpackedResponseBody::to_bytes()`]
521 ///
522 #[must_use]
523 pub fn into_bytes(self) -> Vec<u8> {
524 self.body
525 }
526
527 // to_bytes
528 /// Returns a copy of the response body data converted to a vector of bytes.
529 ///
530 /// This does not consume the response body, but clones it. Following Rust's
531 /// naming conventions and idioms, this method "converts" the data content
532 /// of the response body into a byte representation, in a `Vec<u8>`. (No
533 /// actual conversion takes place because the data is already stored
534 /// internally as a vector of bytes, but this is academic and could change
535 /// in future, so "conversion" is implied and expected as a theoretical
536 /// behaviour.) Ownership of the cloned and converted byte data is
537 /// transferred to the caller, and there are no side effects on the internal
538 /// state of the [`UnpackedResponseBody`] instance.
539 ///
540 /// - This method returns a `Vec<u8>` vector of bytes without consuming
541 /// the response body contents.
542 /// - The original response body value remains intact, and can still be
543 /// used afterward.
544 /// - The response body data is copied, and converted/transformed into
545 /// the output value returned.
546 ///
547 /// Use this method when you need to obtain a copy of the response body's
548 /// byte data in the form of a `Vec<u8>`, without consuming the response
549 /// body itself. This is useful when you need to pass the byte data to a
550 /// function that expects a `Vec<u8>`, or when you want to modify the byte
551 /// data without affecting the original response body.
552 ///
553 /// Note that a better name for this method might be `to_vec()`, but that
554 /// would be inconsistent with the standard library's
555 /// [`String::into_bytes()`] method.
556 ///
557 /// # See also
558 ///
559 /// * [`UnpackedResponseBody::as_bytes()`]
560 /// * [`UnpackedResponseBody::as_mut_bytes()`]
561 /// * [`UnpackedResponseBody::into_bytes()`]
562 ///
563 #[must_use]
564 pub fn to_bytes(&self) -> Vec<u8> {
565 self.body.clone()
566 }
567
568 // to_base64
569 /// Returns the response body data converted to a base64-encoded [`String`].
570 ///
571 /// This does not consume the response body, but clones it, as is necessary
572 /// to perform the conversion to base64. It converts straight from bytes to
573 /// base64, without converting to a [`String`] first, because the response
574 /// body is binary data.
575 ///
576 /// # See also
577 ///
578 /// * [`UnpackedResponseBody::from_base64()`]
579 ///
580 #[must_use]
581 pub fn to_base64(&self) -> String {
582 BASE64.encode(&self.body)
583 }
584
585 // from_base64
586 /// Converts a base64-encoded [`String`] to an [`UnpackedResponseBody`].
587 ///
588 /// This method does not consume the input string, but clones it, as is
589 /// necessary to perform the conversion from [`base64`]. It converts
590 /// straight from base64 to bytes, without converting to a [`String`] first,
591 /// because the response body is binary data. This means that no UTF8
592 /// validation is performed.
593 ///
594 /// Note that unlike the [`From`] type conversion implementations, this
595 /// returns a [`Result`].
596 ///
597 /// # Errors
598 ///
599 /// This method will return an error if the input string is not valid
600 /// base64. Such an error will be returned as a [`DecodeError`], which is
601 /// passed through from the [`base64`] crate.
602 ///
603 /// # See also
604 ///
605 /// * [`UnpackedResponseBody::to_base64()`]
606 ///
607 pub fn from_base64(encoded: &str) -> Result<Self, DecodeError> {
608 let decoded = BASE64.decode(encoded)?;
609 Ok(Self { body: decoded, content_type: ContentType::Binary })
610 }
611
612 // clear
613 /// Removes all contents from the response body.
614 ///
615 /// This method removes all data from the response body, resetting it to an
616 /// empty state. This method has no effect on the capacity of the response
617 /// body, and so does not affect any allocation.
618 ///
619 pub fn clear(&mut self) {
620 self.body.clear();
621 }
622
623 // empty
624 /// Returns an empty response body.
625 ///
626 /// This method returns an empty response body. This is equivalent to
627 /// creating a new response body with [`UnpackedResponseBody::new()`], but
628 /// without having to supply any parameters.
629 ///
630 #[must_use]
631 pub fn empty() -> Self {
632 Self { body: Vec::new(), ..Default::default() }
633 }
634
635 // is_empty
636 /// Returns whether the response body is empty.
637 ///
638 /// This method returns whether the response body is empty. This is
639 /// equivalent to checking whether the length of the response body is zero.
640 ///
641 #[must_use]
642 pub fn is_empty(&self) -> bool {
643 self.body.is_empty()
644 }
645
646 // len
647 /// Returns the length of the response body.
648 ///
649 /// This method returns the length of the response body, in bytes. This is
650 /// equivalent to the length of the vector of bytes that the response body
651 /// contains.
652 ///
653 #[must_use]
654 pub fn len(&self) -> usize {
655 self.body.len()
656 }
657
658 // push
659 /// Appends a byte to the response body.
660 ///
661 /// Appends a given byte onto the end of the response body's existing byte
662 /// data. The response body is not required to be valid UTF8, so this method
663 /// does not check the validity of the byte before appending it.
664 ///
665 /// This method accepts a [`u8`] instead of a [`char`] because a [`char`]
666 /// represents a single Unicode scalar value. In Rust, a [`char`] is always
667 /// 4 bytes long because it can represent any Unicode scalar value,
668 /// including those outside the Basic Multilingual Plane. If `push()`
669 /// accepted a [`char`], it would be signaling that [`UnpackedResponseBody`]
670 /// is Unicode-aware and can handle any Unicode character — which is not the
671 /// case. A [`u8`], on the other hand, represents a single byte. By having
672 /// `push()` accept a [`u8`], it's signaling that [`UnpackedResponseBody`]
673 /// is byte-oriented. A specific [`push_char()`](UnpackedResponseBody::push_char())
674 /// method is also available, but `push()` is the most general method for
675 /// appending bytes to the response body.
676 ///
677 /// # Parameters
678 ///
679 /// * `byte` - The byte to append to the response body.
680 ///
681 /// # See also
682 ///
683 /// * [`UnpackedResponseBody::push_bytes()`]
684 /// * [`UnpackedResponseBody::push_char()`]
685 /// * [`UnpackedResponseBody::push_str()`]
686 ///
687 pub fn push(&mut self, byte: u8) {
688 self.body.push(byte);
689 }
690
691 // push_bytes
692 /// Appends a byte slice to the response body.
693 ///
694 /// Appends a given byte slice onto the end of the response body. The byte
695 /// slice is appended to the end of the response body's existing byte data.
696 /// The response body is not required to be valid UTF8, so this method does
697 /// not check the validity of the byte slice before appending it.
698 ///
699 /// # Parameters
700 ///
701 /// * `bytes` - The byte slice to append to the response body.
702 ///
703 /// # See also
704 ///
705 /// * [`UnpackedResponseBody::push()`]
706 /// * [`UnpackedResponseBody::push_char()`]
707 /// * [`UnpackedResponseBody::push_str()`]
708 ///
709 pub fn push_bytes(&mut self, bytes: &[u8]) {
710 self.body.extend_from_slice(bytes);
711 }
712
713 // push_char
714 /// Appends a [`char`] to the response body.
715 ///
716 /// Appends a given character onto the end of the response body. The
717 /// [`char`] is converted to bytes and then appended to the end of the
718 /// response body's existing byte data.
719 ///
720 /// # Parameters
721 ///
722 /// * `char` - The [`char`] to append to the response body.
723 ///
724 /// # See also
725 ///
726 /// * [`UnpackedResponseBody::push()`]
727 /// * [`UnpackedResponseBody::push_bytes()`]
728 /// * [`UnpackedResponseBody::push_str()`]
729 ///
730 pub fn push_char(&mut self, char: &char) {
731 let mut bytes = [0; 4];
732 let used = char.encode_utf8(&mut bytes).len();
733 #[expect(clippy::indexing_slicing, reason = "Infallible")]
734 self.body.extend(&bytes[..used]);
735 }
736
737 // push_str
738 /// Appends a string slice to the response body.
739 ///
740 /// Appends a given string slice onto the end of the response body. The
741 /// string slice is converted to bytes and then appended to the end of the
742 /// response body's existing byte data.
743 ///
744 /// # Parameters
745 ///
746 /// * `string` - The string slice to append to the response body.
747 ///
748 /// # See also
749 ///
750 /// * [`UnpackedResponseBody::push()`]
751 /// * [`UnpackedResponseBody::push_char()`]
752 /// * [`UnpackedResponseBody::push_bytes()`]
753 ///
754 pub fn push_str(&mut self, string: &str) {
755 self.body.extend_from_slice(string.as_bytes());
756 }
757}
758
759// Add &[u8]
760impl Add<&[u8]> for UnpackedResponseBody {
761 type Output = Self;
762
763 // add
764 /// Adds a [`&[u8]`](https://doc.rust-lang.org/std/primitive.slice.html) to
765 /// an [`UnpackedResponseBody`].
766 fn add(mut self, rhs: &[u8]) -> Self {
767 self.push_bytes(rhs);
768 self
769 }
770}
771
772// Add &[u8; N]
773impl<const N: usize> Add<&[u8; N]> for UnpackedResponseBody {
774 type Output = Self;
775
776 // add
777 /// Adds a [`&[u8; N]`](https://doc.rust-lang.org/std/primitive.slice.html)
778 /// to an [`UnpackedResponseBody`].
779 fn add(mut self, rhs: &[u8; N]) -> Self {
780 self.push_bytes(rhs);
781 self
782 }
783}
784
785// Add char
786impl Add<char> for UnpackedResponseBody {
787 type Output = Self;
788
789 // add
790 /// Adds a [`char`] to an [`UnpackedResponseBody`].
791 fn add(mut self, rhs: char) -> Self {
792 self.push_char(&rhs);
793 self
794 }
795}
796
797// Add &char
798impl Add<&char> for UnpackedResponseBody {
799 type Output = Self;
800
801 // add
802 /// Adds a [`&char`](char) to an [`UnpackedResponseBody`].
803 fn add(mut self, rhs: &char) -> Self {
804 self.push_char(rhs);
805 self
806 }
807}
808
809// Add &str
810impl Add<&str> for UnpackedResponseBody {
811 type Output = Self;
812
813 // add
814 /// Adds a [`&str`](str) to an [`UnpackedResponseBody`].
815 fn add(mut self, rhs: &str) -> Self {
816 self.push_str(rhs);
817 self
818 }
819}
820
821// Add String
822impl Add<String> for UnpackedResponseBody {
823 type Output = Self;
824
825 // add
826 /// Adds a [`String`] to an [`UnpackedResponseBody`].
827 fn add(mut self, rhs: String) -> Self {
828 self.push_str(&rhs);
829 self
830 }
831}
832
833// Add &String
834impl Add<&String> for UnpackedResponseBody {
835 type Output = Self;
836
837 // add
838 /// Adds a [`&String`](String) to an [`UnpackedResponseBody`].
839 fn add(mut self, rhs: &String) -> Self {
840 self.push_str(rhs);
841 self
842 }
843}
844
845// Add Box<str>
846impl Add<Box<str>> for UnpackedResponseBody {
847 type Output = Self;
848
849 // add
850 /// Adds a [boxed](Box) [string](str) slice to an [`UnpackedResponseBody`].
851 fn add(mut self, rhs: Box<str>) -> Self::Output {
852 self.push_str(&rhs);
853 self
854 }
855}
856
857// Add Cow<str>
858impl<'a> Add<Cow<'a, str>> for UnpackedResponseBody {
859 type Output = Self;
860
861 // add
862 /// Adds a [clone-on-write](Cow) [string](str) to an
863 /// [`UnpackedResponseBody`].
864 fn add(mut self, rhs: Cow<'a, str>) -> Self::Output {
865 self.push_str(&rhs);
866 self
867 }
868}
869
870// Add u8
871impl Add<u8> for UnpackedResponseBody {
872 type Output = Self;
873
874 // add
875 /// Adds a [`u8`] to an [`UnpackedResponseBody`].
876 fn add(mut self, rhs: u8) -> Self {
877 self.push(rhs);
878 self
879 }
880}
881
882// Add Vec<u8>
883impl Add<Vec<u8>> for UnpackedResponseBody {
884 type Output = Self;
885
886 // add
887 /// Adds a [`Vec[u8]`](Vec) to an [`UnpackedResponseBody`].
888 fn add(mut self, rhs: Vec<u8>) -> Self {
889 self.push_bytes(&rhs);
890 self
891 }
892}
893
894// Add &Vec<u8>
895impl Add<&Vec<u8>> for UnpackedResponseBody {
896 type Output = Self;
897
898 // add
899 /// Adds a [`&Vec[u8]`](Vec) to an [`UnpackedResponseBody`].
900 fn add(mut self, rhs: &Vec<u8>) -> Self {
901 self.push_bytes(rhs);
902 self
903 }
904}
905
906// Add Self
907impl Add<Self> for UnpackedResponseBody {
908 type Output = Self;
909
910 // add
911 /// Adds an [`UnpackedResponseBody`] to an [`UnpackedResponseBody`].
912 fn add(mut self, rhs: Self) -> Self {
913 self.push_bytes(&rhs.body);
914 self
915 }
916}
917
918// Add &Self
919impl Add<&Self> for UnpackedResponseBody {
920 type Output = Self;
921
922 // add
923 /// Adds an [`&UnpackedResponseBody`](UnpackedResponseBody) to an
924 /// [`UnpackedResponseBody`].
925 fn add(mut self, rhs: &Self) -> Self {
926 self.push_bytes(rhs.as_bytes());
927 self
928 }
929}
930
931// AddAssign &[u8]
932impl AddAssign<&[u8]> for UnpackedResponseBody {
933 // add_assign
934 /// Adds a [`&[u8]`](https://doc.rust-lang.org/std/primitive.slice.html) to
935 /// an [`UnpackedResponseBody`].
936 fn add_assign(&mut self, rhs: &[u8]) {
937 self.push_bytes(rhs);
938 }
939}
940
941// AddAssign &[u8; N]
942impl<const N: usize> AddAssign<&[u8; N]> for UnpackedResponseBody {
943 // add_assign
944 /// Adds a [`&[u8; N]`](https://doc.rust-lang.org/std/primitive.slice.html)
945 /// to an [`UnpackedResponseBody`].
946 fn add_assign(&mut self, rhs: &[u8; N]) {
947 self.push_bytes(rhs);
948 }
949}
950
951// AddAssign char
952impl AddAssign<char> for UnpackedResponseBody {
953 // add_assign
954 /// Adds a [`char`] to an [`UnpackedResponseBody`].
955 fn add_assign(&mut self, rhs: char) {
956 self.push_char(&rhs);
957 }
958}
959
960// AddAssign &char
961impl AddAssign<&char> for UnpackedResponseBody {
962 // add_assign
963 /// Adds a [`&char`](char) to an [`UnpackedResponseBody`].
964 fn add_assign(&mut self, rhs: &char) {
965 self.push_char(rhs);
966 }
967}
968
969// AddAssign &str
970impl AddAssign<&str> for UnpackedResponseBody {
971 // add_assign
972 /// Adds a [`&str`](str) to an [`UnpackedResponseBody`].
973 fn add_assign(&mut self, rhs: &str) {
974 self.push_str(rhs);
975 }
976}
977
978// AddAssign String
979impl AddAssign<String> for UnpackedResponseBody {
980 // add_assign
981 /// Adds a [`String`] to an [`UnpackedResponseBody`].
982 fn add_assign(&mut self, rhs: String) {
983 self.push_str(&rhs);
984 }
985}
986
987// AddAssign &String
988impl AddAssign<&String> for UnpackedResponseBody {
989 // add_assign
990 /// Adds a [`&String`](String) to an [`UnpackedResponseBody`].
991 fn add_assign(&mut self, rhs: &String) {
992 self.push_str(rhs);
993 }
994}
995
996// AddAssign Box<str>
997impl AddAssign<Box<str>> for UnpackedResponseBody {
998 // add_assign
999 /// Adds a [boxed](Box) [string](str) slice to an [`UnpackedResponseBody`].
1000 fn add_assign(&mut self, rhs: Box<str>) {
1001 self.push_str(&rhs);
1002 }
1003}
1004
1005// AddAssign Cow<str>
1006impl<'a> AddAssign<Cow<'a, str>> for UnpackedResponseBody {
1007 // add_assign
1008 /// Adds a [clone-on-write](Cow) [string](str) to an
1009 /// [`UnpackedResponseBody`].
1010 fn add_assign(&mut self, rhs: Cow<'a, str>){
1011 self.push_str(&rhs);
1012 }
1013}
1014
1015// AddAssign u8
1016impl AddAssign<u8> for UnpackedResponseBody {
1017 // add_assign
1018 /// Adds a [`u8`] to an [`UnpackedResponseBody`].
1019 fn add_assign(&mut self, rhs: u8) {
1020 self.push(rhs);
1021 }
1022}
1023
1024// AddAssign Vec<u8>
1025impl AddAssign<Vec<u8>> for UnpackedResponseBody {
1026 // add_assign
1027 /// Adds a [`Vec[u8]`](Vec) to an [`UnpackedResponseBody`].
1028 fn add_assign(&mut self, rhs: Vec<u8>) {
1029 self.push_bytes(&rhs);
1030 }
1031}
1032
1033// AddAssign &Vec<u8>
1034impl AddAssign<&Vec<u8>> for UnpackedResponseBody {
1035 // add_assign
1036 /// Adds a [`&Vec[u8]`](Vec) to an [`UnpackedResponseBody`].
1037 fn add_assign(&mut self, rhs: &Vec<u8>) {
1038 self.push_bytes(rhs);
1039 }
1040}
1041
1042// AddAssign Self
1043impl AddAssign<Self> for UnpackedResponseBody {
1044 // add_assign
1045 /// Adds an [`UnpackedResponseBody`] to an [`UnpackedResponseBody`].
1046 fn add_assign(&mut self, rhs: Self) {
1047 self.push_bytes(&rhs.body);
1048 }
1049}
1050
1051// AddAssign &Self
1052impl AddAssign<&Self> for UnpackedResponseBody {
1053 // add_assign
1054 /// Adds an [`&UnpackedResponseBody`](UnpackedResponseBody) to an
1055 /// [`UnpackedResponseBody`].
1056 fn add_assign(&mut self, rhs: &Self) {
1057 self.push_bytes(rhs.as_bytes());
1058 }
1059}
1060
1061// AsMut [u8]
1062impl AsMut<[u8]> for UnpackedResponseBody {
1063 // as_mut
1064 fn as_mut(&mut self) -> &mut [u8] {
1065 self.as_mut_bytes()
1066 }
1067}
1068
1069// AsRef [u8]
1070impl AsRef<[u8]> for UnpackedResponseBody {
1071 // as_ref
1072 fn as_ref(&self) -> &[u8] {
1073 self.as_bytes()
1074 }
1075}
1076
1077// Clone
1078impl Clone for UnpackedResponseBody {
1079 // clone
1080 fn clone(&self) -> Self {
1081 Self { body: self.body.clone(), ..Default::default() }
1082 }
1083
1084 // clone_from
1085 fn clone_from(&mut self, source: &Self) {
1086 self.body.clone_from(&source.body);
1087 }
1088}
1089
1090// Debug
1091impl Debug for UnpackedResponseBody {
1092 // fmt
1093 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1094 f.debug_struct("UnpackedResponseBody")
1095 .field("body", &self.to_string())
1096 .field("content_type", &self.content_type)
1097 .finish()
1098 }
1099}
1100
1101// Display
1102impl Display for UnpackedResponseBody {
1103 // fmt
1104 /// Formats the response body for display.
1105 ///
1106 /// This method serialises the response body based on the content type. If
1107 /// the content type is [`ContentType::Text`], then the response body is
1108 /// serialised to an ordinary [`String`]. If the content type is
1109 /// [`ContentType::Binary`], then the response body is serialised to a
1110 /// base64-encoded [`String`].
1111 ///
1112 /// Note that as no validation checks are performed on the response body
1113 /// contents, it is not guaranteed to be UTF8, and therefore if not
1114 /// specified as binary it is possible that the serialised string will not
1115 /// totally match the original response body contents. This is because the
1116 /// conversion of the response body bytes to a UTF8 string will be lossy if
1117 /// there are invalid characters.
1118 ///
1119 /// # See also
1120 ///
1121 /// * [`UnpackedResponseBody::serialize()`]
1122 /// * [`UnpackedResponseBody::to_base64()`]
1123 ///
1124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1125 let body = match self.content_type {
1126 ContentType::Text => String::from_utf8_lossy(&self.body),
1127 ContentType::Binary => Cow::Owned(self.to_base64()),
1128 };
1129 write!(f, "{body}")
1130 }
1131}
1132
1133// From &[u8]
1134impl From<&[u8]> for UnpackedResponseBody {
1135 // from
1136 /// Converts a [`&[u8]`](https://doc.rust-lang.org/std/primitive.slice.html)
1137 /// to an [`UnpackedResponseBody`].
1138 fn from(b: &[u8]) -> Self {
1139 Self { body: b.to_vec(), ..Default::default() }
1140 }
1141}
1142
1143// From &[u8; N]
1144impl<const N: usize> From<&[u8; N]> for UnpackedResponseBody {
1145 // from
1146 /// Converts a [`&[u8; N]`](https://doc.rust-lang.org/std/primitive.slice.html)
1147 /// to an [`UnpackedResponseBody`].
1148 fn from(b: &[u8; N]) -> Self {
1149 Self { body: b.to_vec(), ..Default::default() }
1150 }
1151}
1152
1153// From char
1154impl From<char> for UnpackedResponseBody {
1155 // from
1156 /// Converts a [`char`] to an [`UnpackedResponseBody`].
1157 ///
1158 /// Note that it does this in the way that is most compatible with
1159 /// [`String`] conversion. The [`char`] type in Rust represents a Unicode
1160 /// scalar value. That means a single [`char`] value corresponds to one
1161 /// Unicode character. But Unicode characters can have a wide range of
1162 /// values, from `0` to `0x10FFFF` (this range excludes the surrogate
1163 /// pairs), and this value range doesn't fit into a single byte. That's why
1164 /// [`char`] in Rust is 4 bytes, because it has to accommodate any possible
1165 /// Unicode scalar value. The UTF-8 encoded representation of the `ñ`
1166 /// character is `[195, 177]`, but in memory, a [`char`] containing `'ñ'`
1167 /// does not hold the bytes `[195, 177]`. Instead, it holds the Unicode
1168 /// scalar value for `ñ`, which is `U+00F1`, or in integer terms, `241`.
1169 /// When we convert a [`char`] to a [`u32`] directly, we're taking this
1170 /// scalar value (like `241` for `ñ`) and representing it in memory as a
1171 /// 4-byte integer. So using code such as
1172 /// `(c as u32).to_le_bytes().to_vec()` would result in [241, 0, 0, 0], and
1173 /// not [195, 177]. This behaviour would not match expectation and would not
1174 /// match the behaviour of [`String`] conversion. To get the UTF-8 encoded
1175 /// bytes of a [`char`], we need to use encoding methods because we're
1176 /// effectively translating from the Unicode scalar value to its UTF-8 byte
1177 /// sequence. This is what the [`encode_utf8()`](char::encode_utf8()) method
1178 /// provides. To put it another way: [`char`] isn't storing bytes, it's
1179 /// storing a Unicode scalar value. UTF-8 is one of the ways to represent
1180 /// that value (and the most common one in Rust).
1181 ///
1182 fn from(c: char) -> Self {
1183 let mut bytes = [0; 4];
1184 let used = c.encode_utf8(&mut bytes).len();
1185 #[expect(clippy::indexing_slicing, reason = "Infallible")]
1186 Self { body: bytes[..used].to_vec(), ..Default::default() }
1187 }
1188}
1189
1190// From &char
1191impl From<&char> for UnpackedResponseBody {
1192 // from
1193 /// Converts a [`&char`](char) to an [`UnpackedResponseBody`].
1194 fn from(c: &char) -> Self {
1195 Self::from(c.to_owned())
1196 }
1197}
1198
1199// From AxumBody
1200#[cfg(feature = "axum")]
1201impl From<AxumBody> for UnpackedResponseBody {
1202 // from
1203 fn from(b: AxumBody) -> Self {
1204 Self { body: executor::block_on(to_bytes(b, usize::MAX)).unwrap_or_default().to_vec(), ..Default::default() }
1205 }
1206}
1207
1208// From Full<Bytes>
1209impl From<Full<Bytes>> for UnpackedResponseBody {
1210 // from
1211 /// Converts a [`Full<Bytes>`](Full) to an [`UnpackedResponseBody`].
1212 fn from(b: Full<Bytes>) -> Self {
1213 // Collect the body into Collected<Bytes>
1214 let collected = b.collect().now_or_never()
1215 .unwrap_or_else(|| Ok(Collected::default()))
1216 .unwrap_or_else(|_| Collected::default())
1217 ;
1218 Self { body: collected.to_bytes().to_vec(), ..Default::default() }
1219 }
1220}
1221
1222// From Incoming
1223impl From<Incoming> for UnpackedResponseBody {
1224 // from
1225 /// Converts an [`Incoming`] to an [`UnpackedResponseBody`].
1226 fn from(b: Incoming) -> Self {
1227 // Collect the body into Collected<Bytes>
1228 let collected = executor::block_on(b.collect()).unwrap_or_else(|_| Collected::default());
1229 Self { body: collected.to_bytes().to_vec(), ..Default::default() }
1230 }
1231}
1232
1233// From Json
1234impl From<Json> for UnpackedResponseBody {
1235 // from
1236 /// Converts a [`serde_json::Value`] to an [`UnpackedResponseBody`].
1237 fn from(j: Json) -> Self {
1238 Self { body: j.to_string().into_bytes(), ..Default::default() }
1239 }
1240}
1241
1242// From &Json
1243impl From<&Json> for UnpackedResponseBody {
1244 // from
1245 /// Converts a [`&serde_json::Value`](serde_json::Value) to an
1246 /// [`UnpackedResponseBody`].
1247 fn from(j: &Json) -> Self {
1248 Self { body: j.to_string().into_bytes(), ..Default::default() }
1249 }
1250}
1251
1252// From &str
1253impl From<&str> for UnpackedResponseBody {
1254 // from
1255 /// Converts a [`&str`](str) to an [`UnpackedResponseBody`].
1256 fn from(s: &str) -> Self {
1257 Self { body: s.to_owned().as_bytes().to_vec(), ..Default::default() }
1258 }
1259}
1260
1261// From &mut str
1262impl From<&mut str> for UnpackedResponseBody {
1263 // from
1264 /// Converts a [`&mut str`](str) to an [`UnpackedResponseBody`].
1265 fn from(s: &mut str) -> Self {
1266 Self { body: s.to_owned().as_bytes().to_vec(), ..Default::default() }
1267 }
1268}
1269
1270// From String
1271impl From<String> for UnpackedResponseBody {
1272 // from
1273 /// Converts a [`String`] to an [`UnpackedResponseBody`].
1274 fn from(s: String) -> Self {
1275 Self { body: s.into_bytes(), ..Default::default() }
1276 }
1277}
1278
1279// From &String
1280impl From<&String> for UnpackedResponseBody {
1281 // from
1282 /// Converts a [`&String`](String) to an [`UnpackedResponseBody`].
1283 fn from(s: &String) -> Self {
1284 Self { body: s.as_str().as_bytes().to_vec(), ..Default::default() }
1285 }
1286}
1287
1288// From Box<str>
1289impl From<Box<str>> for UnpackedResponseBody {
1290 // from
1291 /// Converts a [boxed](Box) [string](str) slice to an
1292 /// [`UnpackedResponseBody`].
1293 fn from(s: Box<str>) -> Self {
1294 Self { body: s.into_string().into_bytes(), ..Default::default() }
1295 }
1296}
1297
1298// From Cow<str>
1299impl<'a> From<Cow<'a, str>> for UnpackedResponseBody {
1300 // from
1301 /// Converts a [clone-on-write](Cow) [string](str) to an
1302 /// [`UnpackedResponseBody`].
1303 fn from(s: Cow<'a, str>) -> Self {
1304 Self { body: s.into_owned().into_bytes(), ..Default::default() }
1305 }
1306}
1307
1308// From u8
1309impl From<u8> for UnpackedResponseBody {
1310 // from
1311 /// Converts a [`u8`] to an [`UnpackedResponseBody`].
1312 fn from(c: u8) -> Self {
1313 Self { body: Vec::from([c]), ..Default::default() }
1314 }
1315}
1316
1317// From Vec<u8>
1318impl From<Vec<u8>> for UnpackedResponseBody {
1319 // from
1320 /// Converts a [`Vec[u8]`](Vec) to an [`UnpackedResponseBody`].
1321 fn from(v: Vec<u8>) -> Self {
1322 Self { body: v, ..Default::default() }
1323 }
1324}
1325
1326// From &Vec<u8>
1327impl From<&Vec<u8>> for UnpackedResponseBody {
1328 // from
1329 /// Converts a [`&Vec[u8]`](Vec) to an [`UnpackedResponseBody`].
1330 fn from(v: &Vec<u8>) -> Self {
1331 Self { body: v.clone(), ..Default::default() }
1332 }
1333}
1334
1335// FromStr
1336impl FromStr for UnpackedResponseBody {
1337 type Err = Infallible;
1338
1339 // from_str
1340 fn from_str(s: &str) -> Result<Self, Self::Err> {
1341 Ok(Self { body: s.as_bytes().to_vec(), ..Default::default() })
1342 }
1343}
1344
1345// PartialEq
1346impl PartialEq for UnpackedResponseBody {
1347 // eq
1348 fn eq(&self, other: &Self) -> bool {
1349 self.body == other.body
1350 }
1351}
1352
1353// Serialize
1354impl Serialize for UnpackedResponseBody {
1355 // serialize
1356 /// Serialises the response body to a [`String`].
1357 ///
1358 /// This method serialises the response body based on the content type. If
1359 /// the content type is [`ContentType::Text`], then the response body is
1360 /// serialised to an ordinary [`String`]. If the content type is
1361 /// [`ContentType::Binary`], then the response body is serialised to a
1362 /// base64-encoded [`String`].
1363 ///
1364 /// Note that as no validation checks are performed on the response body
1365 /// contents, it is not guaranteed to be UTF8, and therefore if not
1366 /// specified as binary it is possible that the serialised string will not
1367 /// totally match the original response body contents. This is because the
1368 /// conversion of the response body bytes to a UTF8 string will be lossy if
1369 /// there are invalid characters.
1370 ///
1371 /// # See also
1372 ///
1373 /// * [`UnpackedResponseBody::deserialize()`]
1374 /// * [`UnpackedResponseBody::<Display>fmt()`]
1375 /// * [`UnpackedResponseBody::to_base64()`]
1376 ///
1377 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1378 where
1379 S: Serializer,
1380 {
1381 serializer.serialize_str(&self.to_string())
1382 }
1383}
1384
1385// Deserialize
1386impl <'de> Deserialize<'de> for UnpackedResponseBody {
1387 // deserialize
1388 /// Deserialises the response body from a [`String`].
1389 ///
1390 /// This method deserialises the response body based on the content type.
1391 /// However, as this method is not an instance method and it is not possible
1392 /// to specify the content type in advance, it has to try to detect it. It
1393 /// does this by attempting to decode the string as base64. If this succeeds
1394 /// then it will set the content type as [`ContentType::Binary`]. If this
1395 /// fails then it will assume the content type is [`ContentType::Text`], and
1396 /// deserialises the string in standard fashion.
1397 ///
1398 /// Note that as the incoming data is from a [`String`], and Rust strings
1399 /// are are all valid UTF8, the resulting deserialised response body is
1400 /// guaranteed to be UTF8 if the content type is determined to be
1401 /// [`ContentType::Text`]. If base64 is detected then the deserialised bytes
1402 /// are not guaranteed to be valid UTF8, as no validation checks of that
1403 /// nature are performed against the response body.
1404 ///
1405 /// # See also
1406 ///
1407 /// * [`UnpackedResponseBody::deserialize()`]
1408 /// * [`UnpackedResponseBody::from_base64()`]
1409 ///
1410 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1411 where
1412 D: Deserializer<'de>,
1413 {
1414 let string = String::deserialize(deserializer)?;
1415 #[expect(clippy::option_if_let_else, reason = "Using map_or_else() here would not be as clear, and no more concise")]
1416 match BASE64.decode(&string) {
1417 Ok(decoded) => Ok(Self { body: decoded, content_type: ContentType::Binary }),
1418 Err(_) => Ok(Self { body: string.into_bytes(), content_type: ContentType::Text }),
1419 }
1420 }
1421}
1422
1423// Write
1424impl Write for UnpackedResponseBody {
1425 // write_str
1426 fn write_str(&mut self, s: &str) -> fmt::Result {
1427 self.push_str(s);
1428 Ok(())
1429 }
1430}
1431
1432
1433
1434// Traits
1435
1436//§ ResponseExt
1437/// This trait provides additional functionality to [`Response`].
1438pub trait ResponseExt {
1439 // unpack
1440 /// Returns an [`UnpackedResponse`] containing the unpacked response data.
1441 ///
1442 /// This will unpack the response and provide the headers and body in a
1443 /// more accessible form, to allow it to be checked and compared. This is
1444 /// useful for testing, as the entire set of headers plus body can be
1445 /// checked all at once, and also for printing/logging.
1446 ///
1447 /// If specific headers or body content needs to be checked, it is
1448 /// recommended to use the standard functions as they will be more
1449 /// efficient and performant. Notably, this function will consume the
1450 /// response body, which is necessary because the response might be
1451 /// streamed. In order to provide the full response, the whole body must be
1452 /// read first. This will obviously use more memory than would be used under
1453 /// normal circumstances, so it is not recommended to use this function
1454 /// without considering purpose and effect. For tests, ensuring a response
1455 /// body matches, this is fine, as the data is known and constrained, and
1456 /// memory/performance is less of a concern.
1457 ///
1458 /// # Errors
1459 ///
1460 /// This function will potentially return an error if the response body
1461 /// cannot be converted to bytes. This should not happen under normal
1462 /// circumstances, but it may be possible if the response body is streamed
1463 /// and the stream cannot be read. Many implementations of this function are
1464 /// in fact infallible.
1465 ///
1466 /// At present [`ResponseError`] only contains one error variant, but it is
1467 /// possible that more will be added.
1468 ///
1469 /// # See also
1470 ///
1471 /// * [`axum::response`](https://docs.rs/axum/latest/axum/response/index.html)
1472 /// * [`axum::response::Response`](https://docs.rs/axum/latest/axum/response/type.Response.html)
1473 /// * [`http::Response`]
1474 /// * [`hyper::Response`]
1475 /// * [`UnpackedResponse`]
1476 ///
1477 fn unpack(&mut self) -> Result<UnpackedResponse, ResponseError>;
1478}
1479
1480// Response<()>
1481impl ResponseExt for Response<()> {
1482 // unpack
1483 fn unpack(&mut self) -> Result<UnpackedResponse, ResponseError> {
1484 Ok(convert_response(self.status(), self.headers(), &Bytes::new()))
1485 }
1486}
1487
1488// Response<AxumBody>
1489#[cfg(feature = "axum")]
1490impl ResponseExt for Response<AxumBody> {
1491 // unpack
1492 fn unpack(&mut self) -> Result<UnpackedResponse, ResponseError> {
1493 let status = self.status();
1494 let headers = self.headers().clone();
1495 let bytes = executor::block_on(to_bytes(mem::replace(self.body_mut(), AxumBody::empty()), usize::MAX))
1496 .map_err(|e| ResponseError::ConversionError(Box::new(e)))?
1497 ;
1498 Ok(convert_response(status, &headers, &bytes))
1499 }
1500}
1501
1502// Response<Full<Bytes>>
1503impl ResponseExt for Response<Full<Bytes>> {
1504 // unpack
1505 fn unpack(&mut self) -> Result<UnpackedResponse, ResponseError> {
1506 // Collect the body into Collected<Bytes>
1507 let collected = self.body().clone().collect().now_or_never()
1508 .unwrap_or_else(|| Ok(Collected::default()))
1509 .map_err(|e| ResponseError::ConversionError(Box::new(e)))?
1510 ;
1511 Ok(convert_response(self.status(), self.headers(), &collected.to_bytes()))
1512 }
1513}
1514
1515// Response<Incoming>
1516impl ResponseExt for Response<Incoming> {
1517 // unpack
1518 fn unpack(&mut self) -> Result<UnpackedResponse, ResponseError> {
1519 // Collect the body into Collected<Bytes>
1520 let collected = executor::block_on(self.body_mut().collect())
1521 .map_err(|e| ResponseError::ConversionError(Box::new(e)))?
1522 ;
1523 Ok(convert_response(self.status(), self.headers(), &collected.to_bytes()))
1524 }
1525}
1526
1527// Response<String>
1528impl ResponseExt for Response<String> {
1529 // unpack
1530 fn unpack(&mut self) -> Result<UnpackedResponse, ResponseError> {
1531 Ok(convert_response(self.status(), self.headers(), &Bytes::from(self.body().clone())))
1532 }
1533}
1534
1535
1536
1537// Functions
1538
1539// convert_headers
1540/// Returns a vector of unpacked response headers.
1541///
1542/// These are returned in a vector rather than a hashmap because there may be
1543/// multiple headers with the same name. They are sorted by name, and then by
1544/// value, allowing for reliable comparison. Sorting does break the original
1545/// order of the headers, but this should only very rarely matter.
1546///
1547/// # See also
1548///
1549/// * [`ResponseExt::unpack()`]
1550/// * [`UnpackedResponse`]
1551/// * [`UnpackedResponseHeader`]
1552///
1553fn convert_headers(headermap: &HeaderMap<HeaderValue>) -> Vec<UnpackedResponseHeader> {
1554 let mut headers = vec![];
1555 #[expect(clippy::shadow_reuse, reason = "Clear purpose")]
1556 for (name, value) in headermap {
1557 let name = name.as_str().to_owned();
1558 let value = String::from_utf8_lossy(value.as_bytes()).into_owned();
1559 headers.push(UnpackedResponseHeader { name, value });
1560 }
1561 headers.sort_by(|a, b| {
1562 match a.name.cmp(&b.name) {
1563 Ordering::Equal => a.value.cmp(&b.value),
1564 Ordering::Greater => Ordering::Greater,
1565 Ordering::Less => Ordering::Less,
1566 }
1567 });
1568 headers
1569}
1570
1571// convert_response
1572/// Returns an [`UnpackedResponse`] containing the unpacked response data.
1573///
1574/// This function carries out the common part of the conversion process for
1575/// [`ResponseExt::unpack()`]. As [`unpack()`](ResponseExt::unpack()) has a
1576/// number of implementations, the common code is abstracted out into this
1577/// function.
1578///
1579/// # Parameters
1580///
1581/// * `status` - The response status code.
1582/// * `headers` - The response headers.
1583/// * `body` - The response body.
1584///
1585/// # See also
1586///
1587/// * [`axum::response`](https://docs.rs/axum/latest/axum/response/index.html)
1588/// * [`axum::response::Response`](https://docs.rs/axum/latest/axum/response/type.Response.html)
1589/// * [`http::Response`]
1590/// * [`hyper::Response`]
1591/// * [`ResponseExt::unpack()`]
1592/// * [`UnpackedResponse`]
1593/// * [`UnpackedResponseHeader`]
1594///
1595fn convert_response(
1596 status: StatusCode,
1597 headers: &HeaderMap<HeaderValue>,
1598 body: &Bytes,
1599) -> UnpackedResponse {
1600 UnpackedResponse {
1601 status,
1602 headers: convert_headers(headers),
1603 body: UnpackedResponseBody { body: body.to_vec(), ..Default::default() },
1604 }
1605}
1606
1607// serialize_status_code
1608/// Returns the status code as a number.
1609///
1610/// This function is used by [`serde`] to serialise the status code as a number
1611/// rather than an enum. This is necessary because the [`UnpackedResponse`]
1612/// struct is used for comparison, and the status code is not directly
1613/// comparable to a number.
1614///
1615/// # Parameters
1616///
1617/// * `status_code` - The status code to serialise.
1618/// * `serializer` - The serialiser to use.
1619///
1620/// # See also
1621///
1622/// * [`deserialize_status_code()`]
1623/// * [`http::StatusCode`]
1624/// * [`UnpackedResponse`]
1625///
1626#[expect(clippy::trivially_copy_pass_by_ref, reason = "Needs to match trait")]
1627fn serialize_status_code<S>(status_code: &StatusCode, serializer: S) -> Result<S::Ok, S::Error>
1628where
1629 S: Serializer,
1630{
1631 serializer.serialize_u16(status_code.as_u16())
1632}
1633
1634// deserialize_status_code
1635/// Returns the status code as an enum.
1636///
1637/// This function is used by [`serde`] to deserialise the status code as an
1638/// enum rather than a number. This is necessary because the
1639/// [`UnpackedResponse`] struct is used for comparison, and the status code is
1640/// not directly comparable to a number.
1641///
1642/// # Parameters
1643///
1644/// * `deserializer` - The deserialiser to use.
1645///
1646/// # See also
1647///
1648/// * [`http::StatusCode`]
1649/// * [`serialize_status_code()`]
1650/// * [`UnpackedResponse`]
1651///
1652fn deserialize_status_code<'de, D>(deserializer: D) -> Result<StatusCode, D::Error>
1653where
1654 D: Deserializer<'de>,
1655{
1656 let status_code_value: u16 = Deserialize::deserialize(deserializer)?;
1657 let status_code = StatusCode::from_u16(status_code_value).map_err(DeError::custom)?;
1658 Ok(status_code)
1659}
1660
1661