ic_utils/interfaces/
http_request.rs

1//! The canister interface for canisters that implement HTTP requests.
2
3use 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/// A canister that can serve a HTTP request.
25#[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/// A key-value pair for a HTTP header.
36#[derive(Debug, CandidType, Clone, Deserialize)]
37pub struct HeaderField<'a>(pub Cow<'a, str>, pub Cow<'a, str>);
38
39/// The important components of an HTTP request.
40#[derive(Debug, Clone, CandidType)]
41struct HttpRequest<'a, H> {
42    /// The HTTP method string.
43    pub method: &'a str,
44    /// The URL that was visited.
45    pub url: &'a str,
46    /// The request headers.
47    pub headers: H,
48    /// The request body.
49    pub body: &'a [u8],
50    /// The certificate version.
51    pub certificate_version: Option<&'a u16>,
52}
53
54/// The important components of an HTTP update request.
55/// This is the same as `HttpRequest`, excluding the `certificate_version` property.
56#[derive(Debug, Clone, CandidType)]
57struct HttpUpdateRequest<'a, H> {
58    /// The HTTP method string.
59    pub method: &'a str,
60    /// The URL that was visited.
61    pub url: &'a str,
62    /// The request headers.
63    pub headers: H,
64    /// The request body.
65    pub body: &'a [u8],
66}
67
68/// A wrapper around an iterator of headers
69#[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/// A HTTP response.
92#[derive(Debug, Clone, CandidType, Deserialize)]
93pub struct HttpResponse<Token = self::Token, Callback = HttpRequestStreamingCallback> {
94    /// The HTTP status code.
95    pub status_code: u16,
96    /// The response header map.
97    pub headers: Vec<HeaderField<'static>>,
98    /// The response body.
99    #[serde(with = "serde_bytes")]
100    pub body: Vec<u8>,
101    /// The strategy for streaming the rest of the data, if the full response is to be streamed.
102    pub streaming_strategy: Option<StreamingStrategy<Token, Callback>>,
103    /// Whether the query call should be upgraded to an update call.
104    pub upgrade: Option<bool>,
105}
106
107impl<T1, C1> HttpResponse<T1, C1> {
108    /// Convert another streaming strategy
109    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    /// Convert this streaming strategy
119    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    /// Attempt to convert another streaming strategy
127    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    /// Attempt to convert this streaming strategy
146    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/// Possible strategies for a streaming response.
158#[derive(Debug, Clone, CandidType, Deserialize)]
159pub enum StreamingStrategy<Token = self::Token, Callback = HttpRequestStreamingCallback> {
160    /// A callback-based streaming strategy, where a callback function is provided for continuing the stream.
161    Callback(CallbackStrategy<Token, Callback>),
162}
163
164impl<T1, C1> StreamingStrategy<T1, C1> {
165    /// Convert another streaming strategy
166    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    /// Convert this streaming strategy
172    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    /// Attempt to convert another streaming strategy
180    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    /// Attempt to convert this streaming strategy
192    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/// A callback-token pair for a callback streaming strategy.
204#[derive(Debug, Clone, CandidType, Deserialize)]
205pub struct CallbackStrategy<Token = self::Token, Callback = HttpRequestStreamingCallback> {
206    /// The callback function to be called to continue the stream.
207    pub callback: Callback,
208    /// The token to pass to the function.
209    pub token: Token,
210}
211
212impl<T1, C1> CallbackStrategy<T1, C1> {
213    /// Convert another callback strategy
214    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    /// Convert this callback strategy
221    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    /// Attempt to convert another callback strategy
229    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    /// Attempt to convert this callback strategy
242    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/// A callback of any type, extremely permissive
254#[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        // We cannot implement serialize, since our type must be `Reserved` in order to accept anything.
263        // Attempting to serialize this type is always an error and should be regarded as a compile time error.
264        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        // Ya know it says `ignored`, but what if we just didn't ignore it.
271        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/// A callback of type `shared query (Token) -> async StreamingCallbackHttpResponse`
287#[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/// The next chunk of a streaming HTTP response.
334#[derive(Debug, Clone, CandidType, Deserialize)]
335pub struct StreamingCallbackHttpResponse<Token = self::Token> {
336    /// The body of the stream chunk.
337    #[serde(with = "serde_bytes")]
338    pub body: Vec<u8>,
339    /// The new stream continuation token.
340    pub token: Option<Token>,
341}
342
343/// A token for continuing a callback streaming strategy. This type cannot be serialized despite implementing `CandidType`
344#[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        // We cannot implement serialize, since our type must be `Reserved` in order to accept anything.
353        // Attempting to serialize this type is always an error and should be regarded as a compile time error.
354        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        // Ya know it says `ignored`, but what if we just didn't ignore it.
361        deserializer
362            .deserialize_ignored_any(IDLValueVisitor)
363            .map(Token)
364    }
365}
366
367/// A marker type to match unconstrained callback arguments
368#[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        // We cannot implement serialize, since our type must be `Empty` in order to accept anything.
377        // Attempting to serialize this type is always an error and should be regarded as a compile time error.
378        unimplemented!("Token is not serializable")
379    }
380}
381
382impl<'agent> HttpRequestCanister<'agent> {
383    /// Create an instance of a `HttpRequestCanister` interface pointing to the specified Canister ID.
384    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    /// Create a `HttpRequestCanister` interface from an existing canister object.
395    pub fn from_canister(canister: Canister<'agent>) -> Self {
396        Self(canister)
397    }
398}
399
400impl<'agent> HttpRequestCanister<'agent> {
401    /// Performs a HTTP request, receiving a HTTP response.
402    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    /// Performs a HTTP request, receiving a HTTP response.
423    /// `T` and `C` are the `token` and `callback` types for the `streaming_strategy`.
424    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    /// Performs a HTTP request over an update call. Unlike query calls, update calls must pass consensus
449    /// and therefore cannot be tampered with by a malicious node.
450    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    /// Performs a HTTP request over an update call. Unlike query calls, update calls must pass consensus
461    /// and therefore cannot be tampered with by a malicious node.
462    /// `T` and `C` are the `token` and `callback` types for the `streaming_strategy`.
463    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    /// Retrieves the next chunk of a stream from a streaming callback, using the method from [`CallbackStrategy`].
486    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    /// Retrieves the next chunk of a stream from a streaming callback, using the method from [`CallbackStrategy`].
495    /// `T` is the `token` type.
496    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        // Test if we can load legacy responses that use the `Func` workaround hack
606        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            /// The next chunk of a streaming HTTP response.
698            #[derive(Debug, Clone, CandidType, Deserialize)]
699            pub struct StreamingCallbackHttpResponse {
700                /// The body of the stream chunk.
701                #[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}