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(); 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_import::exports::http::header::AUTHORIZATION, let req_builder = req_builder.header(
#ruma_api_import::exports::http::header::HeaderValue::from_str( #ruma_api_import::exports::http::header::AUTHORIZATION,
&::std::format!( #ruma_api_import::exports::http::header::HeaderValue::from_str(
"Bearer {}", &::std::format!(
access_token.ok_or( "Bearer {}",
#ruma_api_import::error::IntoHttpError::NeedsAuthentication access_token.ok_or(
)? #ruma_api_import::error::IntoHttpError::NeedsAuthentication
) )?
)? )
)?
);
}); });
} }
@ -274,13 +276,14 @@ impl ToTokens for Api {
#[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_import::exports::http::Response::builder() let mut resp_builder = #ruma_api_import::exports::http::Response::builder()
.header(#ruma_api_import::exports::http::header::CONTENT_TYPE, "application/json") .header(#ruma_api_import::exports::http::header::CONTENT_TYPE, "application/json");
#serialize_response_headers
.body(#body) #serialize_response_headers
// Since we require header names to come from the `http::header` module,
// this cannot fail. // Since we require header names to come from the `http::header` module,
.unwrap(); // this cannot fail.
let response = resp_builder.body(#body).unwrap();
Ok(response) Ok(response)
} }
} }
@ -341,16 +344,18 @@ impl ToTokens for Api {
> { > {
let metadata = <Self as #ruma_api_import::OutgoingRequest>::METADATA; 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) .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),
#request_path_string, #request_path_string,
#request_query_string, #request_query_string,
)) ));
#( .header(#header_kvs) )*
.body(#request_body)?; #( #header_kvs )*
let http_request = req_builder.body(#request_body)?;
Ok(http_request) Ok(http_request)
} }

View File

@ -49,9 +49,25 @@ impl Request {
let field_name = &field.ident; let field_name = &field.ident;
quote! { match &field.ty {
#import_path::exports::http::header::#header_name, syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. })
#import_path::exports::http::header::HeaderValue::from_str(self.#field_name.as_ref())? 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() }).collect()
} }
@ -68,13 +84,15 @@ impl Request {
let field_name = &field.ident; let field_name = &field.ident;
let header_name_string = header_name.to_string(); let header_name_string = header_name.to_string();
quote! { let (some_case, none_case) = match &field.ty {
#field_name: match headers syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. })
.get(#import_path::exports::http::header::#header_name) if segments.last().unwrap().ident == "Option" =>
.and_then(|v| v.to_str().ok()) // FIXME: Should have a distinct error message
{ {
Some(header) => header.to_owned(), (quote! { Some(header.to_owned()) }, quote! { None })
None => { }
_ => (
quote! { header.to_owned() },
quote! {{
use #import_path::exports::serde::de::Error as _; use #import_path::exports::serde::de::Error as _;
// FIXME: Not a missing json field, a missing header! // FIXME: Not a missing json field, a missing header!
@ -85,7 +103,17 @@ impl Request {
request, request,
) )
.into()); .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) => { ResponseField::Header(_, header_name) => {
quote_spanned! {span=> let optional_header = match &field.ty {
#field_name: #import_path::try_deserialize!( syn::Type::Path(syn::TypePath {
response, path: syn::Path { segments, .. }, ..
headers.remove(#import_path::exports::http::header::#header_name) }) if segments.last().unwrap().ident == "Option" => {
.expect("response missing expected header") quote! {
.to_str() #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() .to_owned()
} },
};
quote_spanned! {span=> #optional_header }
} }
ResponseField::NewtypeBody(_) => { ResponseField::NewtypeBody(_) => {
quote_spanned! {span=> quote_spanned! {span=>
@ -102,8 +117,29 @@ impl Response {
field.ident.as_ref().expect("expected field to have an identifier"); field.ident.as_ref().expect("expected field to have an identifier");
let span = field.span(); 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=> Some(quote_spanned! {span=>
.header(#import_path::exports::http::header::#header_name, response.#field_name) #optional_header
}) })
} else { } else {
None 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>,
}
}