ruma-api-macros: Add more error spans for request block

This commit is contained in:
Jonas Platte 2019-11-18 22:18:38 +01:00
parent eab2374253
commit c277c0d257
No known key found for this signature in database
GPG Key ID: 7D261D771D915378
2 changed files with 102 additions and 66 deletions

View File

@ -309,7 +309,7 @@ pub struct RawApi {
/// The `metadata` section of the macro. /// The `metadata` section of the macro.
pub metadata: RawMetadata, pub metadata: RawMetadata,
/// The `request` section of the macro. /// The `request` section of the macro.
pub request: Vec<Field>, pub request: RawRequest,
/// The `response` section of the macro. /// The `response` section of the macro.
pub response: Vec<Field>, pub response: Vec<Field>,
} }
@ -317,10 +317,7 @@ pub struct RawApi {
impl Parse for RawApi { impl Parse for RawApi {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> { fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let metadata = input.parse::<RawMetadata>()?; let metadata = input.parse::<RawMetadata>()?;
let request = input.parse::<RawRequest>()?;
input.parse::<kw::request>()?;
let request;
braced!(request in input);
input.parse::<kw::response>()?; input.parse::<kw::response>()?;
let response; let response;
@ -328,10 +325,7 @@ impl Parse for RawApi {
Ok(Self { Ok(Self {
metadata, metadata,
request: request request,
.parse_terminated::<Field, Token![,]>(Field::parse_named)?
.into_iter()
.collect(),
response: response response: response
.parse_terminated::<Field, Token![,]>(Field::parse_named)? .parse_terminated::<Field, Token![,]>(Field::parse_named)?
.into_iter() .into_iter()
@ -360,3 +354,24 @@ impl Parse for RawMetadata {
}) })
} }
} }
pub struct RawRequest {
pub request_kw: kw::request,
pub fields: Vec<Field>,
}
impl Parse for RawRequest {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let request_kw = input.parse::<kw::request>()?;
let fields;
braced!(fields in input);
Ok(Self {
request_kw,
fields: fields
.parse_terminated::<Field, Token![,]>(Field::parse_named)?
.into_iter()
.collect(),
})
}
}

View File

@ -1,6 +1,6 @@
//! Details of the `request` section of the procedural macro. //! Details of the `request` section of the procedural macro.
use std::convert::TryFrom; use std::{convert::TryFrom, mem};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens}; use quote::{quote, quote_spanned, ToTokens};
@ -8,7 +8,7 @@ use syn::{spanned::Spanned, Field, Ident};
use crate::api::{ use crate::api::{
attribute::{Meta, MetaNameValue}, attribute::{Meta, MetaNameValue},
strip_serde_attrs, strip_serde_attrs, RawRequest,
}; };
/// The result of processing the `request` section of the macro. /// The result of processing the `request` section of the macro.
@ -121,66 +121,92 @@ impl Request {
} }
} }
impl TryFrom<Vec<Field>> for Request { impl TryFrom<RawRequest> for Request {
type Error = syn::Error; type Error = syn::Error;
fn try_from(fields: Vec<Field>) -> syn::Result<Self> { fn try_from(raw: RawRequest) -> syn::Result<Self> {
let fields: Vec<_> = fields.into_iter().map(|mut field| { let mut newtype_body_field = None;
let fields = raw
.fields
.into_iter()
.map(|mut field| {
let mut field_kind = None; let mut field_kind = None;
let mut header = None; let mut header = None;
field.attrs.retain(|attr| { for attr in mem::replace(&mut field.attrs, Vec::new()) {
let meta = match Meta::from_attribute(attr) { let meta = match Meta::from_attribute(&attr) {
Some(m) => m, Some(m) => m,
None => return true, None => {
field.attrs.push(attr);
continue;
}
}; };
match meta { if field_kind.is_some() {
Meta::Word(ident) => { return Err(syn::Error::new_spanned(
assert!( attr,
field_kind.is_none(), "There can only be one field kind attribute",
"ruma_api! field kind can only be set once per field" ));
); }
field_kind = Some(match &ident.to_string()[..] { field_kind = Some(match meta {
"body" => RequestFieldKind::NewtypeBody, Meta::Word(ident) => {
match &ident.to_string()[..] {
"body" => {
if let Some(f) = &newtype_body_field {
let mut error = syn::Error::new_spanned(
field,
"There can only be one newtype body field",
);
error.combine(syn::Error::new_spanned(
f,
"Previous newtype body field",
));
return Err(error);
}
newtype_body_field = Some(field.clone());
RequestFieldKind::NewtypeBody
}
"path" => RequestFieldKind::Path, "path" => RequestFieldKind::Path,
"query" => RequestFieldKind::Query, "query" => RequestFieldKind::Query,
_ => panic!("ruma_api! single-word attribute on requests must be: body, path, or query"), _ => {
}); return Err(syn::Error::new_spanned(
ident,
"Invalid #[ruma_api] argument, expected one of `body`, `path`, `query`",
));
}
}
} }
Meta::NameValue(MetaNameValue { name, value }) => { Meta::NameValue(MetaNameValue { name, value }) => {
assert!( if name != "header" {
name == "header", return Err(syn::Error::new_spanned(
"ruma_api! name/value pair attribute on requests must be: header" name,
); "Invalid #[ruma_api] argument with value, expected `header`"
assert!( ));
field_kind.is_none(), }
"ruma_api! field kind can only be set once per field"
);
header = Some(value); header = Some(value);
field_kind = Some(RequestFieldKind::Header); RequestFieldKind::Header
} }
}
false
}); });
}
RequestField::new(field_kind.unwrap_or(RequestFieldKind::Body), field, header) Ok(RequestField::new(
}).collect(); field_kind.unwrap_or(RequestFieldKind::Body),
field,
header,
))
})
.collect::<syn::Result<Vec<_>>>()?;
let num_body_fields = fields.iter().filter(|f| f.is_body()).count(); if newtype_body_field.is_some() && fields.iter().any(|f| f.is_body()) {
let num_newtype_body_fields = fields.iter().filter(|f| f.is_newtype_body()).count(); return Err(syn::Error::new_spanned(
assert!( // TODO: raw,
num_newtype_body_fields <= 1, raw.request_kw,
"ruma_api! request can only have one newtype body field" "Can't have both a newtype body field and regular body fields",
); ));
if num_newtype_body_fields == 1 {
assert!(
num_body_fields == 0,
"ruma_api! request can't have both regular body fields and a newtype body field"
);
} }
Ok(Self { fields }) Ok(Self { fields })
@ -329,11 +355,6 @@ impl RequestField {
self.kind() == RequestFieldKind::Header self.kind() == RequestFieldKind::Header
} }
/// Whether or not this request field is a newtype body kind.
fn is_newtype_body(&self) -> bool {
self.kind() == RequestFieldKind::NewtypeBody
}
/// Whether or not this request field is a path kind. /// Whether or not this request field is a path kind.
fn is_path(&self) -> bool { fn is_path(&self) -> bool {
self.kind() == RequestFieldKind::Path self.kind() == RequestFieldKind::Path