improved template parsing (#111)

This commit is contained in:
Joshua M. Clulow 2022-07-08 16:54:53 -07:00 committed by GitHub
parent 236efadeee
commit 6e90509eef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 106 additions and 42 deletions

View File

@ -27,7 +27,6 @@ impl PathTemplate {
let mut fmt = String::new(); let mut fmt = String::new();
fmt.push_str("{}"); fmt.push_str("{}");
for c in self.components.iter() { for c in self.components.iter() {
fmt.push('/');
match c { match c {
Component::Constant(n) => fmt.push_str(n), Component::Constant(n) => fmt.push_str(n),
Component::Parameter(_) => fmt.push_str("{}"), Component::Parameter(_) => fmt.push_str("{}"),
@ -71,7 +70,6 @@ pub fn parse(t: &str) -> Result<PathTemplate> {
Start, Start,
ConstantOrParameter, ConstantOrParameter,
Parameter, Parameter,
ParameterSlash,
Constant, Constant,
} }
@ -83,7 +81,8 @@ pub fn parse(t: &str) -> Result<PathTemplate> {
match s { match s {
State::Start => { State::Start => {
if c == '/' { if c == '/' {
s = State::ConstantOrParameter; s = State::Constant;
a = c.to_string();
} else { } else {
return Err(Error::InvalidPath( return Err(Error::InvalidPath(
"path must start with a slash".to_string(), "path must start with a slash".to_string(),
@ -91,50 +90,38 @@ pub fn parse(t: &str) -> Result<PathTemplate> {
} }
} }
State::ConstantOrParameter => { State::ConstantOrParameter => {
if c == '/' || c == '}' { if c == '{' {
return Err(Error::InvalidPath(
"expected a constant or parameter".to_string(),
));
} else if c == '{' {
s = State::Parameter; s = State::Parameter;
a = String::new();
} else { } else {
s = State::Constant; s = State::Constant;
a.push(c); a = c.to_string();
} }
} }
State::Constant => { State::Constant => {
if c == '/' { if c == '{' {
components.push(Component::Constant(a)); components.push(Component::Constant(a));
a = String::new(); a = String::new();
s = State::ConstantOrParameter; s = State::Parameter;
} else if c == '{' || c == '}' { } else if c == '}' {
return Err(Error::InvalidPath( return Err(Error::InvalidPath("unexpected }".to_string()));
"unexpected parameter".to_string(), } else if c != '/' || a.chars().last() != Some('/') {
));
} else {
a.push(c); a.push(c);
} }
} }
State::Parameter => { State::Parameter => {
if c == '}' { if c == '}' {
if a.contains('/') || a.contains('{') {
return Err(Error::InvalidPath(format!(
"invalid parameter name {:?}",
a,
)));
}
components.push(Component::Parameter(a)); components.push(Component::Parameter(a));
a = String::new(); a = String::new();
s = State::ParameterSlash;
} else if c == '/' || c == '{' {
return Err(Error::InvalidPath(
"unexpected parameter".to_string(),
));
} else {
a.push(c);
}
}
State::ParameterSlash => {
if c == '/' {
s = State::ConstantOrParameter; s = State::ConstantOrParameter;
} else { } else {
return Err(Error::InvalidPath( a.push(c);
"expected a slah after parameter".to_string(),
));
} }
} }
} }
@ -144,7 +131,7 @@ pub fn parse(t: &str) -> Result<PathTemplate> {
State::Start => { State::Start => {
return Err(Error::InvalidPath("empty path".to_string())) return Err(Error::InvalidPath("empty path".to_string()))
} }
State::ConstantOrParameter | State::ParameterSlash => (), State::ConstantOrParameter => (),
State::Constant => components.push(Component::Constant(a)), State::Constant => components.push(Component::Constant(a)),
State::Parameter => { State::Parameter => {
return Err(Error::InvalidPath( return Err(Error::InvalidPath(
@ -164,7 +151,7 @@ impl ToString for PathTemplate {
Component::Constant(s) => s.clone(), Component::Constant(s) => s.clone(),
Component::Parameter(s) => format!("{{{}}}", s), Component::Parameter(s) => format!("{{{}}}", s),
}) })
.fold(String::new(), |a, b| a + "/" + &b) .fold(String::new(), |a, b| a + &b)
} }
} }
@ -178,35 +165,90 @@ mod test {
fn basic() { fn basic() {
let trials = vec![ let trials = vec![
( (
"/info",
"/info", "/info",
PathTemplate { PathTemplate {
components: vec![Component::Constant("info".into())], components: vec![Component::Constant("/info".into())],
}, },
), ),
( (
"/measure/{number}",
"/measure/{number}", "/measure/{number}",
PathTemplate { PathTemplate {
components: vec![ components: vec![
Component::Constant("measure".into()), Component::Constant("/measure/".into()),
Component::Parameter("number".into()), Component::Parameter("number".into()),
], ],
}, },
), ),
( (
"/one/{two}/three",
"/one/{two}/three", "/one/{two}/three",
PathTemplate { PathTemplate {
components: vec![ components: vec![
Component::Constant("one".into()), Component::Constant("/one/".into()),
Component::Parameter("two".into()), Component::Parameter("two".into()),
Component::Constant("three".into()), Component::Constant("/three".into()),
],
},
),
(
"/{foo}-{bar}-{baz}",
"/{foo}-{bar}-{baz}",
PathTemplate {
components: vec![
Component::Constant("/".into()),
Component::Parameter("foo".into()),
Component::Constant("-".into()),
Component::Parameter("bar".into()),
Component::Constant("-".into()),
Component::Parameter("baz".into()),
],
},
),
(
"//normalise/////{adjacent}:x///slashes",
"/normalise/{adjacent}:x/slashes",
PathTemplate {
components: vec![
Component::Constant("/normalise/".into()),
Component::Parameter("adjacent".into()),
Component::Constant(":x/slashes".into()),
],
},
),
(
"/v1/files/{fileId}:completeUpload",
"/v1/files/{fileId}:completeUpload",
PathTemplate {
components: vec![
Component::Constant("/v1/files/".into()),
Component::Parameter("fileId".into()),
Component::Constant(":completeUpload".into()),
],
},
),
(
"/v1/folders/{folderId}/sessions/{sessionId}:complete",
"/v1/folders/{folderId}/sessions/{sessionId}:complete",
PathTemplate {
components: vec![
Component::Constant("/v1/folders/".into()),
Component::Parameter("folderId".into()),
Component::Constant("/sessions/".into()),
Component::Parameter("sessionId".into()),
Component::Constant(":complete".into()),
], ],
}, },
), ),
]; ];
for (path, want) in trials.iter() { for (path, expect_string, want) in trials.iter() {
match parse(path) { match parse(path) {
Ok(t) => assert_eq!(&t, want), Ok(t) => {
assert_eq!(&t, want);
assert_eq!(t.to_string().as_str(), *expect_string);
}
Err(e) => panic!("path {} {}", path, e), Err(e) => panic!("path {} {}", path, e),
} }
} }
@ -246,4 +288,26 @@ mod test {
}; };
assert_eq!(want.to_string(), out.to_string()); assert_eq!(want.to_string(), out.to_string());
} }
#[test]
fn compile2() {
let mut rename = HashMap::new();
let one = "one".to_string();
let two = "two".to_string();
let three = "three".to_string();
rename.insert(&one, &one);
rename.insert(&two, &two);
rename.insert(&three, &three);
let t = parse("/abc/def:{one}:jkl/{two}/a:{three}").unwrap();
let out = t.compile(rename, quote::quote! { self });
let want = quote::quote! {
let url = format!("{}/abc/def:{}:jkl/{}/a:{}",
self.baseurl,
encode_path(&one.to_string()),
encode_path(&two.to_string()),
encode_path(&three.to_string()),
);
};
assert_eq!(want.to_string(), out.to_string());
}
} }

View File

@ -66,12 +66,12 @@ impl Client {
} }
impl Client { impl Client {
///Sends a `POST` request to `` ///Sends a `POST` request to `/`
pub async fn default_params<'a>( pub async fn default_params<'a>(
&'a self, &'a self,
body: &'a types::BodyWithDefaults, body: &'a types::BodyWithDefaults,
) -> Result<ResponseValue<ByteStream>, Error<ByteStream>> { ) -> Result<ResponseValue<ByteStream>, Error<ByteStream>> {
let url = format!("{}", self.baseurl,); let url = format!("{}/", self.baseurl,);
let request = self.client.post(url).json(&body).build()?; let request = self.client.post(url).json(&body).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
let response = result?; let response = result?;

View File

@ -47,11 +47,11 @@ impl Client {
} }
impl Client { impl Client {
///Sends a `GET` request to `` ///Sends a `GET` request to `/`
pub async fn freeform_response<'a>( pub async fn freeform_response<'a>(
&'a self, &'a self,
) -> Result<ResponseValue<ByteStream>, Error<ByteStream>> { ) -> Result<ResponseValue<ByteStream>, Error<ByteStream>> {
let url = format!("{}", self.baseurl,); let url = format!("{}/", self.baseurl,);
let request = self.client.get(url).build()?; let request = self.client.get(url).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
let response = result?; let response = result?;