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}