diff --git a/proxmox-api-macro/src/api.rs b/proxmox-api-macro/src/api.rs index 44946e8e..e6709ca6 100644 --- a/proxmox-api-macro/src/api.rs +++ b/proxmox-api-macro/src/api.rs @@ -14,6 +14,10 @@ mod enums; mod method; mod structs; +pub const INTNAMES: &[&'static str] = &[ + "Integer", "i8", "i16", "i32", "i64", "isize", "u8", "u16", "u32", "u64", "usize", +]; + /// The main `Schema` type. /// /// We have 2 fixed keys: `type` and `description`. The remaining keys depend on the `type`. @@ -122,9 +126,21 @@ impl Schema { } } + fn as_object_mut(&mut self) -> Option<&mut SchemaObject> { + match &mut self.item { + SchemaItem::Object(obj) => Some(obj), + _ => None, + } + } + fn find_object_property(&self, key: &str) -> Option<(bool, &Schema)> { self.as_object().and_then(|obj| obj.find_property(key)) } + + fn find_object_property_mut(&mut self, key: &str) -> Option<(&mut bool, &mut Schema)> { + self.as_object_mut() + .and_then(|obj| obj.find_property_mut(key)) + } } enum SchemaItem { @@ -135,6 +151,7 @@ enum SchemaItem { Object(SchemaObject), Array(SchemaArray), ExternType(ExprPath), + Inferred(Span), } impl SchemaItem { @@ -150,7 +167,7 @@ impl SchemaItem { } else if obj.contains_key("items") { return Ok(SchemaItem::Array(SchemaArray::try_extract_from(obj)?)); } else { - bail!(obj.span(), "failed to guess 'type' in schema definition"); + return Ok(SchemaItem::Inferred(obj.span())); } } }; @@ -170,9 +187,6 @@ impl SchemaItem { .ok_or_else(|| format_err!(&ty.path => "invalid empty path"))? .ident; - const INTNAMES: &[&'static str] = &[ - "Integer", "i8", "i16", "i32", "i64", "isize", "u8", "u16", "u32", "u64", "usize", - ]; if name == "Null" { Ok(SchemaItem::Null) } else if name == "Boolean" || name == "bool" { @@ -239,6 +253,9 @@ impl SchemaItem { ts.extend(quote_spanned! { path.span() => #path::API_SCHEMA }); return Ok(true); } + SchemaItem::Inferred(span) => { + bail!(*span, "failed to guess 'type' in schema definition"); + } } // Then append all the remaining builder-pattern properties: @@ -332,6 +349,19 @@ impl SchemaObject { Err(_) => None, } } + + fn find_property_mut(&mut self, key: &str) -> Option<(&mut bool, &mut Schema)> { + match self + .properties + .binary_search_by(|prope| prope.0.as_str().cmp(key)) + { + Ok(idx) => { + let props = &mut self.properties[idx]; + Some((&mut props.1, &mut props.2)) + } + Err(_) => None, + } + } } struct SchemaArray { diff --git a/proxmox-api-macro/src/api/method.rs b/proxmox-api-macro/src/api/method.rs index fead2b81..b0db1cda 100644 --- a/proxmox-api-macro/src/api/method.rs +++ b/proxmox-api-macro/src/api/method.rs @@ -8,7 +8,7 @@ use quote::{quote, quote_spanned}; use syn::spanned::Spanned; use syn::Ident; -use super::Schema; +use super::{Schema, SchemaItem}; use crate::util::{BareAssignment, JSONObject, SimpleIdent}; /// Parse `input`, `returns` and `protected` attributes out of an function annotated @@ -21,7 +21,7 @@ pub fn handle_method(mut attribs: JSONObject, mut func: syn::ItemFn) -> Result Schema { span: Span::call_site(), description: None, - item: super::SchemaItem::Object(Default::default()), + item: SchemaItem::Object(Default::default()), properties: Vec::new(), }, }; @@ -164,6 +164,22 @@ enum ParameterType<'a> { Other(&'a syn::Type, bool, &'a Schema), } +fn check_input_type(input: &syn::FnArg) -> Result<(&syn::PatType, &syn::PatIdent), Error> { + // `self` types are not supported: + let pat_type = match input { + syn::FnArg::Receiver(r) => bail!(r => "methods taking a 'self' are not supported"), + syn::FnArg::Typed(pat_type) => pat_type, + }; + + // Normally function parameters are simple Ident patterns. Anything else is an error. + let pat = match &*pat_type.pat { + syn::Pat::Ident(pat) => pat, + _ => bail!(pat_type => "unsupported parameter type"), + }; + + Ok((pat_type, pat)) +} + fn handle_function_signature( input_schema: &mut Schema, returns_schema: &mut Option, @@ -183,17 +199,46 @@ fn handle_function_signature( let mut param_list = Vec::<(SimpleIdent, ParameterType)>::new(); for input in sig.inputs.iter() { - // `self` types are not supported: - let pat_type = match input { - syn::FnArg::Receiver(r) => bail!(r => "methods taking a 'self' are not supported"), - syn::FnArg::Typed(pat_type) => pat_type, + let (pat_type, pat) = check_input_type(input)?; + + // For any named type which exists on the function signature... + let schema: &mut Schema = if let Some((_optional, schema)) = + input_schema.find_object_property_mut(&pat.ident.to_string()) + { + // ... if it has no `type` property (item = SchemaItem::Inferred), get a mutable + // reference to the schema, so that we can... + match &mut schema.item { + SchemaItem::Inferred(_span) => schema, + // other types are fine: + _ => continue, + } + } else { + continue; }; - // Normally function parameters are simple Ident patterns. Anything else is an error. - let pat = match &*pat_type.pat { - syn::Pat::Ident(pat) => pat, - _ => bail!(pat_type => "unsupported parameter type"), - }; + // ... infer the type from the function parameters: + match &*pat_type.ty { + syn::Type::Path(path) if path.qself.is_none() => { + if path.path.is_ident("String") { + schema.item = SchemaItem::String; + continue; + } else if path.path.is_ident("bool") { + schema.item = SchemaItem::Boolean; + continue; + } else if super::INTNAMES.iter().any(|n| path.path.is_ident(n)) { + schema.item = SchemaItem::Integer; + continue; + } + } + _ => (), + } + + // if we can't, bail out: + bail!(&pat_type.ty => "cannot infer parameter type from this rust type"); + } + + for input in sig.inputs.iter() { + let (pat_type, pat) = check_input_type(input)?; // Here's the deal: we need to distinguish between parameters we need to extract before // calling the function, a general "Value" parameter covering all the remaining json @@ -223,6 +268,10 @@ fn handle_function_signature( let param_type = if let Some((optional, schema)) = input_schema.find_object_property(&pat.ident.to_string()) { + match &schema.item { + SchemaItem::Inferred(span) => bail!(*span, "failed to infer type"), + _ => (), + } // Found an explicit parameter: extract it: ParameterType::Other(&pat_type.ty, optional, schema) } else if is_api_method_type(&pat_type.ty) { diff --git a/proxmox-api-macro/tests/api2.rs b/proxmox-api-macro/tests/api2.rs new file mode 100644 index 00000000..4b95c4f5 --- /dev/null +++ b/proxmox-api-macro/tests/api2.rs @@ -0,0 +1,19 @@ +use proxmox_api_macro::api; + +use failure::Error; +use serde_json::Value; + +#[api( + input: { + properties: { + message: { + description: "The message to print", + } + } + } +)] +/// Print the given message. +pub fn hello(message: String) -> Result<(), Error> { + println!("Hello there. {}", message); + Ok(()) +}