api-macros: Move parsing logic into a separate module
This commit is contained in:
parent
e7643d4c77
commit
679508f831
@ -2,13 +2,11 @@
|
|||||||
|
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{
|
use syn::Type;
|
||||||
parse::{Parse, ParseStream},
|
|
||||||
Token, Type,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) mod attribute;
|
pub(crate) mod attribute;
|
||||||
pub(crate) mod metadata;
|
pub(crate) mod metadata;
|
||||||
|
pub(crate) mod parse;
|
||||||
pub(crate) mod request;
|
pub(crate) mod request;
|
||||||
pub(crate) mod response;
|
pub(crate) mod response;
|
||||||
|
|
||||||
@ -30,53 +28,6 @@ pub struct Api {
|
|||||||
error_ty: Option<Type>,
|
error_ty: Option<Type>,
|
||||||
}
|
}
|
||||||
|
|
||||||
mod kw {
|
|
||||||
syn::custom_keyword!(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Api {
|
|
||||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
|
||||||
let metadata: Metadata = input.parse()?;
|
|
||||||
let request: Request = input.parse()?;
|
|
||||||
let response: Response = input.parse()?;
|
|
||||||
|
|
||||||
// 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
|
|
||||||
};
|
|
||||||
|
|
||||||
let newtype_body_field = request.newtype_body_field();
|
|
||||||
if metadata.method == "GET" && (request.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 request.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 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expand_all(api: Api) -> syn::Result<TokenStream> {
|
pub fn expand_all(api: Api) -> syn::Result<TokenStream> {
|
||||||
// Guarantee `ruma_api` is available and named something we can refer to.
|
// Guarantee `ruma_api` is available and named something we can refer to.
|
||||||
let ruma_api = util::import_ruma_api();
|
let ruma_api = util::import_ruma_api();
|
||||||
|
296
ruma-api-macros/src/api/parse.rs
Normal file
296
ruma-api-macros/src/api/parse.rs
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
use std::mem;
|
||||||
|
|
||||||
|
use syn::{
|
||||||
|
braced,
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
spanned::Spanned,
|
||||||
|
Attribute, Field, Token,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
attribute::{Meta, MetaNameValue},
|
||||||
|
request::{RequestField, RequestFieldKind, RequestLifetimes},
|
||||||
|
response::{ResponseField, ResponseFieldKind},
|
||||||
|
Api, Metadata, Request, Response,
|
||||||
|
};
|
||||||
|
use crate::util;
|
||||||
|
|
||||||
|
mod kw {
|
||||||
|
syn::custom_keyword!(error);
|
||||||
|
syn::custom_keyword!(request);
|
||||||
|
syn::custom_keyword!(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Api {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
let metadata: Metadata = input.parse()?;
|
||||||
|
let request: Request = input.parse()?;
|
||||||
|
let response: Response = input.parse()?;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
};
|
||||||
|
|
||||||
|
let newtype_body_field = request.newtype_body_field();
|
||||||
|
if metadata.method == "GET" && (request.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 request.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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Request {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
let attributes = input.call(Attribute::parse_outer)?;
|
||||||
|
let request_kw = input.parse::<kw::request>()?;
|
||||||
|
input.parse::<Token![:]>()?;
|
||||||
|
let fields;
|
||||||
|
braced!(fields in input);
|
||||||
|
|
||||||
|
let fields = fields.parse_terminated::<Field, Token![,]>(Field::parse_named)?;
|
||||||
|
|
||||||
|
let mut newtype_body_field = None;
|
||||||
|
let mut query_map_field = None;
|
||||||
|
let mut lifetimes = RequestLifetimes::default();
|
||||||
|
|
||||||
|
let fields = fields
|
||||||
|
.into_iter()
|
||||||
|
.map(|mut field| {
|
||||||
|
let mut field_kind = None;
|
||||||
|
let mut header = None;
|
||||||
|
|
||||||
|
for attr in mem::replace(&mut field.attrs, Vec::new()) {
|
||||||
|
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" => util::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 }) => util::req_res_name_value(
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
&mut header,
|
||||||
|
RequestFieldKind::Header,
|
||||||
|
)?,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
match field_kind.unwrap_or(RequestFieldKind::Body) {
|
||||||
|
RequestFieldKind::Header => {
|
||||||
|
util::collect_lifetime_ident(&mut lifetimes.header, &field.ty)
|
||||||
|
}
|
||||||
|
RequestFieldKind::Body => {
|
||||||
|
util::collect_lifetime_ident(&mut lifetimes.body, &field.ty)
|
||||||
|
}
|
||||||
|
RequestFieldKind::NewtypeBody => {
|
||||||
|
util::collect_lifetime_ident(&mut lifetimes.body, &field.ty)
|
||||||
|
}
|
||||||
|
RequestFieldKind::NewtypeRawBody => {
|
||||||
|
util::collect_lifetime_ident(&mut lifetimes.body, &field.ty)
|
||||||
|
}
|
||||||
|
RequestFieldKind::Path => {
|
||||||
|
util::collect_lifetime_ident(&mut lifetimes.path, &field.ty)
|
||||||
|
}
|
||||||
|
RequestFieldKind::Query => {
|
||||||
|
util::collect_lifetime_ident(&mut lifetimes.query, &field.ty)
|
||||||
|
}
|
||||||
|
RequestFieldKind::QueryMap => {
|
||||||
|
util::collect_lifetime_ident(&mut lifetimes.query, &field.ty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RequestField::new(field_kind.unwrap_or(RequestFieldKind::Body), field, header))
|
||||||
|
})
|
||||||
|
.collect::<syn::Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
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(Self { attributes, fields, lifetimes, ruma_api_import: util::import_ruma_api() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Response {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
let attributes = input.call(Attribute::parse_outer)?;
|
||||||
|
let response_kw = input.parse::<kw::response>()?;
|
||||||
|
input.parse::<Token![:]>()?;
|
||||||
|
let fields;
|
||||||
|
braced!(fields in input);
|
||||||
|
|
||||||
|
let fields = fields
|
||||||
|
.parse_terminated::<Field, Token![,]>(Field::parse_named)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|f| {
|
||||||
|
if util::has_lifetime(&f.ty) {
|
||||||
|
Err(syn::Error::new(
|
||||||
|
f.ident.span(),
|
||||||
|
"Lifetimes on Response fields cannot be supported until GAT are stable",
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(f)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
let mut newtype_body_field = None;
|
||||||
|
|
||||||
|
let fields = fields
|
||||||
|
.into_iter()
|
||||||
|
.map(|mut field| {
|
||||||
|
let mut field_kind = None;
|
||||||
|
let mut header = None;
|
||||||
|
|
||||||
|
for attr in mem::replace(&mut field.attrs, Vec::new()) {
|
||||||
|
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" => util::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 }) => util::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<Vec<_>>>()?;
|
||||||
|
|
||||||
|
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(Self { attributes, fields, ruma_api_import: util::import_ruma_api() })
|
||||||
|
}
|
||||||
|
}
|
@ -1,46 +1,34 @@
|
|||||||
//! Details of the `request` section of the procedural macro.
|
//! Details of the `request` section of the procedural macro.
|
||||||
|
|
||||||
use std::{collections::BTreeSet, mem};
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{quote, quote_spanned, ToTokens};
|
use quote::{quote, quote_spanned, ToTokens};
|
||||||
use syn::{
|
use syn::{spanned::Spanned, Attribute, Field, Ident, Lifetime};
|
||||||
braced,
|
|
||||||
parse::{Parse, ParseStream},
|
|
||||||
spanned::Spanned,
|
|
||||||
Attribute, Field, Ident, Lifetime, Token,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::util;
|
||||||
api::attribute::{Meta, MetaNameValue},
|
|
||||||
util,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod kw {
|
|
||||||
syn::custom_keyword!(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct RequestLifetimes {
|
pub(super) struct RequestLifetimes {
|
||||||
body: BTreeSet<Lifetime>,
|
pub body: BTreeSet<Lifetime>,
|
||||||
path: BTreeSet<Lifetime>,
|
pub path: BTreeSet<Lifetime>,
|
||||||
query: BTreeSet<Lifetime>,
|
pub query: BTreeSet<Lifetime>,
|
||||||
header: BTreeSet<Lifetime>,
|
pub header: BTreeSet<Lifetime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of processing the `request` section of the macro.
|
/// The result of processing the `request` section of the macro.
|
||||||
pub struct Request {
|
pub(crate) struct Request {
|
||||||
/// The attributes that will be applied to the struct definition.
|
/// The attributes that will be applied to the struct definition.
|
||||||
attributes: Vec<Attribute>,
|
pub(super) attributes: Vec<Attribute>,
|
||||||
|
|
||||||
/// The fields of the request.
|
/// The fields of the request.
|
||||||
fields: Vec<RequestField>,
|
pub(super) fields: Vec<RequestField>,
|
||||||
|
|
||||||
/// The collected lifetime identifiers from the declared fields.
|
/// The collected lifetime identifiers from the declared fields.
|
||||||
lifetimes: RequestLifetimes,
|
pub(super) lifetimes: RequestLifetimes,
|
||||||
|
|
||||||
// Guarantee `ruma_api` is available and named something we can refer to.
|
// Guarantee `ruma_api` is available and named something we can refer to.
|
||||||
ruma_api_import: TokenStream,
|
pub(super) ruma_api_import: TokenStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request {
|
impl Request {
|
||||||
@ -300,142 +288,6 @@ impl Request {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Request {
|
|
||||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
|
||||||
let attributes = input.call(Attribute::parse_outer)?;
|
|
||||||
let request_kw = input.parse::<kw::request>()?;
|
|
||||||
input.parse::<Token![:]>()?;
|
|
||||||
let fields;
|
|
||||||
braced!(fields in input);
|
|
||||||
|
|
||||||
let fields = fields.parse_terminated::<Field, Token![,]>(Field::parse_named)?;
|
|
||||||
|
|
||||||
let mut newtype_body_field = None;
|
|
||||||
let mut query_map_field = None;
|
|
||||||
let mut lifetimes = RequestLifetimes::default();
|
|
||||||
|
|
||||||
let fields = fields
|
|
||||||
.into_iter()
|
|
||||||
.map(|mut field| {
|
|
||||||
let mut field_kind = None;
|
|
||||||
let mut header = None;
|
|
||||||
|
|
||||||
for attr in mem::replace(&mut field.attrs, Vec::new()) {
|
|
||||||
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" => util::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 }) => util::req_res_name_value(
|
|
||||||
name,
|
|
||||||
value,
|
|
||||||
&mut header,
|
|
||||||
RequestFieldKind::Header,
|
|
||||||
)?,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
match field_kind.unwrap_or(RequestFieldKind::Body) {
|
|
||||||
RequestFieldKind::Header => {
|
|
||||||
util::collect_lifetime_ident(&mut lifetimes.header, &field.ty)
|
|
||||||
}
|
|
||||||
RequestFieldKind::Body => {
|
|
||||||
util::collect_lifetime_ident(&mut lifetimes.body, &field.ty)
|
|
||||||
}
|
|
||||||
RequestFieldKind::NewtypeBody => {
|
|
||||||
util::collect_lifetime_ident(&mut lifetimes.body, &field.ty)
|
|
||||||
}
|
|
||||||
RequestFieldKind::NewtypeRawBody => {
|
|
||||||
util::collect_lifetime_ident(&mut lifetimes.body, &field.ty)
|
|
||||||
}
|
|
||||||
RequestFieldKind::Path => {
|
|
||||||
util::collect_lifetime_ident(&mut lifetimes.path, &field.ty)
|
|
||||||
}
|
|
||||||
RequestFieldKind::Query => {
|
|
||||||
util::collect_lifetime_ident(&mut lifetimes.query, &field.ty)
|
|
||||||
}
|
|
||||||
RequestFieldKind::QueryMap => {
|
|
||||||
util::collect_lifetime_ident(&mut lifetimes.query, &field.ty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(RequestField::new(field_kind.unwrap_or(RequestFieldKind::Body), field, header))
|
|
||||||
})
|
|
||||||
.collect::<syn::Result<Vec<_>>>()?;
|
|
||||||
|
|
||||||
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(Self { attributes, fields, lifetimes, ruma_api_import: util::import_ruma_api() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToTokens for Request {
|
impl ToTokens for Request {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
let ruma_api = &self.ruma_api_import;
|
let ruma_api = &self.ruma_api_import;
|
||||||
@ -552,7 +404,7 @@ impl ToTokens for Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The types of fields that a request can have.
|
/// The types of fields that a request can have.
|
||||||
pub enum RequestField {
|
pub(crate) enum RequestField {
|
||||||
/// JSON data in the body of the request.
|
/// JSON data in the body of the request.
|
||||||
Body(Field),
|
Body(Field),
|
||||||
|
|
||||||
@ -577,7 +429,7 @@ pub enum RequestField {
|
|||||||
|
|
||||||
impl RequestField {
|
impl RequestField {
|
||||||
/// Creates a new `RequestField`.
|
/// Creates a new `RequestField`.
|
||||||
fn new(kind: RequestFieldKind, field: Field, header: Option<Ident>) -> Self {
|
pub fn new(kind: RequestFieldKind, field: Field, header: Option<Ident>) -> Self {
|
||||||
match kind {
|
match kind {
|
||||||
RequestFieldKind::Body => RequestField::Body(field),
|
RequestFieldKind::Body => RequestField::Body(field),
|
||||||
RequestFieldKind::Header => {
|
RequestFieldKind::Header => {
|
||||||
@ -592,7 +444,7 @@ impl RequestField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the kind of the request field.
|
/// Gets the kind of the request field.
|
||||||
fn kind(&self) -> RequestFieldKind {
|
pub fn kind(&self) -> RequestFieldKind {
|
||||||
match self {
|
match self {
|
||||||
RequestField::Body(..) => RequestFieldKind::Body,
|
RequestField::Body(..) => RequestFieldKind::Body,
|
||||||
RequestField::Header(..) => RequestFieldKind::Header,
|
RequestField::Header(..) => RequestFieldKind::Header,
|
||||||
@ -605,57 +457,57 @@ impl RequestField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not this request field is a body kind.
|
/// Whether or not this request field is a body kind.
|
||||||
fn is_body(&self) -> bool {
|
pub fn is_body(&self) -> bool {
|
||||||
self.kind() == RequestFieldKind::Body
|
self.kind() == RequestFieldKind::Body
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not this request field is a header kind.
|
/// Whether or not this request field is a header kind.
|
||||||
fn is_header(&self) -> bool {
|
pub fn is_header(&self) -> bool {
|
||||||
self.kind() == RequestFieldKind::Header
|
self.kind() == RequestFieldKind::Header
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not this request field is a newtype body kind.
|
/// Whether or not this request field is a newtype body kind.
|
||||||
fn is_newtype_body(&self) -> bool {
|
pub fn is_newtype_body(&self) -> bool {
|
||||||
self.kind() == RequestFieldKind::NewtypeBody
|
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 {
|
pub fn is_path(&self) -> bool {
|
||||||
self.kind() == RequestFieldKind::Path
|
self.kind() == RequestFieldKind::Path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not this request field is a query string kind.
|
/// Whether or not this request field is a query string kind.
|
||||||
fn is_query(&self) -> bool {
|
pub fn is_query(&self) -> bool {
|
||||||
self.kind() == RequestFieldKind::Query
|
self.kind() == RequestFieldKind::Query
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the contained field if this request field is a body kind.
|
/// Return the contained field if this request field is a body kind.
|
||||||
fn as_body_field(&self) -> Option<&Field> {
|
pub fn as_body_field(&self) -> Option<&Field> {
|
||||||
self.field_of_kind(RequestFieldKind::Body)
|
self.field_of_kind(RequestFieldKind::Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the contained field if this request field is a body kind.
|
/// Return the contained field if this request field is a body kind.
|
||||||
fn as_newtype_body_field(&self) -> Option<&Field> {
|
pub fn as_newtype_body_field(&self) -> Option<&Field> {
|
||||||
self.field_of_kind(RequestFieldKind::NewtypeBody)
|
self.field_of_kind(RequestFieldKind::NewtypeBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the contained field if this request field is a raw body kind.
|
/// Return the contained field if this request field is a raw body kind.
|
||||||
fn as_newtype_raw_body_field(&self) -> Option<&Field> {
|
pub fn as_newtype_raw_body_field(&self) -> Option<&Field> {
|
||||||
self.field_of_kind(RequestFieldKind::NewtypeRawBody)
|
self.field_of_kind(RequestFieldKind::NewtypeRawBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the contained field if this request field is a query kind.
|
/// Return the contained field if this request field is a query kind.
|
||||||
fn as_query_field(&self) -> Option<&Field> {
|
pub fn as_query_field(&self) -> Option<&Field> {
|
||||||
self.field_of_kind(RequestFieldKind::Query)
|
self.field_of_kind(RequestFieldKind::Query)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the contained field if this request field is a query map kind.
|
/// Return the contained field if this request field is a query map kind.
|
||||||
fn as_query_map_field(&self) -> Option<&Field> {
|
pub fn as_query_map_field(&self) -> Option<&Field> {
|
||||||
self.field_of_kind(RequestFieldKind::QueryMap)
|
self.field_of_kind(RequestFieldKind::QueryMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the inner `Field` value.
|
/// Gets the inner `Field` value.
|
||||||
fn field(&self) -> &Field {
|
pub fn field(&self) -> &Field {
|
||||||
match self {
|
match self {
|
||||||
RequestField::Body(field)
|
RequestField::Body(field)
|
||||||
| RequestField::Header(field, _)
|
| RequestField::Header(field, _)
|
||||||
@ -668,7 +520,7 @@ impl RequestField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the inner `Field` value if it's of the provided kind.
|
/// Gets the inner `Field` value if it's of the provided kind.
|
||||||
fn field_of_kind(&self, kind: RequestFieldKind) -> Option<&Field> {
|
pub fn field_of_kind(&self, kind: RequestFieldKind) -> Option<&Field> {
|
||||||
if self.kind() == kind {
|
if self.kind() == kind {
|
||||||
Some(self.field())
|
Some(self.field())
|
||||||
} else {
|
} else {
|
||||||
@ -679,7 +531,7 @@ impl RequestField {
|
|||||||
|
|
||||||
/// The types of fields that a request can have, without their values.
|
/// The types of fields that a request can have, without their values.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
enum RequestFieldKind {
|
pub(crate) enum RequestFieldKind {
|
||||||
/// See the similarly named variant of `RequestField`.
|
/// See the similarly named variant of `RequestField`.
|
||||||
Body,
|
Body,
|
||||||
|
|
||||||
|
@ -1,35 +1,19 @@
|
|||||||
//! Details of the `response` section of the procedural macro.
|
//! Details of the `response` section of the procedural macro.
|
||||||
|
|
||||||
use std::mem;
|
|
||||||
|
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{quote, quote_spanned, ToTokens};
|
use quote::{quote, quote_spanned, ToTokens};
|
||||||
use syn::{
|
use syn::{spanned::Spanned, Attribute, Field, Ident};
|
||||||
braced,
|
|
||||||
parse::{Parse, ParseStream},
|
|
||||||
spanned::Spanned,
|
|
||||||
Attribute, Field, Ident, Token,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
api::attribute::{Meta, MetaNameValue},
|
|
||||||
util,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod kw {
|
|
||||||
syn::custom_keyword!(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The result of processing the `response` section of the macro.
|
/// The result of processing the `response` section of the macro.
|
||||||
pub struct Response {
|
pub(crate) struct Response {
|
||||||
/// The attributes that will be applied to the struct definition.
|
/// The attributes that will be applied to the struct definition.
|
||||||
attributes: Vec<Attribute>,
|
pub attributes: Vec<Attribute>,
|
||||||
|
|
||||||
/// The fields of the response.
|
/// The fields of the response.
|
||||||
fields: Vec<ResponseField>,
|
pub fields: Vec<ResponseField>,
|
||||||
|
|
||||||
// Guarantee `ruma_api` is available and named something we can refer to.
|
// Guarantee `ruma_api` is available and named something we can refer to.
|
||||||
ruma_api_import: TokenStream,
|
pub ruma_api_import: TokenStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
@ -211,101 +195,6 @@ impl Response {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Response {
|
|
||||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
|
||||||
let attributes = input.call(Attribute::parse_outer)?;
|
|
||||||
let response_kw = input.parse::<kw::response>()?;
|
|
||||||
input.parse::<Token![:]>()?;
|
|
||||||
let fields;
|
|
||||||
braced!(fields in input);
|
|
||||||
|
|
||||||
let fields = fields
|
|
||||||
.parse_terminated::<Field, Token![,]>(Field::parse_named)?
|
|
||||||
.into_iter()
|
|
||||||
.map(|f| {
|
|
||||||
if util::has_lifetime(&f.ty) {
|
|
||||||
Err(syn::Error::new(
|
|
||||||
f.ident.span(),
|
|
||||||
"Lifetimes on Response fields cannot be supported until GAT are stable",
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Ok(f)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
|
|
||||||
let mut newtype_body_field = None;
|
|
||||||
|
|
||||||
let fields = fields
|
|
||||||
.into_iter()
|
|
||||||
.map(|mut field| {
|
|
||||||
let mut field_kind = None;
|
|
||||||
let mut header = None;
|
|
||||||
|
|
||||||
for attr in mem::replace(&mut field.attrs, Vec::new()) {
|
|
||||||
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" => util::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 }) => util::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<Vec<_>>>()?;
|
|
||||||
|
|
||||||
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(Self { attributes, fields, ruma_api_import: util::import_ruma_api() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToTokens for Response {
|
impl ToTokens for Response {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
let ruma_api = &self.ruma_api_import;
|
let ruma_api = &self.ruma_api_import;
|
||||||
@ -356,7 +245,7 @@ impl ToTokens for Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The types of fields that a response can have.
|
/// The types of fields that a response can have.
|
||||||
pub enum ResponseField {
|
pub(crate) enum ResponseField {
|
||||||
/// JSON data in the body of the response.
|
/// JSON data in the body of the response.
|
||||||
Body(Field),
|
Body(Field),
|
||||||
|
|
||||||
@ -372,7 +261,7 @@ pub enum ResponseField {
|
|||||||
|
|
||||||
impl ResponseField {
|
impl ResponseField {
|
||||||
/// Gets the inner `Field` value.
|
/// Gets the inner `Field` value.
|
||||||
fn field(&self) -> &Field {
|
pub fn field(&self) -> &Field {
|
||||||
match self {
|
match self {
|
||||||
ResponseField::Body(field)
|
ResponseField::Body(field)
|
||||||
| ResponseField::Header(field, _)
|
| ResponseField::Header(field, _)
|
||||||
@ -382,22 +271,22 @@ impl ResponseField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not this response field is a body kind.
|
/// Whether or not this response field is a body kind.
|
||||||
fn is_body(&self) -> bool {
|
pub fn is_body(&self) -> bool {
|
||||||
self.as_body_field().is_some()
|
self.as_body_field().is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not this response field is a header kind.
|
/// Whether or not this response field is a header kind.
|
||||||
fn is_header(&self) -> bool {
|
pub fn is_header(&self) -> bool {
|
||||||
matches!(self, ResponseField::Header(..))
|
matches!(self, ResponseField::Header(..))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not this response field is a newtype body kind.
|
/// Whether or not this response field is a newtype body kind.
|
||||||
fn is_newtype_body(&self) -> bool {
|
pub fn is_newtype_body(&self) -> bool {
|
||||||
self.as_newtype_body_field().is_some()
|
self.as_newtype_body_field().is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the contained field if this response field is a body kind.
|
/// Return the contained field if this response field is a body kind.
|
||||||
fn as_body_field(&self) -> Option<&Field> {
|
pub fn as_body_field(&self) -> Option<&Field> {
|
||||||
match self {
|
match self {
|
||||||
ResponseField::Body(field) => Some(field),
|
ResponseField::Body(field) => Some(field),
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -405,7 +294,7 @@ impl ResponseField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the contained field if this response field is a newtype body kind.
|
/// Return the contained field if this response field is a newtype body kind.
|
||||||
fn as_newtype_body_field(&self) -> Option<&Field> {
|
pub fn as_newtype_body_field(&self) -> Option<&Field> {
|
||||||
match self {
|
match self {
|
||||||
ResponseField::NewtypeBody(field) => Some(field),
|
ResponseField::NewtypeBody(field) => Some(field),
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -413,7 +302,7 @@ impl ResponseField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the contained field if this response field is a newtype raw body kind.
|
/// Return the contained field if this response field is a newtype raw body kind.
|
||||||
fn as_newtype_raw_body_field(&self) -> Option<&Field> {
|
pub fn as_newtype_raw_body_field(&self) -> Option<&Field> {
|
||||||
match self {
|
match self {
|
||||||
ResponseField::NewtypeRawBody(field) => Some(field),
|
ResponseField::NewtypeRawBody(field) => Some(field),
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -422,7 +311,7 @@ impl ResponseField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The types of fields that a response can have, without their values.
|
/// The types of fields that a response can have, without their values.
|
||||||
enum ResponseFieldKind {
|
pub(crate) enum ResponseFieldKind {
|
||||||
/// See the similarly named variant of `ResponseField`.
|
/// See the similarly named variant of `ResponseField`.
|
||||||
Body,
|
Body,
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user