Support optional header values in request/response types

This commit is contained in:
Devin Ragotzy 2020-08-21 13:53:46 -04:00 committed by GitHub
parent 5182015b76
commit a8b4bad684
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 128 additions and 38 deletions

View File

@ -136,15 +136,17 @@ impl ToTokens for Api {
let mut header_kvs = self.request.append_header_kvs();
if requires_authentication.value {
header_kvs.push(quote! {
#ruma_api_import::exports::http::header::AUTHORIZATION,
#ruma_api_import::exports::http::header::HeaderValue::from_str(
&::std::format!(
"Bearer {}",
access_token.ok_or(
#ruma_api_import::error::IntoHttpError::NeedsAuthentication
)?
)
)?
let req_builder = req_builder.header(
#ruma_api_import::exports::http::header::AUTHORIZATION,
#ruma_api_import::exports::http::header::HeaderValue::from_str(
&::std::format!(
"Bearer {}",
access_token.ok_or(
#ruma_api_import::error::IntoHttpError::NeedsAuthentication
)?
)
)?
);
});
}
@ -274,13 +276,14 @@ impl ToTokens for Api {
#[allow(unused_variables)]
fn try_from(response: Response) -> ::std::result::Result<Self, Self::Error> {
let response = #ruma_api_import::exports::http::Response::builder()
.header(#ruma_api_import::exports::http::header::CONTENT_TYPE, "application/json")
#serialize_response_headers
.body(#body)
// Since we require header names to come from the `http::header` module,
// this cannot fail.
.unwrap();
let mut resp_builder = #ruma_api_import::exports::http::Response::builder()
.header(#ruma_api_import::exports::http::header::CONTENT_TYPE, "application/json");
#serialize_response_headers
// Since we require header names to come from the `http::header` module,
// this cannot fail.
let response = resp_builder.body(#body).unwrap();
Ok(response)
}
}
@ -341,16 +344,18 @@ impl ToTokens for Api {
> {
let metadata = <Self as #ruma_api_import::OutgoingRequest>::METADATA;
let http_request = #ruma_api_import::exports::http::Request::builder()
let mut req_builder = #ruma_api_import::exports::http::Request::builder()
.method(#ruma_api_import::exports::http::Method::#method)
.uri(::std::format!(
"{}{}{}",
base_url.strip_suffix("/").unwrap_or(base_url),
#request_path_string,
#request_query_string,
))
#( .header(#header_kvs) )*
.body(#request_body)?;
));
#( #header_kvs )*
let http_request = req_builder.body(#request_body)?;
Ok(http_request)
}

View File

@ -49,9 +49,25 @@ impl Request {
let field_name = &field.ident;
quote! {
#import_path::exports::http::header::#header_name,
#import_path::exports::http::header::HeaderValue::from_str(self.#field_name.as_ref())?
match &field.ty {
syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. })
if segments.last().unwrap().ident == "Option" =>
{
quote! {
if let Some(header_val) = self.#field_name.as_ref() {
req_builder = req_builder.header(
#import_path::exports::http::header::#header_name,
#import_path::exports::http::header::HeaderValue::from_str(header_val)?,
);
}
}
}
_ => quote! {
req_builder = req_builder.header(
#import_path::exports::http::header::#header_name,
#import_path::exports::http::header::HeaderValue::from_str(self.#field_name.as_ref())?,
);
},
}
}).collect()
}
@ -68,13 +84,15 @@ impl Request {
let field_name = &field.ident;
let header_name_string = header_name.to_string();
quote! {
#field_name: match headers
.get(#import_path::exports::http::header::#header_name)
.and_then(|v| v.to_str().ok()) // FIXME: Should have a distinct error message
let (some_case, none_case) = match &field.ty {
syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. })
if segments.last().unwrap().ident == "Option" =>
{
Some(header) => header.to_owned(),
None => {
(quote! { Some(header.to_owned()) }, quote! { None })
}
_ => (
quote! { header.to_owned() },
quote! {{
use #import_path::exports::serde::de::Error as _;
// FIXME: Not a missing json field, a missing header!
@ -85,7 +103,17 @@ impl Request {
request,
)
.into());
}
}},
),
};
quote! {
#field_name: match headers
.get(#import_path::exports::http::header::#header_name)
.and_then(|v| v.to_str().ok()) // FIXME: Should have a distinct error message
{
Some(header) => #some_case,
None => #none_case,
}
}
});

View File

@ -58,15 +58,30 @@ impl Response {
}
}
ResponseField::Header(_, header_name) => {
quote_spanned! {span=>
#field_name: #import_path::try_deserialize!(
response,
headers.remove(#import_path::exports::http::header::#header_name)
.expect("response missing expected header")
.to_str()
let optional_header = match &field.ty {
syn::Type::Path(syn::TypePath {
path: syn::Path { segments, .. }, ..
}) if segments.last().unwrap().ident == "Option" => {
quote! {
#field_name: #import_path::try_deserialize!(
response,
headers.remove(#import_path::exports::http::header::#header_name)
.map(|h| h.to_str().map(|s| s.to_owned()))
.transpose()
)
}
}
_ => quote! {
#field_name: #import_path::try_deserialize!(
response,
headers.remove(#import_path::exports::http::header::#header_name)
.expect("response missing expected header")
.to_str()
)
.to_owned()
}
},
};
quote_spanned! {span=> #optional_header }
}
ResponseField::NewtypeBody(_) => {
quote_spanned! {span=>
@ -102,8 +117,29 @@ impl Response {
field.ident.as_ref().expect("expected field to have an identifier");
let span = field.span();
let optional_header = match &field.ty {
syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. })
if segments.last().unwrap().ident == "Option" =>
{
quote! {
if let Some(header) = response.#field_name {
resp_builder = resp_builder.header(
#import_path::exports::http::header::#header_name,
header,
);
}
}
}
_ => quote! {
resp_builder = resp_builder.header(
#import_path::exports::http::header::#header_name,
response.#field_name,
);
},
};
Some(quote_spanned! {span=>
.header(#import_path::exports::http::header::#header_name, response.#field_name)
#optional_header
})
} else {
None

View File

@ -0,0 +1,21 @@
use ruma_api::ruma_api;
ruma_api! {
metadata: {
description: "Does something.",
method: GET,
name: "no_fields",
path: "/_matrix/my/endpoint",
rate_limited: false,
requires_authentication: false,
}
request: {
#[ruma_api(header = LOCATION)]
location: Option<String>,
}
response: {
#[ruma_api(header = LOCATION)]
stuff: Option<String>,
}
}