twilight_http/response/
mod.rs

1//! Response utility type and related types.
2//!
3//! The heart of the response module is the [`Response`] itself: it's a wrapper
4//! over the underlying HTTP client's response, containing helper methods for
5//! things like [getting the raw bytes][`bytes`] of the response body, getting
6//! an [iterator of the response headers][`headers`], or
7//! [deserializing the body into a model][`model`].
8//!
9//! The [`ResponseFuture`] is a type implementing [`Future`] that resolves to a
10//! [`Response`] when polled or `.await`ed to completion.
11//!
12//! # Examples
13//!
14//! Get a user by ID, check if the request was successful, and if so deserialize
15//! the response body via [`Response::model`][`model`] and print the user's
16//! name:
17//!
18//! ```no_run
19//! # #[tokio::main]
20//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
21//! # let user_id = twilight_model::id::Id::new(1);
22//! use std::env;
23//! use twilight_http::Client;
24//!
25//! let client = Client::new(env::var("DISCORD_TOKEN")?);
26//! let response = client.user(user_id).await?;
27//!
28//! if !response.status().is_success() {
29//!     println!("failed to get user");
30//!
31//!     return Ok(());
32//! }
33//!
34//! // Twilight already knows to deserialize it into a
35//! // `twilight_model::user::User`.
36//! let user = response.model().await?;
37//!
38//! println!("user's name: {}:{}", user.name, user.discriminator);
39//! # Ok(()) }
40//! ```
41//!
42//! [`Future`]: std::future::Future
43//! [`bytes`]: Response::bytes
44//! [`headers`]: Response::headers
45//! [`model`]: Response::model
46
47pub mod marker;
48
49pub(crate) mod future;
50
51mod status_code;
52
53pub use self::{future::ResponseFuture, status_code::StatusCode};
54
55use self::marker::ListBody;
56use http::{
57    header::{HeaderValue, Iter as HeaderMapIter},
58    Response as HyperResponse,
59};
60use http_body_util::BodyExt;
61use hyper::body::{Bytes, Incoming};
62use serde::de::DeserializeOwned;
63use std::{
64    error::Error,
65    fmt::{Display, Formatter, Result as FmtResult},
66    future::Future,
67    iter::FusedIterator,
68    marker::PhantomData,
69    pin::Pin,
70    task::{Context, Poll},
71};
72
73/// Failure when processing a response body.
74#[derive(Debug)]
75pub struct DeserializeBodyError {
76    kind: DeserializeBodyErrorType,
77    source: Option<Box<dyn Error + Send + Sync>>,
78}
79
80impl DeserializeBodyError {
81    /// Immutable reference to the type of error that occurred.
82    #[must_use = "retrieving the type has no effect if left unused"]
83    pub const fn kind(&self) -> &DeserializeBodyErrorType {
84        &self.kind
85    }
86
87    /// Consume the error, returning the source error if there is any.
88    #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
89    pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
90        self.source
91    }
92
93    /// Consume the error, returning the owned error type and the source error.
94    #[must_use = "consuming the error into its parts has no effect if left unused"]
95    pub fn into_parts(
96        self,
97    ) -> (
98        DeserializeBodyErrorType,
99        Option<Box<dyn Error + Send + Sync>>,
100    ) {
101        (self.kind, self.source)
102    }
103}
104
105impl Display for DeserializeBodyError {
106    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
107        match &self.kind {
108            &DeserializeBodyErrorType::BodyNotUtf8 { .. } => {
109                f.write_str("response body is not a utf-8 valid string")
110            }
111            DeserializeBodyErrorType::Chunking => f.write_str("failed to chunk response body"),
112            #[cfg(feature = "decompression")]
113            DeserializeBodyErrorType::Decompressing => {
114                f.write_str("failed to decompress response body")
115            }
116            DeserializeBodyErrorType::Deserializing => {
117                f.write_str("failed to deserialize response body")
118            }
119        }
120    }
121}
122
123impl Error for DeserializeBodyError {
124    fn source(&self) -> Option<&(dyn Error + 'static)> {
125        self.source
126            .as_ref()
127            .map(|source| &**source as &(dyn Error + 'static))
128    }
129}
130
131/// Type of [`DeserializeBodyError`] that occurred.
132#[derive(Debug)]
133#[non_exhaustive]
134pub enum DeserializeBodyErrorType {
135    /// Response body is not a UTF-8 valid string.
136    BodyNotUtf8 {
137        /// Raw response body bytes that could not be converted into a UTF-8
138        /// valid string.
139        bytes: Vec<u8>,
140    },
141    /// Response body couldn't be chunked.
142    Chunking,
143    /// Decompressing the response failed.
144    #[cfg(feature = "decompression")]
145    Decompressing,
146    /// Deserializing the model failed.
147    Deserializing,
148}
149
150/// Response wrapper containing helper functions over the HTTP client's
151/// response.
152///
153/// This exists so that it can be determined whether to deserialize the body.
154/// This is useful when you don't need the body and therefore don't want to
155/// spend the time to deserialize it.
156///
157/// # Examples
158///
159/// ```no_run
160/// # #[tokio::main]
161/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
162/// # let user_id = twilight_model::id::Id::new(1);
163/// use std::env;
164/// use twilight_http::Client;
165///
166/// let client = Client::new(env::var("DISCORD_TOKEN")?);
167/// let response = client.user(user_id).await?;
168/// println!("status code: {}", response.status());
169///
170/// let user = response.model().await?;
171/// println!("username: {}#{:04}", user.name, user.discriminator);
172/// # Ok(()) }
173/// ```
174#[derive(Debug)]
175pub struct Response<T> {
176    inner: HyperResponse<Incoming>,
177    phantom: PhantomData<T>,
178}
179
180impl<T> Response<T> {
181    pub(crate) const fn new(inner: HyperResponse<Incoming>) -> Self {
182        Self {
183            inner,
184            phantom: PhantomData,
185        }
186    }
187
188    /// Iterator of the response headers.
189    #[must_use = "creating an iterator of the headers has no use on its own"]
190    pub fn headers(&self) -> HeaderIter<'_> {
191        HeaderIter(self.inner.headers().iter())
192    }
193
194    /// Status code of the response.
195    #[must_use = "retrieving the status code has no use on its own"]
196    pub fn status(&self) -> StatusCode {
197        // Convert the `hyper` status code into its raw form in order to return
198        // our own.
199        let raw = self.inner.status().as_u16();
200
201        StatusCode::new(raw)
202    }
203
204    /// Consume the response and accumulate the chunked body into bytes.
205    ///
206    /// For a textual representation of the response body [`text`] should be
207    /// preferred.
208    ///
209    /// # Examples
210    ///
211    /// Count the number of bytes in a response body:
212    ///
213    /// ```no_run
214    /// # #[tokio::main]
215    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
216    /// # let user_id = twilight_model::id::Id::new(1);
217    /// use std::env;
218    /// use twilight_http::Client;
219    ///
220    /// let client = Client::new(env::var("DISCORD_TOKEN")?);
221    /// let response = client.user(user_id).await?;
222    /// let bytes = response.bytes().await?;
223    ///
224    /// println!("size of body: {}", bytes.len());
225    /// # Ok(()) }
226    /// ```
227    ///
228    /// # Errors
229    ///
230    /// Returns a [`DeserializeBodyErrorType::Chunking`] error type if the
231    /// response body could not be entirely read.
232    ///
233    /// [`text`]: Self::text
234    pub fn bytes(self) -> BytesFuture {
235        #[cfg(feature = "decompression")]
236        let compressed = self
237            .inner
238            .headers()
239            .get(http::header::CONTENT_ENCODING)
240            .is_some();
241
242        let body = self.inner.into_body();
243
244        let fut = async move {
245            {
246                #[cfg(feature = "decompression")]
247                if compressed {
248                    return decompress(body).await;
249                }
250
251                Ok(body
252                    .collect()
253                    .await
254                    .map_err(|source| DeserializeBodyError {
255                        kind: DeserializeBodyErrorType::Chunking,
256                        source: Some(Box::new(source)),
257                    })?
258                    .to_bytes())
259            }
260        };
261
262        BytesFuture {
263            inner: Box::pin(fut),
264        }
265    }
266
267    /// Consume the response and accumulate the body into a string.
268    ///
269    /// For the raw bytes of the response body [`bytes`] should be preferred.
270    ///
271    /// # Examples
272    ///
273    /// Print the textual response from getting the current user:
274    ///
275    /// ```no_run
276    /// # #[tokio::main]
277    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
278    /// use std::env;
279    /// use twilight_http::Client;
280    ///
281    /// let client = Client::new(env::var("DISCORD_TOKEN")?);
282    /// let response = client.current_user().await?;
283    /// let text = response.text().await?;
284    ///
285    /// println!("body: {text}");
286    /// # Ok(()) }
287    /// ```
288    ///
289    /// # Errors
290    ///
291    /// Returns a [`DeserializeBodyErrorType::BodyNotUtf8`] error type if the
292    /// response body is not UTF-8 valid.
293    ///
294    /// Returns a [`DeserializeBodyErrorType::Chunking`] error type if the
295    /// response body could not be entirely read.
296    ///
297    /// [`bytes`]: Self::bytes
298    pub fn text(self) -> TextFuture {
299        TextFuture(self.bytes())
300    }
301}
302
303impl<T: DeserializeOwned> Response<T> {
304    /// Consume the response, chunking the body and then deserializing it into
305    /// the request's matching model.
306    ///
307    /// # Errors
308    ///
309    /// Returns a [`DeserializeBodyErrorType::Chunking`] error type if the
310    /// response body could not be entirely read.
311    ///
312    /// Returns a [`DeserializeBodyErrorType::Deserializing`] error type if the
313    /// response body could not be deserialized into the target model.
314    pub fn model(self) -> ModelFuture<T> {
315        ModelFuture::new(self.bytes())
316    }
317}
318
319impl<T: DeserializeOwned> Response<ListBody<T>> {
320    /// Consume the response, chunking the body and then deserializing it into
321    /// a list of something.
322    ///
323    /// This is an alias for [`models`].
324    ///
325    /// # Errors
326    ///
327    /// Returns a [`DeserializeBodyErrorType::Chunking`] error type if the
328    /// response body could not be entirely read.
329    ///
330    /// Returns a [`DeserializeBodyErrorType::Deserializing`] error type if the
331    /// response body could not be deserialized into a list of something.
332    ///
333    /// [`models`]: Self::models
334    pub fn model(self) -> ModelFuture<Vec<T>> {
335        self.models()
336    }
337
338    /// Consume the response, chunking the body and then deserializing it into
339    /// a list of something.
340    ///
341    /// # Errors
342    ///
343    /// Returns a [`DeserializeBodyErrorType::Chunking`] error type if the
344    /// response body could not be entirely read.
345    ///
346    /// Returns a [`DeserializeBodyErrorType::Deserializing`] error type if the
347    /// response body could not be deserialized into a list of something.
348    pub fn models(self) -> ModelFuture<Vec<T>> {
349        Response::<Vec<T>>::new(self.inner).model()
350    }
351}
352
353/// Iterator over the headers of a [`Response`].
354///
355/// Header names are returned as a string slice and header values are returned
356/// as a slice of bytes. If a header has multiple values then the same header
357/// name may be returned multiple times.
358///
359/// Obtained via [`Response::headers`].
360///
361/// # Examples
362///
363/// Iterate over all of the header names and values of the response from
364/// creating a message:
365///
366/// ```no_run
367/// # #[tokio::main]
368/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
369/// # let channel_id = twilight_model::id::Id::new(1);
370/// use std::env;
371/// use twilight_http::Client;
372///
373/// let client = Client::new(env::var("DISCORD_TOKEN")?);
374/// let response = client.create_message(channel_id)
375//      .content("test")
376///     .await?;
377/// let mut headers = response.headers();
378///
379/// while let Some((name, value)) = headers.next() {
380///     println!("{name}: {}", String::from_utf8_lossy(value));
381/// }
382/// # Ok(()) }
383/// ```
384#[derive(Debug)]
385#[must_use = "iterators do nothing unless used"]
386pub struct HeaderIter<'a>(HeaderMapIter<'a, HeaderValue>);
387
388// `hyper::header::Iter` implements `FusedIterator` so this is a free impl.
389impl FusedIterator for HeaderIter<'_> {}
390
391impl<'a> Iterator for HeaderIter<'a> {
392    // Header names are UTF-8 valid but values aren't, so the value item has
393    // to be a slice of the bytes.
394    type Item = (&'a str, &'a [u8]);
395
396    fn next(&mut self) -> Option<Self::Item> {
397        let (name, value) = self.0.next()?;
398
399        Some((name.as_str(), value.as_bytes()))
400    }
401
402    fn size_hint(&self) -> (usize, Option<usize>) {
403        // `hyper::header::Iter` implements `Iterator::size_hint`.
404        self.0.size_hint()
405    }
406}
407
408/// Future resolving to the bytes of a response body.
409///
410/// The body of the response is chunked and aggregated into a `Vec` of bytes.
411///
412/// Obtained via [`Response::bytes`].
413///
414/// # Examples
415///
416/// Print the bytes of the body of the response from creating a message:
417///
418/// ```no_run
419/// # #[tokio::main]
420/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
421/// # let channel_id = twilight_model::id::Id::new(1);
422/// # let message_id = twilight_model::id::Id::new(2);
423/// use std::env;
424/// use twilight_http::Client;
425///
426/// let client = Client::new(env::var("DISCORD_TOKEN")?);
427/// let response = client.message(channel_id, message_id).await?;
428/// let bytes = response.bytes().await?;
429///
430/// println!("bytes of the body: {bytes:?}");
431/// # Ok(()) }
432/// ```
433///
434/// # Errors
435///
436/// Returns a [`DeserializeBodyErrorType::Chunking`] error type if the
437/// response body could not be entirely read.
438#[must_use = "futures do nothing unless you `.await` or poll them"]
439pub struct BytesFuture {
440    inner:
441        Pin<Box<dyn Future<Output = Result<Bytes, DeserializeBodyError>> + Send + Sync + 'static>>,
442}
443
444impl Future for BytesFuture {
445    type Output = Result<Vec<u8>, DeserializeBodyError>;
446
447    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
448        if let Poll::Ready(result) = Pin::new(&mut self.inner).poll(cx) {
449            Poll::Ready(result.map(|b| b.to_vec()))
450        } else {
451            Poll::Pending
452        }
453    }
454}
455
456/// Future resolving to a deserialized model.
457///
458/// Obtained via [`Response::model`].
459///
460/// # Examples
461///
462/// Get an emoji by its ID and print its name:
463///
464/// ```no_run
465/// # #[tokio::main]
466/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
467/// # let guild_id = twilight_model::id::Id::new(1);
468/// # let emoji_id = twilight_model::id::Id::new(2);
469/// use std::env;
470/// use twilight_http::Client;
471///
472/// let client = Client::new(env::var("DISCORD_TOKEN")?);
473/// let emoji = client.emoji(guild_id, emoji_id).await?.model().await?;
474///
475/// println!("emoji name: {}", emoji.name);
476/// # Ok(()) }
477/// ```
478///
479/// # Errors
480///
481/// Returns a [`DeserializeBodyErrorType::Chunking`] error type if the
482/// response body could not be entirely read.
483///
484/// Returns a [`DeserializeBodyErrorType::Deserializing`] error type if the
485/// response body could not be deserialized into a model.
486#[must_use = "futures do nothing unless you `.await` or poll them"]
487pub struct ModelFuture<T> {
488    future: BytesFuture,
489    phantom: PhantomData<T>,
490}
491
492impl<T> ModelFuture<T> {
493    const fn new(bytes: BytesFuture) -> Self {
494        Self {
495            future: bytes,
496            phantom: PhantomData,
497        }
498    }
499}
500
501impl<T: DeserializeOwned + Unpin> Future for ModelFuture<T> {
502    type Output = Result<T, DeserializeBodyError>;
503
504    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
505        match Pin::new(&mut self.future).poll(cx) {
506            Poll::Ready(Ok(bytes)) => {
507                Poll::Ready(crate::json::from_bytes(&bytes).map_err(|source| {
508                    DeserializeBodyError {
509                        kind: DeserializeBodyErrorType::Deserializing,
510                        source: Some(Box::new(source)),
511                    }
512                }))
513            }
514            Poll::Ready(Err(source)) => Poll::Ready(Err(source)),
515            Poll::Pending => Poll::Pending,
516        }
517    }
518}
519
520/// Future resolving to the text of a response body.
521///
522/// The body of the response is chunked and aggregated into a string.
523///
524/// Obtained via [`Response::text`].
525///
526/// # Examples
527///
528/// Print the textual body of the response from creating a message:
529///
530/// ```no_run
531/// # #[tokio::main]
532/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
533/// # let channel_id = twilight_model::id::Id::new(1);
534/// # let message_id = twilight_model::id::Id::new(2);
535/// use std::env;
536/// use twilight_http::Client;
537///
538/// let client = Client::new(env::var("DISCORD_TOKEN")?);
539/// let response = client.message(channel_id, message_id).await?;
540/// let text = response.text().await?;
541///
542/// println!("body: {text}");
543/// # Ok(()) }
544/// ```
545///
546/// # Errors
547///
548/// Returns a [`DeserializeBodyErrorType::BodyNotUtf8`] error type if the
549/// response body is not UTF-8 valid.
550///
551/// Returns a [`DeserializeBodyErrorType::Chunking`] error type if the
552/// response body could not be entirely read.
553#[must_use = "futures do nothing unless you `.await` or poll them"]
554pub struct TextFuture(BytesFuture);
555
556impl Future for TextFuture {
557    type Output = Result<String, DeserializeBodyError>;
558
559    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
560        match Pin::new(&mut self.0).poll(cx) {
561            Poll::Ready(Ok(bytes)) => Poll::Ready(String::from_utf8(bytes).map_err(|source| {
562                let utf8_error = source.utf8_error();
563                let bytes = source.into_bytes();
564
565                DeserializeBodyError {
566                    kind: DeserializeBodyErrorType::BodyNotUtf8 { bytes },
567                    source: Some(Box::new(utf8_error)),
568                }
569            })),
570            Poll::Ready(Err(source)) => Poll::Ready(Err(source)),
571            Poll::Pending => Poll::Pending,
572        }
573    }
574}
575
576#[cfg(feature = "decompression")]
577async fn decompress<B: hyper::body::Body>(body: B) -> Result<Bytes, DeserializeBodyError>
578where
579    <B as hyper::body::Body>::Error: Send + Sync + Error + 'static,
580{
581    use brotli_decompressor::Decompressor;
582    use hyper::body::Buf;
583    use std::io::Read;
584
585    let aggregate = body
586        .collect()
587        .await
588        .map_err(|source| DeserializeBodyError {
589            kind: DeserializeBodyErrorType::Chunking,
590            source: Some(Box::new(source)),
591        })?
592        .aggregate();
593
594    // Determine the size of the entire buffer, in order to create the
595    // decompressed and compressed buffers.
596    let size = aggregate.remaining();
597
598    let mut buf = Vec::with_capacity(size);
599
600    Decompressor::new(aggregate.reader(), size)
601        .read_to_end(&mut buf)
602        .map_err(|_| DeserializeBodyError {
603            kind: DeserializeBodyErrorType::Decompressing,
604            source: None,
605        })?;
606
607    Ok(buf.into())
608}
609
610#[cfg(test)]
611mod tests {
612    use super::{
613        marker::{EmptyBody, ListBody},
614        BytesFuture, DeserializeBodyError, DeserializeBodyErrorType, HeaderIter, ModelFuture,
615        Response, TextFuture,
616    };
617    use static_assertions::assert_impl_all;
618    use std::{fmt::Debug, future::Future, iter::FusedIterator};
619    use twilight_model::{channel::Message, guild::Emoji};
620
621    #[cfg(feature = "decompression")]
622    use std::error::Error;
623
624    assert_impl_all!(BytesFuture: Future);
625    assert_impl_all!(DeserializeBodyErrorType: Debug, Send, Sync);
626    assert_impl_all!(DeserializeBodyError: Debug, Send, Sync);
627    assert_impl_all!(HeaderIter<'_>: Debug, FusedIterator, Iterator, Send, Sync);
628    assert_impl_all!(ModelFuture<Emoji>: Future);
629    assert_impl_all!(Response<EmptyBody>: Debug, Send, Sync);
630    assert_impl_all!(Response<ListBody<Message>>: Debug, Send, Sync);
631    assert_impl_all!(TextFuture: Future);
632
633    #[cfg(feature = "decompression")]
634    #[tokio::test]
635    async fn test_decompression() -> Result<(), Box<dyn Error + Send + Sync>> {
636        use super::decompress;
637        use http_body_util::Full;
638        use twilight_model::guild::invite::Invite;
639
640        const COMPRESSED: [u8; 553] = [
641            27, 235, 4, 0, 44, 10, 99, 99, 102, 244, 145, 235, 87, 95, 83, 76, 203, 31, 27, 6, 65,
642            20, 107, 75, 245, 103, 243, 139, 3, 81, 204, 15, 49, 13, 177, 83, 150, 163, 53, 249,
643            217, 44, 58, 93, 125, 117, 56, 81, 249, 9, 5, 129, 64, 112, 146, 109, 175, 185, 252,
644            39, 174, 169, 143, 248, 160, 111, 79, 250, 15, 22, 21, 139, 72, 171, 182, 215, 97, 1,
645            109, 52, 192, 105, 131, 236, 70, 240, 211, 16, 175, 237, 1, 164, 242, 21, 250, 7, 182,
646            87, 200, 84, 121, 177, 139, 184, 62, 86, 239, 221, 212, 206, 23, 176, 184, 173, 182,
647            83, 250, 176, 218, 222, 73, 192, 165, 108, 20, 233, 138, 102, 8, 186, 0, 34, 79, 212,
648            190, 139, 237, 164, 11, 13, 236, 223, 90, 18, 161, 105, 219, 189, 211, 233, 56, 100,
649            27, 53, 61, 230, 220, 103, 22, 220, 157, 206, 198, 33, 124, 46, 160, 49, 72, 66, 109,
650            130, 156, 126, 25, 231, 164, 31, 17, 102, 112, 78, 240, 195, 215, 22, 58, 199, 29, 244,
651            246, 17, 248, 182, 159, 244, 231, 2, 187, 178, 212, 133, 198, 226, 154, 196, 194, 109,
652            105, 237, 98, 73, 70, 73, 174, 133, 214, 16, 22, 165, 73, 132, 37, 25, 78, 185, 13, 20,
653            226, 205, 111, 76, 80, 87, 156, 171, 130, 243, 102, 245, 66, 54, 21, 241, 150, 144,
654            113, 204, 11, 45, 205, 147, 31, 35, 223, 39, 159, 14, 134, 11, 233, 90, 91, 234, 149,
655            220, 63, 225, 191, 155, 78, 23, 26, 233, 239, 12, 87, 75, 185, 112, 53, 5, 218, 162,
656            88, 143, 73, 163, 240, 198, 80, 106, 205, 225, 201, 11, 211, 102, 187, 59, 131, 4, 18,
657            68, 104, 61, 114, 222, 250, 243, 104, 191, 186, 190, 228, 118, 222, 138, 144, 82, 50,
658            65, 20, 233, 128, 139, 237, 52, 175, 75, 228, 168, 57, 75, 2, 210, 98, 28, 86, 21, 106,
659            108, 25, 67, 189, 94, 185, 253, 174, 74, 73, 20, 161, 213, 76, 117, 19, 241, 59, 175,
660            156, 167, 74, 184, 148, 214, 21, 90, 95, 105, 76, 80, 157, 146, 182, 184, 240, 89, 31,
661            94, 80, 68, 218, 177, 126, 147, 26, 184, 109, 211, 32, 123, 49, 11, 120, 16, 190, 124,
662            255, 23, 39, 117, 103, 82, 62, 214, 102, 187, 195, 122, 245, 115, 31, 4, 29, 84, 181,
663            80, 204, 22, 61, 140, 159, 161, 228, 241, 229, 231, 219, 229, 202, 193, 72, 193, 139,
664            151, 179, 135, 40, 217, 140, 251, 3, 18, 106, 142, 249, 255, 73, 62, 156, 133, 5, 28,
665            112, 57, 94, 73, 161, 245, 238, 26, 20, 197, 81, 11, 225, 137, 62, 144, 221, 198, 148,
666            35, 107, 194, 189, 8, 41, 125, 129, 244, 238, 35, 213, 254, 254, 246, 176, 184, 172,
667            112, 85, 54, 235, 239, 79, 250, 151, 27, 34, 79, 149, 124, 0, 103, 230, 132, 251, 122,
668            82, 46, 52, 132, 228, 234, 159, 186, 221, 203, 94, 0, 236, 182, 125, 236, 47, 243, 7,
669            38, 9, 241, 2, 45, 199, 19, 230, 15, 178, 197, 116, 37, 88, 0, 215, 103, 13, 104, 114,
670            248, 15, 240, 7,
671        ];
672
673        let decompressed = decompress(Full::new(COMPRESSED.as_slice())).await?;
674
675        let deserialized = serde_json::from_slice::<Invite>(&decompressed)?;
676
677        assert_eq!(deserialized.code, "twilight-rs");
678
679        Ok(())
680    }
681}