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 quote::{quote, quote_spanned};
use syn::parse::{Parse, ParseStream};
use syn::parse::{Parse, ParseStream, Parser};
use syn::spanned::Spanned;
use syn::Ident;
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.
///
/// 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 (input_schema, returns_schema, protected) =
api_function_attributes(&mut func.attrs, func.sig.span())?;
let mut input_schema: Schema = attribs
.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 mut ts = TokenStream::new();
@ -314,12 +330,10 @@ pub(crate) fn api(_attr: TokenStream, item: TokenStream) -> Result<TokenStream,
}
fn api_function_attributes(
input_schema: &mut Schema,
returns_schema: &mut Schema,
attrs: &mut Vec<syn::Attribute>,
sig_span: Span,
) -> Result<(Schema, Schema, bool), Error> {
let mut protected = false;
let mut input_schema = None;
let mut returns_schema = None;
) -> Result<(), Error> {
let mut doc_comment = String::new();
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());
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 {
attrs.push(attr);
}
}
let mut input_schema =
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))
derive_descriptions(input_schema, returns_schema, &doc_comment, doc_span)
}
fn derive_descriptions(

View File

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

View File

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