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 request_type = &self.request;
let response_type = &self.response; 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) quote!(IncomingRequest)
} else { } else {
quote!(Request) 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) quote!(IncomingResponse)
} else { } else {
quote!(Response) quote!(Response)
@ -222,6 +222,21 @@ impl ToTokens for Api {
let error = &self.error; 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! { let api = quote! {
// FIXME: These can't conflict with other imports, but it would still be nice not to // 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. // bring anything into scope that code outside the macro could then rely on.
@ -260,13 +275,13 @@ impl ToTokens for Api {
#[doc = #response_doc] #[doc = #response_doc]
#response_type #response_type
impl ::std::convert::TryFrom<Response> impl #response_lifetimes ::std::convert::TryFrom<Response #response_lifetimes>
for ::ruma_api::exports::http::Response<Vec<u8>> for ::ruma_api::exports::http::Response<Vec<u8>>
{ {
type Error = ::ruma_api::error::IntoHttpError; type Error = ::ruma_api::error::IntoHttpError;
#[allow(unused_variables)] #[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() let response = ::ruma_api::exports::http::Response::builder()
.header(::ruma_api::exports::http::header::CONTENT_TYPE, "application/json") .header(::ruma_api::exports::http::header::CONTENT_TYPE, "application/json")
#serialize_response_headers #serialize_response_headers
@ -304,8 +319,8 @@ impl ToTokens for Api {
} }
} }
impl ::ruma_api::Endpoint for Request { impl #endpoint_impl_lifetimes ::ruma_api::Endpoint for Request #request_lifetimes {
type Response = Response; type Response = Response #response_lifetimes;
type ResponseError = #error; type ResponseError = #error;
/// Metadata for the `#name` endpoint. /// Metadata for the `#name` endpoint.

View File

@ -1,10 +1,10 @@
//! Details of the `request` section of the procedural macro. //! 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 proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens}; use quote::{quote, quote_spanned, ToTokens};
use syn::{spanned::Spanned, Field, Ident}; use syn::{spanned::Spanned, Field, Ident, Lifetime};
use crate::{ use crate::{
api::{ api::{
@ -18,6 +18,9 @@ use crate::{
pub struct Request { pub struct Request {
/// The fields of the request. /// The fields of the request.
fields: Vec<RequestField>, fields: Vec<RequestField>,
/// The collected lifetime identifiers from the declared fields.
lifetimes: Vec<Lifetime>,
} }
impl Request { impl Request {
@ -98,9 +101,13 @@ impl Request {
self.fields.iter().filter_map(|field| field.as_body_field()) self.fields.iter().filter_map(|field| field.as_body_field())
} }
/// Whether any field has a #[wrap_incoming] attribute. /// Whether any field has a lifetime.
pub fn uses_wrap_incoming(&self) -> bool { pub fn contains_lifetimes(&self) -> bool {
self.fields.iter().any(|f| f.has_wrap_incoming_attr()) 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. /// 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> { fn try_from(raw: RawRequest) -> syn::Result<Self> {
let mut newtype_body_field = None; let mut newtype_body_field = None;
let mut query_map_field = None; let mut query_map_field = None;
let mut lifetimes = BTreeSet::new();
let fields = raw let fields = raw
.fields .fields
@ -202,6 +210,8 @@ impl TryFrom<RawRequest> for Request {
let mut field_kind = None; let mut field_kind = None;
let mut header = None; let mut header = None;
util::copy_lifetime_ident(&mut lifetimes, &field.ty);
for attr in mem::replace(&mut field.attrs, Vec::new()) { 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,
@ -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),* } } quote! { { #(#fields),* } }
}; };
let request_generics = util::generics_to_tokens(self.lifetimes.iter());
let request_body_struct = let request_body_struct =
if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) { 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 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() TokenStream::new()
} else { } else {
quote!(::ruma_api::exports::serde::Deserialize) quote!(::ruma_api::exports::serde::Deserialize)
@ -313,14 +325,18 @@ impl ToTokens for Request {
Some((derive_deserialize, quote! { (#field); })) Some((derive_deserialize, quote! { (#field); }))
} else if self.has_body_fields() { } else if self.has_body_fields() {
let fields = self.fields.iter().filter(|f| f.is_body()); let fields = self.fields.iter().filter(|f| f.is_body());
let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) { let (derive_deserialize, lifetimes) =
TokenStream::new() if fields.clone().any(|f| util::has_lifetime(&f.field().ty)) {
} else { (
quote!(::ruma_api::exports::serde::Deserialize) 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); let fields = fields.map(RequestField::field);
Some((derive_deserialize, quote! { { #(#fields),* } })) Some((derive_deserialize, quote! { #lifetimes { #(#fields),* } }))
} else { } else {
None None
} }
@ -339,6 +355,7 @@ impl ToTokens for Request {
let request_query_struct = if let Some(f) = self.query_map_field() { let request_query_struct = if let Some(f) = self.query_map_field() {
let field = Field { ident: None, colon_token: None, ..f.clone() }; let field = Field { ident: None, colon_token: None, ..f.clone() };
let lifetime = util::collect_generic_idents(Some(&field.ty).into_iter());
quote! { quote! {
/// Data in the request's query string. /// Data in the request's query string.
@ -347,10 +364,11 @@ impl ToTokens for Request {
::ruma_api::exports::serde::Deserialize, ::ruma_api::exports::serde::Deserialize,
::ruma_api::exports::serde::Serialize, ::ruma_api::exports::serde::Serialize,
)] )]
struct RequestQuery(#field); struct RequestQuery #lifetime (#field);
} }
} else if self.has_query_fields() { } else if self.has_query_fields() {
let fields = self.fields.iter().filter_map(RequestField::as_query_field); let fields = self.fields.iter().filter_map(RequestField::as_query_field);
let lifetime = util::collect_generic_idents(fields.clone().map(|f| &f.ty));
quote! { quote! {
/// Data in the request's query string. /// Data in the request's query string.
@ -359,7 +377,7 @@ impl ToTokens for Request {
::ruma_api::exports::serde::Deserialize, ::ruma_api::exports::serde::Deserialize,
::ruma_api::exports::serde::Serialize, ::ruma_api::exports::serde::Serialize,
)] )]
struct RequestQuery { struct RequestQuery #lifetime {
#(#fields),* #(#fields),*
} }
} }
@ -370,7 +388,7 @@ impl ToTokens for Request {
let request = quote! { let request = quote! {
#[derive(Debug, Clone, ::ruma_api::Outgoing)] #[derive(Debug, Clone, ::ruma_api::Outgoing)]
#[incoming_no_deserialize] #[incoming_no_deserialize]
pub struct Request #request_def pub struct Request #request_generics #request_def
#request_body_struct #request_body_struct
#request_query_struct #request_query_struct
@ -498,13 +516,6 @@ impl RequestField {
None 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. /// 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. //! 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 proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens}; use quote::{quote, quote_spanned, ToTokens};
use syn::{spanned::Spanned, Field, Ident}; use syn::{spanned::Spanned, Field, Ident, Lifetime};
use crate::{ use crate::{
api::{ api::{
@ -18,6 +18,9 @@ use crate::{
pub struct Response { pub struct Response {
/// The fields of the response. /// The fields of the response.
fields: Vec<ResponseField>, fields: Vec<ResponseField>,
/// The collected lifetime identifiers from the declared fields.
lifetimes: Vec<Lifetime>,
} }
impl Response { impl Response {
@ -31,9 +34,13 @@ impl Response {
self.fields.iter().any(|field| field.is_header()) self.fields.iter().any(|field| field.is_header())
} }
/// Whether any field has a #[wrap_incoming] attribute. /// Whether any field has a lifetime.
pub fn uses_wrap_incoming(&self) -> bool { pub fn contains_lifetimes(&self) -> bool {
self.fields.iter().any(|f| f.has_wrap_incoming_attr()) 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. /// Produces code for a response struct initializer.
@ -162,6 +169,7 @@ impl TryFrom<RawResponse> for Response {
fn try_from(raw: RawResponse) -> syn::Result<Self> { fn try_from(raw: RawResponse) -> syn::Result<Self> {
let mut newtype_body_field = None; let mut newtype_body_field = None;
let mut lifetimes = BTreeSet::new();
let fields = raw let fields = raw
.fields .fields
@ -170,6 +178,8 @@ impl TryFrom<RawResponse> for Response {
let mut field_kind = None; let mut field_kind = None;
let mut header = None; let mut header = None;
util::copy_lifetime_ident(&mut lifetimes, &field.ty);
for attr in mem::replace(&mut field.attrs, Vec::new()) { 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,
@ -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),* } } quote! { { #(#fields),* } }
}; };
let (derive_deserialize, def) = let (derive_deserialize, def) = if let Some(body_field) =
if let Some(body_field) = self.fields.iter().find(|f| f.is_newtype_body()) { 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 field = Field { ident: None, colon_token: None, ..body_field.field().clone() };
TokenStream::new()
} else {
quote!(::ruma_api::exports::serde::Deserialize)
};
(derive_deserialize, quote! { (#field); }) let (derive_deserialize, lifetimes) = if util::has_lifetime(&body_field.field().ty) {
} else if self.has_body_fields() { (
let fields = self.fields.iter().filter(|f| f.is_body()); TokenStream::new(),
let derive_deserialize = if fields.clone().any(|f| f.has_wrap_incoming_attr()) { util::collect_generic_idents(Some(&body_field.field().ty).into_iter()),
TokenStream::new() )
} else {
quote!(::ruma_api::exports::serde::Deserialize)
};
let fields = fields.map(ResponseField::field);
(derive_deserialize, quote!({ #(#fields),* }))
} else { } 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! { let response_body_struct = quote! {
/// Data in the response body. /// Data in the response body.
#[derive( #[derive(
@ -280,10 +300,11 @@ impl ToTokens for Response {
struct ResponseBody #def struct ResponseBody #def
}; };
let response_generics = util::generics_to_tokens(self.lifetimes.iter());
let response = quote! { let response = quote! {
#[derive(Debug, Clone, ::ruma_api::Outgoing)] #[derive(Debug, Clone, ::ruma_api::Outgoing)]
#[incoming_no_deserialize] #[incoming_no_deserialize]
pub struct Response #response_def pub struct Response #response_generics #response_def
#response_body_struct #response_body_struct
}; };
@ -353,13 +374,6 @@ impl ResponseField {
_ => None, _ => 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. /// The types of fields that a response can have, without their values.

View File

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