diff --git a/src/api/metadata.rs b/src/api/metadata.rs index a2847c54..c167829d 100644 --- a/src/api/metadata.rs +++ b/src/api/metadata.rs @@ -1,7 +1,5 @@ -use quote::{ToTokens, Tokens}; use syn::punctuated::Pair; -use syn::synom::Synom; -use syn::{Expr, ExprStruct, Ident, Lit, Member}; +use syn::{Expr, FieldValue, Lit, Member}; pub struct Metadata { pub description: String, @@ -12,8 +10,8 @@ pub struct Metadata { pub requires_authentication: bool, } -impl From for Metadata { - fn from(expr: ExprStruct) -> Self { +impl From> for Metadata { + fn from(field_values: Vec) -> Self { let mut description = None; let mut method = None; let mut name = None; @@ -21,15 +19,15 @@ impl From for Metadata { let mut rate_limited = None; let mut requires_authentication = None; - for field in expr.fields { - let identifier = match field.member { + for field_value in field_values { + let identifier = match field_value.member { Member::Named(identifier) => identifier, _ => panic!("expected Member::Named"), }; match identifier.as_ref() { "description" => { - let expr_lit = match field.expr { + let expr_lit = match field_value.expr { Expr::Lit(expr_lit) => expr_lit, _ => panic!("expected Expr::Lit"), }; @@ -40,7 +38,7 @@ impl From for Metadata { description = Some(lit_str.value()); } "method" => { - let expr_path = match field.expr { + let expr_path = match field_value.expr { Expr::Path(expr_path) => expr_path, _ => panic!("expected Expr::Path"), }; @@ -57,7 +55,7 @@ impl From for Metadata { method = Some(method_name.ident.to_string()); } "name" => { - let expr_lit = match field.expr { + let expr_lit = match field_value.expr { Expr::Lit(expr_lit) => expr_lit, _ => panic!("expected Expr::Lit"), }; @@ -68,7 +66,7 @@ impl From for Metadata { name = Some(lit_str.value()); } "path" => { - let expr_lit = match field.expr { + let expr_lit = match field_value.expr { Expr::Lit(expr_lit) => expr_lit, _ => panic!("expected Expr::Lit"), }; @@ -79,7 +77,7 @@ impl From for Metadata { path = Some(lit_str.value()); } "rate_limited" => { - let expr_lit = match field.expr { + let expr_lit = match field_value.expr { Expr::Lit(expr_lit) => expr_lit, _ => panic!("expected Expr::Lit"), }; @@ -90,7 +88,7 @@ impl From for Metadata { rate_limited = Some(lit_bool.value) } "requires_authentication" => { - let expr_lit = match field.expr { + let expr_lit = match field_value.expr { Expr::Lit(expr_lit) => expr_lit, _ => panic!("expected Expr::Lit"), }; diff --git a/src/api/mod.rs b/src/api/mod.rs index 1f72ef0d..a5238f9c 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,7 +1,7 @@ use quote::{ToTokens, Tokens}; -use syn::punctuated::Pair; +use syn::punctuated::Punctuated; use syn::synom::Synom; -use syn::{Expr, FieldValue, Ident, Member, Meta}; +use syn::{Field, FieldValue, Meta}; mod metadata; mod request; @@ -11,10 +11,10 @@ use self::metadata::Metadata; use self::request::Request; use self::response::Response; -pub fn strip_serde_attrs(field_value: &FieldValue) -> FieldValue { - let mut field_value = field_value.clone(); +pub fn strip_serde_attrs(field: &Field) -> Field { + let mut field = field.clone(); - field_value.attrs = field_value.attrs.into_iter().filter(|attr| { + field.attrs = field.attrs.into_iter().filter(|attr| { let meta = attr.interpret_meta() .expect("ruma_api! could not parse field attributes"); @@ -30,7 +30,7 @@ pub fn strip_serde_attrs(field_value: &FieldValue) -> FieldValue { false }).collect(); - field_value + field } pub struct Api { @@ -39,59 +39,13 @@ pub struct Api { response: Response, } -impl From> for Api { - fn from(exprs: Vec) -> Self { - if exprs.len() != 3 { - panic!("ruma_api! expects 3 blocks: metadata, request, and response"); - } - - let mut metadata = None; - let mut request = None; - let mut response = None; - - for expr in exprs { - let expr = match expr { - Expr::Struct(expr) => expr, - _ => panic!("ruma_api! blocks should use struct syntax"), - }; - - let segments = expr.path.segments.clone(); - - if segments.len() != 1 { - panic!("ruma_api! blocks must be one of: metadata, request, or response"); - } - - let last_segment = match segments.last().unwrap() { - Pair::End(last_segment) => last_segment, - _ => panic!("expected Pair::End"), - }; - - match last_segment.ident.as_ref() { - "metadata" => metadata = Some(expr.into()), - "request" => request = Some(expr.into()), - "response" => response = Some(expr.into()), - _ => panic!("ruma_api! blocks must be one of: metadata, request, or response"), - } - } - - if metadata.is_none() { - panic!("ruma_api! is missing metadata"); - } - - if request.is_none() { - panic!("ruma_api! is missing request"); - } - - if response.is_none() { - panic!("ruma_api! is missing response"); - } - +impl From for Api { + fn from(raw_api: RawApi) -> Self { Api { - metadata: metadata.unwrap(), - request: request.unwrap(), - response: response.unwrap(), + metadata: raw_api.metadata.into(), + request: raw_api.request.into(), + response: raw_api.response.into(), } - } } @@ -183,10 +137,7 @@ impl ToTokens for Api { }; let add_body_to_request = if let Some(field) = self.request.newtype_body_field() { - let field_name = match field.member { - Member::Named(field_name) => field_name, - _ => panic!("expected Member::Named"), - }; + let field_name = field.ident.expect("expected field to have an identifier"); quote! { let request_body = RequestBody(request.#field_name); @@ -208,10 +159,8 @@ impl ToTokens for Api { }; let deserialize_response_body = if let Some(field) = self.response.newtype_body_field() { - let field_type = match field.expr { - Expr::Path(ref field_type) => field_type, - _ => panic!("expected Expr::Path"), - }; + let field_type = &field.ty; + let mut tokens = Tokens::new(); tokens.append_all(quote! { @@ -362,15 +311,27 @@ impl ToTokens for Api { } } -pub struct Exprs { - pub inner: Vec, +type ParseMetadata = Punctuated; +type ParseFields = Punctuated; + +pub struct RawApi { + pub metadata: Vec, + pub request: Vec, + pub response: Vec, } -impl Synom for Exprs { +impl Synom for RawApi { named!(parse -> Self, do_parse!( - exprs: many0!(syn!(Expr)) >> - (Exprs { - inner: exprs, + custom_keyword!(metadata) >> + metadata: braces!(ParseMetadata::parse_terminated) >> + custom_keyword!(request) >> + request: braces!(call!(ParseFields::parse_terminated_with, Field::parse_named)) >> + custom_keyword!(response) >> + response: braces!(call!(ParseFields::parse_terminated_with, Field::parse_named)) >> + (RawApi { + metadata: metadata.1.into_iter().collect(), + request: request.1.into_iter().collect(), + response: response.1.into_iter().collect(), }) )); } diff --git a/src/api/request.rs b/src/api/request.rs index b6f5b0cd..4abdd02f 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,6 +1,5 @@ use quote::{ToTokens, Tokens}; -use syn::synom::Synom; -use syn::{ExprStruct, Field, FieldValue, FieldsNamed, Member, Meta, NestedMeta}; +use syn::{Field, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -25,7 +24,7 @@ impl Request { self.fields.iter().filter(|field| field.is_path()).count() } - pub fn newtype_body_field(&self) -> Option<&FieldValue> { + pub fn newtype_body_field(&self) -> Option<&Field> { for request_field in self.fields.iter() { match *request_field { RequestField::NewtypeBody(ref field) => { @@ -54,10 +53,7 @@ impl Request { let mut tokens = Tokens::new(); for field in self.fields.iter().flat_map(|f| f.field_(request_field_kind)) { - let field_name = match field.member { - Member::Named(field_name) => field_name, - _ => panic!("expected Member::Named"), - }; + let field_name = field.ident.expect("expected field to have an identifier"); tokens.append_all(quote! { #field_name: request.#field_name, @@ -68,14 +64,14 @@ impl Request { } } -impl From for Request { - fn from(expr: ExprStruct) -> Self { +impl From> for Request { + fn from(fields: Vec) -> Self { let mut has_newtype_body = false; - let fields = expr.fields.into_iter().map(|mut field_value| { + let fields = fields.into_iter().map(|mut field| { let mut field_kind = RequestFieldKind::Body; - field_value.attrs = field_value.attrs.into_iter().filter(|attr| { + field.attrs = field.attrs.into_iter().filter(|attr| { let meta = attr.interpret_meta() .expect("ruma_api! could not parse request field attributes"); @@ -127,7 +123,7 @@ impl From for Request { ); } - RequestField::new(field_kind, field_value) + RequestField::new(field_kind, field) }).collect(); Request { @@ -160,12 +156,12 @@ impl ToTokens for Request { if let Some(newtype_body_field) = self.newtype_body_field() { let mut field = newtype_body_field.clone(); - let expr = field.expr; + let ty = field.ty; tokens.append_all(quote! { /// Data in the request body. #[derive(Debug, Serialize)] - struct RequestBody(#expr); + struct RequestBody(#ty); }); } else if self.has_body_fields() { tokens.append_all(quote! { @@ -239,15 +235,15 @@ impl ToTokens for Request { } pub enum RequestField { - Body(FieldValue), - Header(FieldValue), - NewtypeBody(FieldValue), - Path(FieldValue), - Query(FieldValue), + Body(Field), + Header(Field), + NewtypeBody(Field), + Path(Field), + Query(Field), } impl RequestField { - fn new(kind: RequestFieldKind, field: FieldValue) -> RequestField { + fn new(kind: RequestFieldKind, field: Field) -> RequestField { match kind { RequestFieldKind::Body => RequestField::Body(field), RequestFieldKind::Header => RequestField::Header(field), @@ -279,7 +275,7 @@ impl RequestField { self.kind() == RequestFieldKind::Query } - fn field(&self) -> &FieldValue { + fn field(&self) -> &Field { match *self { RequestField::Body(ref field) => field, RequestField::Header(ref field) => field, @@ -289,7 +285,7 @@ impl RequestField { } } - fn field_(&self, kind: RequestFieldKind) -> Option<&FieldValue> { + fn field_(&self, kind: RequestFieldKind) -> Option<&Field> { if self.kind() == kind { Some(self.field()) } else { diff --git a/src/api/response.rs b/src/api/response.rs index b7b7c514..7246e4e1 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,5 +1,5 @@ use quote::{ToTokens, Tokens}; -use syn::{Expr, ExprStruct, Field, FieldValue, FieldsNamed, Member, Meta, NestedMeta}; +use syn::{Field, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -26,25 +26,15 @@ impl Response { for response_field in self.fields.iter() { match *response_field { ResponseField::Body(ref field) => { - let field_name = match field.member { - Member::Named(field_name) => field_name, - _ => panic!("expected Member::Named"), - }; + let field_name = field.ident.expect("expected field to have an identifier"); tokens.append_all(quote! { #field_name: response_body.#field_name, }); } ResponseField::Header(ref field) => { - let field_name = match field.member { - Member::Named(field_name) => field_name, - _ => panic!("expected Member::Named"), - }; - - let field_type = match field.expr { - Expr::Path(ref field_type) => field_type, - _ => panic!("expected Expr::Path"), - }; + let field_name = field.ident.expect("expected field to have an identifier"); + let field_type = &field.ty; tokens.append_all(quote! { #field_name: headers.remove::<#field_type>() @@ -52,10 +42,7 @@ impl Response { }); } ResponseField::NewtypeBody(ref field) => { - let field_name = match field.member { - Member::Named(field_name) => field_name, - _ => panic!("expected Member::Named"), - }; + let field_name = field.ident.expect("expected field to have an identifier"); tokens.append_all(quote! { #field_name: response_body, @@ -67,7 +54,7 @@ impl Response { tokens } - pub fn newtype_body_field(&self) -> Option<&FieldValue> { + pub fn newtype_body_field(&self) -> Option<&Field> { for response_field in self.fields.iter() { match *response_field { ResponseField::NewtypeBody(ref field) => { @@ -83,14 +70,14 @@ impl Response { } -impl From for Response { - fn from(expr: ExprStruct) -> Self { +impl From> for Response { + fn from(fields: Vec) -> Self { let mut has_newtype_body = false; - let fields = expr.fields.into_iter().map(|mut field_value| { + let fields = fields.into_iter().map(|mut field| { let mut field_kind = ResponseFieldKind::Body; - field_value.attrs = field_value.attrs.into_iter().filter(|attr| { + field.attrs = field.attrs.into_iter().filter(|attr| { let meta = attr.interpret_meta() .expect("ruma_api! could not parse response field attributes"); @@ -138,11 +125,11 @@ impl From for Response { if has_newtype_body { panic!("ruma_api! responses cannot have both normal body fields and a newtype body field"); } else { - return ResponseField::Body(field_value); + return ResponseField::Body(field); } } - ResponseFieldKind::Header => ResponseField::Header(field_value), - ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field_value), + ResponseFieldKind::Header => ResponseField::Header(field), + ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field), } }).collect(); @@ -176,12 +163,12 @@ impl ToTokens for Response { if let Some(newtype_body_field) = self.newtype_body_field() { let mut field = newtype_body_field.clone(); - let expr = field.expr; + let ty = field.ty; tokens.append_all(quote! { /// Data in the response body. #[derive(Debug, Deserialize)] - struct ResponseBody(#expr); + struct ResponseBody(#ty); }); } else if self.has_body_fields() { tokens.append_all(quote! { @@ -209,13 +196,13 @@ impl ToTokens for Response { } pub enum ResponseField { - Body(FieldValue), - Header(FieldValue), - NewtypeBody(FieldValue), + Body(Field), + Header(Field), + NewtypeBody(Field), } impl ResponseField { - fn field(&self) -> &FieldValue { + fn field(&self) -> &Field { match *self { ResponseField::Body(ref field) => field, ResponseField::Header(ref field) => field, diff --git a/src/lib.rs b/src/lib.rs index b6e440e7..05ba19cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,8 @@ //! See the documentation for the `ruma_api!` macro for usage details. #![deny(missing_debug_implementations)] -#![feature(proc_macro, try_from)] +#![feature(proc_macro)] #![recursion_limit="256"] -#![allow(warnings)] extern crate proc_macro; #[macro_use] extern crate quote; @@ -14,11 +13,10 @@ extern crate ruma_api; #[macro_use] extern crate syn; use proc_macro::TokenStream; -use std::convert::TryFrom; -use quote::{ToTokens, Tokens}; +use quote::ToTokens; -use api::{Api, Exprs}; +use api::{Api, RawApi}; mod api; @@ -195,9 +193,9 @@ mod api; /// ``` #[proc_macro] pub fn ruma_api(input: TokenStream) -> TokenStream { - let exprs: Exprs = syn::parse(input).expect("ruma_api! failed to parse input"); + let raw_api: RawApi = syn::parse(input).expect("ruma_api! failed to parse input"); - let api = Api::from(exprs.inner); + let api = Api::from(raw_api); api.into_tokens().into() } diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index cc93423e..5c066220 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -1,7 +1,6 @@ #![feature(associated_consts, proc_macro, try_from)] extern crate futures; -extern crate hyper; extern crate ruma_api; extern crate ruma_api_macros; extern crate serde; @@ -17,7 +16,7 @@ pub mod some_endpoint { ruma_api! { metadata { description: "Does something.", - method: Method::Get, // A `hyper::Method` value. No need to import the name. + method: GET, // An `http::Method` constant. No imports required. name: "some_endpoint", path: "/_matrix/some/endpoint/:baz", rate_limited: false,