Skip to main content

tide_disco/
request.rs

1// Copyright (c) 2022 Espresso Systems (espressosys.com)
2// This file is part of the tide-disco library.
3
4// You should have received a copy of the MIT License
5// along with the tide-disco library. If not, see <https://mit-license.org/>.
6
7use crate::method::Method;
8use snafu::OptionExt;
9use std::any::type_name;
10use std::collections::HashMap;
11use std::fmt::Display;
12use tagged_base64::TaggedBase64;
13use tide::http::{self, Headers, content::Accept, mime::Mime};
14use vbs::{BinarySerializer, Serializer, version::StaticVersionType};
15
16pub use disco_types::request::*;
17
18/// Parameters passed to a route handler.
19///
20/// These parameters describe the incoming request and the current server state.
21#[derive(Clone, Debug)]
22pub struct RequestParams {
23    req: http::Request,
24    post_data: Vec<u8>,
25    params: HashMap<String, RequestParamValue>,
26}
27
28impl RequestParams {
29    pub(crate) async fn new<S>(
30        mut req: tide::Request<S>,
31        formal_params: &[RequestParam],
32    ) -> Result<Self, RequestError> {
33        Ok(Self {
34            post_data: req.body_bytes().await.unwrap(),
35            params: formal_params
36                .iter()
37                .filter_map(|param| match RequestParamValue::new(&req, param) {
38                    Ok(None) => None,
39                    Ok(Some(value)) => Some(Ok((param.name.clone(), value))),
40                    Err(err) => Some(Err(err)),
41                })
42                .collect::<Result<_, _>>()?,
43            req: req.into(),
44        })
45    }
46
47    /// The [Method] used to dispatch the request.
48    pub fn method(&self) -> Method {
49        self.req.method().into()
50    }
51
52    /// The headers of the incoming request.
53    pub fn headers(&self) -> &Headers {
54        self.req.as_ref()
55    }
56
57    /// The [Accept] header of this request.
58    ///
59    /// The media type proposals in the resulting header are sorted in order of decreasing weight.
60    ///
61    /// If no [Accept] header was explicitly set, defaults to the wildcard `Accept: *`.
62    ///
63    /// # Error
64    ///
65    /// Returns [RequestError::Http] if the [Accept] header is malformed.
66    pub fn accept(&self) -> Result<Accept, RequestError> {
67        Self::accept_from_headers(self.headers())
68    }
69
70    pub(crate) fn accept_from_headers(
71        headers: impl AsRef<Headers>,
72    ) -> Result<Accept, RequestError> {
73        match Accept::from_headers(headers).map_err(|err| RequestError::Http {
74            reason: err.to_string(),
75        })? {
76            Some(mut accept) => {
77                accept.sort();
78                Ok(accept)
79            }
80            None => {
81                let mut accept = Accept::new();
82                accept.set_wildcard(true);
83                Ok(accept)
84            }
85        }
86    }
87
88    /// Get the remote address for this request.
89    ///
90    /// This is determined in the following priority:
91    /// 1. `Forwarded` header `for` key
92    /// 2. The first `X-Forwarded-For` header
93    /// 3. Peer address of the transport
94    pub fn remote(&self) -> Option<&str> {
95        self.req.remote()
96    }
97
98    /// Get the value of a named parameter.
99    ///
100    /// The name of the parameter can be given by any type that implements [Display]. Of course, the
101    /// simplest option is to use [str] or [String], as in
102    ///
103    /// ```
104    /// # use tide_disco::*;
105    /// # fn ex(req: &RequestParams) {
106    /// req.param("foo")
107    /// # ;}
108    /// ```
109    ///
110    /// However, you have the option of defining a statically typed enum representing the possible
111    /// parameters of a given route and using enum variants as parameter names. Among other
112    /// benefits, this allows you to change the client-facing parameter names just by tweaking the
113    /// [Display] implementation of your enum, without changing other code.
114    ///
115    /// ```
116    /// use std::fmt::{self, Display, Formatter};
117    ///
118    /// enum RouteParams {
119    ///     Param1,
120    ///     Param2,
121    /// }
122    ///
123    /// impl Display for RouteParams {
124    ///     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
125    ///         let name = match self {
126    ///             Self::Param1 => "param1",
127    ///             Self::Param2 => "param2",
128    ///         };
129    ///         write!(f, "{}", name)
130    ///     }
131    /// }
132    ///
133    /// # use tide_disco::*;
134    /// # fn ex(req: &RequestParams) {
135    /// req.param(&RouteParams::Param1)
136    /// # ;}
137    /// ```
138    ///
139    /// You can also use [strum_macros] to automatically derive the [Display] implementation, so you
140    /// only have to specify the client-facing names of each parameter:
141    ///
142    /// ```
143    /// #[derive(strum_macros::Display)]
144    /// enum RouteParams {
145    ///     #[strum(serialize = "param1")]
146    ///     Param1,
147    ///     #[strum(serialize = "param2")]
148    ///     Param2,
149    /// }
150    ///
151    /// # use tide_disco::*;
152    /// # fn ex(req: &RequestParams) {
153    /// req.param(&RouteParams::Param1)
154    /// # ;}
155    /// ```
156    ///
157    /// # Errors
158    ///
159    /// Returns [RequestError::MissingParam] if a parameter called `name` was not provided with the
160    /// request.
161    ///
162    /// It is recommended to implement `From<RequestError>` for the error type for your API, so that
163    /// you can use `?` with this function in a route handler. If your error type implements
164    /// [Error](crate::Error), you can easily use the [catch_all](crate::Error::catch_all)
165    /// constructor to do this:
166    ///
167    /// ```
168    /// use serde::{Deserialize, Serialize};
169    /// use snafu::Snafu;
170    /// use tide_disco::{Error, RequestError, RequestParams, StatusCode};
171    ///
172    /// type ApiState = ();
173    ///
174    /// #[derive(Debug, Snafu, Deserialize, Serialize)]
175    /// struct ApiError {
176    ///     status: StatusCode,
177    ///     msg: String,
178    /// }
179    ///
180    /// impl Error for ApiError {
181    ///     fn catch_all(status: StatusCode, msg: String) -> Self {
182    ///         Self { status, msg }
183    ///     }
184    ///
185    ///     fn status(&self) -> StatusCode {
186    ///         self.status
187    ///     }
188    /// }
189    ///
190    /// impl From<RequestError> for ApiError {
191    ///     fn from(err: RequestError) -> Self {
192    ///         Self::catch_all(StatusCode::BAD_REQUEST, err.to_string())
193    ///     }
194    /// }
195    ///
196    /// async fn my_route_handler(req: RequestParams, _state: &ApiState) -> Result<(), ApiError> {
197    ///     let param = req.param("my_param")?;
198    ///     Ok(())
199    /// }
200    /// ```
201    pub fn param<Name>(&self, name: &Name) -> Result<&RequestParamValue, RequestError>
202    where
203        Name: ?Sized + Display,
204    {
205        self.opt_param(name).context(MissingParamSnafu {
206            name: name.to_string(),
207        })
208    }
209
210    /// Get the value of a named optional parameter.
211    ///
212    /// Like [param](Self::param), but returns [None] instead of [Err] if the parametre is missing.
213    pub fn opt_param<Name>(&self, name: &Name) -> Option<&RequestParamValue>
214    where
215        Name: ?Sized + Display,
216    {
217        self.params.get(&name.to_string())
218    }
219
220    /// Get the value of a named parameter and convert it to an integer.
221    ///
222    /// Like [param](Self::param), but returns [Err] if the parameter value cannot be converted to
223    /// an integer of the desired size.
224    pub fn integer_param<Name, T>(&self, name: &Name) -> Result<T, RequestError>
225    where
226        Name: ?Sized + Display,
227        T: TryFrom<u128>,
228    {
229        self.opt_integer_param(name)?.context(MissingParamSnafu {
230            name: name.to_string(),
231        })
232    }
233
234    /// Get the value of a named optional parameter and convert it to an integer.
235    ///
236    /// Like [opt_param](Self::opt_param), but returns [Err] if the parameter value cannot be
237    /// converted to an integer of the desired size.
238    pub fn opt_integer_param<Name, T>(&self, name: &Name) -> Result<Option<T>, RequestError>
239    where
240        Name: ?Sized + Display,
241        T: TryFrom<u128>,
242    {
243        self.opt_param(name).map(|val| val.as_integer()).transpose()
244    }
245
246    /// Get the value of a named parameter and convert it to a [bool].
247    ///
248    /// Like [param](Self::param), but returns [Err] if the parameter value cannot be converted to
249    /// a [bool].
250    pub fn boolean_param<Name>(&self, name: &Name) -> Result<bool, RequestError>
251    where
252        Name: ?Sized + Display,
253    {
254        self.opt_boolean_param(name)?.context(MissingParamSnafu {
255            name: name.to_string(),
256        })
257    }
258
259    /// Get the value of a named optional parameter and convert it to a [bool].
260    ///
261    /// Like [opt_param](Self::opt_param), but returns [Err] if the parameter value cannot be
262    /// converted to a [bool].
263    pub fn opt_boolean_param<Name>(&self, name: &Name) -> Result<Option<bool>, RequestError>
264    where
265        Name: ?Sized + Display,
266    {
267        self.opt_param(name).map(|val| val.as_boolean()).transpose()
268    }
269
270    /// Get the value of a named parameter and convert it to a string.
271    ///
272    /// Like [param](Self::param), but returns [Err] if the parameter value cannot be converted to
273    /// a [String].
274    pub fn string_param<Name>(&self, name: &Name) -> Result<&str, RequestError>
275    where
276        Name: ?Sized + Display,
277    {
278        self.opt_string_param(name)?.context(MissingParamSnafu {
279            name: name.to_string(),
280        })
281    }
282
283    /// Get the value of a named optional parameter and convert it to a string.
284    ///
285    /// Like [opt_param](Self::opt_param), but returns [Err] if the parameter value cannot be
286    /// converted to a [String].
287    pub fn opt_string_param<Name>(&self, name: &Name) -> Result<Option<&str>, RequestError>
288    where
289        Name: ?Sized + Display,
290    {
291        self.opt_param(name).map(|val| val.as_string()).transpose()
292    }
293
294    /// Get the value of a named parameter and convert it to [TaggedBase64].
295    ///
296    /// Like [param](Self::param), but returns [Err] if the parameter value cannot be converted to
297    /// [TaggedBase64].
298    pub fn tagged_base64_param<Name>(&self, name: &Name) -> Result<&TaggedBase64, RequestError>
299    where
300        Name: ?Sized + Display,
301    {
302        self.opt_tagged_base64_param(name)?
303            .context(MissingParamSnafu {
304                name: name.to_string(),
305            })
306    }
307
308    /// Get the value of a named optional parameter and convert it to [TaggedBase64].
309    ///
310    /// Like [opt_param](Self::opt_param), but returns [Err] if the parameter value cannot be
311    /// converted to [TaggedBase64].
312    pub fn opt_tagged_base64_param<Name>(
313        &self,
314        name: &Name,
315    ) -> Result<Option<&TaggedBase64>, RequestError>
316    where
317        Name: ?Sized + Display,
318    {
319        self.opt_param(name)
320            .map(|val| val.as_tagged_base64())
321            .transpose()
322    }
323
324    /// Get the value of a named parameter and convert it to a custom type through [TaggedBase64].
325    ///
326    /// Like [param](Self::param), but returns [Err] if the parameter value cannot be converted to
327    /// `T`.
328    pub fn blob_param<'a, Name, T>(&'a self, name: &Name) -> Result<T, RequestError>
329    where
330        Name: ?Sized + Display,
331        T: TryFrom<&'a TaggedBase64>,
332        <T as TryFrom<&'a TaggedBase64>>::Error: Display,
333    {
334        self.opt_blob_param(name)?.context(MissingParamSnafu {
335            name: name.to_string(),
336        })
337    }
338
339    /// Get the value of a named optional parameter and convert it to a custom type through
340    /// [TaggedBase64].
341    ///
342    /// Like [opt_param](Self::opt_param), but returns [Err] if the parameter value cannot be
343    /// converted to `T`.
344    pub fn opt_blob_param<'a, Name, T>(&'a self, name: &Name) -> Result<Option<T>, RequestError>
345    where
346        Name: ?Sized + Display,
347        T: TryFrom<&'a TaggedBase64>,
348        <T as TryFrom<&'a TaggedBase64>>::Error: Display,
349    {
350        self.opt_param(name).map(|val| val.as_blob()).transpose()
351    }
352
353    pub fn body_bytes(&self) -> Vec<u8> {
354        self.post_data.clone()
355    }
356
357    pub fn body_json<T>(&self) -> Result<T, RequestError>
358    where
359        T: serde::de::DeserializeOwned,
360    {
361        serde_json::from_slice(&self.post_data.clone()).map_err(|_| RequestError::Json {})
362    }
363
364    /// Deserialize the body of a request.
365    ///
366    /// The Content-Type header is used to determine the serialization format.
367    pub fn body_auto<T, VER: StaticVersionType>(&self, _: VER) -> Result<T, RequestError>
368    where
369        T: serde::de::DeserializeOwned,
370    {
371        if let Some(content_type) = self.headers().get("Content-Type") {
372            match content_type.as_str() {
373                "application/json" => self.body_json(),
374                "application/octet-stream" => {
375                    let bytes = self.body_bytes();
376                    Serializer::<VER>::deserialize(&bytes).map_err(|_err| RequestError::Binary {})
377                }
378                _content_type => Err(RequestError::UnsupportedContentType {}),
379            }
380        } else {
381            Err(RequestError::UnsupportedContentType {})
382        }
383    }
384}
385
386#[derive(Clone, Debug, PartialEq, Eq)]
387pub enum RequestParamValue {
388    Boolean(bool),
389    Hexadecimal(u128),
390    Integer(u128),
391    TaggedBase64(TaggedBase64),
392    Literal(String),
393}
394
395impl RequestParamValue {
396    /// Parse a parameter from a [Request](tide::Request).
397    ///
398    /// Returns `Ok(Some(value))` if the parameter is present and well-formed according to `formal`,
399    /// `Ok(None)` if the parameter is optional and not present, or an error if the request is
400    /// required and not present, or present and malformed.
401    pub fn new<S>(
402        req: &tide::Request<S>,
403        formal: &RequestParam,
404    ) -> Result<Option<Self>, RequestError> {
405        if let Ok(param) = req.param(&formal.name) {
406            Self::parse(param, formal).map(Some)
407        } else {
408            Ok(None)
409        }
410    }
411
412    pub fn parse(s: &str, formal: &RequestParam) -> Result<Self, RequestError> {
413        match formal.param_type {
414            RequestParamType::Literal => Ok(RequestParamValue::Literal(s.to_string())),
415            RequestParamType::Boolean => Ok(RequestParamValue::Boolean(s.parse().map_err(
416                |err: std::str::ParseBoolError| RequestError::InvalidParam {
417                    param_type: "Boolean".to_string(),
418                    reason: err.to_string(),
419                },
420            )?)),
421            RequestParamType::Integer => Ok(RequestParamValue::Integer(s.parse().map_err(
422                |err: std::num::ParseIntError| RequestError::InvalidParam {
423                    param_type: "Integer".to_string(),
424                    reason: err.to_string(),
425                },
426            )?)),
427            RequestParamType::Hexadecimal => Ok(RequestParamValue::Hexadecimal(
428                s.parse()
429                    .map_err(|err: std::num::ParseIntError| RequestError::InvalidParam {
430                        param_type: "Hexadecimal".to_string(),
431                        reason: err.to_string(),
432                    })?,
433            )),
434            RequestParamType::TaggedBase64 => Ok(RequestParamValue::TaggedBase64(
435                TaggedBase64::parse(s).map_err(|err| RequestError::InvalidParam {
436                    param_type: "TaggedBase64".to_string(),
437                    reason: err.to_string(),
438                })?,
439            )),
440        }
441    }
442
443    pub fn param_type(&self) -> RequestParamType {
444        match self {
445            Self::Boolean(_) => RequestParamType::Boolean,
446            Self::Hexadecimal(_) => RequestParamType::Hexadecimal,
447            Self::Integer(_) => RequestParamType::Integer,
448            Self::TaggedBase64(_) => RequestParamType::TaggedBase64,
449            Self::Literal(_) => RequestParamType::Literal,
450        }
451    }
452
453    pub fn as_string(&self) -> Result<&str, RequestError> {
454        match self {
455            Self::Literal(s) => Ok(s),
456            _ => Err(RequestError::IncorrectParamType {
457                expected: RequestParamType::Literal,
458                actual: self.param_type(),
459            }),
460        }
461    }
462
463    pub fn as_integer<T: TryFrom<u128>>(&self) -> Result<T, RequestError> {
464        match self {
465            Self::Integer(x) | Self::Hexadecimal(x) => {
466                T::try_from(*x).map_err(|_| RequestError::IntegerOverflow {
467                    value: *x,
468                    expected: type_name::<T>().to_string(),
469                })
470            }
471            _ => Err(RequestError::IncorrectParamType {
472                expected: RequestParamType::Integer,
473                actual: self.param_type(),
474            }),
475        }
476    }
477
478    pub fn as_boolean(&self) -> Result<bool, RequestError> {
479        match self {
480            Self::Boolean(x) => Ok(*x),
481            _ => Err(RequestError::IncorrectParamType {
482                expected: RequestParamType::Boolean,
483                actual: self.param_type(),
484            }),
485        }
486    }
487
488    pub fn as_tagged_base64(&self) -> Result<&TaggedBase64, RequestError> {
489        match self {
490            Self::TaggedBase64(x) => Ok(x),
491            _ => Err(RequestError::IncorrectParamType {
492                expected: RequestParamType::TaggedBase64,
493                actual: self.param_type(),
494            }),
495        }
496    }
497
498    pub fn as_blob<'a, T>(&'a self) -> Result<T, RequestError>
499    where
500        T: TryFrom<&'a TaggedBase64>,
501        <T as TryFrom<&'a TaggedBase64>>::Error: Display,
502    {
503        let tb64 = self.as_tagged_base64()?;
504        tb64.try_into()
505            .map_err(
506                |err: <T as TryFrom<&'a TaggedBase64>>::Error| RequestError::TaggedBase64 {
507                    reason: err.to_string(),
508                },
509            )
510    }
511}
512
513#[derive(Clone, Debug)]
514pub struct RequestParam {
515    pub name: String,
516    pub param_type: RequestParamType,
517}
518
519pub(crate) fn best_response_type(
520    accept: &Accept,
521    available: &[Mime],
522) -> Result<Mime, RequestError> {
523    // The Accept type has a `negotiate` method, but it doesn't properly handle wildcards. It
524    // handles * but not */* and basetype/*, because for content type proposals like */* and
525    // basetype/*, it looks for a literal match in `available`, it does not perform pattern
526    // matching. So, we implement negotiation ourselves. Go through each proposed content type, in
527    // the order specified by the client, and match them against our available types, respecting
528    // wildcards.
529    for proposed in accept.iter() {
530        if proposed.basetype() == "*" {
531            // The only acceptable Accept value with a basetype of * is */*, therefore this will
532            // match any available type.
533            return Ok(available[0].clone());
534        } else if proposed.subtype() == "*" {
535            // If the subtype is * but the basetype is not, look for a proposed type with a matching
536            // basetype and any subtype.
537            if let Some(mime) = available
538                .iter()
539                .find(|mime| mime.basetype() == proposed.basetype())
540            {
541                return Ok(mime.clone());
542            }
543        } else {
544            // If neither part of the proposal is a wildcard, look for a literal match.
545            if let Some(mime) = available.iter().find(|mime| {
546                mime.basetype() == proposed.basetype() && mime.subtype() == proposed.subtype()
547            }) {
548                return Ok(mime.clone());
549            }
550        }
551    }
552
553    if accept.wildcard() {
554        // If no proposals are available but a wildcard flag * was given, return any available
555        // content type.
556        Ok(available[0].clone())
557    } else {
558        Err(RequestError::UnsupportedContentType)
559    }
560}
561
562#[cfg(test)]
563mod test {
564    use super::*;
565    use ark_serialize::*;
566    use tagged_base64::tagged;
567
568    fn default_req() -> http::Request {
569        http::Request::new(http::Method::Get, "http://localhost:12345")
570    }
571
572    fn param(ty: RequestParamType, name: &str, val: &str) -> RequestParamValue {
573        RequestParamValue::parse(
574            val,
575            &RequestParam {
576                name: name.to_string(),
577                param_type: ty,
578            },
579        )
580        .unwrap()
581    }
582
583    fn request_from_params(
584        params: impl IntoIterator<Item = (String, RequestParamValue)>,
585    ) -> RequestParams {
586        RequestParams {
587            req: default_req(),
588            post_data: Default::default(),
589            params: params.into_iter().collect(),
590        }
591    }
592
593    #[tagged("BLOB")]
594    #[derive(Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)]
595    struct Blob {
596        data: String,
597    }
598
599    #[test]
600    fn test_params() {
601        let tb64 = TaggedBase64::new("TAG", &[0; 20]).unwrap();
602        let blob = Blob {
603            data: "blob".to_string(),
604        };
605        let string_param = param(RequestParamType::Literal, "string", "hello");
606        let integer_param = param(RequestParamType::Integer, "integer", "42");
607        let boolean_param = param(RequestParamType::Boolean, "boolean", "true");
608        let tagged_base64_param = param(
609            RequestParamType::TaggedBase64,
610            "tagged_base64",
611            &tb64.to_string(),
612        );
613        let blob_param = param(RequestParamType::TaggedBase64, "blob", &blob.to_string());
614        let params = vec![
615            ("string".to_string(), string_param.clone()),
616            ("integer".to_string(), integer_param.clone()),
617            ("boolean".to_string(), boolean_param.clone()),
618            ("tagged_base64".to_string(), tagged_base64_param.clone()),
619            ("blob".to_string(), blob_param.clone()),
620        ];
621        let req = request_from_params(params);
622
623        // Check untyped param.
624        assert_eq!(*req.param("string").unwrap(), string_param);
625        assert_eq!(*req.param("integer").unwrap(), integer_param);
626        assert_eq!(*req.param("boolean").unwrap(), boolean_param);
627        assert_eq!(*req.param("tagged_base64").unwrap(), tagged_base64_param);
628        assert_eq!(*req.param("blob").unwrap(), blob_param);
629        match req.param("nosuchparam").unwrap_err() {
630            RequestError::MissingParam { name } if name == "nosuchparam" => {}
631            err => panic!("expecting MissingParam {{ nosuchparam }}, got {:?}", err),
632        }
633
634        // Check untyped optional param.
635        assert_eq!(*req.opt_param("string").unwrap(), string_param);
636        assert_eq!(*req.opt_param("integer").unwrap(), integer_param);
637        assert_eq!(*req.opt_param("boolean").unwrap(), boolean_param);
638        assert_eq!(
639            *req.opt_param("tagged_base64").unwrap(),
640            tagged_base64_param
641        );
642        assert_eq!(*req.opt_param("blob").unwrap(), blob_param);
643        assert_eq!(req.opt_param("nosuchparam"), None);
644
645        // Check typed params: correct type, incorrect type, and missing cases.
646        assert_eq!(req.string_param("string").unwrap(), "hello");
647        match req.string_param("integer").unwrap_err() {
648            RequestError::IncorrectParamType { actual, expected }
649                if actual == RequestParamType::Integer && expected == RequestParamType::Literal => {
650            }
651            err => panic!(
652                "expecting IncorrectParamType {{ Integer, String }}, got {:?}",
653                err
654            ),
655        }
656        match req.string_param("nosuchparam").unwrap_err() {
657            RequestError::MissingParam { name } if name == "nosuchparam" => {}
658            err => panic!("expecting MissingParam {{ nosuchparam }}, got {:?}", err),
659        };
660
661        assert_eq!(req.integer_param::<_, usize>("integer").unwrap(), 42);
662        match req.integer_param::<_, usize>("string").unwrap_err() {
663            RequestError::IncorrectParamType { actual, expected }
664                if actual == RequestParamType::Literal && expected == RequestParamType::Integer => {
665            }
666            err => panic!(
667                "expecting IncorrectParamType {{ Literal, Integer }}, got {:?}",
668                err
669            ),
670        }
671        match req.integer_param::<_, usize>("nosuchparam").unwrap_err() {
672            RequestError::MissingParam { name } if name == "nosuchparam" => {}
673            err => panic!("expecting MissingParam {{ nosuchparam }}, got {:?}", err),
674        };
675
676        assert!(req.boolean_param("boolean").unwrap());
677        match req.boolean_param("integer").unwrap_err() {
678            RequestError::IncorrectParamType { actual, expected }
679                if actual == RequestParamType::Integer && expected == RequestParamType::Boolean => {
680            }
681            err => panic!(
682                "expecting IncorrectParamType {{ Integer, Boolean }}, got {:?}",
683                err
684            ),
685        }
686        match req.boolean_param("nosuchparam").unwrap_err() {
687            RequestError::MissingParam { name } if name == "nosuchparam" => {}
688            err => panic!("expecting MissingParam {{ nosuchparam }}, got {:?}", err),
689        };
690
691        assert_eq!(*req.tagged_base64_param("tagged_base64").unwrap(), tb64);
692        match req.tagged_base64_param("integer").unwrap_err() {
693            RequestError::IncorrectParamType { actual, expected }
694                if actual == RequestParamType::Integer
695                    && expected == RequestParamType::TaggedBase64 => {}
696            err => panic!(
697                "expecting IncorrectParamType {{ Integer, TaggedBase64 }}, got {:?}",
698                err
699            ),
700        }
701        match req.tagged_base64_param("nosuchparam").unwrap_err() {
702            RequestError::MissingParam { name } if name == "nosuchparam" => {}
703            err => panic!("expecting MissingParam {{ nosuchparam }}, got {:?}", err),
704        };
705
706        assert_eq!(req.blob_param::<_, Blob>("blob").unwrap(), blob);
707        match req.tagged_base64_param("integer").unwrap_err() {
708            RequestError::IncorrectParamType { actual, expected }
709                if actual == RequestParamType::Integer
710                    && expected == RequestParamType::TaggedBase64 => {}
711            err => panic!(
712                "expecting IncorrectParamType {{ Integer, TaggedBase64 }}, got {:?}",
713                err
714            ),
715        }
716        match req.tagged_base64_param("nosuchparam").unwrap_err() {
717            RequestError::MissingParam { name } if name == "nosuchparam" => {}
718            err => panic!("expecting MissingParam {{ nosuchparam }}, got {:?}", err),
719        };
720
721        // Check typed optional params: correct type, incorrect type, and missing cases.
722        assert_eq!(req.opt_string_param("string").unwrap().unwrap(), "hello");
723        match req.opt_string_param("integer").unwrap_err() {
724            RequestError::IncorrectParamType { actual, expected }
725                if actual == RequestParamType::Integer && expected == RequestParamType::Literal => {
726            }
727            err => panic!(
728                "expecting IncorrectParamType {{ Integer, String }}, got {:?}",
729                err
730            ),
731        }
732        assert_eq!(req.opt_string_param("nosuchparam").unwrap(), None);
733
734        assert_eq!(
735            req.opt_integer_param::<_, usize>("integer")
736                .unwrap()
737                .unwrap(),
738            42
739        );
740        match req.opt_integer_param::<_, usize>("string").unwrap_err() {
741            RequestError::IncorrectParamType { actual, expected }
742                if actual == RequestParamType::Literal && expected == RequestParamType::Integer => {
743            }
744            err => panic!(
745                "expecting IncorrectParamType {{ Literal, Integer }}, got {:?}",
746                err
747            ),
748        }
749        assert_eq!(
750            req.opt_integer_param::<_, usize>("nosuchparam").unwrap(),
751            None
752        );
753
754        assert!(req.opt_boolean_param("boolean").unwrap().unwrap());
755        match req.opt_boolean_param("integer").unwrap_err() {
756            RequestError::IncorrectParamType { actual, expected }
757                if actual == RequestParamType::Integer && expected == RequestParamType::Boolean => {
758            }
759            err => panic!(
760                "expecting IncorrectParamType {{ Integer, Boolean }}, got {:?}",
761                err
762            ),
763        }
764        assert_eq!(req.opt_boolean_param("nosuchparam").unwrap(), None);
765
766        assert_eq!(
767            *req.opt_tagged_base64_param("tagged_base64")
768                .unwrap()
769                .unwrap(),
770            tb64
771        );
772        match req.opt_tagged_base64_param("integer").unwrap_err() {
773            RequestError::IncorrectParamType { actual, expected }
774                if actual == RequestParamType::Integer
775                    && expected == RequestParamType::TaggedBase64 => {}
776            err => panic!(
777                "expecting IncorrectParamType {{ Integer, TaggedBase64 }}, got {:?}",
778                err
779            ),
780        }
781        assert_eq!(req.opt_tagged_base64_param("nosuchparam").unwrap(), None);
782
783        assert_eq!(
784            req.opt_blob_param::<_, Blob>("blob").unwrap().unwrap(),
785            blob
786        );
787        match req.opt_blob_param::<_, Blob>("integer").unwrap_err() {
788            RequestError::IncorrectParamType { actual, expected }
789                if actual == RequestParamType::Integer
790                    && expected == RequestParamType::TaggedBase64 => {}
791            err => panic!(
792                "expecting IncorrectParamType {{ Integer, TaggedBase64 }}, got {:?}",
793                err
794            ),
795        }
796        assert_eq!(req.opt_blob_param::<_, Blob>("nosuchparam").unwrap(), None);
797    }
798}