1use 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#[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 pub fn method(&self) -> Method {
89 self.req.method().into()
90 }
91
92 pub fn headers(&self) -> &Headers {
94 self.req.as_ref()
95 }
96
97 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 pub fn remote(&self) -> Option<&str> {
135 self.req.remote()
136 }
137
138 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 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 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 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 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 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 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 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 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 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 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 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 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 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 for proposed in accept.iter() {
581 if proposed.basetype() == "*" {
582 return Ok(available[0].clone());
585 } else if proposed.subtype() == "*" {
586 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 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 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 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 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 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 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}