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