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