remove parameter macro, implement ObjectSchema builder

And store "optional" attribute inside properties hash.
This commit is contained in:
Dietmar Maurer 2018-11-23 11:34:15 +01:00
parent 82df76fff0
commit 7edeec7b06
5 changed files with 112 additions and 122 deletions

View File

@ -71,7 +71,6 @@ impl Registry {
"pve-vmid", "pve-vmid",
IntegerSchema::new("The (unique) ID of the VM.") IntegerSchema::new("The (unique) ID of the VM.")
.minimum(1) .minimum(1)
.optional(false)
); );
self.register_option( self.register_option(

View File

@ -41,7 +41,6 @@ impl fmt::Display for ParameterError {
#[derive(Debug)] #[derive(Debug)]
pub struct BooleanSchema { pub struct BooleanSchema {
pub description: &'static str, pub description: &'static str,
pub optional: bool,
pub default: Option<bool>, pub default: Option<bool>,
} }
@ -50,16 +49,10 @@ impl BooleanSchema {
pub fn new(description: &'static str) -> Self { pub fn new(description: &'static str) -> Self {
BooleanSchema { BooleanSchema {
description: description, description: description,
optional: false,
default: None, default: None,
} }
} }
pub fn optional(mut self, optional: bool) -> Self {
self.optional = optional;
self
}
pub fn default(mut self, default: bool) -> Self { pub fn default(mut self, default: bool) -> Self {
self.default = Some(default); self.default = Some(default);
self self
@ -73,7 +66,6 @@ impl BooleanSchema {
#[derive(Debug)] #[derive(Debug)]
pub struct IntegerSchema { pub struct IntegerSchema {
pub description: &'static str, pub description: &'static str,
pub optional: bool,
pub minimum: Option<isize>, pub minimum: Option<isize>,
pub maximum: Option<isize>, pub maximum: Option<isize>,
pub default: Option<isize>, pub default: Option<isize>,
@ -84,18 +76,12 @@ impl IntegerSchema {
pub fn new(description: &'static str) -> Self { pub fn new(description: &'static str) -> Self {
IntegerSchema { IntegerSchema {
description: description, description: description,
optional: false,
default: None, default: None,
minimum: None, minimum: None,
maximum: None, maximum: None,
} }
} }
pub fn optional(mut self, optional: bool) -> Self {
self.optional = optional;
self
}
pub fn default(mut self, default: isize) -> Self { pub fn default(mut self, default: isize) -> Self {
self.default = Some(default); self.default = Some(default);
self self
@ -120,7 +106,6 @@ impl IntegerSchema {
#[derive(Debug)] #[derive(Debug)]
pub struct StringSchema { pub struct StringSchema {
pub description: &'static str, pub description: &'static str,
pub optional: bool,
pub default: Option<&'static str>, pub default: Option<&'static str>,
pub min_length: Option<usize>, pub min_length: Option<usize>,
pub max_length: Option<usize>, pub max_length: Option<usize>,
@ -132,7 +117,6 @@ impl StringSchema {
pub fn new(description: &'static str) -> Self { pub fn new(description: &'static str) -> Self {
StringSchema { StringSchema {
description: description, description: description,
optional: false,
default: None, default: None,
min_length: None, min_length: None,
max_length: None, max_length: None,
@ -140,11 +124,6 @@ impl StringSchema {
} }
} }
pub fn optional(mut self, optional: bool) -> Self {
self.optional = optional;
self
}
pub fn default(mut self, text: &'static str) -> Self { pub fn default(mut self, text: &'static str) -> Self {
self.default = Some(text); self.default = Some(text);
self self
@ -173,16 +152,45 @@ impl StringSchema {
#[derive(Debug)] #[derive(Debug)]
pub struct ArraySchema { pub struct ArraySchema {
pub description: &'static str, pub description: &'static str,
pub optional: bool,
pub items: Arc<Schema>, pub items: Arc<Schema>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct ObjectSchema { pub struct ObjectSchema {
pub description: &'static str, pub description: &'static str,
pub optional: bool,
pub additional_properties: bool, pub additional_properties: bool,
pub properties: HashMap<&'static str, Arc<Schema>>, pub properties: HashMap<&'static str, (bool, Arc<Schema>)>,
}
impl ObjectSchema {
pub fn new(description: &'static str) -> Self {
let properties = HashMap::new();
ObjectSchema {
description: description,
additional_properties: false,
properties: properties,
}
}
pub fn additional_properties(mut self, additional_properties: bool) -> Self {
self.additional_properties = additional_properties;
self
}
pub fn required(mut self, name: &'static str, schema: Arc<Schema>) -> Self {
self.properties.insert(name, (false, schema));
self
}
pub fn optional(mut self, name: &'static str, schema: Arc<Schema>) -> Self {
self.properties.insert(name, (true, schema));
self
}
pub fn arc(self) -> Arc<Schema> {
Arc::new(self.into())
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -213,6 +221,12 @@ impl From<IntegerSchema> for Schema {
} }
} }
impl From<ObjectSchema> for Schema {
fn from(object_schema: ObjectSchema) -> Self {
Schema::Object(object_schema)
}
}
pub enum ApiStringFormat { pub enum ApiStringFormat {
Enum(Vec<String>), Enum(Vec<String>),
Pattern(Box<Regex>), Pattern(Box<Regex>),
@ -239,33 +253,6 @@ impl std::fmt::Debug for ApiStringFormat {
} }
} }
#[macro_export]
macro_rules! parameter {
() => {{
ObjectSchema {
description: "",
optional: false,
additional_properties: false,
properties: HashMap::<&'static str, Arc<Schema>>::new(),
}
}};
($($name:ident => $e:expr),*) => {{
ObjectSchema {
description: "",
optional: false,
additional_properties: false,
properties: {
let mut map = HashMap::<&'static str, Arc<Schema>>::new();
$(
map.insert(stringify!($name), $e);
)*
map
}
}
}};
}
pub fn parse_boolean(value_str: &str) -> Result<bool, Error> { pub fn parse_boolean(value_str: &str) -> Result<bool, Error> {
match value_str.to_lowercase().as_str() { match value_str.to_lowercase().as_str() {
"1" | "on" | "yes" | "true" => Ok(true), "1" | "on" | "yes" | "true" => Ok(true),
@ -357,7 +344,7 @@ pub fn parse_parameter_strings(data: &Vec<(String, String)>, schema: &ObjectSche
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(prop_schema) = properties.get::<str>(key) { if let Some((_optional, prop_schema)) = properties.get::<str>(key) {
match prop_schema.as_ref() { match prop_schema.as_ref() {
Schema::Array(array_schema) => { Schema::Array(array_schema) => {
if params[key] == Value::Null { if params[key] == Value::Null {
@ -408,16 +395,8 @@ pub fn parse_parameter_strings(data: &Vec<(String, String)>, schema: &ObjectSche
} }
if test_required && errors.len() == 0 { if test_required && errors.len() == 0 {
for (name, prop_schema) in properties { for (name, (optional, _prop_schema)) in properties {
let optional = match prop_schema.as_ref() { if *optional == false && params[name] == Value::Null {
Schema::Boolean(boolean_schema) => boolean_schema.optional,
Schema::Integer(integer_schema) => integer_schema.optional,
Schema::String(string_schema) => string_schema.optional,
Schema::Array(array_schema) => array_schema.optional,
Schema::Object(object_schema) => object_schema.optional,
Schema::Null => true,
};
if optional == false && params[name] == Value::Null {
errors.push(format_err!("parameter '{}': parameter is missing and it is not optional.", name)); errors.push(format_err!("parameter '{}': parameter is missing and it is not optional.", name));
} }
} }
@ -442,7 +421,6 @@ pub fn parse_query_string(query: &str, schema: &ObjectSchema, test_required: boo
fn test_schema1() { fn test_schema1() {
let schema = Schema::Object(ObjectSchema { let schema = Schema::Object(ObjectSchema {
description: "TEST", description: "TEST",
optional: false,
additional_properties: false, additional_properties: false,
properties: { properties: {
let map = HashMap::new(); let map = HashMap::new();
@ -457,25 +435,27 @@ fn test_schema1() {
#[test] #[test]
fn test_query_string() { fn test_query_string() {
let schema = parameter!{name => StringSchema::new("Name.").optional(false).arc()}; let schema = ObjectSchema::new("Parameters.")
.required("name", StringSchema::new("Name.").arc());
let res = parse_query_string("", &schema, true); let res = parse_query_string("", &schema, true);
assert!(res.is_err()); assert!(res.is_err());
let schema = parameter!{name => StringSchema::new("Name.").optional(true).arc()}; let schema = ObjectSchema::new("Parameters.")
.optional("name", StringSchema::new("Name.").arc());
let res = parse_query_string("", &schema, true); let res = parse_query_string("", &schema, true);
assert!(res.is_ok()); assert!(res.is_ok());
// TEST min_length and max_length // TEST min_length and max_length
let schema = parameter!{ let schema = ObjectSchema::new("Parameters.")
name => StringSchema::new("Name.") .required(
.optional(false) "name", StringSchema::new("Name.")
.min_length(5) .min_length(5)
.max_length(10) .max_length(10)
.arc() .arc()
}; );
let res = parse_query_string("name=abcd", &schema, true); let res = parse_query_string("name=abcd", &schema, true);
assert!(res.is_err()); assert!(res.is_err());
@ -491,12 +471,12 @@ fn test_query_string() {
// TEST regex pattern // TEST regex pattern
let schema = parameter!{ let schema = ObjectSchema::new("Parameters.")
name => StringSchema::new("Name.") .required(
.optional(false) "name", StringSchema::new("Name.")
.format(Arc::new(ApiStringFormat::Pattern(Box::new(Regex::new("test").unwrap())))) .format(Arc::new(ApiStringFormat::Pattern(Box::new(Regex::new("test").unwrap()))))
.arc() .arc()
}; );
let res = parse_query_string("name=abcd", &schema, true); let res = parse_query_string("name=abcd", &schema, true);
assert!(res.is_err()); assert!(res.is_err());
@ -504,12 +484,12 @@ fn test_query_string() {
let res = parse_query_string("name=ateststring", &schema, true); let res = parse_query_string("name=ateststring", &schema, true);
assert!(res.is_ok()); assert!(res.is_ok());
let schema = parameter!{ let schema = ObjectSchema::new("Parameters.")
name => StringSchema::new("Name.") .required(
.optional(false) "name", StringSchema::new("Name.")
.format(Arc::new(ApiStringFormat::Pattern(Box::new(Regex::new("^test$").unwrap())))) .format(Arc::new(ApiStringFormat::Pattern(Box::new(Regex::new("^test$").unwrap()))))
.arc() .arc()
}; );
let res = parse_query_string("name=ateststring", &schema, true); let res = parse_query_string("name=ateststring", &schema, true);
assert!(res.is_err()); assert!(res.is_err());
@ -519,12 +499,12 @@ fn test_query_string() {
// TEST string enums // TEST string enums
let schema = parameter!{ let schema = ObjectSchema::new("Parameters.")
name => StringSchema::new("Name.") .required(
.optional(false) "name", StringSchema::new("Name.")
.format(Arc::new(ApiStringFormat::Enum(vec!["ev1".into(), "ev2".into()]))) .format(Arc::new(ApiStringFormat::Enum(vec!["ev1".into(), "ev2".into()])))
.arc() .arc()
}; );
let res = parse_query_string("name=noenum", &schema, true); let res = parse_query_string("name=noenum", &schema, true);
assert!(res.is_err()); assert!(res.is_err());
@ -543,18 +523,22 @@ fn test_query_string() {
#[test] #[test]
fn test_query_integer() { fn test_query_integer() {
let schema = parameter!{count => IntegerSchema::new("Count.").optional(false).arc()}; let schema = ObjectSchema::new("Parameters.")
.required(
"count" , IntegerSchema::new("Count.")
.arc()
);
let res = parse_query_string("", &schema, true); let res = parse_query_string("", &schema, true);
assert!(res.is_err()); assert!(res.is_err());
let schema = parameter!{ let schema = ObjectSchema::new("Parameters.")
count => IntegerSchema::new("Count.") .optional(
.optional(true) "count", IntegerSchema::new("Count.")
.minimum(-3) .minimum(-3)
.maximum(50) .maximum(50)
.arc() .arc()
}; );
let res = parse_query_string("", &schema, true); let res = parse_query_string("", &schema, true);
assert!(res.is_ok()); assert!(res.is_ok());
@ -584,12 +568,20 @@ fn test_query_integer() {
#[test] #[test]
fn test_query_boolean() { fn test_query_boolean() {
let schema = parameter!{force => BooleanSchema::new("Force.").optional(false).arc()}; let schema = ObjectSchema::new("Parameters.")
.required(
"force", BooleanSchema::new("Force.")
.arc()
);
let res = parse_query_string("", &schema, true); let res = parse_query_string("", &schema, true);
assert!(res.is_err()); assert!(res.is_err());
let schema = parameter!{force => BooleanSchema::new("Force.").optional(true).arc()}; let schema = ObjectSchema::new("Parameters.")
.optional(
"force", BooleanSchema::new("Force.")
.arc()
);
let res = parse_query_string("", &schema, true); let res = parse_query_string("", &schema, true);
assert!(res.is_ok()); assert!(res.is_ok());

View File

@ -31,7 +31,7 @@ pub fn router() -> Router {
let route3 = Router::new() let route3 = Router::new()
.get(ApiMethod { .get(ApiMethod {
description: "Another Endpoint.", description: "Another Endpoint.",
parameters: parameter!{}, parameters: ObjectSchema::new("Another Endpoint."),
returns: Schema::Null, returns: Schema::Null,
handler: |param, _info| { handler: |param, _info| {
println!("This is a clousure handler: {}", param); println!("This is a clousure handler: {}", param);
@ -44,11 +44,12 @@ pub fn router() -> Router {
.get(ApiMethod { .get(ApiMethod {
handler: test_sync_api_handler, handler: test_sync_api_handler,
description: "This is a simple test.", description: "This is a simple test.",
parameters: parameter!{ parameters: ObjectSchema::new("This is a simple test.")
force => BooleanSchema::new("Test for boolean options") .optional(
.optional(true) "force",
BooleanSchema::new("Test for boolean options")
.arc() .arc()
}, ),
returns: Schema::Null, returns: Schema::Null,
}) })
.subdirs({ .subdirs({

View File

@ -75,7 +75,7 @@ pub fn parse_arguments(
None => { None => {
let mut want_bool = false; let mut want_bool = false;
let mut can_default = false; let mut can_default = false;
if let Some(param_schema) = properties.get::<str>(&name) { if let Some((_optional, param_schema)) = properties.get::<str>(&name) {
if let Schema::Boolean(boolean_schema) = param_schema.as_ref() { if let Schema::Boolean(boolean_schema) = param_schema.as_ref() {
want_bool = true; want_bool = true;
if let Some(default) = boolean_schema.default { if let Some(default) = boolean_schema.default {
@ -156,7 +156,11 @@ pub fn parse_arguments(
#[test] #[test]
fn test_boolean_arg() { fn test_boolean_arg() {
let schema = parameter!{enable => BooleanSchema::new("Enable").optional(false).arc()}; let schema = ObjectSchema::new("Parameters:")
.required(
"enable", BooleanSchema::new("Enable")
.arc()
);
let mut variants: Vec<(Vec<&str>, bool)> = vec![]; let mut variants: Vec<(Vec<&str>, bool)> = vec![];
variants.push((vec!["-enable"], true)); variants.push((vec!["-enable"], true));
@ -186,10 +190,9 @@ fn test_boolean_arg() {
#[test] #[test]
fn test_argument_paramenter() { fn test_argument_paramenter() {
let schema = parameter!{ let schema = ObjectSchema::new("Parameters:")
enable => BooleanSchema::new("Enable.").optional(false).arc(), .required("enable", BooleanSchema::new("Enable.").arc())
storage => StringSchema::new("Storatge:").optional(false).arc() .required("storage", StringSchema::new("Storage.").arc());
};
let args = vec!["-enable", "local"]; let args = vec!["-enable", "local"];
let string_args = args.iter().map(|s| s.to_string()).collect(); let string_args = args.iter().map(|s| s.to_string()).collect();

View File

@ -1,9 +1,5 @@
#[macro_use]
extern crate apitest; extern crate apitest;
use std::collections::HashMap;
use std::sync::Arc;
use apitest::api::schema::*; use apitest::api::schema::*;
use apitest::api::router::*; use apitest::api::router::*;
use apitest::api::config::*; use apitest::api::config::*;
@ -23,10 +19,9 @@ fn main() {
let prop = StringSchema::new("This is a test").arc(); let prop = StringSchema::new("This is a test").arc();
//let prop = Arc::new(ApiString!{ optional => true }); //let prop = Arc::new(ApiString!{ optional => true });
let schema = parameter!{ let schema = ObjectSchema::new("Parameters.")
name1 => prop.clone(), .required("name1", prop.clone())
name2 => prop.clone() .required("name2", prop.clone());
};
let args: Vec<String> = std::env::args().skip(1).collect(); let args: Vec<String> = std::env::args().skip(1).collect();
match getopts::parse_arguments(&args, &vec![], &schema) { match getopts::parse_arguments(&args, &vec![], &schema) {