2021-05-03 15:50:27 +02:00

351 lines
12 KiB
Rust

use std::{collections::BTreeSet, mem};
use syn::{
braced,
parse::{Parse, ParseStream},
spanned::Spanned,
visit::Visit,
Attribute, Field, Ident, Lifetime, Token, Type,
};
use super::{
attribute::{Meta, MetaNameValue},
request::{RequestField, RequestFieldKind, RequestLifetimes},
response::{ResponseField, ResponseFieldKind},
Api, Metadata, Request, Response,
};
mod kw {
use syn::custom_keyword;
custom_keyword!(error);
custom_keyword!(request);
custom_keyword!(response);
}
impl Parse for Api {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let metadata: Metadata = input.parse()?;
let req_attrs = input.call(Attribute::parse_outer)?;
let (request, attributes) = if input.peek(kw::request) {
let request = parse_request(input, req_attrs)?;
let after_req_attrs = input.call(Attribute::parse_outer)?;
(Some(request), after_req_attrs)
} else {
// There was no `request` field so the attributes are for `response`
(None, req_attrs)
};
let response = if input.peek(kw::response) {
Some(parse_response(input, attributes)?)
} else if !attributes.is_empty() {
return Err(syn::Error::new_spanned(
&attributes[0],
"attributes are not supported on the error type",
));
} else {
None
};
// TODO: Use `bool::then` when MSRV >= 1.50
let error_ty = if input.peek(kw::error) {
let _: kw::error = input.parse()?;
let _: Token![:] = input.parse()?;
Some(input.parse()?)
} else {
None
};
if let Some(req) = &request {
let newtype_body_field = req.newtype_body_field();
if metadata.method == "GET" && (req.has_body_fields() || newtype_body_field.is_some()) {
let mut combined_error: Option<syn::Error> = None;
let mut add_error = |field| {
let error =
syn::Error::new_spanned(field, "GET endpoints can't have body fields");
if let Some(combined_error_ref) = &mut combined_error {
combined_error_ref.combine(error);
} else {
combined_error = Some(error);
}
};
for field in req.body_fields() {
add_error(field);
}
if let Some(field) = newtype_body_field {
add_error(field);
}
return Err(combined_error.unwrap());
}
}
Ok(Self { metadata, request, response, error_ty })
}
}
fn parse_request(input: ParseStream<'_>, attributes: Vec<Attribute>) -> syn::Result<Request> {
let request_kw: kw::request = input.parse()?;
let _: Token![:] = input.parse()?;
let fields;
braced!(fields in input);
let mut newtype_body_field = None;
let mut query_map_field = None;
let mut lifetimes = RequestLifetimes::default();
let fields: Vec<_> = fields
.parse_terminated::<Field, Token![,]>(Field::parse_named)?
.into_iter()
.map(|mut field| {
let mut field_kind = None;
let mut header = None;
for attr in mem::take(&mut field.attrs) {
let meta = match Meta::from_attribute(&attr)? {
Some(m) => m,
None => {
field.attrs.push(attr);
continue;
}
};
if field_kind.is_some() {
return Err(syn::Error::new_spanned(
attr,
"There can only be one field kind attribute",
));
}
field_kind = Some(match meta {
Meta::Word(ident) => match &ident.to_string()[..] {
attr @ "body" | attr @ "raw_body" => req_res_meta_word(
attr,
&field,
&mut newtype_body_field,
RequestFieldKind::NewtypeBody,
RequestFieldKind::NewtypeRawBody,
)?,
"path" => RequestFieldKind::Path,
"query" => RequestFieldKind::Query,
"query_map" => {
if let Some(f) = &query_map_field {
let mut error = syn::Error::new_spanned(
field,
"There can only be one query map field",
);
error.combine(syn::Error::new_spanned(
f,
"Previous query map field",
));
return Err(error);
}
query_map_field = Some(field.clone());
RequestFieldKind::QueryMap
}
_ => {
return Err(syn::Error::new_spanned(
ident,
"Invalid #[ruma_api] argument, expected one of \
`body`, `path`, `query`, `query_map`",
));
}
},
Meta::NameValue(MetaNameValue { name, value }) => {
req_res_name_value(name, value, &mut header, RequestFieldKind::Header)?
}
});
}
match field_kind.unwrap_or(RequestFieldKind::Body) {
RequestFieldKind::Header => {
collect_lifetime_idents(&mut lifetimes.header, &field.ty)
}
RequestFieldKind::Body => collect_lifetime_idents(&mut lifetimes.body, &field.ty),
RequestFieldKind::NewtypeBody => {
collect_lifetime_idents(&mut lifetimes.body, &field.ty)
}
RequestFieldKind::NewtypeRawBody => {
collect_lifetime_idents(&mut lifetimes.body, &field.ty)
}
RequestFieldKind::Path => collect_lifetime_idents(&mut lifetimes.path, &field.ty),
RequestFieldKind::Query => collect_lifetime_idents(&mut lifetimes.query, &field.ty),
RequestFieldKind::QueryMap => {
collect_lifetime_idents(&mut lifetimes.query, &field.ty)
}
}
Ok(RequestField::new(field_kind.unwrap_or(RequestFieldKind::Body), field, header))
})
.collect::<syn::Result<_>>()?;
if newtype_body_field.is_some() && fields.iter().any(|f| f.is_body()) {
// TODO: highlight conflicting fields,
return Err(syn::Error::new_spanned(
request_kw,
"Can't have both a newtype body field and regular body fields",
));
}
if query_map_field.is_some() && fields.iter().any(|f| f.is_query()) {
return Err(syn::Error::new_spanned(
// TODO: raw,
request_kw,
"Can't have both a query map field and regular query fields",
));
}
// TODO when/if `&[(&str, &str)]` is supported remove this
if query_map_field.is_some() && !lifetimes.query.is_empty() {
return Err(syn::Error::new_spanned(
request_kw,
"Lifetimes are not allowed for query_map fields",
));
}
Ok(Request { attributes, fields, lifetimes })
}
fn parse_response(input: ParseStream<'_>, attributes: Vec<Attribute>) -> syn::Result<Response> {
let response_kw: kw::response = input.parse()?;
let _: Token![:] = input.parse()?;
let fields;
braced!(fields in input);
let mut newtype_body_field = None;
let fields: Vec<_> = fields
.parse_terminated::<Field, Token![,]>(Field::parse_named)?
.into_iter()
.map(|mut field| {
if has_lifetime(&field.ty) {
return Err(syn::Error::new(
field.ident.span(),
"Lifetimes on Response fields cannot be supported until GAT are stable",
));
}
let mut field_kind = None;
let mut header = None;
for attr in mem::take(&mut field.attrs) {
let meta = match Meta::from_attribute(&attr)? {
Some(m) => m,
None => {
field.attrs.push(attr);
continue;
}
};
if field_kind.is_some() {
return Err(syn::Error::new_spanned(
attr,
"There can only be one field kind attribute",
));
}
field_kind = Some(match meta {
Meta::Word(ident) => match &ident.to_string()[..] {
s @ "body" | s @ "raw_body" => req_res_meta_word(
s,
&field,
&mut newtype_body_field,
ResponseFieldKind::NewtypeBody,
ResponseFieldKind::NewtypeRawBody,
)?,
_ => {
return Err(syn::Error::new_spanned(
ident,
"Invalid #[ruma_api] argument with value, expected `body`",
));
}
},
Meta::NameValue(MetaNameValue { name, value }) => {
req_res_name_value(name, value, &mut header, ResponseFieldKind::Header)?
}
});
}
Ok(match field_kind.unwrap_or(ResponseFieldKind::Body) {
ResponseFieldKind::Body => ResponseField::Body(field),
ResponseFieldKind::Header => {
ResponseField::Header(field, header.expect("missing header name"))
}
ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field),
ResponseFieldKind::NewtypeRawBody => ResponseField::NewtypeRawBody(field),
})
})
.collect::<syn::Result<_>>()?;
if newtype_body_field.is_some() && fields.iter().any(|f| f.is_body()) {
// TODO: highlight conflicting fields,
return Err(syn::Error::new_spanned(
response_kw,
"Can't have both a newtype body field and regular body fields",
));
}
Ok(Response { attributes, fields })
}
fn has_lifetime(ty: &Type) -> bool {
let mut lifetimes = BTreeSet::new();
collect_lifetime_idents(&mut lifetimes, ty);
!lifetimes.is_empty()
}
fn collect_lifetime_idents(lifetimes: &mut BTreeSet<Lifetime>, ty: &Type) {
struct Visitor<'lt>(&'lt mut BTreeSet<Lifetime>);
impl<'ast> Visit<'ast> for Visitor<'_> {
fn visit_lifetime(&mut self, lt: &'ast Lifetime) {
self.0.insert(lt.clone());
}
}
Visitor(lifetimes).visit_type(ty)
}
fn req_res_meta_word<T>(
attr_kind: &str,
field: &syn::Field,
newtype_body_field: &mut Option<syn::Field>,
body_field_kind: T,
raw_field_kind: T,
) -> syn::Result<T> {
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());
Ok(match attr_kind {
"body" => body_field_kind,
"raw_body" => raw_field_kind,
_ => unreachable!(),
})
}
fn req_res_name_value<T>(
name: Ident,
value: Ident,
header: &mut Option<Ident>,
field_kind: T,
) -> syn::Result<T> {
if name != "header" {
return Err(syn::Error::new_spanned(
name,
"Invalid #[ruma_api] argument with value, expected `header`",
));
}
*header = Some(value);
Ok(field_kind)
}