mirror of
https://git.proxmox.com/git/proxmox
synced 2025-05-17 06:24:49 +00:00
schema: allow AllOf schema as method parameter
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
3f490368cf
commit
0cdd47c8fc
@ -32,7 +32,7 @@ fn parse_arguments(prefix: &str, cli_cmd: &CliCommand, args: Vec<String>) -> Res
|
|||||||
&args,
|
&args,
|
||||||
cli_cmd.arg_param,
|
cli_cmd.arg_param,
|
||||||
&cli_cmd.fixed_param,
|
&cli_cmd.fixed_param,
|
||||||
&cli_cmd.info.parameters,
|
cli_cmd.info.parameters,
|
||||||
) {
|
) {
|
||||||
Ok((p, r)) => (p, r),
|
Ok((p, r)) => (p, r),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use crate::api::router::ParameterSchema;
|
||||||
use crate::api::schema::*;
|
use crate::api::schema::*;
|
||||||
|
|
||||||
fn record_done_argument(
|
fn record_done_argument(
|
||||||
done: &mut HashMap<String, String>,
|
done: &mut HashMap<String, String>,
|
||||||
parameters: &ObjectSchema,
|
parameters: ParameterSchema,
|
||||||
key: &str,
|
key: &str,
|
||||||
value: &str,
|
value: &str,
|
||||||
) {
|
) {
|
||||||
@ -119,11 +120,11 @@ fn get_simple_completion(
|
|||||||
let mut errors = ParameterError::new(); // we simply ignore any parsing errors here
|
let mut errors = ParameterError::new(); // we simply ignore any parsing errors here
|
||||||
let (data, _remaining) = getopts::parse_argument_list(
|
let (data, _remaining) = getopts::parse_argument_list(
|
||||||
&args[0..args.len() - 1],
|
&args[0..args.len() - 1],
|
||||||
&cli_cmd.info.parameters,
|
cli_cmd.info.parameters,
|
||||||
&mut errors,
|
&mut errors,
|
||||||
);
|
);
|
||||||
for (key, value) in &data {
|
for (key, value) in &data {
|
||||||
record_done_argument(done, &cli_cmd.info.parameters, key, value);
|
record_done_argument(done, cli_cmd.info.parameters, key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +149,7 @@ fn get_simple_completion(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut completions = Vec::new();
|
let mut completions = Vec::new();
|
||||||
for (name, _optional, _schema) in cli_cmd.info.parameters.properties {
|
for (name, _optional, _schema) in cli_cmd.info.parameters.properties() {
|
||||||
if done.contains_key(*name) {
|
if done.contains_key(*name) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -210,7 +211,7 @@ fn get_nested_completion(def: &CommandLineInterface, args: &[String]) -> Vec<Str
|
|||||||
CommandLineInterface::Simple(cli_cmd) => {
|
CommandLineInterface::Simple(cli_cmd) => {
|
||||||
let mut done: HashMap<String, String> = HashMap::new();
|
let mut done: HashMap<String, String> = HashMap::new();
|
||||||
cli_cmd.fixed_param.iter().for_each(|(key, value)| {
|
cli_cmd.fixed_param.iter().for_each(|(key, value)| {
|
||||||
record_done_argument(&mut done, &cli_cmd.info.parameters, &key, &value);
|
record_done_argument(&mut done, cli_cmd.info.parameters, &key, &value);
|
||||||
});
|
});
|
||||||
get_simple_completion(cli_cmd, &mut done, &cli_cmd.arg_param, args)
|
get_simple_completion(cli_cmd, &mut done, &cli_cmd.arg_param, args)
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ pub fn generate_usage_str(
|
|||||||
|
|
||||||
let mut options = String::new();
|
let mut options = String::new();
|
||||||
|
|
||||||
for (prop, optional, param_schema) in schema.properties {
|
for (prop, optional, param_schema) in schema.properties() {
|
||||||
if done_hash.contains(prop) {
|
if done_hash.contains(prop) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -150,11 +150,18 @@ pub fn generate_usage_str(
|
|||||||
DocumentationFormat::Long => format!("{}{}{}{}\n", indent, prefix, args, option_indicator),
|
DocumentationFormat::Long => format!("{}{}{}{}\n", indent, prefix, args, option_indicator),
|
||||||
DocumentationFormat::Full => format!(
|
DocumentationFormat::Full => format!(
|
||||||
"{}{}{}{}\n\n{}\n\n",
|
"{}{}{}{}\n\n{}\n\n",
|
||||||
indent, prefix, args, option_indicator, schema.description
|
indent,
|
||||||
|
prefix,
|
||||||
|
args,
|
||||||
|
option_indicator,
|
||||||
|
schema.description()
|
||||||
),
|
),
|
||||||
DocumentationFormat::ReST => format!(
|
DocumentationFormat::ReST => format!(
|
||||||
"``{}{}{}``\n\n{}\n\n",
|
"``{}{}{}``\n\n{}\n\n",
|
||||||
prefix, args, option_indicator, schema.description
|
prefix,
|
||||||
|
args,
|
||||||
|
option_indicator,
|
||||||
|
schema.description()
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ use std::collections::HashMap;
|
|||||||
use anyhow::*;
|
use anyhow::*;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::api::router::ParameterSchema;
|
||||||
use crate::api::schema::*;
|
use crate::api::schema::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -57,7 +58,7 @@ fn parse_argument(arg: &str) -> RawArgument {
|
|||||||
/// Returns parsed data and the remaining arguments as two separate array
|
/// Returns parsed data and the remaining arguments as two separate array
|
||||||
pub(crate) fn parse_argument_list<T: AsRef<str>>(
|
pub(crate) fn parse_argument_list<T: AsRef<str>>(
|
||||||
args: &[T],
|
args: &[T],
|
||||||
schema: &ObjectSchema,
|
schema: ParameterSchema,
|
||||||
errors: &mut ParameterError,
|
errors: &mut ParameterError,
|
||||||
) -> (Vec<(String, String)>, Vec<String>) {
|
) -> (Vec<(String, String)>, Vec<String>) {
|
||||||
let mut data: Vec<(String, String)> = vec![];
|
let mut data: Vec<(String, String)> = vec![];
|
||||||
@ -149,7 +150,7 @@ pub fn parse_arguments<T: AsRef<str>>(
|
|||||||
args: &[T],
|
args: &[T],
|
||||||
arg_param: &[&str],
|
arg_param: &[&str],
|
||||||
fixed_param: &HashMap<&'static str, String>,
|
fixed_param: &HashMap<&'static str, String>,
|
||||||
schema: &ObjectSchema,
|
schema: ParameterSchema,
|
||||||
) -> Result<(Value, Vec<String>), ParameterError> {
|
) -> Result<(Value, Vec<String>), ParameterError> {
|
||||||
let mut errors = ParameterError::new();
|
let mut errors = ParameterError::new();
|
||||||
|
|
||||||
@ -229,7 +230,12 @@ fn test_boolean_arg() {
|
|||||||
variants.push((vec!["--enable", "false"], false));
|
variants.push((vec!["--enable", "false"], false));
|
||||||
|
|
||||||
for (args, expect) in variants {
|
for (args, expect) in variants {
|
||||||
let res = parse_arguments(&args, &vec![], &HashMap::new(), &PARAMETERS);
|
let res = parse_arguments(
|
||||||
|
&args,
|
||||||
|
&vec![],
|
||||||
|
&HashMap::new(),
|
||||||
|
ParameterSchema::from(&PARAMETERS),
|
||||||
|
);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
if let Ok((options, remaining)) = res {
|
if let Ok((options, remaining)) = res {
|
||||||
assert!(options["enable"] == expect);
|
assert!(options["enable"] == expect);
|
||||||
@ -249,7 +255,12 @@ fn test_argument_paramenter() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let args = vec!["-enable", "local"];
|
let args = vec!["-enable", "local"];
|
||||||
let res = parse_arguments(&args, &vec!["storage"], &HashMap::new(), &PARAMETERS);
|
let res = parse_arguments(
|
||||||
|
&args,
|
||||||
|
&vec!["storage"],
|
||||||
|
&HashMap::new(),
|
||||||
|
ParameterSchema::from(&PARAMETERS),
|
||||||
|
);
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
if let Ok((options, remaining)) = res {
|
if let Ok((options, remaining)) = res {
|
||||||
assert!(options["enable"] == true);
|
assert!(options["enable"] == true);
|
||||||
|
@ -256,7 +256,7 @@ fn dump_method_definition(method: &str, path: &str, def: Option<&ApiMethod>) ->
|
|||||||
match def {
|
match def {
|
||||||
None => None,
|
None => None,
|
||||||
Some(api_method) => {
|
Some(api_method) => {
|
||||||
let param_descr = dump_api_parameters(api_method.parameters);
|
let param_descr = dump_api_parameters(&api_method.parameters);
|
||||||
|
|
||||||
let return_descr = dump_api_return_schema(&api_method.returns);
|
let return_descr = dump_api_return_schema(&api_method.returns);
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ use hyper::Body;
|
|||||||
use percent_encoding::percent_decode_str;
|
use percent_encoding::percent_decode_str;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::api::schema::{self, ObjectSchema, Schema};
|
use crate::api::schema::{self, AllOfSchema, ObjectSchema, Schema};
|
||||||
use crate::api::RpcEnvironment;
|
use crate::api::RpcEnvironment;
|
||||||
|
|
||||||
use super::Permission;
|
use super::Permission;
|
||||||
@ -36,10 +36,11 @@ use super::Permission;
|
|||||||
/// &ObjectSchema::new("Hello World Example", &[])
|
/// &ObjectSchema::new("Hello World Example", &[])
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
pub type ApiHandlerFn = &'static (dyn Fn(Value, &ApiMethod, &mut dyn RpcEnvironment) -> Result<Value, Error>
|
pub type ApiHandlerFn =
|
||||||
+ Send
|
&'static (dyn Fn(Value, &ApiMethod, &mut dyn RpcEnvironment) -> Result<Value, Error>
|
||||||
+ Sync
|
+ Send
|
||||||
+ 'static);
|
+ Sync
|
||||||
|
+ 'static);
|
||||||
|
|
||||||
/// Asynchronous API handlers
|
/// Asynchronous API handlers
|
||||||
///
|
///
|
||||||
@ -67,7 +68,11 @@ pub type ApiHandlerFn = &'static (dyn Fn(Value, &ApiMethod, &mut dyn RpcEnvironm
|
|||||||
/// &ObjectSchema::new("Hello World Example (async)", &[])
|
/// &ObjectSchema::new("Hello World Example (async)", &[])
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
pub type ApiAsyncHandlerFn = &'static (dyn for<'a> Fn(Value, &'static ApiMethod, &'a mut dyn RpcEnvironment) -> ApiFuture<'a>
|
pub type ApiAsyncHandlerFn = &'static (dyn for<'a> Fn(
|
||||||
|
Value,
|
||||||
|
&'static ApiMethod,
|
||||||
|
&'a mut dyn RpcEnvironment,
|
||||||
|
) -> ApiFuture<'a>
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync);
|
+ Sync);
|
||||||
|
|
||||||
@ -425,6 +430,59 @@ impl ReturnType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parameters are objects, but we have two types of object schemas, the regular one and the
|
||||||
|
/// `AllOf` schema.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
|
||||||
|
pub enum ParameterSchema {
|
||||||
|
Object(&'static ObjectSchema),
|
||||||
|
AllOf(&'static AllOfSchema),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl schema::ObjectSchemaType for ParameterSchema {
|
||||||
|
type PropertyIter = Box<dyn Iterator<Item = &'static schema::SchemaPropertyEntry>>;
|
||||||
|
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ParameterSchema::Object(o) => o.description(),
|
||||||
|
ParameterSchema::AllOf(o) => o.description(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup(&self, key: &str) -> Option<(bool, &Schema)> {
|
||||||
|
match self {
|
||||||
|
ParameterSchema::Object(o) => o.lookup(key),
|
||||||
|
ParameterSchema::AllOf(o) => o.lookup(key),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn properties(&self) -> Self::PropertyIter {
|
||||||
|
match self {
|
||||||
|
ParameterSchema::Object(o) => Box::new(o.properties()),
|
||||||
|
ParameterSchema::AllOf(o) => Box::new(o.properties()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn additional_properties(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ParameterSchema::Object(o) => o.additional_properties(),
|
||||||
|
ParameterSchema::AllOf(o) => o.additional_properties(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static ObjectSchema> for ParameterSchema {
|
||||||
|
fn from(schema: &'static ObjectSchema) -> Self {
|
||||||
|
ParameterSchema::Object(schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static AllOfSchema> for ParameterSchema {
|
||||||
|
fn from(schema: &'static AllOfSchema) -> Self {
|
||||||
|
ParameterSchema::AllOf(schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This struct defines a synchronous API call which returns the result as json `Value`
|
/// This struct defines a synchronous API call which returns the result as json `Value`
|
||||||
#[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
|
#[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
|
||||||
pub struct ApiMethod {
|
pub struct ApiMethod {
|
||||||
@ -435,7 +493,7 @@ pub struct ApiMethod {
|
|||||||
/// should do a tzset afterwards
|
/// should do a tzset afterwards
|
||||||
pub reload_timezone: bool,
|
pub reload_timezone: bool,
|
||||||
/// Parameter type Schema
|
/// Parameter type Schema
|
||||||
pub parameters: &'static schema::ObjectSchema,
|
pub parameters: ParameterSchema,
|
||||||
/// Return type Schema
|
/// Return type Schema
|
||||||
pub returns: ReturnType,
|
pub returns: ReturnType,
|
||||||
/// Handler function
|
/// Handler function
|
||||||
@ -456,7 +514,7 @@ impl std::fmt::Debug for ApiMethod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ApiMethod {
|
impl ApiMethod {
|
||||||
pub const fn new(handler: &'static ApiHandler, parameters: &'static ObjectSchema) -> Self {
|
pub const fn new_full(handler: &'static ApiHandler, parameters: ParameterSchema) -> Self {
|
||||||
Self {
|
Self {
|
||||||
parameters,
|
parameters,
|
||||||
handler,
|
handler,
|
||||||
@ -470,9 +528,13 @@ impl ApiMethod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn new(handler: &'static ApiHandler, parameters: &'static ObjectSchema) -> Self {
|
||||||
|
Self::new_full(handler, ParameterSchema::Object(parameters))
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn new_dummy(parameters: &'static ObjectSchema) -> Self {
|
pub const fn new_dummy(parameters: &'static ObjectSchema) -> Self {
|
||||||
Self {
|
Self {
|
||||||
parameters,
|
parameters: ParameterSchema::Object(parameters),
|
||||||
handler: &DUMMY_HANDLER,
|
handler: &DUMMY_HANDLER,
|
||||||
returns: ReturnType::new(false, &NULL_SCHEMA),
|
returns: ReturnType::new(false, &NULL_SCHEMA),
|
||||||
protected: false,
|
protected: false,
|
||||||
|
@ -10,6 +10,7 @@ use anyhow::{bail, format_err, Error};
|
|||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use url::form_urlencoded;
|
use url::form_urlencoded;
|
||||||
|
|
||||||
|
use super::router::ParameterSchema;
|
||||||
use crate::api::const_regex::ConstRegexPattern;
|
use crate::api::const_regex::ConstRegexPattern;
|
||||||
|
|
||||||
/// Error type for schema validation
|
/// Error type for schema validation
|
||||||
@ -764,7 +765,7 @@ pub fn parse_boolean(value_str: &str) -> Result<bool, Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a complex property string (`ApiStringFormat::PropertyString`)
|
/// Parse a complex property string (`ApiStringFormat::PropertyString`)
|
||||||
pub fn parse_property_string(value_str: &str, schema: &Schema) -> Result<Value, Error> {
|
pub fn parse_property_string(value_str: &str, schema: &'static Schema) -> Result<Value, Error> {
|
||||||
match schema {
|
match schema {
|
||||||
Schema::Object(object_schema) => {
|
Schema::Object(object_schema) => {
|
||||||
let mut param_list: Vec<(String, String)> = vec![];
|
let mut param_list: Vec<(String, String)> = vec![];
|
||||||
@ -783,7 +784,7 @@ pub fn parse_property_string(value_str: &str, schema: &Schema) -> Result<Value,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_parameter_strings(¶m_list, &object_schema, true).map_err(Error::from)
|
parse_parameter_strings(¶m_list, object_schema, true).map_err(Error::from)
|
||||||
}
|
}
|
||||||
Schema::Array(array_schema) => {
|
Schema::Array(array_schema) => {
|
||||||
let mut array: Vec<Value> = vec![];
|
let mut array: Vec<Value> = vec![];
|
||||||
@ -839,16 +840,24 @@ pub fn parse_simple_value(value_str: &str, schema: &Schema) -> Result<Value, Err
|
|||||||
///
|
///
|
||||||
/// - `test_required`: is set, checks if all required properties are
|
/// - `test_required`: is set, checks if all required properties are
|
||||||
/// present.
|
/// present.
|
||||||
pub fn parse_parameter_strings(
|
pub fn parse_parameter_strings<T: Into<ParameterSchema>>(
|
||||||
data: &[(String, String)],
|
data: &[(String, String)],
|
||||||
schema: &ObjectSchema,
|
schema: T,
|
||||||
|
test_required: bool,
|
||||||
|
) -> Result<Value, ParameterError> {
|
||||||
|
do_parse_parameter_strings(data, schema.into(), test_required)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_parse_parameter_strings(
|
||||||
|
data: &[(String, String)],
|
||||||
|
schema: ParameterSchema,
|
||||||
test_required: bool,
|
test_required: bool,
|
||||||
) -> Result<Value, ParameterError> {
|
) -> Result<Value, ParameterError> {
|
||||||
let mut params = json!({});
|
let mut params = json!({});
|
||||||
|
|
||||||
let mut errors = ParameterError::new();
|
let mut errors = ParameterError::new();
|
||||||
|
|
||||||
let additional_properties = schema.additional_properties;
|
let additional_properties = schema.additional_properties();
|
||||||
|
|
||||||
for (key, value) in data {
|
for (key, value) in data {
|
||||||
if let Some((_optional, prop_schema)) = schema.lookup(&key) {
|
if let Some((_optional, prop_schema)) = schema.lookup(&key) {
|
||||||
@ -911,7 +920,7 @@ pub fn parse_parameter_strings(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if test_required && errors.is_empty() {
|
if test_required && errors.is_empty() {
|
||||||
for (name, optional, _prop_schema) in schema.properties {
|
for (name, optional, _prop_schema) in schema.properties() {
|
||||||
if !(*optional) && params[name] == Value::Null {
|
if !(*optional) && params[name] == Value::Null {
|
||||||
errors.push(format_err!(
|
errors.push(format_err!(
|
||||||
"parameter '{}': parameter is missing and it is not optional.",
|
"parameter '{}': parameter is missing and it is not optional.",
|
||||||
@ -931,16 +940,16 @@ pub fn parse_parameter_strings(
|
|||||||
/// Parse a `form_urlencoded` query string and verify with object schema
|
/// Parse a `form_urlencoded` query string and verify with object schema
|
||||||
/// - `test_required`: is set, checks if all required properties are
|
/// - `test_required`: is set, checks if all required properties are
|
||||||
/// present.
|
/// present.
|
||||||
pub fn parse_query_string(
|
pub fn parse_query_string<T: Into<ParameterSchema>>(
|
||||||
query: &str,
|
query: &str,
|
||||||
schema: &ObjectSchema,
|
schema: T,
|
||||||
test_required: bool,
|
test_required: bool,
|
||||||
) -> Result<Value, ParameterError> {
|
) -> Result<Value, ParameterError> {
|
||||||
let param_list: Vec<(String, String)> = form_urlencoded::parse(query.as_bytes())
|
let param_list: Vec<(String, String)> = form_urlencoded::parse(query.as_bytes())
|
||||||
.into_owned()
|
.into_owned()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
parse_parameter_strings(¶m_list, schema, test_required)
|
parse_parameter_strings(¶m_list, schema.into(), test_required)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify JSON value with `schema`.
|
/// Verify JSON value with `schema`.
|
||||||
|
Loading…
Reference in New Issue
Block a user