forked from Proxmox/proxmox
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:
parent
55f90b4eac
commit
d3ec63f26d
@ -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))
|
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()
|
self.as_object_mut()
|
||||||
.and_then(|obj| obj.find_property_mut(key))
|
.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)]
|
#[derive(Default)]
|
||||||
/// Contains a sorted list of properties:
|
/// Contains a sorted list of properties:
|
||||||
struct SchemaObject {
|
struct SchemaObject {
|
||||||
properties: Vec<(String, bool, Schema)>,
|
properties: Vec<(String, bool, PropertySchema)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SchemaObject {
|
impl SchemaObject {
|
||||||
@ -309,6 +327,7 @@ impl SchemaObject {
|
|||||||
|mut properties, (key, value)| -> Result<_, syn::Error> {
|
|mut properties, (key, value)| -> Result<_, syn::Error> {
|
||||||
let mut schema: JSONObject =
|
let mut schema: JSONObject =
|
||||||
value.into_object("schema definition for field")?;
|
value.into_object("schema definition for field")?;
|
||||||
|
|
||||||
let optional: bool = schema
|
let optional: bool = schema
|
||||||
.remove("optional")
|
.remove("optional")
|
||||||
.map(|opt| -> Result<bool, syn::Error> {
|
.map(|opt| -> Result<bool, syn::Error> {
|
||||||
@ -317,7 +336,19 @@ impl SchemaObject {
|
|||||||
})
|
})
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or(false);
|
.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)
|
Ok(properties)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -340,7 +371,7 @@ impl SchemaObject {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_property(&self, key: &str) -> Option<(bool, &Schema)> {
|
fn find_property(&self, key: &str) -> Option<(bool, &PropertySchema)> {
|
||||||
match self
|
match self
|
||||||
.properties
|
.properties
|
||||||
.binary_search_by(|prope| prope.0.as_str().cmp(key))
|
.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
|
match self
|
||||||
.properties
|
.properties
|
||||||
.binary_search_by(|prope| prope.0.as_str().cmp(key))
|
.binary_search_by(|prope| prope.0.as_str().cmp(key))
|
||||||
|
@ -8,7 +8,7 @@ use quote::{quote, quote_spanned};
|
|||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::Ident;
|
use syn::Ident;
|
||||||
|
|
||||||
use super::{Schema, SchemaItem};
|
use super::{PropertySchema, Schema, SchemaItem};
|
||||||
use crate::util::{BareAssignment, JSONObject, SimpleIdent};
|
use crate::util::{BareAssignment, JSONObject, SimpleIdent};
|
||||||
|
|
||||||
/// Parse `input`, `returns` and `protected` attributes out of an function annotated
|
/// Parse `input`, `returns` and `protected` attributes out of an function annotated
|
||||||
@ -161,7 +161,7 @@ enum ParameterType<'a> {
|
|||||||
Value,
|
Value,
|
||||||
ApiMethod,
|
ApiMethod,
|
||||||
RpcEnv,
|
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> {
|
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)) =
|
let schema: &mut Schema = if let Some((_optional, schema)) =
|
||||||
input_schema.find_object_property_mut(&pat.ident.to_string())
|
input_schema.find_object_property_mut(&pat.ident.to_string())
|
||||||
{
|
{
|
||||||
// ... if it has no `type` property (item = SchemaItem::Inferred), get a mutable
|
match schema {
|
||||||
// reference to the schema, so that we can...
|
PropertySchema::Schema(schema) => match &mut schema.item {
|
||||||
match &mut schema.item {
|
// ... if it has no `type` property (item = SchemaItem::Inferred), get a mutable
|
||||||
SchemaItem::Inferred(_span) => schema,
|
// reference to the schema, so that we can...
|
||||||
// other types are fine:
|
SchemaItem::Inferred(_span) => schema,
|
||||||
|
// other types are fine:
|
||||||
|
_ => continue,
|
||||||
|
},
|
||||||
_ => continue,
|
_ => continue,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -268,8 +271,11 @@ fn handle_function_signature(
|
|||||||
let param_type = if let Some((optional, schema)) =
|
let param_type = if let Some((optional, schema)) =
|
||||||
input_schema.find_object_property(&pat.ident.to_string())
|
input_schema.find_object_property(&pat.ident.to_string())
|
||||||
{
|
{
|
||||||
match &schema.item {
|
match schema {
|
||||||
SchemaItem::Inferred(span) => bail!(*span, "failed to infer type"),
|
PropertySchema::Schema(schema) => match &schema.item {
|
||||||
|
SchemaItem::Inferred(span) => bail!(*span, "failed to infer type"),
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
// Found an explicit parameter: extract it:
|
// Found an explicit parameter: extract it:
|
||||||
|
26
proxmox-api-macro/tests/ext-schema.rs
Normal file
26
proxmox-api-macro/tests/ext-schema.rs
Normal 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(())
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user