1
1
//! The canister interface for canisters that implement HTTP requests.
2
2
3
- use crate :: { call:: AsyncCall , call:: SyncCall , canister:: CanisterBuilder , Canister } ;
4
- use candid:: { CandidType , Deserialize , Func , Nat } ;
3
+ use crate :: {
4
+ call:: { AsyncCall , SyncCall } ,
5
+ canister:: CanisterBuilder ,
6
+ Canister ,
7
+ } ;
8
+ use candid:: {
9
+ parser:: value:: { IDLValue , IDLValueVisitor } ,
10
+ types:: { Serializer , Type } ,
11
+ CandidType , Deserialize , Func ,
12
+ } ;
5
13
use ic_agent:: { export:: Principal , Agent } ;
6
- use serde_bytes:: ByteBuf ;
7
14
use std:: fmt:: Debug ;
8
15
9
16
/// A canister that can serve a HTTP request.
@@ -28,59 +35,72 @@ pub struct HttpRequest<'body> {
28
35
pub body : & ' body [ u8 ] ,
29
36
}
30
37
31
- /// A token for continuing a callback streaming strategy.
32
- #[ derive( Debug , Clone , CandidType , Deserialize ) ]
33
- pub struct Token {
34
- key : String ,
35
- content_encoding : String ,
36
- index : Nat ,
37
- // The sha ensures that a client doesn't stream part of one version of an asset
38
- // followed by part of a different asset, even if not checking the certificate.
39
- sha256 : Option < ByteBuf > ,
40
- }
41
-
42
- /// A callback-token pair for a callback streaming strategy.
43
- #[ derive( Debug , Clone , CandidType , Deserialize ) ]
44
- pub struct CallbackStrategy {
45
- /// The callback function to be called to continue the stream.
46
- pub callback : Func ,
47
- /// The token to pass to the function.
48
- pub token : Token ,
49
- }
50
-
51
- /// Possible strategies for a streaming response.
52
- #[ derive( Debug , Clone , CandidType , Deserialize ) ]
53
- pub enum StreamingStrategy {
54
- /// A callback-based streaming strategy, where a callback function is provided for continuing the stream.
55
- Callback ( CallbackStrategy ) ,
56
- }
57
-
58
38
/// A HTTP response.
59
39
#[ derive( Debug , Clone , CandidType , Deserialize ) ]
60
- pub struct HttpResponse {
40
+ pub struct HttpResponse < Token = self :: Token > {
61
41
/// The HTTP status code.
62
42
pub status_code : u16 ,
63
43
/// The response header map.
64
44
pub headers : Vec < HeaderField > ,
65
- #[ serde( with = "serde_bytes" ) ]
66
45
/// The response body.
46
+ #[ serde( with = "serde_bytes" ) ]
67
47
pub body : Vec < u8 > ,
68
48
/// The strategy for streaming the rest of the data, if the full response is to be streamed.
69
- pub streaming_strategy : Option < StreamingStrategy > ,
49
+ pub streaming_strategy : Option < StreamingStrategy < Token > > ,
70
50
/// Whether the query call should be upgraded to an update call.
71
51
pub upgrade : Option < bool > ,
72
52
}
73
53
54
+ /// Possible strategies for a streaming response.
55
+ #[ derive( Debug , Clone , CandidType , Deserialize ) ]
56
+ pub enum StreamingStrategy < Token = self :: Token > {
57
+ /// A callback-based streaming strategy, where a callback function is provided for continuing the stream.
58
+ Callback ( CallbackStrategy < Token > ) ,
59
+ }
60
+
61
+ /// A callback-token pair for a callback streaming strategy.
62
+ #[ derive( Debug , Clone , CandidType , Deserialize ) ]
63
+ pub struct CallbackStrategy < Token = self :: Token > {
64
+ /// The callback function to be called to continue the stream.
65
+ pub callback : Func ,
66
+ /// The token to pass to the function.
67
+ pub token : Token ,
68
+ }
69
+
74
70
/// The next chunk of a streaming HTTP response.
75
71
#[ derive( Debug , Clone , CandidType , Deserialize ) ]
76
- pub struct StreamingCallbackHttpResponse {
72
+ pub struct StreamingCallbackHttpResponse < Token = self :: Token > {
77
73
/// The body of the stream chunk.
78
74
#[ serde( with = "serde_bytes" ) ]
79
75
pub body : Vec < u8 > ,
80
76
/// The new stream continuation token.
81
77
pub token : Option < Token > ,
82
78
}
83
79
80
+ /// A token for continuing a callback streaming strategy.
81
+ #[ derive( Debug , Clone , PartialEq ) ]
82
+ pub struct Token ( pub IDLValue ) ;
83
+
84
+ impl CandidType for Token {
85
+ fn _ty ( ) -> Type {
86
+ Type :: Reserved
87
+ }
88
+ fn idl_serialize < S : Serializer > ( & self , _serializer : S ) -> Result < ( ) , S :: Error > {
89
+ // We cannot implement serialize, since our type must be `Reserved` in order to accept anything.
90
+ // Attempting to serialize this type is always an error and should be regarded as a compile time error.
91
+ unimplemented ! ( "Token is not serializable" )
92
+ }
93
+ }
94
+
95
+ impl < ' de > Deserialize < ' de > for Token {
96
+ fn deserialize < D : serde:: de:: Deserializer < ' de > > ( deserializer : D ) -> Result < Self , D :: Error > {
97
+ // Ya know it says `ignored`, but what if we just didn't ignore it.
98
+ deserializer
99
+ . deserialize_ignored_any ( IDLValueVisitor )
100
+ . map ( Token )
101
+ }
102
+ }
103
+
84
104
impl HttpRequestCanister {
85
105
/// Create an instance of a [Canister] implementing the [HttpRequestCanister] interface
86
106
/// and pointing to the right Canister ID.
@@ -151,25 +171,30 @@ impl<'agent> Canister<'agent, HttpRequestCanister> {
151
171
method : M ,
152
172
token : Token ,
153
173
) -> impl ' agent + SyncCall < ( StreamingCallbackHttpResponse , ) > {
154
- self . query_ ( & method. into ( ) ) . with_arg ( token) . build ( )
174
+ self . query_ ( & method. into ( ) ) . with_value_arg ( token. 0 ) . build ( )
155
175
}
156
176
}
157
177
158
178
#[ cfg( test) ]
159
179
mod test {
160
- use super :: HttpResponse ;
161
- use candid:: { Decode , Encode } ;
180
+ use super :: {
181
+ CallbackStrategy , HttpResponse , StreamingCallbackHttpResponse , StreamingStrategy , Token ,
182
+ } ;
183
+ use candid:: {
184
+ parser:: value:: { IDLField , IDLValue } ,
185
+ Decode , Encode ,
186
+ } ;
162
187
163
188
mod pre_update_legacy {
164
189
use candid:: { CandidType , Deserialize , Func , Nat } ;
165
190
use serde_bytes:: ByteBuf ;
166
191
167
192
#[ derive( CandidType , Deserialize ) ]
168
193
pub struct Token {
169
- key : String ,
170
- content_encoding : String ,
171
- index : Nat ,
172
- sha256 : Option < ByteBuf > ,
194
+ pub key : String ,
195
+ pub content_encoding : String ,
196
+ pub index : Nat ,
197
+ pub sha256 : Option < ByteBuf > ,
173
198
}
174
199
175
200
#[ derive( CandidType , Deserialize ) ]
@@ -208,4 +233,127 @@ mod test {
208
233
209
234
let _response = Decode ! ( & bytes, HttpResponse ) . unwrap ( ) ;
210
235
}
236
+
237
+ #[ test]
238
+ fn deserialize_response_with_token ( ) {
239
+ use candid:: { types:: Label , Func , Principal } ;
240
+
241
+ let bytes: Vec < u8 > = Encode ! ( & HttpResponse {
242
+ status_code: 100 ,
243
+ headers: Vec :: new( ) ,
244
+ body: Vec :: new( ) ,
245
+ streaming_strategy: Some ( StreamingStrategy :: Callback ( CallbackStrategy {
246
+ callback: Func {
247
+ principal: Principal :: from_text( "2chl6-4hpzw-vqaaa-aaaaa-c" ) . unwrap( ) ,
248
+ method: "callback" . into( )
249
+ } ,
250
+ token: pre_update_legacy:: Token {
251
+ key: "foo" . into( ) ,
252
+ content_encoding: "bar" . into( ) ,
253
+ index: 42 . into( ) ,
254
+ sha256: None ,
255
+ } ,
256
+ } ) ) ,
257
+ upgrade: None ,
258
+ } )
259
+ . unwrap ( ) ;
260
+
261
+ let response = Decode ! ( & bytes, HttpResponse ) . unwrap ( ) ;
262
+ assert_eq ! ( response. status_code, 100 ) ;
263
+ let token = match response. streaming_strategy {
264
+ Some ( StreamingStrategy :: Callback ( CallbackStrategy { token, .. } ) ) => token,
265
+ _ => panic ! ( "streaming_strategy was missing" ) ,
266
+ } ;
267
+ let fields = match token {
268
+ Token ( IDLValue :: Record ( fields) ) => fields,
269
+ _ => panic ! ( "token type mismatched {:?}" , token) ,
270
+ } ;
271
+ assert ! ( fields. contains( & IDLField {
272
+ id: Label :: Named ( "key" . into( ) ) ,
273
+ val: IDLValue :: Text ( "foo" . into( ) )
274
+ } ) ) ;
275
+ assert ! ( fields. contains( & IDLField {
276
+ id: Label :: Named ( "content_encoding" . into( ) ) ,
277
+ val: IDLValue :: Text ( "bar" . into( ) )
278
+ } ) ) ;
279
+ assert ! ( fields. contains( & IDLField {
280
+ id: Label :: Named ( "index" . into( ) ) ,
281
+ val: IDLValue :: Nat ( 42 . into( ) )
282
+ } ) ) ;
283
+ assert ! ( fields. contains( & IDLField {
284
+ id: Label :: Named ( "sha256" . into( ) ) ,
285
+ val: IDLValue :: None
286
+ } ) ) ;
287
+ }
288
+
289
+ #[ test]
290
+ fn deserialize_streaming_response_with_token ( ) {
291
+ use candid:: types:: Label ;
292
+
293
+ let bytes: Vec < u8 > = Encode ! ( & StreamingCallbackHttpResponse {
294
+ body: b"this is a body" . as_ref( ) . into( ) ,
295
+ token: Some ( pre_update_legacy:: Token {
296
+ key: "foo" . into( ) ,
297
+ content_encoding: "bar" . into( ) ,
298
+ index: 42 . into( ) ,
299
+ sha256: None ,
300
+ } ) ,
301
+ } )
302
+ . unwrap ( ) ;
303
+
304
+ let response = Decode ! ( & bytes, StreamingCallbackHttpResponse ) . unwrap ( ) ;
305
+ assert_eq ! ( response. body, b"this is a body" ) ;
306
+ let fields = match response. token {
307
+ Some ( Token ( IDLValue :: Record ( fields) ) ) => fields,
308
+ _ => panic ! ( "token type mismatched {:?}" , response. token) ,
309
+ } ;
310
+ assert ! ( fields. contains( & IDLField {
311
+ id: Label :: Named ( "key" . into( ) ) ,
312
+ val: IDLValue :: Text ( "foo" . into( ) )
313
+ } ) ) ;
314
+ assert ! ( fields. contains( & IDLField {
315
+ id: Label :: Named ( "content_encoding" . into( ) ) ,
316
+ val: IDLValue :: Text ( "bar" . into( ) )
317
+ } ) ) ;
318
+ assert ! ( fields. contains( & IDLField {
319
+ id: Label :: Named ( "index" . into( ) ) ,
320
+ val: IDLValue :: Nat ( 42 . into( ) )
321
+ } ) ) ;
322
+ assert ! ( fields. contains( & IDLField {
323
+ id: Label :: Named ( "sha256" . into( ) ) ,
324
+ val: IDLValue :: None
325
+ } ) ) ;
326
+ }
327
+
328
+ #[ test]
329
+ fn deserialize_streaming_response_without_token ( ) {
330
+ mod missing_token {
331
+ use candid:: { CandidType , Deserialize } ;
332
+ /// The next chunk of a streaming HTTP response.
333
+ #[ derive( Debug , Clone , CandidType , Deserialize ) ]
334
+ pub struct StreamingCallbackHttpResponse {
335
+ /// The body of the stream chunk.
336
+ #[ serde( with = "serde_bytes" ) ]
337
+ pub body : Vec < u8 > ,
338
+ }
339
+ }
340
+ let bytes: Vec < u8 > = Encode ! ( & missing_token:: StreamingCallbackHttpResponse {
341
+ body: b"this is a body" . as_ref( ) . into( ) ,
342
+ } )
343
+ . unwrap ( ) ;
344
+
345
+ let response = Decode ! ( & bytes, StreamingCallbackHttpResponse ) . unwrap ( ) ;
346
+ assert_eq ! ( response. body, b"this is a body" ) ;
347
+ assert_eq ! ( response. token, None ) ;
348
+
349
+ let bytes: Vec < u8 > = Encode ! ( & StreamingCallbackHttpResponse {
350
+ body: b"this is a body" . as_ref( ) . into( ) ,
351
+ token: Option :: <pre_update_legacy:: Token >:: None ,
352
+ } )
353
+ . unwrap ( ) ;
354
+
355
+ let response = Decode ! ( & bytes, StreamingCallbackHttpResponse ) . unwrap ( ) ;
356
+ assert_eq ! ( response. body, b"this is a body" ) ;
357
+ assert_eq ! ( response. token, None ) ;
358
+ }
211
359
}
0 commit comments