api-macro: parse properties as attributes to #[api]

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2019-11-27 11:01:40 +01:00
parent 676ef1796d
commit ebda5a3c5c
3 changed files with 86 additions and 96 deletions

View File

@ -8,7 +8,7 @@ use failure::Error;
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned}; use quote::{quote, quote_spanned};
use syn::parse::{Parse, ParseStream}; use syn::parse::{Parse, ParseStream, Parser};
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::Ident; use syn::Ident;
use syn::{parenthesized, Token}; use syn::{parenthesized, Token};
@ -275,11 +275,27 @@ impl<T: Parse> Parse for BareAssignment<T> {
/// with an `#[api]` attribute and produce a `const ApiMethod` named after the function. /// with an `#[api]` attribute and produce a `const ApiMethod` named after the function.
/// ///
/// See the top level macro documentation for a complete example. /// See the top level macro documentation for a complete example.
pub(crate) fn api(_attr: TokenStream, item: TokenStream) -> Result<TokenStream, Error> { pub(crate) fn api(attr: TokenStream, item: TokenStream) -> Result<TokenStream, Error> {
let mut attribs = JSONObject::parse_inner.parse2(attr)?;
let mut func: syn::ItemFn = syn::parse2(item)?; let mut func: syn::ItemFn = syn::parse2(item)?;
let (input_schema, returns_schema, protected) = let mut input_schema: Schema = attribs
api_function_attributes(&mut func.attrs, func.sig.span())?; .remove_required_element("input")?
.into_object("input schema definition")?
.try_into()?;
let mut returns_schema: Schema = attribs
.remove_required_element("returns")?
.into_object("return schema definition")?
.try_into()?;
let protected: bool = attribs
.remove("protected")
.map(TryFrom::try_from)
.transpose()?
.unwrap_or(false);
api_function_attributes(&mut input_schema, &mut returns_schema, &mut func.attrs)?;
let input_schema = { let input_schema = {
let mut ts = TokenStream::new(); let mut ts = TokenStream::new();
@ -314,12 +330,10 @@ pub(crate) fn api(_attr: TokenStream, item: TokenStream) -> Result<TokenStream,
} }
fn api_function_attributes( fn api_function_attributes(
input_schema: &mut Schema,
returns_schema: &mut Schema,
attrs: &mut Vec<syn::Attribute>, attrs: &mut Vec<syn::Attribute>,
sig_span: Span, ) -> Result<(), Error> {
) -> Result<(Schema, Schema, bool), Error> {
let mut protected = false;
let mut input_schema = None;
let mut returns_schema = None;
let mut doc_comment = String::new(); let mut doc_comment = String::new();
let doc_span = Span::call_site(); // FIXME: set to first doc comment let doc_span = Span::call_site(); // FIXME: set to first doc comment
@ -337,38 +351,12 @@ fn api_function_attributes(
} }
doc_comment.push_str(doc.content.value().trim()); doc_comment.push_str(doc.content.value().trim());
attrs.push(attr); attrs.push(attr);
} else if attr.path.is_ident("input") {
let input: Parenthesized<Schema> = syn::parse2(attr.tokens)?;
input_schema = Some(input.content);
} else if attr.path.is_ident("returns") {
let input: Parenthesized<Schema> = syn::parse2(attr.tokens)?;
returns_schema = Some(input.content);
} else if attr.path.is_ident("protected") {
if attr.tokens.is_empty() {
protected = true;
} else {
let value: Parenthesized<syn::LitBool> = syn::parse2(attr.tokens)?;
protected = value.content.value;
}
} else { } else {
attrs.push(attr); attrs.push(attr);
} }
} }
let mut input_schema = derive_descriptions(input_schema, returns_schema, &doc_comment, doc_span)
input_schema.ok_or_else(|| format_err!(sig_span, "missing input schema"))?;
let mut returns_schema =
returns_schema.ok_or_else(|| format_err!(sig_span, "missing returns schema"))?;
derive_descriptions(
&mut input_schema,
&mut returns_schema,
&doc_comment,
doc_span,
)?;
Ok((input_schema, returns_schema, protected))
} }
fn derive_descriptions( fn derive_descriptions(

View File

@ -70,39 +70,40 @@ fn router_do(item: TokenStream) -> Result<TokenStream, Error> {
use failure::Error; use failure::Error;
use serde_json::Value; use serde_json::Value;
#[api] #[api(
#[input({ input: {
type: Object, type: Object,
properties: { properties: {
username: { username: {
type: String, type: String,
description: "User name", description: "User name",
max_length: 64, max_length: 64,
}, },
password: { password: {
type: String, type: String,
description: "The secret password or a valid ticket.", description: "The secret password or a valid ticket.",
}, },
} }
})] },
#[returns({ returns: {
type: Object, type: Object,
description: "Returns a ticket", description: "Returns a ticket",
properties: { properties: {
"username": { "username": {
type: String, type: String,
description: "User name.", description: "User name.",
}, },
"ticket": { "ticket": {
type: String, type: String,
description: "Auth ticket.", description: "Auth ticket.",
}, },
"CSRFPreventionToken": { "CSRFPreventionToken": {
type: String, type: String,
description: "Cross Site Request Forgerty Prevention Token.", description: "Cross Site Request Forgerty Prevention Token.",
},
}, },
}, },
})] )]
/// Create or verify authentication ticket. /// Create or verify authentication ticket.
/// ///
/// Returns: ... /// Returns: ...

View File

@ -6,37 +6,38 @@ use proxmox_api_macro::api;
use failure::Error; use failure::Error;
use serde_json::Value; use serde_json::Value;
#[api] #[api(
#[input({ input: {
properties: { properties: {
username: { username: {
type: String, type: String,
description: "User name", description: "User name",
max_length: 64, max_length: 64,
}, },
password: { password: {
type: String, type: String,
description: "The secret password or a valid ticket.", description: "The secret password or a valid ticket.",
}, },
} }
})] },
#[returns({ returns: {
properties: { properties: {
"username": { "username": {
type: String, type: String,
description: "User name.", description: "User name.",
}, },
"ticket": { "ticket": {
type: String, type: String,
description: "Auth ticket.", description: "Auth ticket.",
}, },
"CSRFPreventionToken": { "CSRFPreventionToken": {
type: String, type: String,
description: "Cross Site Request Forgerty Prevention Token.", description: "Cross Site Request Forgerty Prevention Token.",
},
}, },
}, },
})] protected: true,
#[protected] )]
/// Create or verify authentication ticket. /// Create or verify authentication ticket.
/// ///
/// Returns: A ticket. /// Returns: A ticket.