Add query_map attribute to ruma_api

This commit is contained in:
Wim de With 2019-11-20 14:31:38 +01:00
parent 71372ae910
commit f0e724d391
3 changed files with 116 additions and 4 deletions

View File

@ -135,7 +135,41 @@ impl ToTokens for Api {
} }
}; };
let set_request_query = if self.request.has_query_fields() { let set_request_query = if let Some(field) = self.request.query_map_field() {
let field_name = field.ident.as_ref().expect("expected field to have identifier");
let field_type = &field.ty;
quote! {
// This function exists so that the compiler will throw an
// error when the type of the field with the query_map
// attribute doesn't implement IntoIterator<Item = (String, String)>
//
// This is necessary because the serde_urlencoded::to_string
// call will result in a runtime error when the type cannot be
// encoded as a list key-value pairs (?key1=value1&key2=value2)
//
// By asserting that it implements the iterator trait, we can
// ensure that it won't fail.
fn assert_trait_impl<T>()
where
T: std::iter::IntoIterator<Item = (std::string::String, std::string::String)>,
{}
assert_trait_impl::<#field_type>();
let request_query = RequestQuery(request.#field_name);
let query_str = ruma_api::exports::serde_urlencoded::to_string(
request_query,
)?;
let query_opt: Option<&str> = if query_str.is_empty() {
None
} else {
Some(&query_str)
};
url.set_query(query_opt);
}
} else if self.request.has_query_fields() {
let request_query_init_fields = self.request.request_query_init_fields(); let request_query_init_fields = self.request.request_query_init_fields();
quote! { quote! {

View File

@ -82,6 +82,11 @@ impl Request {
self.fields.iter().find_map(RequestField::as_newtype_body_field) self.fields.iter().find_map(RequestField::as_newtype_body_field)
} }
/// Returns the query map field.
pub fn query_map_field(&self) -> Option<&Field> {
self.fields.iter().find_map(RequestField::as_query_map_field)
}
/// Produces code for a struct initializer for body fields on a variable named `request`. /// Produces code for a struct initializer for body fields on a variable named `request`.
pub fn request_body_init_fields(&self) -> TokenStream { pub fn request_body_init_fields(&self) -> TokenStream {
self.struct_init_fields(RequestFieldKind::Body, quote!(request)) self.struct_init_fields(RequestFieldKind::Body, quote!(request))
@ -127,6 +132,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 fields = raw let fields = raw
.fields .fields
@ -172,10 +178,26 @@ impl TryFrom<RawRequest> for Request {
} }
"path" => RequestFieldKind::Path, "path" => RequestFieldKind::Path,
"query" => RequestFieldKind::Query, "query" => RequestFieldKind::Query,
"query_map" => {
if let Some(f) = &query_map_field {
let mut error = syn::Error::new_spanned(
field,
"There can only be one query map field",
);
error.combine(syn::Error::new_spanned(
f,
"Previous query map field",
));
return Err(error);
}
query_map_field = Some(field.clone());
RequestFieldKind::QueryMap
},
_ => { _ => {
return Err(syn::Error::new_spanned( return Err(syn::Error::new_spanned(
ident, ident,
"Invalid #[ruma_api] argument, expected one of `body`, `path`, `query`", "Invalid #[ruma_api] argument, expected one of `body`, `path`, `query`, `query_map`",
)); ));
} }
} }
@ -210,6 +232,14 @@ impl TryFrom<RawRequest> for Request {
)); ));
} }
if query_map_field.is_some() && fields.iter().any(|f| f.is_query()) {
return Err(syn::Error::new_spanned(
// TODO: raw,
raw.request_kw,
"Can't have both a query map field and regular query fields",
));
}
Ok(Self { fields }) Ok(Self { fields })
} }
} }
@ -275,7 +305,20 @@ impl ToTokens for Request {
TokenStream::new() TokenStream::new()
}; };
let request_query_struct = if self.has_query_fields() { let request_query_struct = if let Some(field) = self.query_map_field() {
let ty = &field.ty;
let span = field.span();
quote_spanned! {span=>
/// Data in the request's query string.
#[derive(
Debug,
ruma_api::exports::serde::Deserialize,
ruma_api::exports::serde::Serialize,
)]
struct RequestQuery(#ty);
}
} 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);
quote! { quote! {
@ -317,6 +360,8 @@ pub enum RequestField {
Path(Field), Path(Field),
/// Data that appears in the query string. /// Data that appears in the query string.
Query(Field), Query(Field),
/// Data that appears in the query string as dynamic key-value pairs.
QueryMap(Field),
} }
impl RequestField { impl RequestField {
@ -330,6 +375,7 @@ impl RequestField {
RequestFieldKind::NewtypeBody => RequestField::NewtypeBody(field), RequestFieldKind::NewtypeBody => RequestField::NewtypeBody(field),
RequestFieldKind::Path => RequestField::Path(field), RequestFieldKind::Path => RequestField::Path(field),
RequestFieldKind::Query => RequestField::Query(field), RequestFieldKind::Query => RequestField::Query(field),
RequestFieldKind::QueryMap => RequestField::QueryMap(field),
} }
} }
@ -341,6 +387,7 @@ impl RequestField {
RequestField::NewtypeBody(..) => RequestFieldKind::NewtypeBody, RequestField::NewtypeBody(..) => RequestFieldKind::NewtypeBody,
RequestField::Path(..) => RequestFieldKind::Path, RequestField::Path(..) => RequestFieldKind::Path,
RequestField::Query(..) => RequestFieldKind::Query, RequestField::Query(..) => RequestFieldKind::Query,
RequestField::QueryMap(..) => RequestFieldKind::QueryMap,
} }
} }
@ -384,6 +431,11 @@ impl RequestField {
self.field_of_kind(RequestFieldKind::Query) self.field_of_kind(RequestFieldKind::Query)
} }
/// Return the contained field if this request field is a query map kind.
fn as_query_map_field(&self) -> Option<&Field> {
self.field_of_kind(RequestFieldKind::QueryMap)
}
/// Gets the inner `Field` value. /// Gets the inner `Field` value.
fn field(&self) -> &Field { fn field(&self) -> &Field {
match self { match self {
@ -391,7 +443,8 @@ impl RequestField {
| RequestField::Header(field, _) | RequestField::Header(field, _)
| RequestField::NewtypeBody(field) | RequestField::NewtypeBody(field)
| RequestField::Path(field) | RequestField::Path(field)
| RequestField::Query(field) => field, | RequestField::Query(field)
| RequestField::QueryMap(field) => field,
} }
} }
@ -418,4 +471,6 @@ enum RequestFieldKind {
Path, Path,
/// See the similarly named variant of `RequestField`. /// See the similarly named variant of `RequestField`.
Query, Query,
/// See the similarly named variant of `RequestField`.
QueryMap,
} }

View File

@ -69,3 +69,26 @@ pub mod newtype_body_endpoint {
} }
} }
} }
pub mod query_map_endpoint {
use ruma_api_macros::ruma_api;
ruma_api! {
metadata {
description: "Does something.",
method: GET,
name: "newtype_body_endpoint",
path: "/_matrix/some/query/map/endpoint",
rate_limited: false,
requires_authentication: false,
}
request {
#[ruma_api(query_map)]
pub fields: Vec<(String, String)>,
}
response {
}
}
}