Add lifetime params to Request/Response when needed

This commit is contained in:
Devin Ragotzy 2020-08-04 20:02:20 -04:00 committed by Jonas Platte
parent c2342e3ef7
commit e7f7c3bb9d
4 changed files with 123 additions and 83 deletions

View File

@ -102,13 +102,13 @@ impl ToTokens for Api {
let request_type = &self.request;
let response_type = &self.response;
let request_try_from_type = if self.request.uses_wrap_incoming() {
let request_try_from_type = if self.request.contains_lifetimes() {
quote!(IncomingRequest)
} else {
quote!(Request)
};
let response_try_from_type = if self.response.uses_wrap_incoming() {
let response_try_from_type = if self.response.contains_lifetimes() {
quote!(IncomingResponse)
} else {
quote!(Response)
@ -222,6 +222,21 @@ impl ToTokens for Api {
let error = &self.error;
let res_life = self.response.lifetimes().collect::<Vec<_>>();
let req_life = self.request.lifetimes().collect::<Vec<_>>();
let response_lifetimes = util::generics_to_tokens(res_life.iter().cloned());
let request_lifetimes = util::generics_to_tokens(req_life.iter().cloned());
let endpoint_impl_lifetimes = if res_life != req_life {
let diff =
res_life.into_iter().filter(|resp| !req_life.contains(resp)).collect::<Vec<_>>();
util::generics_to_tokens(req_life.iter().cloned().chain(diff))
} else {
request_lifetimes.clone()
};
let api = quote! {
// FIXME: These can't conflict with other imports, but it would still be nice not to
// bring anything into scope that code outside the macro could then rely on.
@ -260,13 +275,13 @@ impl ToTokens for Api {
#[doc = #response_doc]
#response_type
impl ::std::convert::TryFrom<Response>
impl #response_lifetimes ::std::convert::TryFrom<Response #response_lifetimes>
for ::ruma_api::exports::http::Response<Vec<u8>>
{
type Error = ::ruma_api::error::IntoHttpError;
#[allow(unused_variables)]
fn try_from(response: Response) -> ::std::result::Result<Self, Self::Error> {
fn try_from(response: Response #response_lifetimes) -> ::std::result::Result<Self, Self::Error> {
let response = ::ruma_api::exports::http::Response::builder()
.header(::ruma_api::exports::http::header::CONTENT_TYPE, "application/json")
#serialize_response_headers
@ -304,8 +319,8 @@ impl ToTokens for Api {
}
}
impl ::ruma_api::Endpoint for Request {
type Response = Response;
impl #endpoint_impl_lifetimes ::ruma_api::Endpoint for Request #request_lifetimes {
type Response = Response #response_lifetimes;
type ResponseError = #error;
/// Metadata for the `#name` endpoint.

View File

@ -1,10 +1,10 @@
//! Details of the `request` section of the procedural macro.
use std::{convert::TryFrom, mem};
use std::{collections::BTreeSet, convert::TryFrom, mem};
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::{spanned::Spanned, Field, Ident};
use syn::{spanned::Spanned, Field, Ident, Lifetime};
use crate::{
api::{
@ -18,6 +18,9 @@ use crate::{
pub struct Request {
/// The fields of the request.
fields: Vec<RequestField>,
/// The collected lifetime identifiers from the declared fields.
lifetimes: Vec<Lifetime>,
}
impl Request {
@ -98,9 +101,13 @@ impl Request {
self.fields.iter().filter_map(|field| field.as_body_field())
}
/// Whether any field has a #[wrap_incoming] attribute.
pub fn uses_wrap_incoming(&self) -> bool {
self.fields.iter().any(|f| f.has_wrap_incoming_attr())
/// Whether any field has a lifetime.
pub fn contains_lifetimes(&self) -> bool {
self.fields.iter().any(|f| util::has_lifetime(&f.field().ty))
}
pub fn lifetimes(&self) -> impl Iterator<Item = &Lifetime> {
self.lifetimes.iter()
}
/// Produces an iterator over all the header fields.
@ -194,6 +201,7 @@ impl TryFrom<RawRequest> for Request {
fn try_from(raw: RawRequest) -> syn::Result<Self> {
let mut newtype_body_field = None;
let mut query_map_field = None;
let mut lifetimes = BTreeSet::new();
let fields = raw
.fields
@ -202,6 +210,8 @@ impl TryFrom<RawRequest> for Request {
let mut field_kind = None;
let mut header = None;
util::copy_lifetime_ident(&mut lifetimes, &field.ty);
for attr in mem::replace(&mut field.attrs, Vec::new()) {
let meta = match Meta::from_attribute(&attr)? {
Some(m) => m,
@ -287,7 +297,7 @@ impl TryFrom<RawRequest> for Request {
));
}
Ok(Self { fields })
Ok(Self { fields, lifetimes: lifetimes.into_iter().collect() })
}
}
@ -301,10 +311,12 @@ impl ToTokens for Request {
quote! { { #(#fields),* } }
};
let request_generics = util::generics_to_tokens(self.lifetimes.iter());
let request_body_struct =
if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) {
let field = Field { ident: None, colon_token: None, ..body_field.field().clone() };
let derive_deserialize = if body_field.has_wrap_incoming_attr() {
let derive_deserialize = if util::has_lifetime(&body_field.field().ty) {
TokenStream::new()
} else {
quote!(::ruma_api::exports::serde::Deserialize)
@ -313,14 +325,18 @@ impl ToTokens for Request {
Some((derive_deserialize, quote! { (#field); }))
} else if self.has_body_fields() {
let fields = self.fields.iter().filter(|f| f.is_body());
let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) {
TokenStream::new()
} else {
quote!(::ruma_api::exports::serde::Deserialize)
};
let (derive_deserialize, lifetimes) =
if fields.clone().any(|f| util::has_lifetime(&f.field().ty)) {
(
TokenStream::new(),
util::collect_generic_idents(fields.clone().map(|f| &f.field().ty)),
)
} else {
(quote!(::ruma_api::exports::serde::Deserialize), TokenStream::new())
};
let fields = fields.map(RequestField::field);
Some((derive_deserialize, quote! { { #(#fields),* } }))
Some((derive_deserialize, quote! { #lifetimes { #(#fields),* } }))
} else {
None
}
@ -339,6 +355,7 @@ impl ToTokens for Request {
let request_query_struct = if let Some(f) = self.query_map_field() {
let field = Field { ident: None, colon_token: None, ..f.clone() };
let lifetime = util::collect_generic_idents(Some(&field.ty).into_iter());
quote! {
/// Data in the request's query string.
@ -347,10 +364,11 @@ impl ToTokens for Request {
::ruma_api::exports::serde::Deserialize,
::ruma_api::exports::serde::Serialize,
)]
struct RequestQuery(#field);
struct RequestQuery #lifetime (#field);
}
} else if self.has_query_fields() {
let fields = self.fields.iter().filter_map(RequestField::as_query_field);
let lifetime = util::collect_generic_idents(fields.clone().map(|f| &f.ty));
quote! {
/// Data in the request's query string.
@ -359,7 +377,7 @@ impl ToTokens for Request {
::ruma_api::exports::serde::Deserialize,
::ruma_api::exports::serde::Serialize,
)]
struct RequestQuery {
struct RequestQuery #lifetime {
#(#fields),*
}
}
@ -370,7 +388,7 @@ impl ToTokens for Request {
let request = quote! {
#[derive(Debug, Clone, ::ruma_api::Outgoing)]
#[incoming_no_deserialize]
pub struct Request #request_def
pub struct Request #request_generics #request_def
#request_body_struct
#request_query_struct
@ -498,13 +516,6 @@ impl RequestField {
None
}
}
/// Whether or not the request field has a #[wrap_incoming] attribute.
fn has_wrap_incoming_attr(&self) -> bool {
self.field().attrs.iter().any(|attr| {
attr.path.segments.len() == 1 && attr.path.segments[0].ident == "wrap_incoming"
})
}
}
/// The types of fields that a request can have, without their values.

View File

@ -1,10 +1,10 @@
//! Details of the `response` section of the procedural macro.
use std::{convert::TryFrom, mem};
use std::{collections::BTreeSet, convert::TryFrom, mem};
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::{spanned::Spanned, Field, Ident};
use syn::{spanned::Spanned, Field, Ident, Lifetime};
use crate::{
api::{
@ -18,6 +18,9 @@ use crate::{
pub struct Response {
/// The fields of the response.
fields: Vec<ResponseField>,
/// The collected lifetime identifiers from the declared fields.
lifetimes: Vec<Lifetime>,
}
impl Response {
@ -31,9 +34,13 @@ impl Response {
self.fields.iter().any(|field| field.is_header())
}
/// Whether any field has a #[wrap_incoming] attribute.
pub fn uses_wrap_incoming(&self) -> bool {
self.fields.iter().any(|f| f.has_wrap_incoming_attr())
/// Whether any field has a lifetime.
pub fn contains_lifetimes(&self) -> bool {
self.fields.iter().any(|f| util::has_lifetime(&f.field().ty))
}
pub fn lifetimes(&self) -> impl Iterator<Item = &Lifetime> {
self.lifetimes.iter()
}
/// Produces code for a response struct initializer.
@ -162,6 +169,7 @@ impl TryFrom<RawResponse> for Response {
fn try_from(raw: RawResponse) -> syn::Result<Self> {
let mut newtype_body_field = None;
let mut lifetimes = BTreeSet::new();
let fields = raw
.fields
@ -170,6 +178,8 @@ impl TryFrom<RawResponse> for Response {
let mut field_kind = None;
let mut header = None;
util::copy_lifetime_ident(&mut lifetimes, &field.ty);
for attr in mem::replace(&mut field.attrs, Vec::new()) {
let meta = match Meta::from_attribute(&attr)? {
Some(m) => m,
@ -230,7 +240,7 @@ impl TryFrom<RawResponse> for Response {
));
}
Ok(Self { fields })
Ok(Self { fields, lifetimes: lifetimes.into_iter().collect() })
}
}
@ -245,30 +255,40 @@ impl ToTokens for Response {
quote! { { #(#fields),* } }
};
let (derive_deserialize, def) =
if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) {
let field = Field { ident: None, colon_token: None, ..body_field.field().clone() };
let derive_deserialize = if body_field.has_wrap_incoming_attr() {
TokenStream::new()
} else {
quote!(::ruma_api::exports::serde::Deserialize)
};
let (derive_deserialize, def) = if let Some(body_field) =
self.fields.iter().find(|f| f.is_newtype_body())
{
let field = Field { ident: None, colon_token: None, ..body_field.field().clone() };
(derive_deserialize, quote! { (#field); })
} else if self.has_body_fields() {
let fields = self.fields.iter().filter(|f| f.is_body());
let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) {
TokenStream::new()
} else {
quote!(::ruma_api::exports::serde::Deserialize)
};
let fields = fields.map(ResponseField::field);
(derive_deserialize, quote!({ #(#fields),* }))
let (derive_deserialize, lifetimes) = if util::has_lifetime(&body_field.field().ty) {
(
TokenStream::new(),
util::collect_generic_idents(Some(&body_field.field().ty).into_iter()),
)
} else {
(TokenStream::new(), quote!({}))
(quote!(::ruma_api::exports::serde::Deserialize), TokenStream::new())
};
(derive_deserialize, quote! { #lifetimes (#field); })
} else if self.has_body_fields() {
let fields = self.fields.iter().filter(|f| f.is_body());
let (derive_deserialize, lifetimes) =
if fields.clone().any(|f| util::has_lifetime(&f.field().ty)) {
(
TokenStream::new(),
util::collect_generic_idents(fields.clone().map(|f| &f.field().ty)),
)
} else {
(quote!(::ruma_api::exports::serde::Deserialize), TokenStream::new())
};
let fields = fields.map(ResponseField::field);
(derive_deserialize, quote!( #lifetimes { #(#fields),* }))
} else {
(TokenStream::new(), quote!({}))
};
let response_body_struct = quote! {
/// Data in the response body.
#[derive(
@ -280,10 +300,11 @@ impl ToTokens for Response {
struct ResponseBody #def
};
let response_generics = util::generics_to_tokens(self.lifetimes.iter());
let response = quote! {
#[derive(Debug, Clone, ::ruma_api::Outgoing)]
#[incoming_no_deserialize]
pub struct Response #response_def
pub struct Response #response_generics #response_def
#response_body_struct
};
@ -353,13 +374,6 @@ impl ResponseField {
_ => None,
}
}
/// Whether or not the response field has a #[wrap_incoming] attribute.
fn has_wrap_incoming_attr(&self) -> bool {
self.field().attrs.iter().any(|attr| {
attr.path.segments.len() == 1 && attr.path.segments[0].ident == "wrap_incoming"
})
}
}
/// The types of fields that a response can have, without their values.

View File

@ -54,24 +54,24 @@ ruma_api! {
}
}
// #[derive(Outgoing)]
// #[incoming_no_deserialize]
// pub struct Request<'a, T> {
// pub abc: &'a str,
// pub thing: Thing<'a, T>,
// pub device_id: &'a ::ruma_identifiers::DeviceId,
// pub user_id: &'a UserId,
// pub bytes: &'a [u8],
// pub recursive: &'a [Thing<'a, T>],
// pub option: Option<&'a [u8]>,
// }
#[derive(Outgoing)]
#[incoming_no_deserialize]
pub struct FakeRequest<'a, T> {
pub abc: &'a str,
pub thing: Thing<'a, T>,
pub device_id: &'a ::ruma_identifiers::DeviceId,
pub user_id: &'a UserId,
pub bytes: &'a [u8],
pub recursive: &'a [Thing<'a, T>],
pub option: Option<&'a [u8]>,
}
// #[derive(Outgoing)]
// #[incoming_no_deserialize]
// pub enum EnumThing<'a, T> {
// Abc(&'a str),
// Stuff(Thing<'a, T>),
// Boxy(&'a ::ruma_identifiers::DeviceId),
// Other(Option<&'a str>),
// StructVar { stuff: &'a str, more: &'a ::ruma_identifiers::ServerName },
// }
#[derive(Outgoing)]
#[incoming_no_deserialize]
pub enum EnumThing<'a, T> {
Abc(&'a str),
Stuff(Thing<'a, T>),
Boxy(&'a ::ruma_identifiers::DeviceId),
Other(Option<&'a str>),
StructVar { stuff: &'a str, more: &'a ::ruma_identifiers::ServerName },
}