api-macros: Allow ruma_api! invocation without request / response

This commit is contained in:
Jonas Platte 2021-04-05 14:49:34 +02:00
parent 95fef0b0ec
commit 345d0cf990
No known key found for this signature in database
GPG Key ID: CC154DE0E30B7C67
7 changed files with 340 additions and 227 deletions

View File

@ -19,10 +19,10 @@ pub struct Api {
metadata: Metadata, metadata: Metadata,
/// The `request` section of the macro. /// The `request` section of the macro.
request: Request, request: Option<Request>,
/// The `response` section of the macro. /// The `response` section of the macro.
response: Response, response: Option<Response>,
/// The `error` section of the macro. /// The `error` section of the macro.
error_ty: Option<Type>, error_ty: Option<Type>,
@ -32,12 +32,12 @@ pub fn expand_all(api: Api) -> syn::Result<TokenStream> {
let ruma_api = util::import_ruma_api(); let ruma_api = util::import_ruma_api();
let http = quote! { #ruma_api::exports::http }; let http = quote! { #ruma_api::exports::http };
let description = &api.metadata.description; let metadata = &api.metadata;
let method = &api.metadata.method; let description = &metadata.description;
let name = &api.metadata.name; let method = &metadata.method;
let path = &api.metadata.path; let name = &metadata.name;
let rate_limited: TokenStream = api let path = &metadata.path;
.metadata let rate_limited: TokenStream = metadata
.rate_limited .rate_limited
.iter() .iter()
.map(|r| { .map(|r| {
@ -66,8 +66,8 @@ pub fn expand_all(api: Api) -> syn::Result<TokenStream> {
let error_ty = let error_ty =
api.error_ty.map_or_else(|| quote! { #ruma_api::error::Void }, |err_ty| quote! { #err_ty }); api.error_ty.map_or_else(|| quote! { #ruma_api::error::Void }, |err_ty| quote! { #err_ty });
let request = api.request.expand(&api.metadata, &error_ty, &ruma_api); let request = api.request.map(|req| req.expand(metadata, &error_ty, &ruma_api));
let response = api.response.expand(&api.metadata, &error_ty, &ruma_api); let response = api.response.map(|res| res.expand(metadata, &error_ty, &ruma_api));
let metadata_doc = format!("Metadata for the `{}` API endpoint.", name.value()); let metadata_doc = format!("Metadata for the `{}` API endpoint.", name.value());

View File

@ -26,8 +26,27 @@ mod kw {
impl Parse for Api { impl Parse for Api {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> { fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let metadata: Metadata = input.parse()?; let metadata: Metadata = input.parse()?;
let request: Request = input.parse()?;
let response: Response = input.parse()?; let attributes = input.call(Attribute::parse_outer)?;
let (request, attributes) = if input.peek(kw::request) {
let request = parse_request(input, attributes)?;
let attributes = input.call(Attribute::parse_outer)?;
(Some(request), attributes)
} else {
(None, attributes)
};
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
};
// TODO: Use `bool::then` when MSRV >= 1.50 // TODO: Use `bool::then` when MSRV >= 1.50
let error_ty = if input.peek(kw::error) { let error_ty = if input.peek(kw::error) {
@ -39,11 +58,13 @@ impl Parse for Api {
None None
}; };
let newtype_body_field = request.newtype_body_field(); if let Some(req) = &request {
if metadata.method == "GET" && (request.has_body_fields() || newtype_body_field.is_some()) { let newtype_body_field = req.newtype_body_field();
if metadata.method == "GET" && (req.has_body_fields() || newtype_body_field.is_some()) {
let mut combined_error: Option<syn::Error> = None; let mut combined_error: Option<syn::Error> = None;
let mut add_error = |field| { let mut add_error = |field| {
let error = syn::Error::new_spanned(field, "GET endpoints can't have body fields"); let error =
syn::Error::new_spanned(field, "GET endpoints can't have body fields");
if let Some(combined_error_ref) = &mut combined_error { if let Some(combined_error_ref) = &mut combined_error {
combined_error_ref.combine(error); combined_error_ref.combine(error);
} else { } else {
@ -51,7 +72,7 @@ impl Parse for Api {
} }
}; };
for field in request.body_fields() { for field in req.body_fields() {
add_error(field); add_error(field);
} }
@ -61,14 +82,13 @@ impl Parse for Api {
return Err(combined_error.unwrap()); return Err(combined_error.unwrap());
} }
}
Ok(Self { metadata, request, response, error_ty }) Ok(Self { metadata, request, response, error_ty })
} }
} }
impl Parse for Request { fn parse_request(input: ParseStream<'_>, attributes: Vec<Attribute>) -> syn::Result<Request> {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let attributes = input.call(Attribute::parse_outer)?;
let request_kw: kw::request = input.parse()?; let request_kw: kw::request = input.parse()?;
let _: Token![:] = input.parse()?; let _: Token![:] = input.parse()?;
let fields; let fields;
@ -146,21 +166,15 @@ impl Parse for Request {
RequestFieldKind::Header => { RequestFieldKind::Header => {
collect_lifetime_idents(&mut lifetimes.header, &field.ty) collect_lifetime_idents(&mut lifetimes.header, &field.ty)
} }
RequestFieldKind::Body => { RequestFieldKind::Body => collect_lifetime_idents(&mut lifetimes.body, &field.ty),
collect_lifetime_idents(&mut lifetimes.body, &field.ty)
}
RequestFieldKind::NewtypeBody => { RequestFieldKind::NewtypeBody => {
collect_lifetime_idents(&mut lifetimes.body, &field.ty) collect_lifetime_idents(&mut lifetimes.body, &field.ty)
} }
RequestFieldKind::NewtypeRawBody => { RequestFieldKind::NewtypeRawBody => {
collect_lifetime_idents(&mut lifetimes.body, &field.ty) collect_lifetime_idents(&mut lifetimes.body, &field.ty)
} }
RequestFieldKind::Path => { RequestFieldKind::Path => collect_lifetime_idents(&mut lifetimes.path, &field.ty),
collect_lifetime_idents(&mut lifetimes.path, &field.ty) RequestFieldKind::Query => collect_lifetime_idents(&mut lifetimes.query, &field.ty),
}
RequestFieldKind::Query => {
collect_lifetime_idents(&mut lifetimes.query, &field.ty)
}
RequestFieldKind::QueryMap => { RequestFieldKind::QueryMap => {
collect_lifetime_idents(&mut lifetimes.query, &field.ty) collect_lifetime_idents(&mut lifetimes.query, &field.ty)
} }
@ -194,13 +208,10 @@ impl Parse for Request {
)); ));
} }
Ok(Self { attributes, fields, lifetimes }) Ok(Request { attributes, fields, lifetimes })
}
} }
impl Parse for Response { fn parse_response(input: ParseStream<'_>, attributes: Vec<Attribute>) -> syn::Result<Response> {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let attributes = input.call(Attribute::parse_outer)?;
let response_kw: kw::response = input.parse()?; let response_kw: kw::response = input.parse()?;
let _: Token![:] = input.parse()?; let _: Token![:] = input.parse()?;
let fields; let fields;
@ -279,8 +290,7 @@ impl Parse for Response {
)); ));
} }
Ok(Self { attributes, fields }) Ok(Response { attributes, fields })
}
} }
fn has_lifetime(ty: &Type) -> bool { fn has_lifetime(ty: &Type) -> bool {

View File

@ -5,4 +5,7 @@ fn ui() {
t.compile_fail("tests/ui/02-invalid-path.rs"); t.compile_fail("tests/ui/02-invalid-path.rs");
t.pass("tests/ui/03-move-value.rs"); t.pass("tests/ui/03-move-value.rs");
t.compile_fail("tests/ui/04-attributes.rs"); t.compile_fail("tests/ui/04-attributes.rs");
t.pass("tests/ui/05-request-only.rs");
t.pass("tests/ui/06-response-only.rs");
t.compile_fail("tests/ui/07-error-type-attribute.rs");
} }

View File

@ -0,0 +1,50 @@
use std::convert::TryFrom;
use ruma_api::{
error::{FromHttpResponseError, IntoHttpError, Void},
ruma_api,
};
use ruma_serde::Outgoing;
ruma_api! {
metadata: {
description: "Does something.",
method: POST, // An `http::Method` constant. No imports required.
name: "some_endpoint",
path: "/_matrix/some/endpoint/:baz",
rate_limited: false,
authentication: None,
}
#[derive(PartialEq)] // Make sure attributes work
request: {
// With no attribute on the field, it will be put into the body of the request.
pub foo: String,
}
}
#[derive(Outgoing)]
pub struct Response;
impl TryFrom<http::Response<Vec<u8>>> for Response {
type Error = FromHttpResponseError<Void>;
fn try_from(_: http::Response<Vec<u8>>) -> Result<Self, Self::Error> {
todo!()
}
}
impl TryFrom<Response> for http::Response<Vec<u8>> {
type Error = IntoHttpError;
fn try_from(_: Response) -> Result<Self, Self::Error> {
todo!()
}
}
fn main() {
let req1 = Request { foo: "foo".into() };
let req2 = req1.clone();
assert_eq!(req1, req2);
}

View File

@ -0,0 +1,24 @@
use ruma_api::ruma_api;
ruma_api! {
metadata: {
description: "Does something.",
method: POST, // An `http::Method` constant. No imports required.
name: "some_endpoint",
path: "/_matrix/some/endpoint/:baz",
rate_limited: false,
authentication: None,
}
#[derive(PartialEq)] // Make sure attributes work
response: {
pub flag: bool,
}
}
fn main() {
let res1 = Response { flag: false };
let res2 = res1.clone();
assert_eq!(res1, res2);
}

View File

@ -0,0 +1,21 @@
use ruma_api::ruma_api;
ruma_api! {
metadata: {
description: "Does something.",
method: POST, // An `http::Method` constant. No imports required.
name: "some_endpoint",
path: "/_matrix/some/endpoint/:baz",
rate_limited: false,
authentication: None,
}
request: {}
response: {}
#[derive(Default)]
error: ruma_api::error::Void
}
fn main() {}

View File

@ -0,0 +1,5 @@
error: unexpected token
--> $DIR/07-error-type-attribute.rs:17:5
|
17 | #[derive(Default)]
| ^