]> git.proxmox.com Git - proxmox.git/commitdiff
api-macro: allow referencing external schemas in properties
authorWolfgang Bumiller <w.bumiller@proxmox.com>
Tue, 3 Dec 2019 10:11:46 +0000 (11:11 +0100)
committerWolfgang Bumiller <w.bumiller@proxmox.com>
Tue, 3 Dec 2019 10:11:46 +0000 (11:11 +0100)
Reference a predefined BACKUP_ARCHIVE_NAME StringSchema like
this:
    #[api(
        input: {
            properties: {
                archive: {
                    optional: true,
                    schema: BACKUP_ARCHIVE_NAME,
                },
            }
        }
    )]
    fn get_archive(archive: String) -> Result<(), Error> {
        ...
    }

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
proxmox-api-macro/src/api.rs
proxmox-api-macro/src/api/method.rs
proxmox-api-macro/tests/ext-schema.rs [new file with mode: 0644]

index e6709ca6e92f72f422bcd9f4cc4953b0a092605c..91f74f84036ffe9c061b662816b091bf29ce76f5 100644 (file)
@@ -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<bool, syn::Error> {
@@ -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))
index b0db1cdafeb2b8169a5ec9a0d3222422ee19f0ae..002293013a580c72683168326439ea939e41eb9c 100644 (file)
@@ -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 (file)
index 0000000..c4123d8
--- /dev/null
@@ -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(())
+}