1use 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#[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 pub fn method(&self) -> Method {
49 self.req.method().into()
50 }
51
52 pub fn headers(&self) -> &Headers {
54 self.req.as_ref()
55 }
56
57 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 pub fn remote(&self) -> Option<&str> {
95 self.req.remote()
96 }
97
98 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 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 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 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 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 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 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 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 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 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 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 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 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 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 for proposed in accept.iter() {
530 if proposed.basetype() == "*" {
531 return Ok(available[0].clone());
534 } else if proposed.subtype() == "*" {
535 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 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 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 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 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 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 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}