Use a custom parser for the raw input.

This commit is contained in:
Jimmy Cuadra 2018-05-12 23:56:23 -07:00
parent 8f6bc5af77
commit 38746660b6
6 changed files with 86 additions and 147 deletions

View File

@ -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<ExprStruct> for Metadata {
fn from(expr: ExprStruct) -> Self {
impl From<Vec<FieldValue>> for Metadata {
fn from(field_values: Vec<FieldValue>) -> Self {
let mut description = None;
let mut method = None;
let mut name = None;
@ -21,15 +19,15 @@ impl From<ExprStruct> 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<ExprStruct> 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<ExprStruct> 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<ExprStruct> 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<ExprStruct> 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<ExprStruct> 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"),
};

View File

@ -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<Vec<Expr>> for Api {
fn from(exprs: Vec<Expr>) -> 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<RawApi> 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<Expr>,
type ParseMetadata = Punctuated<FieldValue, Token![,]>;
type ParseFields = Punctuated<Field, Token![,]>;
pub struct RawApi {
pub metadata: Vec<FieldValue>,
pub request: Vec<Field>,
pub response: Vec<Field>,
}
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(),
})
));
}

View File

@ -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<ExprStruct> for Request {
fn from(expr: ExprStruct) -> Self {
impl From<Vec<Field>> for Request {
fn from(fields: Vec<Field>) -> 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<ExprStruct> 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 {

View File

@ -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<ExprStruct> for Response {
fn from(expr: ExprStruct) -> Self {
impl From<Vec<Field>> for Response {
fn from(fields: Vec<Field>) -> 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<ExprStruct> 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,

View File

@ -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()
}

View File

@ -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,