diff --git a/proxmox-api-macro/src/api.rs b/proxmox-api-macro/src/api.rs index 36a4400d..c4bba1e4 100644 --- a/proxmox-api-macro/src/api.rs +++ b/proxmox-api-macro/src/api.rs @@ -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 Parse for BareAssignment { /// 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 { +pub(crate) fn api(attr: TokenStream, item: TokenStream) -> Result { + 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, - 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 = syn::parse2(attr.tokens)?; - input_schema = Some(input.content); - } else if attr.path.is_ident("returns") { - let input: Parenthesized = 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::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( diff --git a/proxmox-api-macro/src/lib.rs b/proxmox-api-macro/src/lib.rs index 438c1a4f..673045aa 100644 --- a/proxmox-api-macro/src/lib.rs +++ b/proxmox-api-macro/src/lib.rs @@ -70,39 +70,40 @@ fn router_do(item: TokenStream) -> Result { 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: ... diff --git a/proxmox-api-macro/tests/api1.rs b/proxmox-api-macro/tests/api1.rs index e8247d99..a22c67f8 100644 --- a/proxmox-api-macro/tests/api1.rs +++ b/proxmox-api-macro/tests/api1.rs @@ -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.