macros: Remove ruma_api! macro
This commit is contained in:
parent
80c060cb69
commit
bdb5950fe4
@ -235,18 +235,15 @@ Structs also should not be borrowed, with the exception that if a struct:
|
|||||||
|
|
||||||
- has fields that should be borrowed according to the table
|
- has fields that should be borrowed according to the table
|
||||||
above (strings, identifiers, `Vec`s), and
|
above (strings, identifiers, `Vec`s), and
|
||||||
- is only used inside request blocks (i.e. not in response blocks or in
|
- is only used inside request blocks (i.e. not in response blocks or in events),
|
||||||
events),
|
|
||||||
|
|
||||||
then the struct should be lifetime-parameterized and apply the same rules to
|
then the struct should be lifetime-parameterized and apply the same rules to
|
||||||
their fields. So instead of
|
their fields. So instead of
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
ruma_api! {
|
#[request]
|
||||||
request: {
|
pub struct Request {
|
||||||
my_field: MyStruct,
|
my_field: MyStruct,
|
||||||
}
|
|
||||||
// ...
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MyStruct {
|
pub struct MyStruct {
|
||||||
@ -257,11 +254,9 @@ pub struct MyStruct {
|
|||||||
use
|
use
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
ruma_api! {
|
#[request]
|
||||||
request: {
|
pub struct Request<'a> {
|
||||||
my_field: MyStruct<'a>,
|
my_field: MyStruct<'a>,
|
||||||
}
|
|
||||||
// ...
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MyStruct<'a> {
|
pub struct MyStruct<'a> {
|
||||||
|
@ -18,182 +18,6 @@ use bytes::BufMut;
|
|||||||
|
|
||||||
use crate::UserId;
|
use crate::UserId;
|
||||||
|
|
||||||
/// Generates [`IncomingRequest`] and [`OutgoingRequest`] from a concise definition.
|
|
||||||
///
|
|
||||||
/// The macro expects the following structure as input:
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// ruma_api! {
|
|
||||||
/// metadata: {
|
|
||||||
/// description: &'static str,
|
|
||||||
/// method: http::Method,
|
|
||||||
/// name: &'static str,
|
|
||||||
/// path: &'static str,
|
|
||||||
/// rate_limited: bool,
|
|
||||||
/// authentication: ruma_common::api::AuthScheme,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// request: {
|
|
||||||
/// // Struct fields for each piece of data required
|
|
||||||
/// // to make a request to this API endpoint.
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// response: {
|
|
||||||
/// // Struct fields for each piece of data expected
|
|
||||||
/// // in the response from this API endpoint.
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// // The error returned when a response fails, defaults to `MatrixError`.
|
|
||||||
/// error: path::to::Error
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// This will generate a [`Metadata`] value to be used for the associated constants of
|
|
||||||
/// [`IncomingRequest`] and [`OutgoingRequest`], single `Request` and `Response` structs, and
|
|
||||||
/// the necessary trait implementations to convert the request into a `http::Request` and to
|
|
||||||
/// create a response from a `http::Response` and vice versa.
|
|
||||||
///
|
|
||||||
/// The details of each of the three sections of the macros are documented below.
|
|
||||||
///
|
|
||||||
/// ## Metadata
|
|
||||||
///
|
|
||||||
/// * `description`: A short description of what the endpoint does.
|
|
||||||
/// * `method`: The HTTP method used for requests to the endpoint. It's not necessary to import
|
|
||||||
/// `http::Method`'s associated constants. Just write the value as if it was imported, e.g.
|
|
||||||
/// `GET`.
|
|
||||||
/// * `name`: A unique name for the endpoint. Generally this will be the same as the containing
|
|
||||||
/// module.
|
|
||||||
/// * `path`: The path component of the URL for the endpoint, e.g. "/foo/bar". Components of
|
|
||||||
/// the path that are parameterized can indicate a variable by using a Rust identifier
|
|
||||||
/// prefixed with a colon, e.g. `/foo/:some_parameter`. A corresponding query string
|
|
||||||
/// parameter will be expected in the request struct (see below for details).
|
|
||||||
/// * `rate_limited`: Whether or not the endpoint enforces rate limiting on requests.
|
|
||||||
/// * `authentication`: What authentication scheme the endpoint uses.
|
|
||||||
///
|
|
||||||
/// ## Request
|
|
||||||
///
|
|
||||||
/// The request block contains normal struct field definitions. Doc comments and attributes are
|
|
||||||
/// allowed as normal. There are also a few special attributes available to control how the
|
|
||||||
/// struct is converted into an `http::Request`:
|
|
||||||
///
|
|
||||||
/// * `#[ruma_api(header = HEADER_NAME)]`: Fields with this attribute will be treated as HTTP
|
|
||||||
/// headers on the request. The value must implement `AsRef<str>`. Generally this is a
|
|
||||||
/// `String`. The attribute value shown above as `HEADER_NAME` must be a `const` expression
|
|
||||||
/// of the type `http::header::HeaderName`, like one of the constants from `http::header`,
|
|
||||||
/// e.g. `CONTENT_TYPE`.
|
|
||||||
/// * `#[ruma_api(path)]`: Fields with this attribute will be inserted into the matching path
|
|
||||||
/// component of the request URL.
|
|
||||||
/// * `#[ruma_api(query)]`: Fields with this attribute will be inserting into the URL's query
|
|
||||||
/// string.
|
|
||||||
/// * `#[ruma_api(query_map)]`: Instead of individual query fields, one query_map field, of any
|
|
||||||
/// type that implements `IntoIterator<Item = (String, String)>` (e.g. `HashMap<String,
|
|
||||||
/// String>`, can be used for cases where an endpoint supports arbitrary query parameters.
|
|
||||||
///
|
|
||||||
/// Any field that does not include one of these attributes will be part of the request's JSON
|
|
||||||
/// body.
|
|
||||||
///
|
|
||||||
/// ## Response
|
|
||||||
///
|
|
||||||
/// Like the request block, the response block consists of normal struct field definitions.
|
|
||||||
/// Doc comments and attributes are allowed as normal.
|
|
||||||
/// There is also a special attribute available to control how the struct is created from a
|
|
||||||
/// `http::Request`:
|
|
||||||
///
|
|
||||||
/// * `#[ruma_api(header = HEADER_NAME)]`: Fields with this attribute will be treated as HTTP
|
|
||||||
/// headers on the response. The value must implement `AsRef<str>`. Generally this is a
|
|
||||||
/// `String`. The attribute value shown above as `HEADER_NAME` must be a header name constant
|
|
||||||
/// from `http::header`, e.g. `CONTENT_TYPE`.
|
|
||||||
///
|
|
||||||
/// Any field that does not include the above attribute will be expected in the response's JSON
|
|
||||||
/// body.
|
|
||||||
///
|
|
||||||
/// ## Newtype bodies
|
|
||||||
///
|
|
||||||
/// Both the request and response block also support "newtype bodies" by using the
|
|
||||||
/// `#[ruma_api(body)]` attribute on a field. If present on a field, the entire request or
|
|
||||||
/// response body will be treated as the value of the field. This allows you to treat the
|
|
||||||
/// entire request or response body as a specific type, rather than a JSON object with named
|
|
||||||
/// fields. Only one field in each struct can be marked with this attribute. It is an error to
|
|
||||||
/// have a newtype body field and normal body fields within the same struct.
|
|
||||||
///
|
|
||||||
/// There is another kind of newtype body that is enabled with `#[ruma_api(raw_body)]`. It is
|
|
||||||
/// used for endpoints in which the request or response body can be arbitrary bytes instead of
|
|
||||||
/// a JSON objects. A field with `#[ruma_api(raw_body)]` needs to have the type `Vec<u8>`.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// pub mod some_endpoint {
|
|
||||||
/// use http::header::CONTENT_TYPE;
|
|
||||||
/// use ruma_common::api::ruma_api;
|
|
||||||
///
|
|
||||||
/// ruma_api! {
|
|
||||||
/// metadata: {
|
|
||||||
/// description: "Does something.",
|
|
||||||
/// method: POST,
|
|
||||||
/// name: "some_endpoint",
|
|
||||||
/// stable_path: "/_matrix/some/endpoint/:baz",
|
|
||||||
/// rate_limited: false,
|
|
||||||
/// authentication: None,
|
|
||||||
/// added: 1.1,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// request: {
|
|
||||||
/// pub foo: String,
|
|
||||||
///
|
|
||||||
/// #[ruma_api(header = CONTENT_TYPE)]
|
|
||||||
/// pub content_type: String,
|
|
||||||
///
|
|
||||||
/// #[ruma_api(query)]
|
|
||||||
/// pub bar: String,
|
|
||||||
///
|
|
||||||
/// #[ruma_api(path)]
|
|
||||||
/// pub baz: String,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// response: {
|
|
||||||
/// #[ruma_api(header = CONTENT_TYPE)]
|
|
||||||
/// pub content_type: String,
|
|
||||||
///
|
|
||||||
/// pub value: String,
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// pub mod newtype_body_endpoint {
|
|
||||||
/// use ruma_common::api::ruma_api;
|
|
||||||
/// use serde::{Deserialize, Serialize};
|
|
||||||
///
|
|
||||||
/// #[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
/// pub struct MyCustomType {
|
|
||||||
/// pub foo: String,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// ruma_api! {
|
|
||||||
/// metadata: {
|
|
||||||
/// description: "Does something.",
|
|
||||||
/// method: PUT,
|
|
||||||
/// name: "newtype_body_endpoint",
|
|
||||||
/// stable_path: "/_matrix/some/newtype/body/endpoint",
|
|
||||||
/// rate_limited: false,
|
|
||||||
/// authentication: None,
|
|
||||||
/// added: 1.1,
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// request: {
|
|
||||||
/// #[ruma_api(raw_body)]
|
|
||||||
/// pub file: &'a [u8],
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// response: {
|
|
||||||
/// #[ruma_api(body)]
|
|
||||||
/// pub my_custom_type: MyCustomType,
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub use ruma_macros::ruma_api;
|
|
||||||
|
|
||||||
/// Generates [`OutgoingRequest`] and [`IncomingRequest`] implementations.
|
/// Generates [`OutgoingRequest`] and [`IncomingRequest`] implementations.
|
||||||
///
|
///
|
||||||
/// The `OutgoingRequest` impl is on the `Request` type this attribute is used on. It is
|
/// The `OutgoingRequest` impl is on the `Request` type this attribute is used on. It is
|
||||||
@ -655,7 +479,6 @@ macro_rules! metadata {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Simple literal case: used for description, name, rate_limited
|
// Simple literal case: used for description, name, rate_limited
|
||||||
// Also used by ruma_api! while it still exists, for the history field
|
|
||||||
( @field $_field:ident: $rhs:expr ) => { $rhs };
|
( @field $_field:ident: $rhs:expr ) => { $rhs };
|
||||||
|
|
||||||
( @history_impl
|
( @history_impl
|
||||||
|
@ -4,10 +4,10 @@ const _: Metadata = metadata! {
|
|||||||
description: "This will fail.",
|
description: "This will fail.",
|
||||||
method: GET,
|
method: GET,
|
||||||
name: "invalid_versions",
|
name: "invalid_versions",
|
||||||
unstable => "/a/path",
|
|
||||||
rate_limited: false,
|
rate_limited: false,
|
||||||
authentication: None,
|
authentication: None,
|
||||||
history: {
|
history: {
|
||||||
|
unstable => "/a/path",
|
||||||
1.1 => deprecated,
|
1.1 => deprecated,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,14 @@
|
|||||||
error: no rules expected the token `=>`
|
error: no rules expected the token `history_impl`
|
||||||
--> tests/api/ui/deprecated-without-added.rs:7:14
|
--> tests/api/ui/deprecated-without-added.rs:3:21
|
||||||
|
|
|
|
||||||
7 | unstable => "/a/path",
|
3 | const _: Metadata = metadata! {
|
||||||
| ^^ no rules expected this token in macro call
|
| _____________________^
|
||||||
|
4 | | description: "This will fail.",
|
||||||
|
5 | | method: GET,
|
||||||
|
6 | | name: "invalid_versions",
|
||||||
|
... |
|
||||||
|
12 | | }
|
||||||
|
13 | | };
|
||||||
|
| |_^ no rules expected this token in macro call
|
||||||
|
|
|
||||||
|
= note: this error originates in the macro `$crate::metadata` which comes from the expansion of the macro `metadata` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
@ -4,10 +4,10 @@ const METADATA: Metadata = metadata! {
|
|||||||
description: "This will fail.",
|
description: "This will fail.",
|
||||||
method: GET,
|
method: GET,
|
||||||
name: "invalid_versions",
|
name: "invalid_versions",
|
||||||
unstable => "/a/path",
|
|
||||||
rate_limited: false,
|
rate_limited: false,
|
||||||
authentication: None,
|
authentication: None,
|
||||||
history: {
|
history: {
|
||||||
|
unstable => "/a/path",
|
||||||
1.1 => removed,
|
1.1 => removed,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,14 @@
|
|||||||
error: no rules expected the token `=>`
|
error: no rules expected the token `history_impl`
|
||||||
--> tests/api/ui/removed-without-deprecated.rs:7:14
|
--> tests/api/ui/removed-without-deprecated.rs:3:28
|
||||||
|
|
|
|
||||||
7 | unstable => "/a/path",
|
3 | const METADATA: Metadata = metadata! {
|
||||||
| ^^ no rules expected this token in macro call
|
| ____________________________^
|
||||||
|
4 | | description: "This will fail.",
|
||||||
|
5 | | method: GET,
|
||||||
|
6 | | name: "invalid_versions",
|
||||||
|
... |
|
||||||
|
12 | | }
|
||||||
|
13 | | };
|
||||||
|
| |_^ no rules expected this token in macro call
|
||||||
|
|
|
||||||
|
= note: this error originates in the macro `$crate::metadata` which comes from the expansion of the macro `metadata` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
@ -3,21 +3,10 @@
|
|||||||
use std::{env, fs, path::Path};
|
use std::{env, fs, path::Path};
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::Span;
|
||||||
use quote::quote;
|
|
||||||
use serde::{de::IgnoredAny, Deserialize};
|
use serde::{de::IgnoredAny, Deserialize};
|
||||||
use syn::{
|
|
||||||
braced,
|
|
||||||
parse::{Parse, ParseStream},
|
|
||||||
Attribute, Field, Token, Type,
|
|
||||||
};
|
|
||||||
|
|
||||||
use self::{api_metadata::Metadata, api_request::Request, api_response::Response};
|
|
||||||
use crate::util::import_ruma_common;
|
|
||||||
|
|
||||||
mod api_metadata;
|
mod api_metadata;
|
||||||
mod api_request;
|
|
||||||
mod api_response;
|
|
||||||
mod attribute;
|
mod attribute;
|
||||||
mod auth_scheme;
|
mod auth_scheme;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
@ -26,137 +15,11 @@ mod util;
|
|||||||
mod version;
|
mod version;
|
||||||
|
|
||||||
mod kw {
|
mod kw {
|
||||||
use syn::custom_keyword;
|
syn::custom_keyword!(error);
|
||||||
|
|
||||||
custom_keyword!(error);
|
|
||||||
custom_keyword!(request);
|
|
||||||
custom_keyword!(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of processing the `ruma_api` macro, ready for output back to source code.
|
// Returns an error with a helpful error if the crate the request or response macro is used from
|
||||||
pub struct Api {
|
// doesn't declare both a `client` and a `server` feature.
|
||||||
/// The `metadata` section of the macro.
|
|
||||||
metadata: Metadata,
|
|
||||||
|
|
||||||
/// The `request` section of the macro.
|
|
||||||
request: Option<Request>,
|
|
||||||
|
|
||||||
/// The `response` section of the macro.
|
|
||||||
response: Option<Response>,
|
|
||||||
|
|
||||||
/// The `error` section of the macro.
|
|
||||||
error_ty: Option<Type>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Api {
|
|
||||||
pub fn expand_all(self) -> TokenStream {
|
|
||||||
let ruma_common = import_ruma_common();
|
|
||||||
|
|
||||||
let metadata = &self.metadata;
|
|
||||||
let description = &metadata.description;
|
|
||||||
let method = &metadata.method;
|
|
||||||
let name = &metadata.name;
|
|
||||||
let rate_limited = &self.metadata.rate_limited;
|
|
||||||
let authentication = &self.metadata.authentication;
|
|
||||||
let history = &self.metadata.history;
|
|
||||||
|
|
||||||
let error_ty = self.error_ty.map_or_else(
|
|
||||||
|| quote! { #ruma_common::api::error::MatrixError },
|
|
||||||
|err_ty| quote! { #err_ty },
|
|
||||||
);
|
|
||||||
|
|
||||||
let request = self.request.map(|req| req.expand(metadata, &error_ty, &ruma_common));
|
|
||||||
let response = self.response.map(|res| res.expand(metadata, &error_ty, &ruma_common));
|
|
||||||
|
|
||||||
let metadata_doc = format!("Metadata for the `{}` API endpoint.", name.value());
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
// For some reason inlining the expression causes issues with macro parsing
|
|
||||||
const _RUMA_API_VERSION_HISTORY: #ruma_common::api::VersionHistory = #history;
|
|
||||||
|
|
||||||
#[doc = #metadata_doc]
|
|
||||||
pub const METADATA: #ruma_common::api::Metadata = #ruma_common::metadata! {
|
|
||||||
description: #description,
|
|
||||||
method: #method,
|
|
||||||
name: #name,
|
|
||||||
rate_limited: #rate_limited,
|
|
||||||
authentication: #authentication,
|
|
||||||
history: _RUMA_API_VERSION_HISTORY,
|
|
||||||
};
|
|
||||||
|
|
||||||
#request
|
|
||||||
#response
|
|
||||||
|
|
||||||
#[cfg(not(any(feature = "client", feature = "server")))]
|
|
||||||
type _SilenceUnusedError = #error_ty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Api {
|
|
||||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
|
||||||
let metadata: Metadata = input.parse()?;
|
|
||||||
|
|
||||||
let req_attrs = input.call(Attribute::parse_outer)?;
|
|
||||||
let (request, attributes) = if input.peek(kw::request) {
|
|
||||||
let request = parse_request(input, req_attrs)?;
|
|
||||||
let after_req_attrs = input.call(Attribute::parse_outer)?;
|
|
||||||
|
|
||||||
(Some(request), after_req_attrs)
|
|
||||||
} else {
|
|
||||||
// There was no `request` field so the attributes are for `response`
|
|
||||||
(None, req_attrs)
|
|
||||||
};
|
|
||||||
|
|
||||||
let response = if input.peek(kw::response) {
|
|
||||||
Some(parse_response(input, attributes)?)
|
|
||||||
} else if !attributes.is_empty() {
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
&attributes[0],
|
|
||||||
"attributes are not supported on the error type",
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let error_ty = input
|
|
||||||
.peek(kw::error)
|
|
||||||
.then(|| {
|
|
||||||
let _: kw::error = input.parse()?;
|
|
||||||
let _: Token![:] = input.parse()?;
|
|
||||||
|
|
||||||
input.parse()
|
|
||||||
})
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
Ok(Self { metadata, request, response, error_ty })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_request(input: ParseStream<'_>, attributes: Vec<Attribute>) -> syn::Result<Request> {
|
|
||||||
let request_kw: kw::request = input.parse()?;
|
|
||||||
let _: Token![:] = input.parse()?;
|
|
||||||
let fields;
|
|
||||||
braced!(fields in input);
|
|
||||||
|
|
||||||
let fields = fields.parse_terminated::<_, Token![,]>(Field::parse_named)?;
|
|
||||||
|
|
||||||
Ok(Request { request_kw, attributes, fields })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_response(input: ParseStream<'_>, attributes: Vec<Attribute>) -> syn::Result<Response> {
|
|
||||||
let response_kw: kw::response = input.parse()?;
|
|
||||||
let _: Token![:] = input.parse()?;
|
|
||||||
let fields;
|
|
||||||
braced!(fields in input);
|
|
||||||
|
|
||||||
let fields = fields.parse_terminated::<_, Token![,]>(Field::parse_named)?;
|
|
||||||
|
|
||||||
Ok(Response { attributes, fields, response_kw })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns an error with a helpful error if the crate `ruma_api!` is used from doesn't declare both
|
|
||||||
// a `client` and a `server` feature.
|
|
||||||
fn ensure_feature_presence() -> Option<&'static syn::Error> {
|
fn ensure_feature_presence() -> Option<&'static syn::Error> {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct CargoToml {
|
struct CargoToml {
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
//! Details of the `request` section of the procedural macro.
|
|
||||||
|
|
||||||
use std::collections::btree_map::{BTreeMap, Entry};
|
|
||||||
|
|
||||||
use proc_macro2::TokenStream;
|
|
||||||
use quote::quote;
|
|
||||||
use syn::{
|
|
||||||
parse_quote, punctuated::Punctuated, spanned::Spanned, visit::Visit, Attribute, Field, Ident,
|
|
||||||
Lifetime, Token,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
api_metadata::Metadata,
|
|
||||||
kw,
|
|
||||||
util::{all_cfgs, extract_cfg},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The result of processing the `request` section of the macro.
|
|
||||||
pub(crate) struct Request {
|
|
||||||
/// The `request` keyword
|
|
||||||
pub(super) request_kw: kw::request,
|
|
||||||
|
|
||||||
/// The attributes that will be applied to the struct definition.
|
|
||||||
pub(super) attributes: Vec<Attribute>,
|
|
||||||
|
|
||||||
/// The fields of the request.
|
|
||||||
pub(super) fields: Punctuated<Field, Token![,]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Request {
|
|
||||||
/// The combination of every fields unique lifetime annotation.
|
|
||||||
fn all_lifetimes(&self) -> BTreeMap<Lifetime, Option<Attribute>> {
|
|
||||||
let mut lifetimes = BTreeMap::new();
|
|
||||||
|
|
||||||
struct Visitor<'lt> {
|
|
||||||
field_cfg: Option<Attribute>,
|
|
||||||
lifetimes: &'lt mut BTreeMap<Lifetime, Option<Attribute>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'ast> Visit<'ast> for Visitor<'_> {
|
|
||||||
fn visit_lifetime(&mut self, lt: &'ast Lifetime) {
|
|
||||||
match self.lifetimes.entry(lt.clone()) {
|
|
||||||
Entry::Vacant(v) => {
|
|
||||||
v.insert(self.field_cfg.clone());
|
|
||||||
}
|
|
||||||
Entry::Occupied(mut o) => {
|
|
||||||
let lifetime_cfg = o.get_mut();
|
|
||||||
|
|
||||||
// If at least one field uses this lifetime and has no cfg attribute, we
|
|
||||||
// don't need a cfg attribute for the lifetime either.
|
|
||||||
*lifetime_cfg = Option::zip(lifetime_cfg.as_ref(), self.field_cfg.as_ref())
|
|
||||||
.map(|(a, b)| {
|
|
||||||
let expr_a = extract_cfg(a);
|
|
||||||
let expr_b = extract_cfg(b);
|
|
||||||
parse_quote! { #[cfg( any( #expr_a, #expr_b ) )] }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for field in &self.fields {
|
|
||||||
let field_cfg = if field.attrs.is_empty() { None } else { all_cfgs(&field.attrs) };
|
|
||||||
Visitor { lifetimes: &mut lifetimes, field_cfg }.visit_type(&field.ty);
|
|
||||||
}
|
|
||||||
|
|
||||||
lifetimes
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn expand(
|
|
||||||
&self,
|
|
||||||
metadata: &Metadata,
|
|
||||||
error_ty: &TokenStream,
|
|
||||||
ruma_common: &TokenStream,
|
|
||||||
) -> TokenStream {
|
|
||||||
let ruma_macros = quote! { #ruma_common::exports::ruma_macros };
|
|
||||||
|
|
||||||
let docs = format!(
|
|
||||||
"Data for a request to the `{}` API endpoint.\n\n{}",
|
|
||||||
metadata.name.value(),
|
|
||||||
metadata.description.value(),
|
|
||||||
);
|
|
||||||
let struct_attributes = &self.attributes;
|
|
||||||
|
|
||||||
let request_ident = Ident::new("Request", self.request_kw.span());
|
|
||||||
let lifetimes = self.all_lifetimes();
|
|
||||||
let lifetimes = lifetimes.iter().map(|(lt, attr)| quote! { #attr #lt });
|
|
||||||
let fields = &self.fields;
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#[doc = #docs]
|
|
||||||
#[#ruma_macros::request(error = #error_ty)]
|
|
||||||
#( #struct_attributes )*
|
|
||||||
pub struct #request_ident < #(#lifetimes),* > {
|
|
||||||
#fields
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
//! Details of the `response` section of the procedural macro.
|
|
||||||
|
|
||||||
use proc_macro2::TokenStream;
|
|
||||||
use quote::quote;
|
|
||||||
use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Field, Ident, Token};
|
|
||||||
|
|
||||||
use super::{api_metadata::Metadata, kw};
|
|
||||||
|
|
||||||
/// The result of processing the `response` section of the macro.
|
|
||||||
pub(crate) struct Response {
|
|
||||||
/// The `response` keyword
|
|
||||||
pub(super) response_kw: kw::response,
|
|
||||||
|
|
||||||
/// The attributes that will be applied to the struct definition.
|
|
||||||
pub attributes: Vec<Attribute>,
|
|
||||||
|
|
||||||
/// The fields of the response.
|
|
||||||
pub fields: Punctuated<Field, Token![,]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Response {
|
|
||||||
pub(super) fn expand(
|
|
||||||
&self,
|
|
||||||
metadata: &Metadata,
|
|
||||||
error_ty: &TokenStream,
|
|
||||||
ruma_common: &TokenStream,
|
|
||||||
) -> TokenStream {
|
|
||||||
let ruma_macros = quote! { #ruma_common::exports::ruma_macros };
|
|
||||||
|
|
||||||
let docs =
|
|
||||||
format!("Data in the response from the `{}` API endpoint.", metadata.name.value());
|
|
||||||
let struct_attributes = &self.attributes;
|
|
||||||
|
|
||||||
let response_ident = Ident::new("Response", self.response_kw.span());
|
|
||||||
let fields = &self.fields;
|
|
||||||
quote! {
|
|
||||||
#[doc = #docs]
|
|
||||||
#[#ruma_macros::response(error = #error_ty)]
|
|
||||||
#( #struct_attributes )*
|
|
||||||
pub struct #response_ident {
|
|
||||||
#fields
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ use std::collections::BTreeSet;
|
|||||||
|
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
use syn::{parse_quote, visit::Visit, Attribute, Lifetime, NestedMeta, Type};
|
use syn::{visit::Visit, Lifetime, Type};
|
||||||
|
|
||||||
pub fn map_option_literal<T: ToTokens>(ver: &Option<T>) -> TokenStream {
|
pub fn map_option_literal<T: ToTokens>(ver: &Option<T>) -> TokenStream {
|
||||||
match ver {
|
match ver {
|
||||||
@ -27,30 +27,3 @@ pub fn collect_lifetime_idents(lifetimes: &mut BTreeSet<Lifetime>, ty: &Type) {
|
|||||||
|
|
||||||
Visitor(lifetimes).visit_type(ty);
|
Visitor(lifetimes).visit_type(ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn all_cfgs_expr(cfgs: &[Attribute]) -> Option<TokenStream> {
|
|
||||||
let sub_cfgs: Vec<_> = cfgs.iter().filter_map(extract_cfg).collect();
|
|
||||||
(!sub_cfgs.is_empty()).then(|| quote! { all( #(#sub_cfgs),* ) })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn all_cfgs(cfgs: &[Attribute]) -> Option<Attribute> {
|
|
||||||
let cfg_expr = all_cfgs_expr(cfgs)?;
|
|
||||||
Some(parse_quote! { #[cfg( #cfg_expr )] })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_cfg(attr: &Attribute) -> Option<NestedMeta> {
|
|
||||||
if !attr.path.is_ident("cfg") {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let meta = attr.parse_meta().expect("cfg attribute can be parsed to syn::Meta");
|
|
||||||
let mut list = match meta {
|
|
||||||
syn::Meta::List(l) => l,
|
|
||||||
_ => panic!("unexpected cfg syntax"),
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(list.path.is_ident("cfg"), "expected cfg attributes only");
|
|
||||||
assert_eq!(list.nested.len(), 1, "expected one item inside cfg()");
|
|
||||||
|
|
||||||
Some(list.nested.pop().unwrap().into_value())
|
|
||||||
}
|
|
||||||
|
@ -28,7 +28,6 @@ use self::{
|
|||||||
api::{
|
api::{
|
||||||
request::{expand_derive_request, expand_request},
|
request::{expand_derive_request, expand_request},
|
||||||
response::{expand_derive_response, expand_response},
|
response::{expand_derive_response, expand_response},
|
||||||
Api,
|
|
||||||
},
|
},
|
||||||
events::{
|
events::{
|
||||||
event::expand_event,
|
event::expand_event,
|
||||||
@ -380,14 +379,6 @@ pub fn fake_derive_serde(_input: TokenStream) -> TokenStream {
|
|||||||
TokenStream::new()
|
TokenStream::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// > ⚠ If this is the only documentation you see, please navigate to the docs for
|
|
||||||
/// > `ruma_common::api::ruma_api`, where actual documentation can be found.
|
|
||||||
#[proc_macro]
|
|
||||||
pub fn ruma_api(input: TokenStream) -> TokenStream {
|
|
||||||
let api = parse_macro_input!(input as Api);
|
|
||||||
api.expand_all().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// > ⚠ If this is the only documentation you see, please navigate to the docs for
|
/// > ⚠ If this is the only documentation you see, please navigate to the docs for
|
||||||
/// > `ruma_common::api::request`, where actual documentation can be found.
|
/// > `ruma_common::api::request`, where actual documentation can be found.
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
@ -406,14 +397,14 @@ pub fn response(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
expand_response(attr, item).into()
|
expand_response(attr, item).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal helper taking care of the request-specific parts of `ruma_api!`.
|
/// Internal helper that the request macro delegates most of its work to.
|
||||||
#[proc_macro_derive(Request, attributes(ruma_api))]
|
#[proc_macro_derive(Request, attributes(ruma_api))]
|
||||||
pub fn derive_request(input: TokenStream) -> TokenStream {
|
pub fn derive_request(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
expand_derive_request(input).unwrap_or_else(syn::Error::into_compile_error).into()
|
expand_derive_request(input).unwrap_or_else(syn::Error::into_compile_error).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal helper taking care of the response-specific parts of `ruma_api!`.
|
/// Internal helper that the response macro delegates most of its work to.
|
||||||
#[proc_macro_derive(Response, attributes(ruma_api))]
|
#[proc_macro_derive(Response, attributes(ruma_api))]
|
||||||
pub fn derive_response(input: TokenStream) -> TokenStream {
|
pub fn derive_response(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user