diff --git a/README.md b/README.md index 20af7394..494cdd9c 100644 --- a/README.md +++ b/README.md @@ -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. ``` rust -#![feature(associated_consts, proc_macro, try_from)] +#![feature(proc_macro, try_from)] extern crate futures; extern crate http; diff --git a/src/api/mod.rs b/src/api/mod.rs index dc355895..e755ec54 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -99,7 +99,7 @@ impl ToTokens for Api { }); } else { tokens.append_all(quote! { - ("#segment"); + (#segment); }); } } @@ -125,6 +125,18 @@ impl ToTokens for Api { 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 field_name = field.ident.expect("expected field to have an identifier"); @@ -211,7 +223,7 @@ impl ToTokens for Api { #request_types - impl ::std::convert::TryFrom for ::http::Request { + impl ::std::convert::TryFrom for ::http::Request { type Error = ::ruma_api::Error; #[allow(unused_mut, unused_variables)] @@ -232,6 +244,8 @@ impl ToTokens for Api { url.into_string().parse().unwrap(), ); + { #add_headers_to_request } + { #add_body_to_request } Ok(http_request) @@ -240,12 +254,12 @@ impl ToTokens for Api { #response_types - impl ::futures::future::FutureFrom<::http::Response> for Response { + impl ::futures::future::FutureFrom<::http::Response> for Response { type Future = Box<_Future>; type Error = ::ruma_api::Error; #[allow(unused_variables)] - fn future_from(http_response: ::http::Response) + fn future_from(http_response: ::http::Response) -> Box<_Future> { #extract_headers diff --git a/src/api/request.rs b/src/api/request.rs index 2c9084fb..505c6a14 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,6 +1,6 @@ use quote::{ToTokens, Tokens}; use syn::spanned::Spanned; -use syn::{Field, Meta, NestedMeta}; +use syn::{Field, Ident, Lit, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -9,10 +9,31 @@ pub struct 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 { 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 { self.fields.iter().any(|field| field.is_path()) } @@ -21,6 +42,10 @@ impl Request { self.fields.iter().any(|field| field.is_query()) } + pub fn header_fields(&self) -> impl Iterator { + self.fields.iter().filter(|field| field.is_header()) + } + pub fn path_field_count(&self) -> usize { self.fields.iter().filter(|field| field.is_path()).count() } @@ -72,6 +97,7 @@ impl From> for Request { let fields = fields.into_iter().map(|mut field| { let mut field_kind = RequestFieldKind::Body; + let mut header = None; field.attrs = field.attrs.into_iter().filter(|attr| { let meta = attr.interpret_meta() @@ -103,7 +129,14 @@ impl From> for Request { } Meta::NameValue(name_value) => { 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"), } } @@ -126,7 +159,7 @@ impl From> for Request { ); } - RequestField::new(field_kind, field) + RequestField::new(field_kind, field, header) }).collect(); Request { @@ -267,17 +300,17 @@ impl ToTokens for Request { pub enum RequestField { Body(Field), - Header(Field), + Header(Field, String), NewtypeBody(Field), Path(Field), Query(Field), } impl RequestField { - fn new(kind: RequestFieldKind, field: Field) -> RequestField { + fn new(kind: RequestFieldKind, field: Field, header: Option) -> RequestField { match kind { 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::Path => RequestField::Path(field), RequestFieldKind::Query => RequestField::Query(field), @@ -286,11 +319,11 @@ impl RequestField { fn kind(&self) -> RequestFieldKind { match *self { - RequestField::Body(_) => RequestFieldKind::Body, - RequestField::Header(_) => RequestFieldKind::Header, - RequestField::NewtypeBody(_) => RequestFieldKind::NewtypeBody, - RequestField::Path(_) => RequestFieldKind::Path, - RequestField::Query(_) => RequestFieldKind::Query, + RequestField::Body(..) => RequestFieldKind::Body, + RequestField::Header(..) => RequestFieldKind::Header, + RequestField::NewtypeBody(..) => RequestFieldKind::NewtypeBody, + RequestField::Path(..) => RequestFieldKind::Path, + RequestField::Query(..) => RequestFieldKind::Query, } } @@ -298,6 +331,10 @@ impl RequestField { self.kind() == RequestFieldKind::Body } + fn is_header(&self) -> bool { + self.kind() == RequestFieldKind::Header + } + fn is_path(&self) -> bool { self.kind() == RequestFieldKind::Path } @@ -309,7 +346,7 @@ impl RequestField { fn field(&self) -> &Field { match *self { RequestField::Body(ref field) => field, - RequestField::Header(ref field) => field, + RequestField::Header(ref field, _) => field, RequestField::NewtypeBody(ref field) => field, RequestField::Path(ref field) => field, RequestField::Query(ref field) => field, diff --git a/src/api/response.rs b/src/api/response.rs index d4388851..3f08327e 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,6 +1,6 @@ use quote::{ToTokens, Tokens}; use syn::spanned::Spanned; -use syn::{Field, Meta, NestedMeta}; +use syn::{Field, Ident, Lit, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -34,13 +34,13 @@ impl Response { #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_type = &field.ty; + let header_name = Ident::from(header.as_ref()); let span = field.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"), }); } @@ -80,6 +80,7 @@ impl From> for Response { let fields = fields.into_iter().map(|mut field| { let mut field_kind = ResponseFieldKind::Body; + let mut header = None; field.attrs = field.attrs.into_iter().filter(|attr| { let meta = attr.interpret_meta() @@ -109,7 +110,14 @@ impl From> for Response { } Meta::NameValue(name_value) => { 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"), } } @@ -133,7 +141,7 @@ impl From> for Response { return ResponseField::Body(field); } } - ResponseFieldKind::Header => ResponseField::Header(field), + ResponseFieldKind::Header => ResponseField::Header(field, header.expect("missing header name")), ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field), } }).collect(); @@ -220,7 +228,7 @@ impl ToTokens for Response { pub enum ResponseField { Body(Field), - Header(Field), + Header(Field, String), NewtypeBody(Field), } @@ -228,21 +236,21 @@ impl ResponseField { fn field(&self) -> &Field { match *self { ResponseField::Body(ref field) => field, - ResponseField::Header(ref field) => field, + ResponseField::Header(ref field, _) => field, ResponseField::NewtypeBody(ref field) => field, } } fn is_body(&self) -> bool { match *self { - ResponseField::Body(_) => true, + ResponseField::Body(..) => true, _ => false, } } fn is_header(&self) -> bool { match *self { - ResponseField::Header(_) => true, + ResponseField::Header(..) => true, _ => false, } } diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 9379ae5d..43cd5472 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -1,4 +1,4 @@ -#![feature(associated_consts, proc_macro, try_from)] +#![feature(proc_macro, try_from)] extern crate futures; extern crate http;