From 62971e63cd437054407e33b20891c61df720dcea Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 30 Jun 2017 01:29:23 +1000 Subject: [PATCH] Implement substitution of variables in endpoint paths --- Cargo.toml | 1 - src/api/mod.rs | 62 +++++++++++++++++++++++++++++++++++++++++++--- src/api/request.rs | 41 +++++++++++++++++++++++++++++- 3 files changed, 99 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2e823d57..54f122fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,6 @@ serde = "1.0.8" serde_derive = "1.0.8" serde_json = "1.0.2" serde_urlencoded = "0.5.1" -url = "1.5.1" [lib] doctest = false diff --git a/src/api/mod.rs b/src/api/mod.rs index caa2a7eb..a85f7c39 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -36,6 +36,61 @@ impl ToTokens for Api { tokens }; + let set_request_path = if self.request.has_path_fields() { + let path_str_quoted = path.as_str(); + assert!( + path_str_quoted.starts_with('"') && path_str_quoted.ends_with('"'), + "path needs to be a string literal" + ); + + let path_str = &path_str_quoted[1 .. path_str_quoted.len() - 1]; + + assert!(path_str.starts_with('/'), "path needs to start with '/'"); + assert!( + path_str.chars().filter(|c| *c == ':').count() == self.request.path_field_count(), + "number of declared path parameters needs to match amount of placeholders in path" + ); + + let request_path_init_fields = self.request.request_path_init_fields(); + + let mut tokens = quote! { + let request_path = RequestPath { + #request_path_init_fields + }; + + // This `unwrap()` can only fail when the url is a + // cannot-be-base url like `mailto:` or `data:`, which is not + // the case for our placeholder url. + let mut path_segments = url.path_segments_mut().unwrap(); + }; + + for segment in path_str[1..].split('/') { + tokens.append(quote! { + path_segments.push + }); + + tokens.append("("); + + if segment.starts_with(':') { + tokens.append("&request_path."); + tokens.append(&segment[1..]); + tokens.append(".to_string()"); + } else { + tokens.append("\""); + tokens.append(segment); + tokens.append("\""); + } + + tokens.append(");"); + } + + tokens + } else { + quote! { + url.set_path(metadata.path); + } + }; + let set_request_query = if self.request.has_query_fields() { let request_query_init_fields = self.request.request_query_init_fields(); @@ -159,8 +214,9 @@ impl ToTokens for Api { // the calling code. Previously (with hyper::Uri) this was // not required, but Url::parse only accepts absolute urls. let mut url = ::url::Url::parse("http://invalid-host-please-change/").unwrap(); - url.set_path(metadata.path); - #set_request_query + + { #set_request_path } + { #set_request_query } let mut hyper_request = ::hyper::Request::new( metadata.method, @@ -168,7 +224,7 @@ impl ToTokens for Api { url.into_string().parse().unwrap(), ); - #add_body_to_request + { #add_body_to_request } Ok(hyper_request) } diff --git a/src/api/request.rs b/src/api/request.rs index b47f6258..d1794509 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -11,10 +11,18 @@ impl Request { self.fields.iter().any(|field| field.is_body()) } + pub fn has_path_fields(&self) -> bool { + self.fields.iter().any(|field| field.is_path()) + } + pub fn has_query_fields(&self) -> bool { self.fields.iter().any(|field| field.is_query()) } + pub fn path_field_count(&self) -> usize { + self.fields.iter().filter(|field| field.is_path()).count() + } + pub fn newtype_body_field(&self) -> Option<&Field> { for request_field in self.fields.iter() { match *request_field { @@ -32,6 +40,10 @@ impl Request { self.struct_init_fields(RequestFieldKind::Body) } + pub fn request_path_init_fields(&self) -> Tokens { + self.struct_init_fields(RequestFieldKind::Path) + } + pub fn request_query_init_fields(&self) -> Tokens { self.struct_init_fields(RequestFieldKind::Query) } @@ -178,9 +190,32 @@ impl ToTokens for Request { tokens.append("}"); } + if self.has_path_fields() { + tokens.append(quote! { + /// Data in the request path. + #[derive(Debug)] + struct RequestPath + }); + + tokens.append("{"); + + for request_field in self.fields.iter() { + match *request_field { + RequestField::Path(ref field) => { + field.to_tokens(&mut tokens); + + tokens.append(","); + } + _ => {} + } + } + + tokens.append("}"); + } + if self.has_query_fields() { tokens.append(quote! { - /// Data in the request url's query parameters + /// Data in the request url's query parameters. #[derive(Debug, Serialize)] struct RequestQuery }); @@ -237,6 +272,10 @@ impl RequestField { self.kind() == RequestFieldKind::Body } + fn is_path(&self) -> bool { + self.kind() == RequestFieldKind::Path + } + fn is_query(&self) -> bool { self.kind() == RequestFieldKind::Query }