Merge pull request #20 from ruma/spans
Use spans to improve error reporting
This commit is contained in:
commit
02bcb5f038
@ -39,7 +39,7 @@ pub mod some_endpoint {
|
|||||||
pub foo: String,
|
pub foo: String,
|
||||||
|
|
||||||
// This value will be put into the "Content-Type" HTTP header.
|
// This value will be put into the "Content-Type" HTTP header.
|
||||||
#[ruma_api(header = "CONTENT_TYPE")]
|
#[ruma_api(header = CONTENT_TYPE)]
|
||||||
pub content_type: String
|
pub content_type: String
|
||||||
|
|
||||||
// This value will be put into the query string of the request's URL.
|
// This value will be put into the query string of the request's URL.
|
||||||
@ -54,7 +54,7 @@ pub mod some_endpoint {
|
|||||||
|
|
||||||
response {
|
response {
|
||||||
// This value will be extracted from the "Content-Type" HTTP header.
|
// This value will be extracted from the "Content-Type" HTTP header.
|
||||||
#[ruma_api(header = "CONTENT_TYPE")]
|
#[ruma_api(header = CONTENT_TYPE)]
|
||||||
pub content_type: String
|
pub content_type: String
|
||||||
|
|
||||||
// With no attribute on the field, it will be extracted from the body of the response.
|
// With no attribute on the field, it will be extracted from the body of the response.
|
||||||
|
63
src/api/attribute.rs
Normal file
63
src/api/attribute.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
//! Details of the `#[ruma_api(...)]` attributes.
|
||||||
|
|
||||||
|
use syn::{
|
||||||
|
parenthesized,
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
Ident, Token,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Like syn::Meta, but only parses ruma_api attributes
|
||||||
|
pub enum Meta {
|
||||||
|
/// A single word, like `query` in `#[ruma_api(query)]`
|
||||||
|
Word(Ident),
|
||||||
|
/// A name-value pair, like `header = CONTENT_TYPE` in `#[ruma_api(header = CONTENT_TYPE)]`
|
||||||
|
NameValue(MetaNameValue),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Meta {
|
||||||
|
/// Check if the given attribute is a ruma_api attribute. If it is, parse it, if not, return
|
||||||
|
/// it unchanged. Panics if the argument is an invalid ruma_api attribute.
|
||||||
|
pub fn from_attribute(attr: syn::Attribute) -> Result<Self, syn::Attribute> {
|
||||||
|
match &attr.path {
|
||||||
|
syn::Path {
|
||||||
|
leading_colon: None,
|
||||||
|
segments,
|
||||||
|
} => {
|
||||||
|
if segments.len() == 1 && segments[0].ident == "ruma_api" {
|
||||||
|
Ok(syn::parse2(attr.tts)
|
||||||
|
.expect("ruma_api! could not parse request field attributes"))
|
||||||
|
} else {
|
||||||
|
Err(attr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(attr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like syn::MetaNameValue, but expects an identifier as the value. Also, we don't care about the
|
||||||
|
/// the span of the equals sign, so we don't have the `eq_token` field from syn::MetaNameValue.
|
||||||
|
pub struct MetaNameValue {
|
||||||
|
/// The part left of the equals sign
|
||||||
|
pub name: Ident,
|
||||||
|
/// The part right of the equals sign
|
||||||
|
pub value: Ident,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Meta {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let content;
|
||||||
|
let _ = parenthesized!(content in input);
|
||||||
|
let ident = content.parse()?;
|
||||||
|
|
||||||
|
if content.peek(Token![=]) {
|
||||||
|
let _ = content.parse::<Token![=]>();
|
||||||
|
Ok(Meta::NameValue(MetaNameValue {
|
||||||
|
name: ident,
|
||||||
|
value: content.parse()?,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(Meta::Word(ident))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,22 @@
|
|||||||
//! Details of the `metadata` section of the procedural macro.
|
//! Details of the `metadata` section of the procedural macro.
|
||||||
|
|
||||||
use syn::{punctuated::Pair, Expr, FieldValue, Lit, Member};
|
use proc_macro2::Ident;
|
||||||
|
use syn::{Expr, ExprLit, FieldValue, Lit, LitBool, LitStr, Member};
|
||||||
|
|
||||||
/// The result of processing the `metadata` section of the macro.
|
/// The result of processing the `metadata` section of the macro.
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
/// The description field.
|
/// The description field.
|
||||||
pub description: String,
|
pub description: LitStr,
|
||||||
/// The method field.
|
/// The method field.
|
||||||
pub method: String,
|
pub method: Ident,
|
||||||
/// The name field.
|
/// The name field.
|
||||||
pub name: String,
|
pub name: LitStr,
|
||||||
/// The path field.
|
/// The path field.
|
||||||
pub path: String,
|
pub path: LitStr,
|
||||||
/// The rate_limited field.
|
/// The rate_limited field.
|
||||||
pub rate_limited: bool,
|
pub rate_limited: LitBool,
|
||||||
/// The description field.
|
/// The description field.
|
||||||
pub requires_authentication: bool,
|
pub requires_authentication: LitBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Vec<FieldValue>> for Metadata {
|
impl From<Vec<FieldValue>> for Metadata {
|
||||||
@ -35,15 +36,13 @@ impl From<Vec<FieldValue>> for Metadata {
|
|||||||
|
|
||||||
match &identifier.to_string()[..] {
|
match &identifier.to_string()[..] {
|
||||||
"description" => {
|
"description" => {
|
||||||
let expr_lit = match field_value.expr {
|
let literal = match field_value.expr {
|
||||||
Expr::Lit(expr_lit) => expr_lit,
|
Expr::Lit(ExprLit {
|
||||||
_ => panic!("expected Expr::Lit"),
|
lit: Lit::Str(s), ..
|
||||||
|
}) => s,
|
||||||
|
_ => panic!("expected string literal"),
|
||||||
};
|
};
|
||||||
let lit_str = match expr_lit.lit {
|
description = Some(literal);
|
||||||
Lit::Str(lit_str) => lit_str,
|
|
||||||
_ => panic!("expected Lit::Str"),
|
|
||||||
};
|
|
||||||
description = Some(lit_str.value());
|
|
||||||
}
|
}
|
||||||
"method" => {
|
"method" => {
|
||||||
let expr_path = match field_value.expr {
|
let expr_path = match field_value.expr {
|
||||||
@ -51,60 +50,49 @@ impl From<Vec<FieldValue>> for Metadata {
|
|||||||
_ => panic!("expected Expr::Path"),
|
_ => panic!("expected Expr::Path"),
|
||||||
};
|
};
|
||||||
let path = expr_path.path;
|
let path = expr_path.path;
|
||||||
let segments = path.segments;
|
let mut segments = path.segments.iter();
|
||||||
if segments.len() != 1 {
|
let method_name = segments.next().expect("expected non-empty path");
|
||||||
panic!("ruma_api! expects a one component path for `metadata` `method`");
|
assert!(
|
||||||
}
|
segments.next().is_none(),
|
||||||
let pair = segments.first().unwrap(); // safe because we just checked
|
"ruma_api! expects a one-component path for `metadata` `method`"
|
||||||
let method_name = match pair {
|
);
|
||||||
Pair::End(method_name) => method_name,
|
method = Some(method_name.ident.clone());
|
||||||
_ => panic!("expected Pair::End"),
|
|
||||||
};
|
|
||||||
method = Some(method_name.ident.to_string());
|
|
||||||
}
|
}
|
||||||
"name" => {
|
"name" => {
|
||||||
let expr_lit = match field_value.expr {
|
let literal = match field_value.expr {
|
||||||
Expr::Lit(expr_lit) => expr_lit,
|
Expr::Lit(ExprLit {
|
||||||
_ => panic!("expected Expr::Lit"),
|
lit: Lit::Str(s), ..
|
||||||
|
}) => s,
|
||||||
|
_ => panic!("expected string literal"),
|
||||||
};
|
};
|
||||||
let lit_str = match expr_lit.lit {
|
name = Some(literal);
|
||||||
Lit::Str(lit_str) => lit_str,
|
|
||||||
_ => panic!("expected Lit::Str"),
|
|
||||||
};
|
|
||||||
name = Some(lit_str.value());
|
|
||||||
}
|
}
|
||||||
"path" => {
|
"path" => {
|
||||||
let expr_lit = match field_value.expr {
|
let literal = match field_value.expr {
|
||||||
Expr::Lit(expr_lit) => expr_lit,
|
Expr::Lit(ExprLit {
|
||||||
_ => panic!("expected Expr::Lit"),
|
lit: Lit::Str(s), ..
|
||||||
|
}) => s,
|
||||||
|
_ => panic!("expected string literal"),
|
||||||
};
|
};
|
||||||
let lit_str = match expr_lit.lit {
|
path = Some(literal);
|
||||||
Lit::Str(lit_str) => lit_str,
|
|
||||||
_ => panic!("expected Lit::Str"),
|
|
||||||
};
|
|
||||||
path = Some(lit_str.value());
|
|
||||||
}
|
}
|
||||||
"rate_limited" => {
|
"rate_limited" => {
|
||||||
let expr_lit = match field_value.expr {
|
let literal = match field_value.expr {
|
||||||
Expr::Lit(expr_lit) => expr_lit,
|
Expr::Lit(ExprLit {
|
||||||
|
lit: Lit::Bool(b), ..
|
||||||
|
}) => b,
|
||||||
_ => panic!("expected Expr::Lit"),
|
_ => panic!("expected Expr::Lit"),
|
||||||
};
|
};
|
||||||
let lit_bool = match expr_lit.lit {
|
rate_limited = Some(literal)
|
||||||
Lit::Bool(lit_bool) => lit_bool,
|
|
||||||
_ => panic!("expected Lit::Bool"),
|
|
||||||
};
|
|
||||||
rate_limited = Some(lit_bool.value)
|
|
||||||
}
|
}
|
||||||
"requires_authentication" => {
|
"requires_authentication" => {
|
||||||
let expr_lit = match field_value.expr {
|
let literal = match field_value.expr {
|
||||||
Expr::Lit(expr_lit) => expr_lit,
|
Expr::Lit(ExprLit {
|
||||||
|
lit: Lit::Bool(b), ..
|
||||||
|
}) => b,
|
||||||
_ => panic!("expected Expr::Lit"),
|
_ => panic!("expected Expr::Lit"),
|
||||||
};
|
};
|
||||||
let lit_bool = match expr_lit.lit {
|
requires_authentication = Some(literal)
|
||||||
Lit::Bool(lit_bool) => lit_bool,
|
|
||||||
_ => panic!("expected Lit::Bool"),
|
|
||||||
};
|
|
||||||
requires_authentication = Some(lit_bool.value)
|
|
||||||
}
|
}
|
||||||
_ => panic!("ruma_api! metadata included unexpected field"),
|
_ => panic!("ruma_api! metadata included unexpected field"),
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//! Details of the `ruma-api` procedural macro.
|
//! Details of the `ruma_api` procedural macro.
|
||||||
|
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
@ -8,6 +8,7 @@ use syn::{
|
|||||||
Field, FieldValue, Ident, Meta, Token,
|
Field, FieldValue, Ident, Meta, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod attribute;
|
||||||
mod metadata;
|
mod metadata;
|
||||||
mod request;
|
mod request;
|
||||||
mod response;
|
mod response;
|
||||||
@ -65,8 +66,11 @@ impl From<RawApi> for Api {
|
|||||||
impl ToTokens for Api {
|
impl ToTokens for Api {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
let description = &self.metadata.description;
|
let description = &self.metadata.description;
|
||||||
let method = Ident::new(self.metadata.method.as_ref(), Span::call_site());
|
let method = &self.metadata.method;
|
||||||
let name = &self.metadata.name;
|
// We don't (currently) use this literal as a literal in the generated code. Instead we just
|
||||||
|
// put it into doc comments, for which the span information is irrelevant. So we can work
|
||||||
|
// with only the literal's value from here on.
|
||||||
|
let name = &self.metadata.name.value();
|
||||||
let path = &self.metadata.path;
|
let path = &self.metadata.path;
|
||||||
let rate_limited = &self.metadata.rate_limited;
|
let rate_limited = &self.metadata.rate_limited;
|
||||||
let requires_authentication = &self.metadata.requires_authentication;
|
let requires_authentication = &self.metadata.requires_authentication;
|
||||||
@ -85,7 +89,7 @@ impl ToTokens for Api {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (set_request_path, parse_request_path) = if self.request.has_path_fields() {
|
let (set_request_path, parse_request_path) = if self.request.has_path_fields() {
|
||||||
let path_str = path.as_str();
|
let path_str = path.value();
|
||||||
|
|
||||||
assert!(path_str.starts_with('/'), "path needs to start with '/'");
|
assert!(path_str.starts_with('/'), "path needs to start with '/'");
|
||||||
assert!(
|
assert!(
|
||||||
@ -313,7 +317,7 @@ impl ToTokens for Api {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let endpoint_doc = format!("The `{}` API endpoint.\n\n{}", name, description);
|
let endpoint_doc = format!("The `{}` API endpoint.\n\n{}", name, description.value());
|
||||||
let request_doc = format!("Data for a request to the `{}` API endpoint.", name);
|
let request_doc = format!("Data for a request to the `{}` API endpoint.", name);
|
||||||
let response_doc = format!("Data in the response from the `{}` API endpoint.", name);
|
let response_doc = format!("Data in the response from the `{}` API endpoint.", name);
|
||||||
|
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
//! Details of the `request` section of the procedural macro.
|
//! Details of the `request` section of the procedural macro.
|
||||||
|
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::TokenStream;
|
||||||
use quote::{quote, quote_spanned, ToTokens};
|
use quote::{quote, quote_spanned, ToTokens};
|
||||||
use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta};
|
use syn::{spanned::Spanned, Field, Ident};
|
||||||
|
|
||||||
use crate::api::strip_serde_attrs;
|
use crate::api::{
|
||||||
|
attribute::{Meta, MetaNameValue},
|
||||||
|
strip_serde_attrs,
|
||||||
|
};
|
||||||
|
|
||||||
/// The result of processing the `request` section of the macro.
|
/// The result of processing the `request` section of the macro.
|
||||||
pub struct Request {
|
pub struct Request {
|
||||||
@ -16,13 +19,12 @@ impl Request {
|
|||||||
/// Produces code to add necessary HTTP headers to an `http::Request`.
|
/// Produces code to add necessary HTTP headers to an `http::Request`.
|
||||||
pub fn add_headers_to_request(&self) -> TokenStream {
|
pub fn add_headers_to_request(&self) -> TokenStream {
|
||||||
let append_stmts = self.header_fields().map(|request_field| {
|
let append_stmts = self.header_fields().map(|request_field| {
|
||||||
let (field, header_name_string) = match request_field {
|
let (field, header_name) = match request_field {
|
||||||
RequestField::Header(field, header_name_string) => (field, header_name_string),
|
RequestField::Header(field, header_name) => (field, header_name),
|
||||||
_ => panic!("expected request field to be header variant"),
|
_ => panic!("expected request field to be header variant"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let field_name = &field.ident;
|
let field_name = &field.ident;
|
||||||
let header_name = Ident::new(header_name_string.as_ref(), Span::call_site());
|
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
headers.append(
|
headers.append(
|
||||||
@ -41,13 +43,13 @@ impl Request {
|
|||||||
/// Produces code to extract fields from the HTTP headers in an `http::Request`.
|
/// Produces code to extract fields from the HTTP headers in an `http::Request`.
|
||||||
pub fn parse_headers_from_request(&self) -> TokenStream {
|
pub fn parse_headers_from_request(&self) -> TokenStream {
|
||||||
let fields = self.header_fields().map(|request_field| {
|
let fields = self.header_fields().map(|request_field| {
|
||||||
let (field, header_name_string) = match request_field {
|
let (field, header_name) = match request_field {
|
||||||
RequestField::Header(field, header_name_string) => (field, header_name_string),
|
RequestField::Header(field, header_name) => (field, header_name),
|
||||||
_ => panic!("expected request field to be header variant"),
|
_ => panic!("expected request field to be header variant"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let field_name = &field.ident;
|
let field_name = &field.ident;
|
||||||
let header_name = Ident::new(header_name_string.as_ref(), Span::call_site());
|
let header_name_string = header_name.to_string();
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#field_name: headers.get(::http::header::#header_name)
|
#field_name: headers.get(::http::header::#header_name)
|
||||||
@ -180,23 +182,13 @@ impl From<Vec<Field>> for Request {
|
|||||||
let mut field_kind = RequestFieldKind::Body;
|
let mut field_kind = RequestFieldKind::Body;
|
||||||
let mut header = None;
|
let mut header = None;
|
||||||
|
|
||||||
field.attrs = field.attrs.into_iter().filter(|attr| {
|
field.attrs = field.attrs.into_iter().filter_map(|attr| {
|
||||||
let meta = attr.interpret_meta()
|
let meta = match Meta::from_attribute(attr) {
|
||||||
.expect("ruma_api! could not parse request field attributes");
|
Ok(meta) => meta,
|
||||||
|
Err(attr) => return Some(attr),
|
||||||
let meta_list = match meta {
|
|
||||||
Meta::List(meta_list) => meta_list,
|
|
||||||
_ => return true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if &meta_list.ident.to_string() != "ruma_api" {
|
match meta {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for nested_meta_item in meta_list.nested {
|
|
||||||
match nested_meta_item {
|
|
||||||
NestedMeta::Meta(meta_item) => {
|
|
||||||
match meta_item {
|
|
||||||
Meta::Word(ident) => {
|
Meta::Word(ident) => {
|
||||||
match &ident.to_string()[..] {
|
match &ident.to_string()[..] {
|
||||||
"body" => {
|
"body" => {
|
||||||
@ -208,29 +200,18 @@ impl From<Vec<Field>> for Request {
|
|||||||
_ => panic!("ruma_api! single-word attribute on requests must be: body, path, or query"),
|
_ => panic!("ruma_api! single-word attribute on requests must be: body, path, or query"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Meta::NameValue(name_value) => {
|
Meta::NameValue(MetaNameValue { name, value }) => {
|
||||||
match &name_value.ident.to_string()[..] {
|
assert!(
|
||||||
"header" => {
|
name == "header",
|
||||||
match name_value.lit {
|
"ruma_api! name/value pair attribute on requests must be: header"
|
||||||
Lit::Str(lit_str) => header = Some(lit_str.value()),
|
);
|
||||||
_ => panic!("ruma_api! header attribute's value must be a string literal"),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
header = Some(value);
|
||||||
field_kind = RequestFieldKind::Header;
|
field_kind = RequestFieldKind::Header;
|
||||||
}
|
}
|
||||||
_ => panic!("ruma_api! name/value pair attribute on requests must be: header"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => panic!("ruma_api! attributes on requests must be a single word or a name/value pair"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NestedMeta::Literal(_) => panic!(
|
|
||||||
"ruma_api! attributes on requests must be: body, header, path, or query"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
None
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
if field_kind == RequestFieldKind::Body {
|
if field_kind == RequestFieldKind::Body {
|
||||||
@ -370,7 +351,7 @@ pub enum RequestField {
|
|||||||
/// JSON data in the body of the request.
|
/// JSON data in the body of the request.
|
||||||
Body(Field),
|
Body(Field),
|
||||||
/// Data in an HTTP header.
|
/// Data in an HTTP header.
|
||||||
Header(Field, String),
|
Header(Field, Ident),
|
||||||
/// A specific data type in the body of the request.
|
/// A specific data type in the body of the request.
|
||||||
NewtypeBody(Field),
|
NewtypeBody(Field),
|
||||||
/// Data that appears in the URL path.
|
/// Data that appears in the URL path.
|
||||||
@ -381,7 +362,7 @@ pub enum RequestField {
|
|||||||
|
|
||||||
impl RequestField {
|
impl RequestField {
|
||||||
/// Creates a new `RequestField`.
|
/// Creates a new `RequestField`.
|
||||||
fn new(kind: RequestFieldKind, field: Field, header: Option<String>) -> Self {
|
fn new(kind: RequestFieldKind, field: Field, header: Option<Ident>) -> Self {
|
||||||
match kind {
|
match kind {
|
||||||
RequestFieldKind::Body => RequestField::Body(field),
|
RequestFieldKind::Body => RequestField::Body(field),
|
||||||
RequestFieldKind::Header => {
|
RequestFieldKind::Header => {
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
//! Details of the `response` section of the procedural macro.
|
//! Details of the `response` section of the procedural macro.
|
||||||
|
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::TokenStream;
|
||||||
use quote::{quote, quote_spanned, ToTokens};
|
use quote::{quote, quote_spanned, ToTokens};
|
||||||
use syn::{spanned::Spanned, Field, Ident, Lit, Meta, NestedMeta};
|
use syn::{spanned::Spanned, Field, Ident};
|
||||||
|
|
||||||
use crate::api::strip_serde_attrs;
|
use crate::api::{
|
||||||
|
attribute::{Meta, MetaNameValue},
|
||||||
|
strip_serde_attrs,
|
||||||
|
};
|
||||||
|
|
||||||
/// The result of processing the `request` section of the macro.
|
/// The result of processing the `request` section of the macro.
|
||||||
pub struct Response {
|
pub struct Response {
|
||||||
@ -50,12 +53,11 @@ impl Response {
|
|||||||
#field_name: response_body.#field_name
|
#field_name: response_body.#field_name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ResponseField::Header(ref field, ref header) => {
|
ResponseField::Header(ref field, ref header_name) => {
|
||||||
let field_name = field
|
let field_name = field
|
||||||
.ident
|
.ident
|
||||||
.clone()
|
.clone()
|
||||||
.expect("expected field to have an identifier");
|
.expect("expected field to have an identifier");
|
||||||
let header_name = Ident::new(header.as_ref(), Span::call_site());
|
|
||||||
let span = field.span();
|
let span = field.span();
|
||||||
|
|
||||||
quote_spanned! {span=>
|
quote_spanned! {span=>
|
||||||
@ -87,12 +89,11 @@ impl Response {
|
|||||||
/// Produces code to add necessary HTTP headers to an `http::Response`.
|
/// Produces code to add necessary HTTP headers to an `http::Response`.
|
||||||
pub fn apply_header_fields(&self) -> TokenStream {
|
pub fn apply_header_fields(&self) -> TokenStream {
|
||||||
let header_calls = self.fields.iter().filter_map(|response_field| {
|
let header_calls = self.fields.iter().filter_map(|response_field| {
|
||||||
if let ResponseField::Header(ref field, ref header) = *response_field {
|
if let ResponseField::Header(ref field, ref header_name) = *response_field {
|
||||||
let field_name = field
|
let field_name = field
|
||||||
.ident
|
.ident
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("expected field to have an identifier");
|
.expect("expected field to have an identifier");
|
||||||
let header_name = Ident::new(header.as_ref(), Span::call_site());
|
|
||||||
let span = field.span();
|
let span = field.span();
|
||||||
|
|
||||||
Some(quote_spanned! {span=>
|
Some(quote_spanned! {span=>
|
||||||
@ -165,65 +166,45 @@ impl From<Vec<Field>> for Response {
|
|||||||
let mut field_kind = ResponseFieldKind::Body;
|
let mut field_kind = ResponseFieldKind::Body;
|
||||||
let mut header = None;
|
let mut header = None;
|
||||||
|
|
||||||
field.attrs = field.attrs.into_iter().filter(|attr| {
|
field.attrs = field.attrs.into_iter().filter_map(|attr| {
|
||||||
let meta = attr.interpret_meta()
|
let meta = match Meta::from_attribute(attr) {
|
||||||
.expect("ruma_api! could not parse response field attributes");
|
Ok(meta) => meta,
|
||||||
|
Err(attr) => return Some(attr),
|
||||||
let meta_list = match meta {
|
|
||||||
Meta::List(meta_list) => meta_list,
|
|
||||||
_ => return true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if &meta_list.ident.to_string() != "ruma_api" {
|
match meta {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for nested_meta_item in meta_list.nested {
|
|
||||||
match nested_meta_item {
|
|
||||||
NestedMeta::Meta(meta_item) => {
|
|
||||||
match meta_item {
|
|
||||||
Meta::Word(ident) => {
|
Meta::Word(ident) => {
|
||||||
match &ident.to_string()[..] {
|
assert!(
|
||||||
"body" => {
|
ident == "body",
|
||||||
|
"ruma_api! single-word attribute on responses must be: body"
|
||||||
|
);
|
||||||
|
|
||||||
has_newtype_body = true;
|
has_newtype_body = true;
|
||||||
field_kind = ResponseFieldKind::NewtypeBody;
|
field_kind = ResponseFieldKind::NewtypeBody;
|
||||||
}
|
}
|
||||||
_ => panic!("ruma_api! single-word attribute on responses must be: body"),
|
Meta::NameValue(MetaNameValue { name, value }) => {
|
||||||
}
|
assert!(
|
||||||
}
|
name == "header",
|
||||||
Meta::NameValue(name_value) => {
|
"ruma_api! name/value pair attribute on requests must be: header"
|
||||||
match &name_value.ident.to_string()[..] {
|
);
|
||||||
"header" => {
|
|
||||||
match name_value.lit {
|
|
||||||
Lit::Str(lit_str) => header = Some(lit_str.value()),
|
|
||||||
_ => panic!("ruma_api! header attribute's value must be a string literal"),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
header = Some(value);
|
||||||
field_kind = ResponseFieldKind::Header;
|
field_kind = ResponseFieldKind::Header;
|
||||||
}
|
}
|
||||||
_ => panic!("ruma_api! name/value pair attribute on requests must be: header"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => panic!("ruma_api! attributes on responses must be a single word or a name/value pair"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NestedMeta::Literal(_) => panic!(
|
|
||||||
"ruma_api! attribute meta item on responses must be: header"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
None
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
match field_kind {
|
match field_kind {
|
||||||
ResponseFieldKind::Body => {
|
ResponseFieldKind::Body => {
|
||||||
if has_newtype_body {
|
assert!(
|
||||||
panic!("ruma_api! responses cannot have both normal body fields and a newtype body field");
|
!has_newtype_body,
|
||||||
} else {
|
"ruma_api! responses cannot have both normal body fields and a newtype body field"
|
||||||
|
);
|
||||||
|
|
||||||
ResponseField::Body(field)
|
ResponseField::Body(field)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
ResponseFieldKind::Header => ResponseField::Header(field, header.expect("missing header name")),
|
ResponseFieldKind::Header => ResponseField::Header(field, header.expect("missing header name")),
|
||||||
ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field),
|
ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field),
|
||||||
}
|
}
|
||||||
@ -307,7 +288,7 @@ pub enum ResponseField {
|
|||||||
/// JSON data in the body of the response.
|
/// JSON data in the body of the response.
|
||||||
Body(Field),
|
Body(Field),
|
||||||
/// Data in an HTTP header.
|
/// Data in an HTTP header.
|
||||||
Header(Field, String),
|
Header(Field, Ident),
|
||||||
/// A specific data type in the body of the response.
|
/// A specific data type in the body of the response.
|
||||||
NewtypeBody(Field),
|
NewtypeBody(Field),
|
||||||
}
|
}
|
||||||
|
@ -158,7 +158,7 @@ mod api;
|
|||||||
/// request {
|
/// request {
|
||||||
/// pub foo: String,
|
/// pub foo: String,
|
||||||
///
|
///
|
||||||
/// #[ruma_api(header = "CONTENT_TYPE")]
|
/// #[ruma_api(header = CONTENT_TYPE)]
|
||||||
/// pub content_type: String,
|
/// pub content_type: String,
|
||||||
///
|
///
|
||||||
/// #[ruma_api(query)]
|
/// #[ruma_api(query)]
|
||||||
@ -169,7 +169,7 @@ mod api;
|
|||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// response {
|
/// response {
|
||||||
/// #[ruma_api(header = "CONTENT_TYPE")]
|
/// #[ruma_api(header = CONTENT_TYPE)]
|
||||||
/// pub content_type: String,
|
/// pub content_type: String,
|
||||||
///
|
///
|
||||||
/// pub value: String,
|
/// pub value: String,
|
||||||
|
@ -17,7 +17,7 @@ pub mod some_endpoint {
|
|||||||
pub foo: String,
|
pub foo: String,
|
||||||
|
|
||||||
// This value will be put into the "Content-Type" HTTP header.
|
// This value will be put into the "Content-Type" HTTP header.
|
||||||
#[ruma_api(header = "CONTENT_TYPE")]
|
#[ruma_api(header = CONTENT_TYPE)]
|
||||||
pub content_type: String,
|
pub content_type: String,
|
||||||
|
|
||||||
// This value will be put into the query string of the request's URL.
|
// This value will be put into the query string of the request's URL.
|
||||||
@ -32,7 +32,7 @@ pub mod some_endpoint {
|
|||||||
|
|
||||||
response {
|
response {
|
||||||
// This value will be extracted from the "Content-Type" HTTP header.
|
// This value will be extracted from the "Content-Type" HTTP header.
|
||||||
#[ruma_api(header = "CONTENT_TYPE")]
|
#[ruma_api(header = CONTENT_TYPE)]
|
||||||
pub content_type: String,
|
pub content_type: String,
|
||||||
|
|
||||||
// With no attribute on the field, it will be extracted from the body of the response.
|
// With no attribute on the field, it will be extracted from the body of the response.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user