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 quote::quote;
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
Token, Type,
|
||||
};
|
||||
use syn::Type;
|
||||
|
||||
pub(crate) mod attribute;
|
||||
pub(crate) mod metadata;
|
||||
pub(crate) mod parse;
|
||||
pub(crate) mod request;
|
||||
pub(crate) mod response;
|
||||
|
||||
@ -30,53 +28,6 @@ pub struct Api {
|
||||
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> {
|
||||
// Guarantee `ruma_api` is available and named something we can refer to.
|
||||
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.
|
||||
|
||||
use std::{collections::BTreeSet, mem};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::{
|
||||
braced,
|
||||
parse::{Parse, ParseStream},
|
||||
spanned::Spanned,
|
||||
Attribute, Field, Ident, Lifetime, Token,
|
||||
};
|
||||
use syn::{spanned::Spanned, Attribute, Field, Ident, Lifetime};
|
||||
|
||||
use crate::{
|
||||
api::attribute::{Meta, MetaNameValue},
|
||||
util,
|
||||
};
|
||||
|
||||
mod kw {
|
||||
syn::custom_keyword!(request);
|
||||
}
|
||||
use crate::util;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct RequestLifetimes {
|
||||
body: BTreeSet<Lifetime>,
|
||||
path: BTreeSet<Lifetime>,
|
||||
query: BTreeSet<Lifetime>,
|
||||
header: BTreeSet<Lifetime>,
|
||||
pub(super) struct RequestLifetimes {
|
||||
pub body: BTreeSet<Lifetime>,
|
||||
pub path: BTreeSet<Lifetime>,
|
||||
pub query: BTreeSet<Lifetime>,
|
||||
pub header: BTreeSet<Lifetime>,
|
||||
}
|
||||
|
||||
/// 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.
|
||||
attributes: Vec<Attribute>,
|
||||
pub(super) attributes: Vec<Attribute>,
|
||||
|
||||
/// The fields of the request.
|
||||
fields: Vec<RequestField>,
|
||||
pub(super) fields: Vec<RequestField>,
|
||||
|
||||
/// 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.
|
||||
ruma_api_import: TokenStream,
|
||||
pub(super) ruma_api_import: TokenStream,
|
||||
}
|
||||
|
||||
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 {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let ruma_api = &self.ruma_api_import;
|
||||
@ -552,7 +404,7 @@ impl ToTokens for Request {
|
||||
}
|
||||
|
||||
/// The types of fields that a request can have.
|
||||
pub enum RequestField {
|
||||
pub(crate) enum RequestField {
|
||||
/// JSON data in the body of the request.
|
||||
Body(Field),
|
||||
|
||||
@ -577,7 +429,7 @@ pub enum RequestField {
|
||||
|
||||
impl 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 {
|
||||
RequestFieldKind::Body => RequestField::Body(field),
|
||||
RequestFieldKind::Header => {
|
||||
@ -592,7 +444,7 @@ impl RequestField {
|
||||
}
|
||||
|
||||
/// Gets the kind of the request field.
|
||||
fn kind(&self) -> RequestFieldKind {
|
||||
pub fn kind(&self) -> RequestFieldKind {
|
||||
match self {
|
||||
RequestField::Body(..) => RequestFieldKind::Body,
|
||||
RequestField::Header(..) => RequestFieldKind::Header,
|
||||
@ -605,57 +457,57 @@ impl RequestField {
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// Gets the inner `Field` value.
|
||||
fn field(&self) -> &Field {
|
||||
pub fn field(&self) -> &Field {
|
||||
match self {
|
||||
RequestField::Body(field)
|
||||
| RequestField::Header(field, _)
|
||||
@ -668,7 +520,7 @@ impl RequestField {
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
Some(self.field())
|
||||
} else {
|
||||
@ -679,7 +531,7 @@ impl RequestField {
|
||||
|
||||
/// The types of fields that a request can have, without their values.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum RequestFieldKind {
|
||||
pub(crate) enum RequestFieldKind {
|
||||
/// See the similarly named variant of `RequestField`.
|
||||
Body,
|
||||
|
||||
|
@ -1,35 +1,19 @@
|
||||
//! Details of the `response` section of the procedural macro.
|
||||
|
||||
use std::mem;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::{
|
||||
braced,
|
||||
parse::{Parse, ParseStream},
|
||||
spanned::Spanned,
|
||||
Attribute, Field, Ident, Token,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
api::attribute::{Meta, MetaNameValue},
|
||||
util,
|
||||
};
|
||||
|
||||
mod kw {
|
||||
syn::custom_keyword!(response);
|
||||
}
|
||||
use syn::{spanned::Spanned, Attribute, Field, Ident};
|
||||
|
||||
/// 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.
|
||||
attributes: Vec<Attribute>,
|
||||
pub attributes: Vec<Attribute>,
|
||||
|
||||
/// The fields of the response.
|
||||
fields: Vec<ResponseField>,
|
||||
pub fields: Vec<ResponseField>,
|
||||
|
||||
// Guarantee `ruma_api` is available and named something we can refer to.
|
||||
ruma_api_import: TokenStream,
|
||||
pub ruma_api_import: TokenStream,
|
||||
}
|
||||
|
||||
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 {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let ruma_api = &self.ruma_api_import;
|
||||
@ -356,7 +245,7 @@ impl ToTokens for Response {
|
||||
}
|
||||
|
||||
/// The types of fields that a response can have.
|
||||
pub enum ResponseField {
|
||||
pub(crate) enum ResponseField {
|
||||
/// JSON data in the body of the response.
|
||||
Body(Field),
|
||||
|
||||
@ -372,7 +261,7 @@ pub enum ResponseField {
|
||||
|
||||
impl ResponseField {
|
||||
/// Gets the inner `Field` value.
|
||||
fn field(&self) -> &Field {
|
||||
pub fn field(&self) -> &Field {
|
||||
match self {
|
||||
ResponseField::Body(field)
|
||||
| ResponseField::Header(field, _)
|
||||
@ -382,22 +271,22 @@ impl ResponseField {
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
/// 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(..))
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
ResponseField::Body(field) => Some(field),
|
||||
_ => None,
|
||||
@ -405,7 +294,7 @@ impl ResponseField {
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
ResponseField::NewtypeBody(field) => Some(field),
|
||||
_ => None,
|
||||
@ -413,7 +302,7 @@ impl ResponseField {
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
ResponseField::NewtypeRawBody(field) => Some(field),
|
||||
_ => None,
|
||||
@ -422,7 +311,7 @@ impl ResponseField {
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
Body,
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user