diff --git a/src/api/registry.rs b/src/api/registry.rs new file mode 100644 index 00000000..c2bcc28e --- /dev/null +++ b/src/api/registry.rs @@ -0,0 +1,91 @@ +use crate::api::schema::*; + +use failure::*; +use std::collections::HashMap; +use std::sync::Arc; + +pub struct Registry { + formats: HashMap<&'static str, Arc>, + options: HashMap<&'static str, Arc>, +} + +impl Registry { + + pub fn new() -> Self { + + let mut me = Self { + formats: HashMap::new(), + options: HashMap::new(), + }; + + me.initialize_formats(); + + me.initialize_options(); + + me + } + + pub fn register_format(&mut self, name: &'static str, format: ApiStringFormat) { + + if let Some(format) = self.formats.get(name) { + panic!("standard format '{}' already registered.", name); // fixme: really panic? + } + + self.formats.insert(name, Arc::new(format)); + } + + pub fn lookup_format(&self, name: &str) -> Option> { + + if let Some(format) = self.formats.get(name) { + return Some(format.clone()); + } + None + } + + pub fn register_option(&mut self, name: &'static str, schema: Schema) { + + if let Some(schema) = self.options.get(name) { + panic!("standard option '{}' already registered.", name); // fixme: really panic? + } + + self.options.insert(name, Arc::new(schema)); + } + + pub fn lookup_option(&self, name: &str) -> Option> { + + if let Some(schema) = self.options.get(name) { + return Some(schema.clone()); + } + None + } + + fn initialize_formats(&mut self) { + + self.register_format("pve-node", ApiStringFormat::VerifyFn(verify_pve_node)); + + } + + fn initialize_options(&mut self) { + + self.register_option( + "pve-vmid", + Integer!{ + description => "The (unique) ID of the VM.", + minimum => Some(1), + optional => false + }); + + self.register_option( + "pve-node", + ApiString!{ + description => "The cluster node name.", + format => self.lookup_format("pve-node") + }); + } + +} + +fn verify_pve_node(value: &str) -> Result<(), Error> { + + Ok(()) +} diff --git a/src/api/schema.rs b/src/api/schema.rs index c4319260..2f8f6409 100644 --- a/src/api/schema.rs +++ b/src/api/schema.rs @@ -61,7 +61,7 @@ pub struct StringSchema { pub default: Option<&'static str>, pub min_length: Option, pub max_length: Option, - pub format: ApiStringFormat, + pub format: Option>, } #[derive(Debug)] @@ -123,15 +123,33 @@ pub const DEFAULTSTRING: StringSchema = StringSchema { default: None, min_length: None, max_length: None, - format: ApiStringFormat::None, + format: None, }; -#[derive(Debug)] pub enum ApiStringFormat { - None, Enum(Vec), Pattern(Box), Complex(Arc), + VerifyFn(fn(&str) -> Result<(), Error>), +} + +impl std::fmt::Debug for ApiStringFormat { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ApiStringFormat::VerifyFn(fnptr) => { + write!(f, "VerifyFn({:p}", fnptr) + } + ApiStringFormat::Enum(strvec) => { + write!(f, "Enum({:?}", strvec) + } + ApiStringFormat::Pattern(regex) => { + write!(f, "Pattern({:?}", regex) + } + ApiStringFormat::Complex(schema) => { + write!(f, "Complex({:?}", schema) + } + } + } } #[macro_export] @@ -219,20 +237,24 @@ fn parse_simple_value(value_str: &str, schema: &Schema) -> Result } } - match string_schema.format { - ApiStringFormat::None => { /* do nothing */ } - ApiStringFormat::Pattern(ref regex) => { - if !regex.is_match(&res) { - bail!("value does not match the regex pattern"); + if let Some(ref format) = string_schema.format { + match format.as_ref() { + ApiStringFormat::Pattern(ref regex) => { + if !regex.is_match(&res) { + bail!("value does not match the regex pattern"); + } } - } - ApiStringFormat::Enum(ref stringvec) => { - if stringvec.iter().find(|&e| *e == res) == None { - bail!("value is not defined in the enumeration."); + ApiStringFormat::Enum(ref stringvec) => { + if stringvec.iter().find(|&e| *e == res) == None { + bail!("value is not defined in the enumeration."); + } + } + ApiStringFormat::Complex(ref _subschema) => { + bail!("implement me!"); + } + ApiStringFormat::VerifyFn(verify_fn) => { + verify_fn(&res)?; } - } - ApiStringFormat::Complex(ref _subschema) => { - bail!("implement me!"); } } @@ -390,7 +412,7 @@ fn test_query_string() { let schema = parameter!{name => Arc::new(ApiString!{ optional => false, - format => ApiStringFormat::Pattern(Box::new(Regex::new("test").unwrap())) + format => Some(Arc::new(ApiStringFormat::Pattern(Box::new(Regex::new("test").unwrap())))) })}; let res = parse_query_string("name=abcd", &schema, true); @@ -401,7 +423,7 @@ fn test_query_string() { let schema = parameter!{name => Arc::new(ApiString!{ optional => false, - format => ApiStringFormat::Pattern(Box::new(Regex::new("^test$").unwrap())) + format => Some(Arc::new(ApiStringFormat::Pattern(Box::new(Regex::new("^test$").unwrap())))) })}; let res = parse_query_string("name=ateststring", &schema, true); @@ -414,7 +436,7 @@ fn test_query_string() { let schema = parameter!{name => Arc::new(ApiString!{ optional => false, - format => ApiStringFormat::Enum(vec!["ev1".into(), "ev2".into()]) + format => Some(Arc::new(ApiStringFormat::Enum(vec!["ev1".into(), "ev2".into()]))) })}; let res = parse_query_string("name=noenum", &schema, true); diff --git a/src/lib.rs b/src/lib.rs index 8dd39e4b..3ebf84fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ pub mod api { #[macro_use] pub mod schema; + pub mod registry; #[macro_use] pub mod router; pub mod config; diff --git a/src/main.rs b/src/main.rs index 9c97bfde..073b40ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,8 +20,10 @@ use hyper; fn main() { println!("Proxmox REST Server example."); + let prop = Arc::new(ApiString!{ optional => true }); let schema = parameter!{ - name => Arc::new(ApiString!{ optional => true }) + name1 => prop.clone(), + name2 => prop.clone() }; let args: Vec = std::env::args().skip(1).collect();