From 54043a7769b6bb2141e96de22ab80b9d673f1a89 Mon Sep 17 00:00:00 2001 From: Jeb Bearer Date: Tue, 16 Aug 2022 15:05:35 -0700 Subject: [PATCH 1/2] Support integer and TaggedBase64 request parameters --- Cargo.toml | 2 + examples/hello-world/main.rs | 2 +- src/request.rs | 118 +++++++++++++++++++++++++++++++---- 3 files changed, 109 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8a2c1395..034102c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ name = "hello-world" test = true [dependencies] +ark-serialize = "0.3.0" async-std = { version = "1.8.0", features = ["attributes"] } async-trait = "0.1.51" bincode = "1.3.3" @@ -23,6 +24,7 @@ edit-distance = "2.1.0" futures = "0.3.21" futures-util = "0.3.8" http = "0.2.7" +jf-utils = { features = ["std"], git = "https://github.com/EspressoSystems/jellyfish.git", tag = "0.1.1" } lazy_static = "1.4.0" libc = "0.2.126" markdown = "0.3" diff --git a/examples/hello-world/main.rs b/examples/hello-world/main.rs index 50c79242..6572dd3f 100644 --- a/examples/hello-world/main.rs +++ b/examples/hello-world/main.rs @@ -63,7 +63,7 @@ async fn serve(port: u16) -> io::Result<()> { async move { let new_greeting = req.string_param("greeting")?; info!("called /setgreeting with :greeting = {}", new_greeting); - *greeting = new_greeting; + *greeting = new_greeting.to_string(); Ok(()) } .boxed() diff --git a/src/request.rs b/src/request.rs index 92227c7b..56040538 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,3 +1,6 @@ +use ark_serialize::CanonicalDeserialize; +use jf_utils::Tagged; +use serde::{Deserialize, Serialize}; use snafu::{OptionExt, Snafu}; use std::collections::HashMap; use std::fmt::Display; @@ -5,7 +8,7 @@ use strum_macros::EnumString; use tagged_base64::TaggedBase64; use tide::http::{content::Accept, Headers}; -#[derive(Clone, Debug, Snafu)] +#[derive(Clone, Debug, Snafu, Deserialize, Serialize)] pub enum RequestError { #[snafu(display("missing required parameter: {}", name))] MissingParam { name: String }, @@ -35,11 +38,20 @@ pub enum RequestError { #[snafu(display("Unable to deserialize from bincode"))] Bincode, + #[snafu(display("Unable to deserialize from ark format: {}", reason))] + ArkSerialize { reason: String }, + #[snafu(display("Body type not specified or type not supported"))] UnsupportedBody, #[snafu(display("HTTP protocol error: {}", reason))] Http { reason: String }, + + #[snafu(display("error parsing {} parameter: {}", param_type, reason))] + InvalidParam { param_type: String, reason: String }, + + #[snafu(display("unexpected tag in TaggedBase64: {} (expected {})", actual, expected))] + TagMismatch { actual: String, expected: String }, } /// Parameters passed to a route handler. @@ -259,7 +271,7 @@ impl RequestParams { /// /// Like [param](Self::param), but returns [None] if the parameter value cannot be converted to /// a [String]. - pub fn string_param(&self, name: &Name) -> Result + pub fn string_param(&self, name: &Name) -> Result<&str, RequestError> where Name: ?Sized + Display, { @@ -272,6 +284,46 @@ impl RequestParams { }) } + /// Get the value of a named parameter and convert it to [TaggedBase64]. + /// + /// Like [param](Self::param), but returns [None] if the parameter value cannot be converted to + /// [TaggedBase64]. + pub fn tagged_base64_param(&self, name: &Name) -> Result<&TaggedBase64, RequestError> + where + Name: ?Sized + Display, + { + self.param(name).and_then(|val| { + val.as_tagged_base64().context(IncorrectParamTypeSnafu { + name: name.to_string(), + param_type: val.param_type(), + expected: "TaggedBase64".to_string(), + }) + }) + } + + /// Get the value of a named parameter and convert it to a custom type through [TaggedBase64]. + /// + /// Like [param](Self::param), but returns [None] if the parameter value cannot be converted to + /// `T`. + pub fn blob_param(&self, name: &Name) -> Result + where + Name: ?Sized + Display, + T: Tagged + CanonicalDeserialize, + { + self.tagged_base64_param(name).and_then(|tb64| { + if tb64.tag() == T::tag() { + T::deserialize(&*tb64.value()).map_err(|source| RequestError::ArkSerialize { + reason: source.to_string(), + }) + } else { + Err(RequestError::TagMismatch { + actual: tb64.tag(), + expected: T::tag(), + }) + } + }) + } + pub fn body_bytes(&self) -> Vec { self.post_data.clone() } @@ -329,9 +381,36 @@ impl RequestParamValue { RequestParamType::Literal => { Ok(Some(RequestParamValue::Literal(param.to_string()))) } - _ => unimplemented!( - "parsing String into RequestParamValue based on formal.param_type" - ), + RequestParamType::Boolean => { + Ok(Some(RequestParamValue::Boolean(param.parse().map_err( + |err: std::str::ParseBoolError| RequestError::InvalidParam { + param_type: "Boolean".to_string(), + reason: err.to_string(), + }, + )?))) + } + RequestParamType::Integer => { + Ok(Some(RequestParamValue::Integer(param.parse().map_err( + |err: std::num::ParseIntError| RequestError::InvalidParam { + param_type: "Integer".to_string(), + reason: err.to_string(), + }, + )?))) + } + RequestParamType::Hexadecimal => Ok(Some(RequestParamValue::Hexadecimal( + param.parse().map_err(|err: std::num::ParseIntError| { + RequestError::InvalidParam { + param_type: "Hexadecimal".to_string(), + reason: err.to_string(), + } + })?, + ))), + RequestParamType::TaggedBase64 => Ok(Some(RequestParamValue::TaggedBase64( + TaggedBase64::parse(param).map_err(|err| RequestError::InvalidParam { + param_type: "TaggedBase64".to_string(), + reason: err.to_string(), + })?, + ))), } } else { unimplemented!("check for the parameter in the request body") @@ -348,21 +427,36 @@ impl RequestParamValue { } } - pub fn as_string(&self) -> Option { + pub fn as_string(&self) -> Option<&str> { match self { - Self::Literal(s) => Some(s.clone()), - _ => { - unimplemented!("extracting a String from other parameter types, like TaggedBase64") - } + Self::Literal(s) => Some(s), + _ => None, } } pub fn as_integer(&self) -> Option { - unimplemented!() + match self { + Self::Integer(x) | Self::Hexadecimal(x) => Some(*x), + _ => None, + } + } + + pub fn as_boolean(&self) -> Option { + match self { + Self::Boolean(x) => Some(*x), + _ => None, + } + } + + pub fn as_tagged_base64(&self) -> Option<&TaggedBase64> { + match self { + Self::TaggedBase64(x) => Some(x), + _ => None, + } } } -#[derive(Clone, Copy, Debug, EnumString, strum_macros::Display)] +#[derive(Clone, Copy, Debug, EnumString, strum_macros::Display, Deserialize, Serialize)] pub enum RequestParamType { Boolean, Hexadecimal, From 5a95f752d518f558fa3e47cf3a00b5fe1308e5b0 Mon Sep 17 00:00:00 2001 From: Jeb Bearer Date: Tue, 16 Aug 2022 15:21:20 -0700 Subject: [PATCH 2/2] Fix clippy --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 904e8584..d588c4f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -685,7 +685,7 @@ pub fn check_literals(url: &Url, api: &Value, first_segment: &str) -> String { let mut typos = String::new(); let meta = &api["meta"]; let api_map = api[ROUTE.as_ref()].as_table().unwrap(); - api_map[&*first_segment][PATH.as_ref()] + api_map[first_segment][PATH.as_ref()] .as_array() .unwrap() .iter()