diff --git a/proxmox-api-macro/src/api.rs b/proxmox-api-macro/src/api.rs index e6709ca6..91f74f84 100644 --- a/proxmox-api-macro/src/api.rs +++ b/proxmox-api-macro/src/api.rs @@ -133,11 +133,11 @@ impl Schema { } } - fn find_object_property(&self, key: &str) -> Option<(bool, &Schema)> { + fn find_object_property(&self, key: &str) -> Option<(bool, &PropertySchema)> { self.as_object().and_then(|obj| obj.find_property(key)) } - fn find_object_property_mut(&mut self, key: &str) -> Option<(&mut bool, &mut Schema)> { + fn find_object_property_mut(&mut self, key: &str) -> Option<(&mut bool, &mut PropertySchema)> { self.as_object_mut() .and_then(|obj| obj.find_property_mut(key)) } @@ -291,10 +291,28 @@ impl SchemaItem { } } +/// A property in an object either has its own schema or references an external schema. +enum PropertySchema { + Schema(Schema), + Ref(ExprPath), +} + +impl PropertySchema { + fn to_schema(&self, ts: &mut TokenStream) -> Result<(), Error> { + match self { + PropertySchema::Schema(s) => s.to_schema(ts), + PropertySchema::Ref(path) => { + ts.extend(quote! { & #path }); + Ok(()) + } + } + } +} + #[derive(Default)] /// Contains a sorted list of properties: struct SchemaObject { - properties: Vec<(String, bool, Schema)>, + properties: Vec<(String, bool, PropertySchema)>, } impl SchemaObject { @@ -309,6 +327,7 @@ impl SchemaObject { |mut properties, (key, value)| -> Result<_, syn::Error> { let mut schema: JSONObject = value.into_object("schema definition for field")?; + let optional: bool = schema .remove("optional") .map(|opt| -> Result { @@ -317,7 +336,19 @@ impl SchemaObject { }) .transpose()? .unwrap_or(false); - properties.push((key.to_string(), optional, schema.try_into()?)); + + let external_schema = schema + .remove("schema") + .map(ExprPath::try_from) + .transpose()?; + + let schema = match external_schema { + Some(path) => PropertySchema::Ref(path), + None => PropertySchema::Schema(schema.try_into()?), + }; + + properties.push((key.to_string(), optional, schema)); + Ok(properties) }, ) @@ -340,7 +371,7 @@ impl SchemaObject { Ok(()) } - fn find_property(&self, key: &str) -> Option<(bool, &Schema)> { + fn find_property(&self, key: &str) -> Option<(bool, &PropertySchema)> { match self .properties .binary_search_by(|prope| prope.0.as_str().cmp(key)) @@ -350,7 +381,7 @@ impl SchemaObject { } } - fn find_property_mut(&mut self, key: &str) -> Option<(&mut bool, &mut Schema)> { + fn find_property_mut(&mut self, key: &str) -> Option<(&mut bool, &mut PropertySchema)> { match self .properties .binary_search_by(|prope| prope.0.as_str().cmp(key)) diff --git a/proxmox-api-macro/src/api/method.rs b/proxmox-api-macro/src/api/method.rs index b0db1cda..00229301 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, SchemaItem}; +use super::{PropertySchema, Schema, SchemaItem}; use crate::util::{BareAssignment, JSONObject, SimpleIdent}; /// Parse `input`, `returns` and `protected` attributes out of an function annotated @@ -161,7 +161,7 @@ enum ParameterType<'a> { Value, ApiMethod, RpcEnv, - Other(&'a syn::Type, bool, &'a Schema), + Other(&'a syn::Type, bool, &'a PropertySchema), } fn check_input_type(input: &syn::FnArg) -> Result<(&syn::PatType, &syn::PatIdent), Error> { @@ -205,11 +205,14 @@ fn handle_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: + match schema { + PropertySchema::Schema(schema) => match &mut schema.item { + // ... if it has no `type` property (item = SchemaItem::Inferred), get a mutable + // reference to the schema, so that we can... + SchemaItem::Inferred(_span) => schema, + // other types are fine: + _ => continue, + }, _ => continue, } } else { @@ -268,8 +271,11 @@ 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"), + match schema { + PropertySchema::Schema(schema) => match &schema.item { + SchemaItem::Inferred(span) => bail!(*span, "failed to infer type"), + _ => (), + }, _ => (), } // Found an explicit parameter: extract it: diff --git a/proxmox-api-macro/tests/ext-schema.rs b/proxmox-api-macro/tests/ext-schema.rs new file mode 100644 index 00000000..c4123d86 --- /dev/null +++ b/proxmox-api-macro/tests/ext-schema.rs @@ -0,0 +1,26 @@ +//! This should test the usage of "external" schemas. If a property is declared with a path instead +//! of an object, we expect the path to lead to a schema. + +use proxmox::api::schema; +use proxmox_api_macro::api; + +use failure::Error; + +pub const NAME_SCHEMA: schema::Schema = schema::StringSchema::new("Archive name.") + //.format(&FILENAME_FORMAT) + .schema(); + +#[api( + input: { + properties: { + name: { + schema: NAME_SCHEMA, + } + } + } +)] +/// Get an archive. +pub fn get_archive(name: String) -> Result<(), Error> { + let _ = name; + Ok(()) +}