1use crate::{
4 call::{AsyncCall, SyncCall},
5 Canister,
6};
7use candid::{
8 types::{
9 reference::FuncVisitor,
10 value::{IDLValue, IDLValueVisitor},
11 Compound, Serializer, Type, TypeInner,
12 },
13 CandidType, Deserialize, Func,
14};
15use ic_agent::{export::Principal, Agent};
16use std::{
17 borrow::Cow,
18 convert::TryInto,
19 fmt::Debug,
20 marker::PhantomData,
21 ops::{Deref, DerefMut},
22};
23
24#[derive(Debug, Clone)]
26pub struct HttpRequestCanister<'agent>(Canister<'agent>);
27
28impl<'agent> Deref for HttpRequestCanister<'agent> {
29 type Target = Canister<'agent>;
30 fn deref(&self) -> &Self::Target {
31 &self.0
32 }
33}
34
35#[derive(Debug, CandidType, Clone, Deserialize)]
37pub struct HeaderField<'a>(pub Cow<'a, str>, pub Cow<'a, str>);
38
39#[derive(Debug, Clone, CandidType)]
41struct HttpRequest<'a, H> {
42 pub method: &'a str,
44 pub url: &'a str,
46 pub headers: H,
48 pub body: &'a [u8],
50 pub certificate_version: Option<&'a u16>,
52}
53
54#[derive(Debug, Clone, CandidType)]
57struct HttpUpdateRequest<'a, H> {
58 pub method: &'a str,
60 pub url: &'a str,
62 pub headers: H,
64 pub body: &'a [u8],
66}
67
68#[derive(Debug, Clone)]
70pub struct Headers<H>(H);
71
72impl<'a, H: Clone + ExactSizeIterator<Item = HeaderField<'a>>> From<H> for Headers<H> {
73 fn from(h: H) -> Self {
74 Headers(h)
75 }
76}
77
78impl<'a, H: Clone + ExactSizeIterator<Item = HeaderField<'a>>> CandidType for Headers<H> {
79 fn _ty() -> Type {
80 TypeInner::Vec(HeaderField::ty()).into()
81 }
82 fn idl_serialize<S: Serializer>(&self, serializer: S) -> Result<(), S::Error> {
83 let mut ser = serializer.serialize_vec(self.0.len())?;
84 for e in self.0.clone() {
85 Compound::serialize_element(&mut ser, &e)?;
86 }
87 Ok(())
88 }
89}
90
91#[derive(Debug, Clone, CandidType, Deserialize)]
93pub struct HttpResponse<Token = self::Token, Callback = HttpRequestStreamingCallback> {
94 pub status_code: u16,
96 pub headers: Vec<HeaderField<'static>>,
98 #[serde(with = "serde_bytes")]
100 pub body: Vec<u8>,
101 pub streaming_strategy: Option<StreamingStrategy<Token, Callback>>,
103 pub upgrade: Option<bool>,
105}
106
107impl<T1, C1> HttpResponse<T1, C1> {
108 pub fn from<T2: Into<T1>, C2: Into<C1>>(v: HttpResponse<T2, C2>) -> Self {
110 Self {
111 status_code: v.status_code,
112 headers: v.headers,
113 body: v.body,
114 streaming_strategy: v.streaming_strategy.map(StreamingStrategy::from),
115 upgrade: v.upgrade,
116 }
117 }
118 pub fn into<T2, C2>(self) -> HttpResponse<T2, C2>
120 where
121 T1: Into<T2>,
122 C1: Into<C2>,
123 {
124 HttpResponse::from(self)
125 }
126 pub fn try_from<T2, C2, E>(v: HttpResponse<T2, C2>) -> Result<Self, E>
128 where
129 T2: TryInto<T1>,
130 C2: TryInto<C1>,
131 T2::Error: Into<E>,
132 C2::Error: Into<E>,
133 {
134 Ok(Self {
135 status_code: v.status_code,
136 headers: v.headers,
137 body: v.body,
138 streaming_strategy: v
139 .streaming_strategy
140 .map(StreamingStrategy::try_from)
141 .transpose()?,
142 upgrade: v.upgrade,
143 })
144 }
145 pub fn try_into<T2, C2, E>(self) -> Result<HttpResponse<T2, C2>, E>
147 where
148 T1: TryInto<T2>,
149 C1: TryInto<C2>,
150 T1::Error: Into<E>,
151 C1::Error: Into<E>,
152 {
153 HttpResponse::try_from(self)
154 }
155}
156
157#[derive(Debug, Clone, CandidType, Deserialize)]
159pub enum StreamingStrategy<Token = self::Token, Callback = HttpRequestStreamingCallback> {
160 Callback(CallbackStrategy<Token, Callback>),
162}
163
164impl<T1, C1> StreamingStrategy<T1, C1> {
165 pub fn from<T2: Into<T1>, C2: Into<C1>>(v: StreamingStrategy<T2, C2>) -> Self {
167 match v {
168 StreamingStrategy::Callback(c) => Self::Callback(c.into()),
169 }
170 }
171 pub fn into<T2, C2>(self) -> StreamingStrategy<T2, C2>
173 where
174 T1: Into<T2>,
175 C1: Into<C2>,
176 {
177 StreamingStrategy::from(self)
178 }
179 pub fn try_from<T2, C2, E>(v: StreamingStrategy<T2, C2>) -> Result<Self, E>
181 where
182 T2: TryInto<T1>,
183 C2: TryInto<C1>,
184 T2::Error: Into<E>,
185 C2::Error: Into<E>,
186 {
187 Ok(match v {
188 StreamingStrategy::Callback(c) => Self::Callback(c.try_into()?),
189 })
190 }
191 pub fn try_into<T2, C2, E>(self) -> Result<StreamingStrategy<T2, C2>, E>
193 where
194 T1: TryInto<T2>,
195 C1: TryInto<C2>,
196 T1::Error: Into<E>,
197 C1::Error: Into<E>,
198 {
199 StreamingStrategy::try_from(self)
200 }
201}
202
203#[derive(Debug, Clone, CandidType, Deserialize)]
205pub struct CallbackStrategy<Token = self::Token, Callback = HttpRequestStreamingCallback> {
206 pub callback: Callback,
208 pub token: Token,
210}
211
212impl<T1, C1> CallbackStrategy<T1, C1> {
213 pub fn from<T2: Into<T1>, C2: Into<C1>>(v: CallbackStrategy<T2, C2>) -> Self {
215 Self {
216 callback: v.callback.into(),
217 token: v.token.into(),
218 }
219 }
220 pub fn into<T2, C2>(self) -> CallbackStrategy<T2, C2>
222 where
223 T1: Into<T2>,
224 C1: Into<C2>,
225 {
226 CallbackStrategy::from(self)
227 }
228 pub fn try_from<T2, C2, E>(v: CallbackStrategy<T2, C2>) -> Result<Self, E>
230 where
231 T2: TryInto<T1>,
232 C2: TryInto<C1>,
233 T2::Error: Into<E>,
234 C2::Error: Into<E>,
235 {
236 Ok(Self {
237 callback: v.callback.try_into().map_err(Into::into)?,
238 token: v.token.try_into().map_err(Into::into)?,
239 })
240 }
241 pub fn try_into<T2, C2, E>(self) -> Result<CallbackStrategy<T2, C2>, E>
243 where
244 T1: TryInto<T2>,
245 C1: TryInto<C2>,
246 T1::Error: Into<E>,
247 C1::Error: Into<E>,
248 {
249 CallbackStrategy::try_from(self)
250 }
251}
252
253#[derive(Debug, Clone)]
255pub struct HttpRequestStreamingCallbackAny(pub Func);
256
257impl CandidType for HttpRequestStreamingCallbackAny {
258 fn _ty() -> Type {
259 TypeInner::Reserved.into()
260 }
261 fn idl_serialize<S: Serializer>(&self, _serializer: S) -> Result<(), S::Error> {
262 unimplemented!("Callback is not serializable")
265 }
266}
267
268impl<'de> Deserialize<'de> for HttpRequestStreamingCallbackAny {
269 fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
270 deserializer.deserialize_ignored_any(FuncVisitor).map(Self)
272 }
273}
274
275impl From<Func> for HttpRequestStreamingCallbackAny {
276 fn from(f: Func) -> Self {
277 Self(f)
278 }
279}
280impl From<HttpRequestStreamingCallbackAny> for Func {
281 fn from(c: HttpRequestStreamingCallbackAny) -> Self {
282 c.0
283 }
284}
285
286#[derive(Debug, Clone)]
288pub struct HttpRequestStreamingCallback<ArgToken = self::ArgToken>(
289 pub Func,
290 pub PhantomData<ArgToken>,
291);
292
293impl<ArgToken: CandidType> CandidType for HttpRequestStreamingCallback<ArgToken> {
294 fn _ty() -> Type {
295 candid::func!((ArgToken) -> (StreamingCallbackHttpResponse::<ArgToken>) query)
296 }
297 fn idl_serialize<S: Serializer>(&self, serializer: S) -> Result<(), S::Error> {
298 self.0.idl_serialize(serializer)
299 }
300}
301
302impl<'de, ArgToken> Deserialize<'de> for HttpRequestStreamingCallback<ArgToken> {
303 fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
304 Func::deserialize(deserializer).map(Self::from)
305 }
306}
307
308impl<ArgToken> From<Func> for HttpRequestStreamingCallback<ArgToken> {
309 fn from(f: Func) -> Self {
310 Self(f, PhantomData)
311 }
312}
313
314impl<ArgToken> From<HttpRequestStreamingCallback<ArgToken>> for Func {
315 fn from(c: HttpRequestStreamingCallback<ArgToken>) -> Self {
316 c.0
317 }
318}
319
320impl<ArgToken> Deref for HttpRequestStreamingCallback<ArgToken> {
321 type Target = Func;
322 fn deref(&self) -> &Func {
323 &self.0
324 }
325}
326
327impl<ArgToken> DerefMut for HttpRequestStreamingCallback<ArgToken> {
328 fn deref_mut(&mut self) -> &mut Func {
329 &mut self.0
330 }
331}
332
333#[derive(Debug, Clone, CandidType, Deserialize)]
335pub struct StreamingCallbackHttpResponse<Token = self::Token> {
336 #[serde(with = "serde_bytes")]
338 pub body: Vec<u8>,
339 pub token: Option<Token>,
341}
342
343#[derive(Debug, Clone, PartialEq)]
345pub struct Token(pub IDLValue);
346
347impl CandidType for Token {
348 fn _ty() -> Type {
349 TypeInner::Reserved.into()
350 }
351 fn idl_serialize<S: Serializer>(&self, _serializer: S) -> Result<(), S::Error> {
352 unimplemented!("Token is not serializable")
355 }
356}
357
358impl<'de> Deserialize<'de> for Token {
359 fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
360 deserializer
362 .deserialize_ignored_any(IDLValueVisitor)
363 .map(Token)
364 }
365}
366
367#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
369pub struct ArgToken;
370
371impl CandidType for ArgToken {
372 fn _ty() -> Type {
373 TypeInner::Empty.into()
374 }
375 fn idl_serialize<S: Serializer>(&self, _serializer: S) -> Result<(), S::Error> {
376 unimplemented!("Token is not serializable")
379 }
380}
381
382impl<'agent> HttpRequestCanister<'agent> {
383 pub fn create(agent: &'agent Agent, canister_id: Principal) -> Self {
385 Self(
386 Canister::builder()
387 .with_agent(agent)
388 .with_canister_id(canister_id)
389 .build()
390 .unwrap(),
391 )
392 }
393
394 pub fn from_canister(canister: Canister<'agent>) -> Self {
396 Self(canister)
397 }
398}
399
400impl<'agent> HttpRequestCanister<'agent> {
401 pub fn http_request<'canister: 'agent>(
403 &'canister self,
404 method: impl AsRef<str>,
405 url: impl AsRef<str>,
406 headers: impl IntoIterator<
407 Item = HeaderField<'agent>,
408 IntoIter = impl 'agent + Send + Sync + Clone + ExactSizeIterator<Item = HeaderField<'agent>>,
409 >,
410 body: impl AsRef<[u8]>,
411 certificate_version: Option<&u16>,
412 ) -> impl 'agent + SyncCall<Value = (HttpResponse,)> {
413 self.http_request_custom(
414 method.as_ref(),
415 url.as_ref(),
416 headers.into_iter(),
417 body.as_ref(),
418 certificate_version,
419 )
420 }
421
422 pub fn http_request_custom<'canister: 'agent, H, T, C>(
425 &'canister self,
426 method: &str,
427 url: &str,
428 headers: H,
429 body: &[u8],
430 certificate_version: Option<&u16>,
431 ) -> impl 'agent + SyncCall<Value = (HttpResponse<T, C>,)>
432 where
433 H: 'agent + Send + Sync + Clone + ExactSizeIterator<Item = HeaderField<'agent>>,
434 T: 'agent + Send + Sync + CandidType + for<'de> Deserialize<'de>,
435 C: 'agent + Send + Sync + CandidType + for<'de> Deserialize<'de>,
436 {
437 self.query("http_request")
438 .with_arg(HttpRequest {
439 method,
440 url,
441 headers: Headers(headers),
442 body,
443 certificate_version,
444 })
445 .build()
446 }
447
448 pub fn http_request_update<'canister: 'agent>(
451 &'canister self,
452 method: impl AsRef<str>,
453 url: impl AsRef<str>,
454 headers: impl 'agent + Send + Sync + Clone + ExactSizeIterator<Item = HeaderField<'agent>>,
455 body: impl AsRef<[u8]>,
456 ) -> impl 'agent + AsyncCall<Value = (HttpResponse,)> {
457 self.http_request_update_custom(method.as_ref(), url.as_ref(), headers, body.as_ref())
458 }
459
460 pub fn http_request_update_custom<'canister: 'agent, H, T, C>(
464 &'canister self,
465 method: &str,
466 url: &str,
467 headers: H,
468 body: &[u8],
469 ) -> impl 'agent + AsyncCall<Value = (HttpResponse<T, C>,)>
470 where
471 H: 'agent + Send + Sync + Clone + ExactSizeIterator<Item = HeaderField<'agent>>,
472 T: 'agent + Send + Sync + CandidType + for<'de> Deserialize<'de>,
473 C: 'agent + Send + Sync + CandidType + for<'de> Deserialize<'de>,
474 {
475 self.update("http_request_update")
476 .with_arg(HttpUpdateRequest {
477 method,
478 url,
479 headers: Headers(headers),
480 body,
481 })
482 .build()
483 }
484
485 pub fn http_request_stream_callback<'canister: 'agent>(
487 &'canister self,
488 method: impl AsRef<str>,
489 token: Token,
490 ) -> impl 'agent + SyncCall<Value = (StreamingCallbackHttpResponse,)> {
491 self.query(method.as_ref()).with_value_arg(token.0).build()
492 }
493
494 pub fn http_request_stream_callback_custom<'canister: 'agent, T>(
497 &'canister self,
498 method: impl AsRef<str>,
499 token: T,
500 ) -> impl 'agent + SyncCall<Value = (StreamingCallbackHttpResponse<T>,)>
501 where
502 T: 'agent + Send + Sync + CandidType + for<'de> Deserialize<'de>,
503 {
504 self.query(method.as_ref()).with_arg(token).build()
505 }
506}
507
508#[cfg(test)]
509mod test {
510 use crate::interfaces::http_request::HttpRequestStreamingCallbackAny;
511
512 use super::{
513 CallbackStrategy, HttpRequestStreamingCallback, HttpResponse,
514 StreamingCallbackHttpResponse, StreamingStrategy, Token,
515 };
516 use candid::{
517 types::value::{IDLField, IDLValue},
518 CandidType, Decode, Deserialize, Encode,
519 };
520 use serde::de::DeserializeOwned;
521
522 mod pre_update_legacy {
523 use candid::{define_function, CandidType, Deserialize, Nat};
524 use serde_bytes::ByteBuf;
525
526 #[derive(CandidType, Deserialize)]
527 pub struct Token {
528 pub key: String,
529 pub content_encoding: String,
530 pub index: Nat,
531 pub sha256: Option<ByteBuf>,
532 }
533
534 define_function!(pub CallbackFunc : () -> ());
535 #[derive(CandidType, Deserialize)]
536 pub struct CallbackStrategy {
537 pub callback: CallbackFunc,
538 pub token: Token,
539 }
540
541 #[derive(CandidType, Clone, Deserialize)]
542 pub struct HeaderField(pub String, pub String);
543
544 #[derive(CandidType, Deserialize)]
545 pub enum StreamingStrategy {
546 Callback(CallbackStrategy),
547 }
548
549 #[derive(CandidType, Deserialize)]
550 pub struct HttpResponse {
551 pub status_code: u16,
552 pub headers: Vec<HeaderField>,
553 #[serde(with = "serde_bytes")]
554 pub body: Vec<u8>,
555 pub streaming_strategy: Option<StreamingStrategy>,
556 }
557 }
558
559 #[test]
560 fn deserialize_legacy_http_response() {
561 let bytes: Vec<u8> = Encode!(&pre_update_legacy::HttpResponse {
562 status_code: 100,
563 headers: Vec::new(),
564 body: Vec::new(),
565 streaming_strategy: None,
566 })
567 .unwrap();
568
569 let _response = Decode!(&bytes, HttpResponse).unwrap();
570 }
571
572 #[test]
573 fn deserialize_response_with_token() {
574 use candid::{types::Label, Func, Principal};
575
576 fn decode<C: CandidType + DeserializeOwned>(bytes: &[u8]) {
577 let response = Decode!(bytes, HttpResponse::<_, C>).unwrap();
578 assert_eq!(response.status_code, 100);
579 let Some(StreamingStrategy::Callback(CallbackStrategy { token, .. })) =
580 response.streaming_strategy
581 else {
582 panic!("streaming_strategy was missing");
583 };
584 let Token(IDLValue::Record(fields)) = token else {
585 panic!("token type mismatched {token:?}");
586 };
587 assert!(fields.contains(&IDLField {
588 id: Label::Named("key".into()),
589 val: IDLValue::Text("foo".into())
590 }));
591 assert!(fields.contains(&IDLField {
592 id: Label::Named("content_encoding".into()),
593 val: IDLValue::Text("bar".into())
594 }));
595 assert!(fields.contains(&IDLField {
596 id: Label::Named("index".into()),
597 val: IDLValue::Nat(42u8.into())
598 }));
599 assert!(fields.contains(&IDLField {
600 id: Label::Named("sha256".into()),
601 val: IDLValue::None
602 }));
603 }
604
605 let bytes = Encode!(&HttpResponse {
607 status_code: 100,
608 headers: Vec::new(),
609 body: Vec::new(),
610 streaming_strategy: Some(StreamingStrategy::Callback(CallbackStrategy {
611 callback: pre_update_legacy::CallbackFunc(Func {
612 principal: Principal::from_text("2chl6-4hpzw-vqaaa-aaaaa-c").unwrap(),
613 method: "callback".into()
614 }),
615 token: pre_update_legacy::Token {
616 key: "foo".into(),
617 content_encoding: "bar".into(),
618 index: 42u8.into(),
619 sha256: None,
620 },
621 })),
622 upgrade: None,
623 })
624 .unwrap();
625 decode::<pre_update_legacy::CallbackFunc>(&bytes);
626 decode::<HttpRequestStreamingCallbackAny>(&bytes);
627
628 let bytes = Encode!(&HttpResponse {
629 status_code: 100,
630 headers: Vec::new(),
631 body: Vec::new(),
632 streaming_strategy: Some(StreamingStrategy::Callback(CallbackStrategy::<
633 _,
634 HttpRequestStreamingCallback,
635 > {
636 callback: Func {
637 principal: Principal::from_text("2chl6-4hpzw-vqaaa-aaaaa-c").unwrap(),
638 method: "callback".into()
639 }
640 .into(),
641 token: pre_update_legacy::Token {
642 key: "foo".into(),
643 content_encoding: "bar".into(),
644 index: 42u8.into(),
645 sha256: None,
646 },
647 })),
648 upgrade: None,
649 })
650 .unwrap();
651 decode::<HttpRequestStreamingCallback>(&bytes);
652 decode::<HttpRequestStreamingCallbackAny>(&bytes);
653 }
654
655 #[test]
656 fn deserialize_streaming_response_with_token() {
657 use candid::types::Label;
658
659 let bytes: Vec<u8> = Encode!(&StreamingCallbackHttpResponse {
660 body: b"this is a body".as_ref().into(),
661 token: Some(pre_update_legacy::Token {
662 key: "foo".into(),
663 content_encoding: "bar".into(),
664 index: 42u8.into(),
665 sha256: None,
666 }),
667 })
668 .unwrap();
669
670 let response = Decode!(&bytes, StreamingCallbackHttpResponse).unwrap();
671 assert_eq!(response.body, b"this is a body");
672 let Some(Token(IDLValue::Record(fields))) = response.token else {
673 panic!("token type mismatched {:?}", response.token);
674 };
675 assert!(fields.contains(&IDLField {
676 id: Label::Named("key".into()),
677 val: IDLValue::Text("foo".into())
678 }));
679 assert!(fields.contains(&IDLField {
680 id: Label::Named("content_encoding".into()),
681 val: IDLValue::Text("bar".into())
682 }));
683 assert!(fields.contains(&IDLField {
684 id: Label::Named("index".into()),
685 val: IDLValue::Nat(42u8.into())
686 }));
687 assert!(fields.contains(&IDLField {
688 id: Label::Named("sha256".into()),
689 val: IDLValue::None
690 }));
691 }
692
693 #[test]
694 fn deserialize_streaming_response_without_token() {
695 mod missing_token {
696 use candid::{CandidType, Deserialize};
697 #[derive(Debug, Clone, CandidType, Deserialize)]
699 pub struct StreamingCallbackHttpResponse {
700 #[serde(with = "serde_bytes")]
702 pub body: Vec<u8>,
703 }
704 }
705 let bytes: Vec<u8> = Encode!(&missing_token::StreamingCallbackHttpResponse {
706 body: b"this is a body".as_ref().into(),
707 })
708 .unwrap();
709
710 let response = Decode!(&bytes, StreamingCallbackHttpResponse).unwrap();
711 assert_eq!(response.body, b"this is a body");
712 assert_eq!(response.token, None);
713
714 let bytes: Vec<u8> = Encode!(&StreamingCallbackHttpResponse {
715 body: b"this is a body".as_ref().into(),
716 token: Option::<pre_update_legacy::Token>::None,
717 })
718 .unwrap();
719
720 let response = Decode!(&bytes, StreamingCallbackHttpResponse).unwrap();
721 assert_eq!(response.body, b"this is a body");
722 assert_eq!(response.token, None);
723 }
724
725 #[test]
726 fn deserialize_with_enum_token() {
727 #[derive(Debug, Clone, CandidType, Deserialize)]
728 pub enum EnumToken {
729 Foo,
730 Bar,
731 Baz,
732 }
733 #[derive(Debug, Clone, CandidType, Deserialize)]
734 pub struct EmbedToken {
735 value: String,
736 other_value: EnumToken,
737 }
738
739 let bytes: Vec<u8> = Encode!(&StreamingCallbackHttpResponse {
740 body: b"this is a body".as_ref().into(),
741 token: Some(EnumToken::Foo),
742 })
743 .unwrap();
744
745 let response = Decode!(&bytes, StreamingCallbackHttpResponse).unwrap();
746 assert_eq!(response.body, b"this is a body");
747 assert!(response.token.is_some());
748
749 let bytes: Vec<u8> = Encode!(&StreamingCallbackHttpResponse {
750 body: b"this is a body".as_ref().into(),
751 token: Option::<EnumToken>::None,
752 })
753 .unwrap();
754
755 let response = Decode!(&bytes, StreamingCallbackHttpResponse).unwrap();
756 assert_eq!(response.body, b"this is a body");
757 assert_eq!(response.token, None);
758
759 let bytes: Vec<u8> = Encode!(&StreamingCallbackHttpResponse {
760 body: b"this is a body".as_ref().into(),
761 token: Some(EmbedToken {
762 value: "token string".into(),
763 other_value: EnumToken::Foo
764 }),
765 })
766 .unwrap();
767
768 let response = Decode!(&bytes, StreamingCallbackHttpResponse).unwrap();
769 assert_eq!(response.body, b"this is a body");
770 assert!(response.token.is_some());
771
772 let bytes: Vec<u8> = Encode!(&StreamingCallbackHttpResponse {
773 body: b"this is a body".as_ref().into(),
774 token: Option::<EmbedToken>::None,
775 })
776 .unwrap();
777
778 let response = Decode!(&bytes, StreamingCallbackHttpResponse).unwrap();
779 assert_eq!(response.body, b"this is a body");
780 assert_eq!(response.token, None);
781 }
782}