Update request/response header logic for new style.

This commit is contained in:
Jimmy Cuadra 2018-05-16 00:40:51 -07:00
parent f0f4f9bd17
commit c9454caff1
5 changed files with 87 additions and 28 deletions

View File

@ -10,7 +10,7 @@ You define the endpoint's metadata, request fields, and response fields, and the
Here is an example that shows most of the macro's functionality. Here is an example that shows most of the macro's functionality.
``` rust ``` rust
#![feature(associated_consts, proc_macro, try_from)] #![feature(proc_macro, try_from)]
extern crate futures; extern crate futures;
extern crate http; extern crate http;

View File

@ -99,7 +99,7 @@ impl ToTokens for Api {
}); });
} else { } else {
tokens.append_all(quote! { tokens.append_all(quote! {
("#segment"); (#segment);
}); });
} }
} }
@ -125,6 +125,18 @@ impl ToTokens for Api {
Tokens::new() Tokens::new()
}; };
let add_headers_to_request = if self.request.has_header_fields() {
let mut header_tokens = quote! {
let headers = http_request.headers_mut();
};
header_tokens.append_all(self.request.add_headers_to_request());
header_tokens
} else {
Tokens::new()
};
let add_body_to_request = if let Some(field) = self.request.newtype_body_field() { let add_body_to_request = if let Some(field) = self.request.newtype_body_field() {
let field_name = field.ident.expect("expected field to have an identifier"); let field_name = field.ident.expect("expected field to have an identifier");
@ -211,7 +223,7 @@ impl ToTokens for Api {
#request_types #request_types
impl ::std::convert::TryFrom<Request> for ::http::Request { impl<T> ::std::convert::TryFrom<Request> for ::http::Request<T> {
type Error = ::ruma_api::Error; type Error = ::ruma_api::Error;
#[allow(unused_mut, unused_variables)] #[allow(unused_mut, unused_variables)]
@ -232,6 +244,8 @@ impl ToTokens for Api {
url.into_string().parse().unwrap(), url.into_string().parse().unwrap(),
); );
{ #add_headers_to_request }
{ #add_body_to_request } { #add_body_to_request }
Ok(http_request) Ok(http_request)
@ -240,12 +254,12 @@ impl ToTokens for Api {
#response_types #response_types
impl ::futures::future::FutureFrom<::http::Response> for Response { impl<T> ::futures::future::FutureFrom<::http::Response<T>> for Response {
type Future = Box<_Future<Item = Self, Error = Self::Error>>; type Future = Box<_Future<Item = Self, Error = Self::Error>>;
type Error = ::ruma_api::Error; type Error = ::ruma_api::Error;
#[allow(unused_variables)] #[allow(unused_variables)]
fn future_from(http_response: ::http::Response) fn future_from(http_response: ::http::Response<T>)
-> Box<_Future<Item = Self, Error = Self::Error>> { -> Box<_Future<Item = Self, Error = Self::Error>> {
#extract_headers #extract_headers

View File

@ -1,6 +1,6 @@
use quote::{ToTokens, Tokens}; use quote::{ToTokens, Tokens};
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::{Field, Meta, NestedMeta}; use syn::{Field, Ident, Lit, Meta, NestedMeta};
use api::strip_serde_attrs; use api::strip_serde_attrs;
@ -9,10 +9,31 @@ pub struct Request {
} }
impl Request { impl Request {
pub fn add_headers_to_request(&self) -> Tokens {
self.header_fields().fold(Tokens::new(), |mut header_tokens, request_field| {
let (field, header_name_string) = match request_field {
RequestField::Header(field, header_name_string) => (field, header_name_string),
_ => panic!("expected request field to be header variant"),
};
let field_name = &field.ident;
let header_name = Ident::from(header_name_string.as_ref());
header_tokens.append_all(quote! {
headers.append(::http::header::#header_name, request.#field_name);
});
header_tokens
})
}
pub fn has_body_fields(&self) -> bool { pub fn has_body_fields(&self) -> bool {
self.fields.iter().any(|field| field.is_body()) self.fields.iter().any(|field| field.is_body())
} }
pub fn has_header_fields(&self) -> bool {
self.fields.iter().any(|field| field.is_header())
}
pub fn has_path_fields(&self) -> bool { pub fn has_path_fields(&self) -> bool {
self.fields.iter().any(|field| field.is_path()) self.fields.iter().any(|field| field.is_path())
} }
@ -21,6 +42,10 @@ impl Request {
self.fields.iter().any(|field| field.is_query()) self.fields.iter().any(|field| field.is_query())
} }
pub fn header_fields(&self) -> impl Iterator<Item = &RequestField> {
self.fields.iter().filter(|field| field.is_header())
}
pub fn path_field_count(&self) -> usize { pub fn path_field_count(&self) -> usize {
self.fields.iter().filter(|field| field.is_path()).count() self.fields.iter().filter(|field| field.is_path()).count()
} }
@ -72,6 +97,7 @@ impl From<Vec<Field>> for Request {
let fields = fields.into_iter().map(|mut field| { let fields = fields.into_iter().map(|mut field| {
let mut field_kind = RequestFieldKind::Body; let mut field_kind = RequestFieldKind::Body;
let mut header = None;
field.attrs = field.attrs.into_iter().filter(|attr| { field.attrs = field.attrs.into_iter().filter(|attr| {
let meta = attr.interpret_meta() let meta = attr.interpret_meta()
@ -103,7 +129,14 @@ impl From<Vec<Field>> for Request {
} }
Meta::NameValue(name_value) => { Meta::NameValue(name_value) => {
match name_value.ident.as_ref() { match name_value.ident.as_ref() {
"header" => field_kind = RequestFieldKind::Header, "header" => {
match name_value.lit {
Lit::Str(lit_str) => header = Some(lit_str.value()),
_ => panic!("ruma_api! header attribute's value must be a string literal"),
}
field_kind = RequestFieldKind::Header;
}
_ => panic!("ruma_api! name/value pair attribute on requests must be: header"), _ => panic!("ruma_api! name/value pair attribute on requests must be: header"),
} }
} }
@ -126,7 +159,7 @@ impl From<Vec<Field>> for Request {
); );
} }
RequestField::new(field_kind, field) RequestField::new(field_kind, field, header)
}).collect(); }).collect();
Request { Request {
@ -267,17 +300,17 @@ impl ToTokens for Request {
pub enum RequestField { pub enum RequestField {
Body(Field), Body(Field),
Header(Field), Header(Field, String),
NewtypeBody(Field), NewtypeBody(Field),
Path(Field), Path(Field),
Query(Field), Query(Field),
} }
impl RequestField { impl RequestField {
fn new(kind: RequestFieldKind, field: Field) -> RequestField { fn new(kind: RequestFieldKind, field: Field, header: Option<String>) -> RequestField {
match kind { match kind {
RequestFieldKind::Body => RequestField::Body(field), RequestFieldKind::Body => RequestField::Body(field),
RequestFieldKind::Header => RequestField::Header(field), RequestFieldKind::Header => RequestField::Header(field, header.expect("missing header name")),
RequestFieldKind::NewtypeBody => RequestField::NewtypeBody(field), RequestFieldKind::NewtypeBody => RequestField::NewtypeBody(field),
RequestFieldKind::Path => RequestField::Path(field), RequestFieldKind::Path => RequestField::Path(field),
RequestFieldKind::Query => RequestField::Query(field), RequestFieldKind::Query => RequestField::Query(field),
@ -286,11 +319,11 @@ impl RequestField {
fn kind(&self) -> RequestFieldKind { fn kind(&self) -> RequestFieldKind {
match *self { match *self {
RequestField::Body(_) => RequestFieldKind::Body, RequestField::Body(..) => RequestFieldKind::Body,
RequestField::Header(_) => RequestFieldKind::Header, RequestField::Header(..) => RequestFieldKind::Header,
RequestField::NewtypeBody(_) => RequestFieldKind::NewtypeBody, RequestField::NewtypeBody(..) => RequestFieldKind::NewtypeBody,
RequestField::Path(_) => RequestFieldKind::Path, RequestField::Path(..) => RequestFieldKind::Path,
RequestField::Query(_) => RequestFieldKind::Query, RequestField::Query(..) => RequestFieldKind::Query,
} }
} }
@ -298,6 +331,10 @@ impl RequestField {
self.kind() == RequestFieldKind::Body self.kind() == RequestFieldKind::Body
} }
fn is_header(&self) -> bool {
self.kind() == RequestFieldKind::Header
}
fn is_path(&self) -> bool { fn is_path(&self) -> bool {
self.kind() == RequestFieldKind::Path self.kind() == RequestFieldKind::Path
} }
@ -309,7 +346,7 @@ impl RequestField {
fn field(&self) -> &Field { fn field(&self) -> &Field {
match *self { match *self {
RequestField::Body(ref field) => field, RequestField::Body(ref field) => field,
RequestField::Header(ref field) => field, RequestField::Header(ref field, _) => field,
RequestField::NewtypeBody(ref field) => field, RequestField::NewtypeBody(ref field) => field,
RequestField::Path(ref field) => field, RequestField::Path(ref field) => field,
RequestField::Query(ref field) => field, RequestField::Query(ref field) => field,

View File

@ -1,6 +1,6 @@
use quote::{ToTokens, Tokens}; use quote::{ToTokens, Tokens};
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::{Field, Meta, NestedMeta}; use syn::{Field, Ident, Lit, Meta, NestedMeta};
use api::strip_serde_attrs; use api::strip_serde_attrs;
@ -34,13 +34,13 @@ impl Response {
#field_name: response_body.#field_name, #field_name: response_body.#field_name,
}); });
} }
ResponseField::Header(ref field) => { ResponseField::Header(ref field, ref header) => {
let field_name = field.ident.expect("expected field to have an identifier"); let field_name = field.ident.expect("expected field to have an identifier");
let field_type = &field.ty; let header_name = Ident::from(header.as_ref());
let span = field.span(); let span = field.span();
tokens.append_all(quote_spanned! {span=> tokens.append_all(quote_spanned! {span=>
#field_name: headers.remove::<#field_type>() #field_name: headers.remove(::http::header::#header_name)
.expect("missing expected request header"), .expect("missing expected request header"),
}); });
} }
@ -80,6 +80,7 @@ impl From<Vec<Field>> for Response {
let fields = fields.into_iter().map(|mut field| { let fields = fields.into_iter().map(|mut field| {
let mut field_kind = ResponseFieldKind::Body; let mut field_kind = ResponseFieldKind::Body;
let mut header = None;
field.attrs = field.attrs.into_iter().filter(|attr| { field.attrs = field.attrs.into_iter().filter(|attr| {
let meta = attr.interpret_meta() let meta = attr.interpret_meta()
@ -109,7 +110,14 @@ impl From<Vec<Field>> for Response {
} }
Meta::NameValue(name_value) => { Meta::NameValue(name_value) => {
match name_value.ident.as_ref() { match name_value.ident.as_ref() {
"header" => field_kind = ResponseFieldKind::Header, "header" => {
match name_value.lit {
Lit::Str(lit_str) => header = Some(lit_str.value()),
_ => panic!("ruma_api! header attribute's value must be a string literal"),
}
field_kind = ResponseFieldKind::Header;
}
_ => panic!("ruma_api! name/value pair attribute on requests must be: header"), _ => panic!("ruma_api! name/value pair attribute on requests must be: header"),
} }
} }
@ -133,7 +141,7 @@ impl From<Vec<Field>> for Response {
return ResponseField::Body(field); return ResponseField::Body(field);
} }
} }
ResponseFieldKind::Header => ResponseField::Header(field), ResponseFieldKind::Header => ResponseField::Header(field, header.expect("missing header name")),
ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field), ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field),
} }
}).collect(); }).collect();
@ -220,7 +228,7 @@ impl ToTokens for Response {
pub enum ResponseField { pub enum ResponseField {
Body(Field), Body(Field),
Header(Field), Header(Field, String),
NewtypeBody(Field), NewtypeBody(Field),
} }
@ -228,21 +236,21 @@ impl ResponseField {
fn field(&self) -> &Field { fn field(&self) -> &Field {
match *self { match *self {
ResponseField::Body(ref field) => field, ResponseField::Body(ref field) => field,
ResponseField::Header(ref field) => field, ResponseField::Header(ref field, _) => field,
ResponseField::NewtypeBody(ref field) => field, ResponseField::NewtypeBody(ref field) => field,
} }
} }
fn is_body(&self) -> bool { fn is_body(&self) -> bool {
match *self { match *self {
ResponseField::Body(_) => true, ResponseField::Body(..) => true,
_ => false, _ => false,
} }
} }
fn is_header(&self) -> bool { fn is_header(&self) -> bool {
match *self { match *self {
ResponseField::Header(_) => true, ResponseField::Header(..) => true,
_ => false, _ => false,
} }
} }

View File

@ -1,4 +1,4 @@
#![feature(associated_consts, proc_macro, try_from)] #![feature(proc_macro, try_from)]
extern crate futures; extern crate futures;
extern crate http; extern crate http;