Use ruma_api crate from ruma or if renamed or not in crate prelude

This commit is contained in:
Devin Ragotzy 2020-08-10 16:34:14 -04:00 committed by Jonas Platte
parent 45aad4d10c
commit 6877c2f38d
4 changed files with 105 additions and 72 deletions

View File

@ -19,6 +19,7 @@ edition = "2018"
proc-macro2 = "1.0.19" proc-macro2 = "1.0.19"
quote = "1.0.7" quote = "1.0.7"
syn = { version = "1.0.38", features = ["full", "extra-traits"] } syn = { version = "1.0.38", features = ["full", "extra-traits"] }
proc-macro-crate = "0.1.5"
[lib] [lib]
proc-macro = true proc-macro = true

View File

@ -2,13 +2,14 @@
use std::convert::{TryFrom, TryInto as _}; use std::convert::{TryFrom, TryInto as _};
use proc_macro2::TokenStream; use proc_macro2::{Span, TokenStream};
use proc_macro_crate::crate_name;
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use syn::{ use syn::{
braced, braced,
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
spanned::Spanned, spanned::Spanned,
Field, FieldValue, Token, Type, Field, FieldValue, Ident, Token, Type,
}; };
pub(crate) mod attribute; pub(crate) mod attribute;
@ -26,6 +27,15 @@ pub fn strip_serde_attrs(field: &Field) -> Field {
field field
} }
pub fn import_ruma_api() -> TokenStream {
if let Ok(name) = crate_name("ruma-api") {
let import = Ident::new(&name, Span::call_site());
quote! { ::#import }
} else {
quote! { ::ruma_api }
}
}
/// The result of processing the `ruma_api` macro, ready for output back to source code. /// The result of processing the `ruma_api` macro, ready for output back to source code.
pub struct Api { pub struct Api {
/// The `metadata` section of the macro. /// The `metadata` section of the macro.
@ -82,6 +92,9 @@ impl TryFrom<RawApi> for Api {
impl ToTokens for Api { impl ToTokens for Api {
fn to_tokens(&self, tokens: &mut TokenStream) { fn to_tokens(&self, tokens: &mut TokenStream) {
// Guarantee `ruma_api` is available and named something we can refer to.
let ruma_api_import = import_ruma_api();
let description = &self.metadata.description; let description = &self.metadata.description;
let method = &self.metadata.method; let method = &self.metadata.method;
// We don't (currently) use this literal as a literal in the generated code. Instead we just // We don't (currently) use this literal as a literal in the generated code. Instead we just
@ -130,12 +143,12 @@ impl ToTokens for Api {
let mut header_kvs = self.request.append_header_kvs(); let mut header_kvs = self.request.append_header_kvs();
if requires_authentication.value { if requires_authentication.value {
header_kvs.push(quote! { header_kvs.push(quote! {
::ruma_api::exports::http::header::AUTHORIZATION, #ruma_api_import::exports::http::header::AUTHORIZATION,
::ruma_api::exports::http::header::HeaderValue::from_str( #ruma_api_import::exports::http::header::HeaderValue::from_str(
&::std::format!( &::std::format!(
"Bearer {}", "Bearer {}",
access_token.ok_or_else( access_token.ok_or_else(
::ruma_api::error::IntoHttpError::needs_authentication #ruma_api_import::error::IntoHttpError::needs_authentication
)? )?
) )
)? )?
@ -162,10 +175,10 @@ impl ToTokens for Api {
TokenStream::new() TokenStream::new()
}; };
quote! { quote! {
let request_body: <RequestBody #body_lifetimes as ::ruma_api::Outgoing>::Incoming = let request_body: <RequestBody #body_lifetimes as #ruma_api_import::Outgoing>::Incoming =
::ruma_api::try_deserialize!( #ruma_api_import::try_deserialize!(
request, request,
::ruma_api::exports::serde_json::from_slice(request.body().as_slice()) #ruma_api_import::exports::serde_json::from_slice(request.body().as_slice())
); );
} }
} else { } else {
@ -190,18 +203,19 @@ impl ToTokens for Api {
TokenStream::new() TokenStream::new()
}; };
let typed_response_body_decl = let typed_response_body_decl = if self.response.has_body_fields()
if self.response.has_body_fields() || self.response.newtype_body_field().is_some() { || self.response.newtype_body_field().is_some()
quote! { {
let response_body: <ResponseBody as ::ruma_api::Outgoing>::Incoming = quote! {
::ruma_api::try_deserialize!( let response_body: <ResponseBody as #ruma_api_import::Outgoing>::Incoming =
response, #ruma_api_import::try_deserialize!(
::ruma_api::exports::serde_json::from_slice(response.body().as_slice()), response,
); #ruma_api_import::exports::serde_json::from_slice(response.body().as_slice()),
} );
} else { }
TokenStream::new() } else {
}; TokenStream::new()
};
let response_init_fields = self.response.init_fields(); let response_init_fields = self.response.init_fields();
@ -224,22 +238,23 @@ impl ToTokens for Api {
TokenStream::new() TokenStream::new()
} else { } else {
quote! { quote! {
impl #request_lifetimes ::ruma_api::NonAuthEndpoint impl #request_lifetimes #ruma_api_import::NonAuthEndpoint
for Request #request_lifetimes for Request #request_lifetimes
{} {}
} }
}; };
let api = quote! { let api = quote! {
#[doc = #request_doc] #[doc = #request_doc]
#request_type #request_type
impl ::std::convert::TryFrom<::ruma_api::exports::http::Request<Vec<u8>>> for #request_try_from_type { impl ::std::convert::TryFrom<#ruma_api_import::exports::http::Request<Vec<u8>>> for #request_try_from_type {
type Error = ::ruma_api::error::FromHttpRequestError; type Error = #ruma_api_import::error::FromHttpRequestError;
#[allow(unused_variables)] #[allow(unused_variables)]
fn try_from( fn try_from(
request: ::ruma_api::exports::http::Request<Vec<u8>> request: #ruma_api_import::exports::http::Request<Vec<u8>>
) -> ::std::result::Result<Self, Self::Error> { ) -> ::std::result::Result<Self, Self::Error> {
#extract_request_path #extract_request_path
#extract_request_query #extract_request_query
@ -258,13 +273,13 @@ impl ToTokens for Api {
#[doc = #response_doc] #[doc = #response_doc]
#response_type #response_type
impl ::std::convert::TryFrom<Response> for ::ruma_api::exports::http::Response<Vec<u8>> { impl ::std::convert::TryFrom<Response> for #ruma_api_import::exports::http::Response<Vec<u8>> {
type Error = ::ruma_api::error::IntoHttpError; type Error = #ruma_api_import::error::IntoHttpError;
#[allow(unused_variables)] #[allow(unused_variables)]
fn try_from(response: Response) -> ::std::result::Result<Self, Self::Error> { fn try_from(response: Response) -> ::std::result::Result<Self, Self::Error> {
let response = ::ruma_api::exports::http::Response::builder() let response = #ruma_api_import::exports::http::Response::builder()
.header(::ruma_api::exports::http::header::CONTENT_TYPE, "application/json") .header(#ruma_api_import::exports::http::header::CONTENT_TYPE, "application/json")
#serialize_response_headers #serialize_response_headers
.body(#body) .body(#body)
// Since we require header names to come from the `http::header` module, // Since we require header names to come from the `http::header` module,
@ -274,12 +289,12 @@ impl ToTokens for Api {
} }
} }
impl ::std::convert::TryFrom<::ruma_api::exports::http::Response<Vec<u8>>> for Response { impl ::std::convert::TryFrom<#ruma_api_import::exports::http::Response<Vec<u8>>> for Response {
type Error = ::ruma_api::error::FromHttpResponseError<#error>; type Error = #ruma_api_import::error::FromHttpResponseError<#error>;
#[allow(unused_variables)] #[allow(unused_variables)]
fn try_from( fn try_from(
response: ::ruma_api::exports::http::Response<Vec<u8>>, response: #ruma_api_import::exports::http::Response<Vec<u8>>,
) -> ::std::result::Result<Self, Self::Error> { ) -> ::std::result::Result<Self, Self::Error> {
if response.status().as_u16() < 400 { if response.status().as_u16() < 400 {
#extract_response_headers #extract_response_headers
@ -290,26 +305,26 @@ impl ToTokens for Api {
#response_init_fields #response_init_fields
}) })
} else { } else {
match <#error as ::ruma_api::EndpointError>::try_from_response(response) { match <#error as #ruma_api_import::EndpointError>::try_from_response(response) {
Ok(err) => Err(::ruma_api::error::ServerError::Known(err).into()), Ok(err) => Err(#ruma_api_import::error::ServerError::Known(err).into()),
Err(response_err) => { Err(response_err) => {
Err(::ruma_api::error::ServerError::Unknown(response_err).into()) Err(#ruma_api_import::error::ServerError::Unknown(response_err).into())
} }
} }
} }
} }
} }
impl #request_lifetimes ::ruma_api::Endpoint for Request #request_lifetimes { impl #request_lifetimes #ruma_api_import::Endpoint for Request #request_lifetimes {
type Response = Response; type Response = Response;
type ResponseError = #error; type ResponseError = #error;
type IncomingRequest = <Self as ::ruma_api::Outgoing>::Incoming; type IncomingRequest = <Self as #ruma_api_import::Outgoing>::Incoming;
type IncomingResponse = <Response as ::ruma_api::Outgoing>::Incoming; type IncomingResponse = <Response as #ruma_api_import::Outgoing>::Incoming;
/// Metadata for the `#name` endpoint. /// Metadata for the `#name` endpoint.
const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata { const METADATA: #ruma_api_import::Metadata = #ruma_api_import::Metadata {
description: #description, description: #description,
method: ::ruma_api::exports::http::Method::#method, method: #ruma_api_import::exports::http::Method::#method,
name: #name, name: #name,
path: #path, path: #path,
rate_limited: #rate_limited, rate_limited: #rate_limited,
@ -322,13 +337,13 @@ impl ToTokens for Api {
base_url: &::std::primitive::str, base_url: &::std::primitive::str,
access_token: ::std::option::Option<&str>, access_token: ::std::option::Option<&str>,
) -> ::std::result::Result< ) -> ::std::result::Result<
::ruma_api::exports::http::Request<Vec<u8>>, #ruma_api_import::exports::http::Request<Vec<u8>>,
::ruma_api::error::IntoHttpError, #ruma_api_import::error::IntoHttpError,
> { > {
let metadata = Request::METADATA; let metadata = Request::METADATA;
let http_request = ::ruma_api::exports::http::Request::builder() let http_request = #ruma_api_import::exports::http::Request::builder()
.method(::ruma_api::exports::http::Method::#method) .method(#ruma_api_import::exports::http::Method::#method)
.uri(::std::format!( .uri(::std::format!(
"{}{}{}", "{}{}{}",
base_url.strip_suffix("/").unwrap_or(base_url), base_url.strip_suffix("/").unwrap_or(base_url),

View File

@ -9,7 +9,7 @@ use syn::{spanned::Spanned, Field, Ident, Lifetime};
use crate::{ use crate::{
api::{ api::{
attribute::{Meta, MetaNameValue}, attribute::{Meta, MetaNameValue},
strip_serde_attrs, RawRequest, import_ruma_api, strip_serde_attrs, RawRequest,
}, },
util, util,
}; };
@ -29,11 +29,15 @@ pub struct Request {
/// The collected lifetime identifiers from the declared fields. /// The collected lifetime identifiers from the declared fields.
lifetimes: RequestLifetimes, lifetimes: RequestLifetimes,
// Guarantee `ruma_api` is available and named something we can refer to.
ruma_api_import: TokenStream,
} }
impl Request { impl Request {
/// Produces code to add necessary HTTP headers to an `http::Request`. /// Produces code to add necessary HTTP headers to an `http::Request`.
pub fn append_header_kvs(&self) -> Vec<TokenStream> { pub fn append_header_kvs(&self) -> Vec<TokenStream> {
let ruma_api = &self.ruma_api_import;
self.header_fields().map(|request_field| { self.header_fields().map(|request_field| {
let (field, header_name) = match request_field { let (field, header_name) = match request_field {
RequestField::Header(field, header_name) => (field, header_name), RequestField::Header(field, header_name) => (field, header_name),
@ -43,14 +47,15 @@ impl Request {
let field_name = &field.ident; let field_name = &field.ident;
quote! { quote! {
::ruma_api::exports::http::header::#header_name, #ruma_api::exports::http::header::#header_name,
::ruma_api::exports::http::header::HeaderValue::from_str(self.#field_name.as_ref())? #ruma_api::exports::http::header::HeaderValue::from_str(self.#field_name.as_ref())?
} }
}).collect() }).collect()
} }
/// Produces code to extract fields from the HTTP headers in an `http::Request`. /// Produces code to extract fields from the HTTP headers in an `http::Request`.
pub fn parse_headers_from_request(&self) -> TokenStream { pub fn parse_headers_from_request(&self) -> TokenStream {
let ruma_api = &self.ruma_api_import;
let fields = self.header_fields().map(|request_field| { let fields = self.header_fields().map(|request_field| {
let (field, header_name) = match request_field { let (field, header_name) = match request_field {
RequestField::Header(field, header_name) => (field, header_name), RequestField::Header(field, header_name) => (field, header_name),
@ -62,16 +67,16 @@ impl Request {
quote! { quote! {
#field_name: match headers #field_name: match headers
.get(::ruma_api::exports::http::header::#header_name) .get(#ruma_api::exports::http::header::#header_name)
.and_then(|v| v.to_str().ok()) // FIXME: Should have a distinct error message .and_then(|v| v.to_str().ok()) // FIXME: Should have a distinct error message
{ {
Some(header) => header.to_owned(), Some(header) => header.to_owned(),
None => { None => {
use ::ruma_api::exports::serde::de::Error as _; use #ruma_api::exports::serde::de::Error as _;
// FIXME: Not a missing json field, a missing header! // FIXME: Not a missing json field, a missing header!
return Err(::ruma_api::error::RequestDeserializationError::new( return Err(#ruma_api::error::RequestDeserializationError::new(
::ruma_api::exports::serde_json::Error::missing_field( #ruma_api::exports::serde_json::Error::missing_field(
#header_name_string #header_name_string
), ),
request, request,
@ -377,12 +382,13 @@ impl TryFrom<RawRequest> for Request {
)); ));
} }
Ok(Self { fields, lifetimes }) Ok(Self { fields, lifetimes, ruma_api_import: 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 request_def = if self.fields.is_empty() { let request_def = if self.fields.is_empty() {
quote!(;) quote!(;)
} else { } else {
@ -403,7 +409,7 @@ impl ToTokens for Request {
let (derive_deserialize, lifetimes) = if self.has_body_lifetimes() { let (derive_deserialize, lifetimes) = if self.has_body_lifetimes() {
(TokenStream::new(), self.body_lifetimes()) (TokenStream::new(), self.body_lifetimes())
} else { } else {
(quote!(::ruma_api::exports::serde::Deserialize), TokenStream::new()) (quote!(#ruma_api::exports::serde::Deserialize), TokenStream::new())
}; };
Some((derive_deserialize, quote! { #lifetimes (#field); })) Some((derive_deserialize, quote! { #lifetimes (#field); }))
@ -412,7 +418,7 @@ impl ToTokens for Request {
let (derive_deserialize, lifetimes) = if self.has_body_lifetimes() { let (derive_deserialize, lifetimes) = if self.has_body_lifetimes() {
(TokenStream::new(), self.body_lifetimes()) (TokenStream::new(), self.body_lifetimes())
} else { } else {
(quote!(::ruma_api::exports::serde::Deserialize), TokenStream::new()) (quote!(#ruma_api::exports::serde::Deserialize), TokenStream::new())
}; };
let fields = fields.map(RequestField::field); let fields = fields.map(RequestField::field);
@ -425,8 +431,8 @@ impl ToTokens for Request {
/// Data in the request body. /// Data in the request body.
#[derive( #[derive(
Debug, Debug,
::ruma_api::Outgoing, #ruma_api::Outgoing,
::ruma_api::exports::serde::Serialize, #ruma_api::exports::serde::Serialize,
#derive_deserialize #derive_deserialize
)] )]
struct RequestBody #def struct RequestBody #def
@ -438,15 +444,15 @@ impl ToTokens for Request {
let (derive_deserialize, lifetime) = if self.has_query_lifetimes() { let (derive_deserialize, lifetime) = if self.has_query_lifetimes() {
(TokenStream::new(), self.query_lifetimes()) (TokenStream::new(), self.query_lifetimes())
} else { } else {
(quote!(::ruma_api::exports::serde::Deserialize), TokenStream::new()) (quote!(#ruma_api::exports::serde::Deserialize), TokenStream::new())
}; };
quote! { quote! {
/// Data in the request's query string. /// Data in the request's query string.
#[derive( #[derive(
Debug, Debug,
::ruma_api::Outgoing, #ruma_api::Outgoing,
::ruma_api::exports::serde::Serialize, #ruma_api::exports::serde::Serialize,
#derive_deserialize #derive_deserialize
)] )]
struct RequestQuery #lifetime (#field); struct RequestQuery #lifetime (#field);
@ -456,15 +462,15 @@ impl ToTokens for Request {
let (derive_deserialize, lifetime) = if self.has_query_lifetimes() { let (derive_deserialize, lifetime) = if self.has_query_lifetimes() {
(TokenStream::new(), self.query_lifetimes()) (TokenStream::new(), self.query_lifetimes())
} else { } else {
(quote!(::ruma_api::exports::serde::Deserialize), TokenStream::new()) (quote!(#ruma_api::exports::serde::Deserialize), TokenStream::new())
}; };
quote! { quote! {
/// Data in the request's query string. /// Data in the request's query string.
#[derive( #[derive(
Debug, Debug,
::ruma_api::Outgoing, #ruma_api::Outgoing,
::ruma_api::exports::serde::Serialize, #ruma_api::exports::serde::Serialize,
#derive_deserialize #derive_deserialize
)] )]
struct RequestQuery #lifetime { struct RequestQuery #lifetime {
@ -476,7 +482,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_generics #request_def pub struct Request #request_generics #request_def

View File

@ -9,7 +9,7 @@ use syn::{spanned::Spanned, Field, Ident};
use crate::{ use crate::{
api::{ api::{
attribute::{Meta, MetaNameValue}, attribute::{Meta, MetaNameValue},
strip_serde_attrs, RawResponse, import_ruma_api, strip_serde_attrs, RawResponse,
}, },
util, util,
}; };
@ -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>,
// Guarantee `ruma_api` is available and named something we can refer to.
ruma_api_import: TokenStream,
} }
impl Response { impl Response {
@ -33,6 +36,8 @@ impl Response {
/// Produces code for a response struct initializer. /// Produces code for a response struct initializer.
pub fn init_fields(&self) -> TokenStream { pub fn init_fields(&self) -> TokenStream {
let ruma_api = &self.ruma_api_import;
let mut fields = vec![]; let mut fields = vec![];
let mut new_type_raw_body = None; let mut new_type_raw_body = None;
for response_field in &self.fields { for response_field in &self.fields {
@ -51,9 +56,9 @@ impl Response {
} }
ResponseField::Header(_, header_name) => { ResponseField::Header(_, header_name) => {
quote_spanned! {span=> quote_spanned! {span=>
#field_name: ::ruma_api::try_deserialize!( #field_name: #ruma_api::try_deserialize!(
response, response,
headers.remove(::ruma_api::exports::http::header::#header_name) headers.remove(#ruma_api::exports::http::header::#header_name)
.expect("response missing expected header") .expect("response missing expected header")
.to_str() .to_str()
) )
@ -86,6 +91,8 @@ impl Response {
/// Produces code to add necessary HTTP headers to an `http::Response`. /// Produces code to add necessary HTTP headers to an `http::Response`.
pub fn apply_header_fields(&self) -> TokenStream { pub fn apply_header_fields(&self) -> TokenStream {
let ruma_api = &self.ruma_api_import;
let header_calls = self.fields.iter().filter_map(|response_field| { let header_calls = self.fields.iter().filter_map(|response_field| {
if let ResponseField::Header(ref field, ref header_name) = *response_field { if let ResponseField::Header(ref field, ref header_name) = *response_field {
let field_name = let field_name =
@ -93,7 +100,7 @@ impl Response {
let span = field.span(); let span = field.span();
Some(quote_spanned! {span=> Some(quote_spanned! {span=>
.header(::ruma_api::exports::http::header::#header_name, response.#field_name) .header(#ruma_api::exports::http::header::#header_name, response.#field_name)
}) })
} else { } else {
None None
@ -105,6 +112,8 @@ impl Response {
/// Produces code to initialize the struct that will be used to create the response body. /// Produces code to initialize the struct that will be used to create the response body.
pub fn to_body(&self) -> TokenStream { pub fn to_body(&self) -> TokenStream {
let ruma_api = &self.ruma_api_import;
if let Some(field) = self.newtype_raw_body_field() { if let Some(field) = self.newtype_raw_body_field() {
let field_name = field.ident.as_ref().expect("expected field to have an identifier"); let field_name = field.ident.as_ref().expect("expected field to have an identifier");
let span = field.span(); let span = field.span();
@ -138,7 +147,7 @@ impl Response {
} }
}; };
quote!(::ruma_api::exports::serde_json::to_vec(&#body)?) quote!(#ruma_api::exports::serde_json::to_vec(&#body)?)
} }
/// Gets the newtype body field, if this response has one. /// Gets the newtype body field, if this response has one.
@ -225,12 +234,14 @@ impl TryFrom<RawResponse> for Response {
)); ));
} }
Ok(Self { fields }) Ok(Self { fields, ruma_api_import: 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 response_def = if self.fields.is_empty() { let response_def = if self.fields.is_empty() {
quote!(;) quote!(;)
} else { } else {
@ -258,15 +269,15 @@ impl ToTokens for Response {
/// Data in the response body. /// Data in the response body.
#[derive( #[derive(
Debug, Debug,
::ruma_api::Outgoing, #ruma_api::Outgoing,
::ruma_api::exports::serde::Deserialize, #ruma_api::exports::serde::Deserialize,
::ruma_api::exports::serde::Serialize, #ruma_api::exports::serde::Serialize,
)] )]
struct ResponseBody #def struct ResponseBody #def
}; };
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_def