ic_http_certification/http/http_request.rs
1use crate::{HeaderField, HttpCertificationError, HttpCertificationResult};
2use candid::{
3 types::{Serializer, Type, TypeInner},
4 CandidType, Deserialize,
5};
6pub use http::Method;
7use http::Uri;
8use serde::Deserializer;
9use std::{borrow::Cow, str::FromStr};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12struct MethodWrapper(Method);
13
14impl CandidType for MethodWrapper {
15 fn _ty() -> Type {
16 TypeInner::Text.into()
17 }
18
19 fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
20 where
21 S: Serializer,
22 {
23 self.0.as_str().idl_serialize(serializer)
24 }
25}
26
27impl<'de> Deserialize<'de> for MethodWrapper {
28 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
29 where
30 D: Deserializer<'de>,
31 {
32 String::deserialize(deserializer).and_then(|method| {
33 Method::from_str(&method)
34 .map(Into::into)
35 .map_err(|_| serde::de::Error::custom("Invalid HTTP method"))
36 })
37 }
38}
39
40impl From<Method> for MethodWrapper {
41 fn from(method: Method) -> Self {
42 Self(method)
43 }
44}
45
46/// A Candid-encodable representation of an HTTP request. This struct is used by
47/// the `http_request` method of the HTTP Gateway Protocol's Candid interface.
48///
49/// # Examples
50///
51/// ```
52/// use ic_http_certification::{HttpRequest, Method};
53///
54/// let request = HttpRequest::builder()
55/// .with_method(Method::GET)
56/// .with_url("/")
57/// .with_headers(vec![("X-Custom-Foo".into(), "Bar".into())])
58/// .with_body(&[1, 2, 3])
59/// .with_certificate_version(2)
60/// .build();
61///
62/// assert_eq!(request.method(), Method::GET);
63/// assert_eq!(request.url(), "/");
64/// assert_eq!(request.headers(), &[("X-Custom-Foo".into(), "Bar".into())]);
65/// assert_eq!(request.body(), &[1, 2, 3]);
66/// assert_eq!(request.certificate_version(), Some(2));
67/// ```
68///
69/// # Helpers
70///
71/// There are also a number of convenience methods for quickly creating an [HttpRequest] with
72/// commonly used HTTP methods:
73///
74/// - [GET](HttpRequest::get)
75/// - [POST](HttpRequest::post)
76/// - [PUT](HttpRequest::put)
77/// - [PATCH](HttpRequest::patch)
78/// - [DELETE](HttpRequest::delete)
79///
80/// ```
81/// use ic_http_certification::HttpRequest;
82///
83/// let request = HttpRequest::get("/").build();
84///
85/// assert_eq!(request.method(), "GET");
86/// assert_eq!(request.url(), "/");
87/// ```
88#[derive(Clone, Debug, CandidType, Deserialize, PartialEq, Eq)]
89pub struct HttpRequest<'a> {
90 /// HTTP request method.
91 method: MethodWrapper,
92
93 /// HTTP request URL.
94 url: String,
95
96 /// HTTP request headers.
97 headers: Vec<HeaderField>,
98
99 /// HTTP request body as an array of bytes.
100 body: Cow<'a, [u8]>,
101
102 /// The max response verification version to use in the response's
103 /// certificate.
104 certificate_version: Option<u16>,
105}
106
107impl<'a> HttpRequest<'a> {
108 /// Creates a new [HttpRequestBuilder] initialized with a GET method and
109 /// the given URL.
110 ///
111 /// This method returns an instance of [HttpRequestBuilder] which can be
112 /// used to create an [HttpRequest].
113 ///
114 /// # Examples
115 ///
116 /// ```
117 /// use ic_http_certification::{HttpRequest, Method};
118 ///
119 /// let request = HttpRequest::get("/").build();
120 ///
121 /// assert_eq!(request.method(), Method::GET);
122 /// ```
123 pub fn get(url: impl Into<String>) -> HttpRequestBuilder<'a> {
124 HttpRequestBuilder::new()
125 .with_method(Method::GET)
126 .with_url(url)
127 }
128
129 /// Creates a new [HttpRequestBuilder] initialized with a POST method and
130 /// the given URL.
131 ///
132 /// This method returns an instance of [HttpRequestBuilder] which can be
133 /// used to create an [HttpRequest].
134 ///
135 /// # Examples
136 ///
137 /// ```
138 /// use ic_http_certification::{HttpRequest, Method};
139 ///
140 /// let request = HttpRequest::post("/").build();
141 ///
142 /// assert_eq!(request.method(), Method::POST);
143 /// ```
144 pub fn post(url: impl Into<String>) -> HttpRequestBuilder<'a> {
145 HttpRequestBuilder::new()
146 .with_method(Method::POST)
147 .with_url(url)
148 }
149
150 /// Creates a new [HttpRequestBuilder] initialized with a PUT method and
151 /// the given URL.
152 ///
153 /// This method returns an instance of [HttpRequestBuilder] which can be
154 /// used to create an [HttpRequest].
155 ///
156 /// # Examples
157 ///
158 /// ```
159 /// use ic_http_certification::{HttpRequest, Method};
160 ///
161 /// let request = HttpRequest::put("/").build();
162 ///
163 /// assert_eq!(request.method(), Method::PUT);
164 /// ```
165 pub fn put(url: impl Into<String>) -> HttpRequestBuilder<'a> {
166 HttpRequestBuilder::new()
167 .with_method(Method::PUT)
168 .with_url(url)
169 }
170
171 /// Creates a new [HttpRequestBuilder] initialized with a PATCH method and
172 /// the given URL.
173 ///
174 /// This method returns an instance of [HttpRequestBuilder] which can be
175 /// used to create an [HttpRequest].
176 ///
177 /// # Examples
178 ///
179 /// ```
180 /// use ic_http_certification::{HttpRequest, Method};
181 ///
182 /// let request = HttpRequest::patch("/").build();
183 ///
184 /// assert_eq!(request.method(), Method::PATCH);
185 /// ```
186 pub fn patch(url: impl Into<String>) -> HttpRequestBuilder<'a> {
187 HttpRequestBuilder::new()
188 .with_method(Method::PATCH)
189 .with_url(url)
190 }
191
192 /// Creates a new [HttpRequestBuilder] initialized with a DELETE method and
193 /// the given URL.
194 ///
195 /// This method returns an instance of [HttpRequestBuilder] which can be
196 /// used to create an [HttpRequest].
197 ///
198 /// # Examples
199 ///
200 /// ```
201 /// use ic_http_certification::{HttpRequest, Method};
202 ///
203 /// let request = HttpRequest::delete("/").build();
204 ///
205 /// assert_eq!(request.method(), Method::DELETE);
206 /// ```
207 pub fn delete(url: impl Into<String>) -> HttpRequestBuilder<'a> {
208 HttpRequestBuilder::new()
209 .with_method(Method::DELETE)
210 .with_url(url)
211 }
212
213 /// Creates and returns an instance of [HttpRequestBuilder], a builder-style object
214 /// which can be used to create an [HttpRequest].
215 ///
216 /// # Examples
217 ///
218 /// ```
219 /// use ic_http_certification::{HttpRequest, Method};
220 ///
221 /// let request = HttpRequest::builder()
222 /// .with_method(Method::GET)
223 /// .with_url("/")
224 /// .with_headers(vec![("X-Custom-Foo".into(), "Bar".into())])
225 /// .with_body(&[1, 2, 3])
226 /// .with_certificate_version(2)
227 /// .build();
228 ///
229 /// assert_eq!(request.method(), Method::GET);
230 /// assert_eq!(request.url(), "/");
231 /// assert_eq!(request.headers(), &[("X-Custom-Foo".into(), "Bar".into())]);
232 /// assert_eq!(request.body(), &[1, 2, 3]);
233 /// assert_eq!(request.certificate_version(), Some(2));
234 /// ```
235 #[inline]
236 pub fn builder() -> HttpRequestBuilder<'a> {
237 HttpRequestBuilder::new()
238 }
239
240 /// Returns the HTTP method of the request.
241 ///
242 /// # Examples
243 ///
244 /// ```
245 /// use ic_http_certification::HttpRequest;
246 ///
247 /// let request = HttpRequest::get("/").build();
248 ///
249 /// assert_eq!(request.method(), "GET");
250 /// ```
251 #[inline]
252 pub fn method(&self) -> &Method {
253 &self.method.0
254 }
255
256 /// Returns the URL of the request.
257 ///
258 /// # Examples
259 ///
260 /// ```
261 /// use ic_http_certification::HttpRequest;
262 ///
263 /// let request = HttpRequest::get("/").build();
264 ///
265 /// assert_eq!(request.url(), "/");
266 /// ```
267 #[inline]
268 pub fn url(&self) -> &str {
269 &self.url
270 }
271
272 /// Returns the headers of the request.
273 ///
274 /// # Examples
275 ///
276 /// ```
277 /// use ic_http_certification::HttpRequest;
278 ///
279 /// let request = HttpRequest::get("/")
280 /// .with_headers(vec![("Accept".into(), "text/plain".into())])
281 /// .build();
282 ///
283 /// assert_eq!(request.headers(), &[("Accept".into(), "text/plain".into())]);
284 /// ```
285 #[inline]
286 pub fn headers(&self) -> &[HeaderField] {
287 &self.headers
288 }
289
290 /// Returns a mutable reference to the HTTP headers of the request.
291 ///
292 /// # Examples
293 ///
294 /// ```
295 /// use ic_http_certification::HttpRequest;
296 ///
297 /// let mut request = HttpRequest::get("/")
298 /// .with_headers(vec![("Content-Type".into(), "text/plain".into())])
299 /// .build();
300 ///
301 /// request.headers_mut().push(("Content-Length".into(), "13".into()));
302 ///
303 /// assert_eq!(request.headers(), &[("Content-Type".into(), "text/plain".into()), ("Content-Length".into(), "13".into())]);
304 /// ```
305 #[inline]
306 pub fn headers_mut(&mut self) -> &mut Vec<HeaderField> {
307 &mut self.headers
308 }
309
310 /// Returns the body of the request.
311 ///
312 /// # Examples
313 ///
314 /// ```
315 /// use ic_http_certification::HttpRequest;
316 ///
317 /// let request = HttpRequest::get("/")
318 /// .with_body(&[1, 2, 3])
319 /// .build();
320 ///
321 /// assert_eq!(request.body(), &[1, 2, 3]);
322 /// ```
323 #[inline]
324 pub fn body(&self) -> &[u8] {
325 &self.body
326 }
327
328 /// Returns the max response verification version to use in the response's
329 /// certificate.
330 ///
331 /// # Examples
332 ///
333 /// ```
334 /// use ic_http_certification::HttpRequest;
335 ///
336 /// let request = HttpRequest::get("/")
337 /// .with_certificate_version(2)
338 /// .build();
339 ///
340 /// assert_eq!(request.certificate_version(), Some(2));
341 /// ```
342 #[inline]
343 pub fn certificate_version(&self) -> Option<u16> {
344 self.certificate_version
345 }
346
347 /// Returns the path of the request URL, without domain, query parameters or fragments.
348 ///
349 /// # Examples
350 ///
351 /// ```
352 /// use ic_http_certification::HttpRequest;
353 ///
354 /// let request = HttpRequest::get("https://canister.com/sample-asset.txt").build();
355 ///
356 /// assert_eq!(request.get_path().unwrap(), "/sample-asset.txt");
357 /// ```
358 pub fn get_path(&self) -> HttpCertificationResult<String> {
359 let uri = self
360 .url
361 .parse::<Uri>()
362 .map_err(|_| HttpCertificationError::MalformedUrl(self.url.to_string()))?;
363
364 let decoded_path = urlencoding::decode(uri.path()).map(|path| path.into_owned())?;
365 Ok(decoded_path)
366 }
367
368 /// Returns the query parameters of the request URL, if any, as a string.
369 ///
370 /// # Examples
371 ///
372 /// ```
373 /// use ic_http_certification::HttpRequest;
374 ///
375 /// let request = HttpRequest::get("https://canister.com/sample-asset.txt?foo=bar").build();
376 ///
377 /// assert_eq!(request.get_query().unwrap(), Some("foo=bar".to_string()));
378 /// ```
379 pub fn get_query(&self) -> HttpCertificationResult<Option<String>> {
380 self.url
381 .parse::<Uri>()
382 .map(|uri| uri.query().map(|uri| uri.to_owned()))
383 .map_err(|_| HttpCertificationError::MalformedUrl(self.url.to_string()))
384 }
385}
386
387/// An HTTP request builder.
388///
389/// This type can be used to construct an instance of an [HttpRequest] using a builder-like
390/// pattern.
391///
392/// # Examples
393///
394/// ```
395/// use ic_http_certification::{HttpRequestBuilder, Method};
396///
397/// let request = HttpRequestBuilder::new()
398/// .with_method(Method::GET)
399/// .with_url("/")
400/// .with_headers(vec![("X-Custom-Foo".into(), "Bar".into())])
401/// .with_body(&[1, 2, 3])
402/// .with_certificate_version(2)
403/// .build();
404///
405/// assert_eq!(request.method(), Method::GET);
406/// assert_eq!(request.url(), "/");
407/// assert_eq!(request.headers(), &[("X-Custom-Foo".into(), "Bar".into())]);
408/// assert_eq!(request.body(), &[1, 2, 3]);
409/// assert_eq!(request.certificate_version(), Some(2));
410/// ```
411#[derive(Debug, Clone, Default)]
412pub struct HttpRequestBuilder<'a> {
413 method: Option<MethodWrapper>,
414 url: Option<String>,
415 headers: Vec<HeaderField>,
416 body: Cow<'a, [u8]>,
417 certificate_version: Option<u16>,
418}
419
420impl<'a> HttpRequestBuilder<'a> {
421 /// Creates a new instance of the [HttpRequestBuilder] that can be used to
422 /// construct an [HttpRequest].
423 ///
424 /// # Examples
425 ///
426 /// ```
427 /// use ic_http_certification::{HttpRequestBuilder, Method};
428 ///
429 /// let request = HttpRequestBuilder::new()
430 /// .with_method(Method::GET)
431 /// .with_url("/")
432 /// .with_headers(vec![("X-Custom-Foo".into(), "Bar".into())])
433 /// .with_body(&[1, 2, 3])
434 /// .with_certificate_version(2)
435 /// .build();
436 ///
437 /// assert_eq!(request.method(), Method::GET);
438 /// assert_eq!(request.url(), "/");
439 /// assert_eq!(request.headers(), &[("X-Custom-Foo".into(), "Bar".into())]);
440 /// assert_eq!(request.body(), &[1, 2, 3]);
441 /// assert_eq!(request.certificate_version(), Some(2));
442 /// ```
443 #[inline]
444 pub fn new() -> Self {
445 Self::default()
446 }
447
448 /// Set the HTTP method of the [HttpRequest].
449 ///
450 /// This function will accept both owned and borrowed values. By default,
451 /// the method will be set to `"GET"`.
452 ///
453 /// # Examples
454 ///
455 /// ```
456 /// use ic_http_certification::{HttpRequestBuilder, Method};
457 ///
458 /// let request = HttpRequestBuilder::new()
459 /// .with_method(Method::GET)
460 /// .build();
461 ///
462 /// assert_eq!(request.method(), Method::GET);
463 /// ```
464 #[inline]
465 pub fn with_method(mut self, method: Method) -> Self {
466 self.method = Some(method.into());
467
468 self
469 }
470
471 /// Set the HTTP URL of the [HttpRequest].
472 ///
473 /// This function will accept both owned and borrowed values. By default,
474 /// the URL will be set to `"/"`.
475 ///
476 /// # Examples
477 ///
478 /// ```
479 /// use ic_http_certification::HttpRequestBuilder;
480 ///
481 /// let request = HttpRequestBuilder::new()
482 /// .with_url("/")
483 /// .build();
484 ///
485 /// assert_eq!(request.url(), "/");
486 /// ```
487 #[inline]
488 pub fn with_url(mut self, url: impl Into<String>) -> Self {
489 self.url = Some(url.into());
490
491 self
492 }
493
494 /// Set the HTTP headers of the [HttpRequest].
495 ///
496 /// By default the headers will be an empty array.
497 ///
498 /// # Examples
499 ///
500 /// ```
501 /// use ic_http_certification::{HttpRequestBuilder, HeaderField};
502 ///
503 /// let request = HttpRequestBuilder::new()
504 /// .with_headers(vec![("X-Custom-Foo".into(), "Bar".into())])
505 /// .build();
506 ///
507 /// assert_eq!(request.headers(), &[("X-Custom-Foo".into(), "Bar".into())]);
508 /// ```
509 #[inline]
510 pub fn with_headers(mut self, headers: Vec<HeaderField>) -> Self {
511 self.headers = headers;
512
513 self
514 }
515
516 /// Set the HTTP body of the [HttpRequest].
517 ///
518 /// This function will accept both owned and borrowed values. By default,
519 /// the body will be an empty array.
520 ///
521 /// # Examples
522 ///
523 /// ```
524 /// use ic_http_certification::HttpRequestBuilder;
525 ///
526 /// let request = HttpRequestBuilder::new()
527 /// .with_body(&[1, 2, 3])
528 /// .build();
529 ///
530 /// assert_eq!(request.body(), &[1, 2, 3]);
531 /// ```
532 #[inline]
533 pub fn with_body(mut self, body: impl Into<Cow<'a, [u8]>>) -> Self {
534 self.body = body.into();
535
536 self
537 }
538
539 /// Set the max response verification vwersion to use in the
540 /// [crate::HttpResponse] certificate.
541 ///
542 /// By default, the certificate version will be `None`, which
543 /// is equivalent to setting it to version `1`.
544 ///
545 /// # Examples
546 ///
547 /// ```
548 /// use ic_http_certification::HttpRequestBuilder;
549 ///
550 /// let request = HttpRequestBuilder::new()
551 /// .with_certificate_version(2)
552 /// .build();
553 ///
554 /// assert_eq!(request.certificate_version(), Some(2));
555 /// ```
556 #[inline]
557 pub fn with_certificate_version(mut self, certificate_version: u16) -> Self {
558 self.certificate_version = Some(certificate_version);
559
560 self
561 }
562
563 /// Build an [HttpRequest] from the builder.
564 ///
565 /// If the method is not set, it will default to `"GET"`.
566 /// If the URL is not set, it will default to `"/"`.
567 /// If the certificate version is not set, it will default to `1`.
568 /// If the headers or body are not set, they will default to empty arrays.
569 ///
570 /// # Examples
571 ///
572 /// ```
573 /// use ic_http_certification::{HttpRequestBuilder, Method};
574 ///
575 /// let request = HttpRequestBuilder::new()
576 /// .with_method(Method::GET)
577 /// .with_url("/")
578 /// .with_headers(vec![("X-Custom-Foo".into(), "Bar".into())])
579 /// .with_body(&[1, 2, 3])
580 /// .with_certificate_version(2)
581 /// .build();
582 ///
583 /// assert_eq!(request.method(), Method::GET);
584 /// assert_eq!(request.url(), "/");
585 /// assert_eq!(request.headers(), &[("X-Custom-Foo".into(), "Bar".into())]);
586 /// assert_eq!(request.body(), &[1, 2, 3]);
587 /// assert_eq!(request.certificate_version(), Some(2));
588 /// ```
589 #[inline]
590 pub fn build(self) -> HttpRequest<'a> {
591 HttpRequest {
592 method: self.method.unwrap_or(Method::GET.into()),
593 url: self.url.unwrap_or("/".to_string()),
594 headers: self.headers,
595 body: self.body,
596 certificate_version: self.certificate_version,
597 }
598 }
599
600 /// Build an [HttpUpdateRequest] from the builder.
601 ///
602 /// If the method is not set, it will default to `"GET"`.
603 /// If the URL is not set, it will default to `"/"`.
604 /// If the headers or body are not set, they will default to empty arrays.
605 ///
606 /// # Examples
607 ///
608 /// ```
609 /// use ic_http_certification::{HttpRequestBuilder, Method};
610 ///
611 /// let update_request = HttpRequestBuilder::new()
612 /// .with_method(Method::GET)
613 /// .with_url("/")
614 /// .with_headers(vec![("X-Custom-Foo".into(), "Bar".into())])
615 /// .with_body(&[1, 2, 3])
616 /// .build_update();
617 ///
618 /// assert_eq!(update_request.method(), Method::GET);
619 /// assert_eq!(update_request.url(), "/");
620 /// assert_eq!(update_request.headers(), &[("X-Custom-Foo".into(), "Bar".into())]);
621 /// assert_eq!(update_request.body(), &[1, 2, 3]);
622 /// ```
623 #[inline]
624 pub fn build_update(self) -> HttpUpdateRequest<'a> {
625 HttpUpdateRequest {
626 method: self.method.unwrap_or(Method::GET.into()),
627 url: self.url.unwrap_or("/".to_string()),
628 headers: self.headers,
629 body: self.body,
630 }
631 }
632}
633
634/// A Candid-encodable representation of an HTTP update request. This struct is
635/// used by the `http_update_request` method of the HTTP Gateway Protocol.
636///
637/// This is the same as [HttpRequest], excluding the
638/// [certificate_version](HttpRequest::certificate_version) property.
639///
640/// # Examples
641///
642/// ```
643/// use ic_http_certification::{HttpUpdateRequest, HttpRequest, Method};
644///
645/// let request = HttpRequest::get("/")
646/// .with_method(Method::GET)
647/// .with_url("/")
648/// .with_headers(vec![("X-Custom-Foo".into(), "Bar".into())])
649/// .with_body(&[1, 2, 3])
650/// .with_certificate_version(2)
651/// .build();
652/// let update_request = HttpUpdateRequest::from(request);
653///
654/// assert_eq!(update_request.method(), Method::GET);
655/// assert_eq!(update_request.url(), "/");
656/// assert_eq!(update_request.headers(), &[("X-Custom-Foo".into(), "Bar".into())]);
657/// assert_eq!(update_request.body(), &[1, 2, 3]);
658/// ```
659#[derive(Clone, Debug, CandidType, Deserialize, PartialEq, Eq)]
660pub struct HttpUpdateRequest<'a> {
661 /// HTTP request method.
662 method: MethodWrapper,
663
664 /// HTTP request URL.
665 url: String,
666
667 /// HTTP request headers.
668 headers: Vec<HeaderField>,
669
670 /// HTTP request body as an array of bytes.
671 body: Cow<'a, [u8]>,
672}
673
674impl<'a> HttpUpdateRequest<'a> {
675 /// Returns the HTTP method of the request.
676 ///
677 /// # Examples
678 ///
679 /// ```
680 /// use ic_http_certification::HttpRequest;
681 ///
682 /// let request = HttpRequest::get("/").build_update();
683 ///
684 /// assert_eq!(request.method(), "GET");
685 /// ```
686 #[inline]
687 pub fn method(&self) -> &Method {
688 &self.method.0
689 }
690
691 /// Returns the URL of the request.
692 ///
693 /// # Examples
694 ///
695 /// ```
696 /// use ic_http_certification::HttpRequest;
697 ///
698 /// let request = HttpRequest::get("/").build_update();
699 ///
700 /// assert_eq!(request.url(), "/");
701 /// ```
702 #[inline]
703 pub fn url(&self) -> &str {
704 &self.url
705 }
706
707 /// Returns the headers of the request.
708 ///
709 /// # Examples
710 ///
711 /// ```
712 /// use ic_http_certification::HttpRequest;
713 ///
714 /// let request = HttpRequest::get("/")
715 /// .with_headers(vec![("Accept".into(), "text/plain".into())])
716 /// .build_update();
717 ///
718 /// assert_eq!(request.headers(), &[("Accept".into(), "text/plain".into())]);
719 /// ```
720 #[inline]
721 pub fn headers(&self) -> &[HeaderField] {
722 &self.headers
723 }
724
725 /// Returns the body of the request.
726 ///
727 /// # Examples
728 ///
729 /// ```
730 /// use ic_http_certification::HttpRequest;
731 ///
732 /// let request = HttpRequest::get("/")
733 /// .with_body(&[1, 2, 3])
734 /// .build_update();
735 ///
736 /// assert_eq!(request.body(), &[1, 2, 3]);
737 /// ```
738 #[inline]
739 pub fn body(&self) -> &[u8] {
740 &self.body
741 }
742
743 /// Returns the path of the request URL, without domain, query parameters or fragments.
744 ///
745 /// # Examples
746 ///
747 /// ```
748 /// use ic_http_certification::HttpRequest;
749 ///
750 /// let request = HttpRequest::get("https://canister.com/sample-asset.txt").build();
751 ///
752 /// assert_eq!(request.get_path().unwrap(), "/sample-asset.txt");
753 /// ```
754 pub fn get_path(&self) -> HttpCertificationResult<String> {
755 let uri = self
756 .url
757 .parse::<Uri>()
758 .map_err(|_| HttpCertificationError::MalformedUrl(self.url.to_string()))?;
759
760 let decoded_path = urlencoding::decode(uri.path()).map(|path| path.into_owned())?;
761 Ok(decoded_path)
762 }
763
764 /// Returns the query parameters of the request URL, if any, as a string.
765 ///
766 /// # Examples
767 ///
768 /// ```
769 /// use ic_http_certification::HttpRequest;
770 ///
771 /// let request = HttpRequest::get("https://canister.com/sample-asset.txt?foo=bar").build();
772 ///
773 /// assert_eq!(request.get_query().unwrap(), Some("foo=bar".to_string()));
774 /// ```
775 pub fn get_query(&self) -> HttpCertificationResult<Option<String>> {
776 self.url
777 .parse::<Uri>()
778 .map(|uri| uri.query().map(|uri| uri.to_owned()))
779 .map_err(|_| HttpCertificationError::MalformedUrl(self.url.to_string()))
780 }
781}
782
783impl<'a> From<HttpRequest<'a>> for HttpUpdateRequest<'a> {
784 fn from(req: HttpRequest<'a>) -> Self {
785 HttpUpdateRequest {
786 method: req.method,
787 url: req.url,
788 headers: req.headers,
789 body: req.body,
790 }
791 }
792}
793
794#[cfg(test)]
795mod tests {
796 use super::*;
797
798 #[test]
799 fn request_get_uri() {
800 let req = HttpRequest::get("https://canister.com/sample-asset.txt").build();
801
802 let path = req.get_path().unwrap();
803 let query = req.get_query().unwrap();
804
805 assert_eq!(path, "/sample-asset.txt");
806 assert!(query.is_none());
807 }
808
809 #[test]
810 fn request_get_encoded_uri() {
811 let test_requests = [
812 (
813 HttpRequest::get("https://canister.com/%73ample-asset.txt").build(),
814 "/sample-asset.txt",
815 "",
816 ),
817 (
818 HttpRequest::get("https://canister.com/path/123?foo=test%20component&bar=1").build(),
819 "/path/123",
820 "foo=test%20component&bar=1",
821 ),
822 (
823 HttpRequest::get("https://canister.com/a%20file.txt").build(),
824 "/a file.txt",
825 "",
826 ),
827 (
828 HttpRequest::get("https://canister.com/mujin0722/3888-zjfrd-tqaaa-aaaaf-qakia-cai/%E6%97%A0%E8%AE%BA%E7%BE%8E%E8%81%94%E5%82%A8%E6%98%AF%E5%90%A6%E5%8A%A0%E6%81%AFbtc%E4%BB%8D%E5%B0%86%E5%9B%9E%E5%88%B07%E4%B8%87%E5%88%80").build(),
829 "/mujin0722/3888-zjfrd-tqaaa-aaaaf-qakia-cai/无论美联储是否加息btc仍将回到7万刀",
830 "",
831 ),
832 ];
833
834 for (req, expected_path, expected_query) in test_requests.iter() {
835 let path = req.get_path().unwrap();
836 let query = req.get_query().unwrap();
837
838 assert_eq!(path, *expected_path);
839 assert_eq!(query.unwrap_or_default(), *expected_query);
840 }
841 }
842}