api-macro: allow referencing external schemas in properties

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>
This commit is contained in:
Wolfgang Bumiller 2019-12-03 11:11:46 +01:00
parent 55f90b4eac
commit d3ec63f26d
3 changed files with 78 additions and 15 deletions

View 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))

View 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:

View File

@ -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(())
}