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}