implement schema registry

This commit is contained in:
Dietmar Maurer 2018-11-22 11:23:49 +01:00
parent 00c908df85
commit dc454ef096
4 changed files with 136 additions and 20 deletions

91
src/api/registry.rs Normal file
View File

@ -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<ApiStringFormat>>,
options: HashMap<&'static str, Arc<Schema>>,
}
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<Arc<ApiStringFormat>> {
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<Arc<Schema>> {
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(())
}

View File

@ -61,7 +61,7 @@ pub struct StringSchema {
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>,
pub format: ApiStringFormat, pub format: Option<Arc<ApiStringFormat>>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -123,15 +123,33 @@ pub const DEFAULTSTRING: StringSchema = StringSchema {
default: None, default: None,
min_length: None, min_length: None,
max_length: None, max_length: None,
format: ApiStringFormat::None, format: None,
}; };
#[derive(Debug)]
pub enum ApiStringFormat { pub enum ApiStringFormat {
None,
Enum(Vec<String>), Enum(Vec<String>),
Pattern(Box<Regex>), Pattern(Box<Regex>),
Complex(Arc<Schema>), Complex(Arc<Schema>),
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] #[macro_export]
@ -219,8 +237,8 @@ fn parse_simple_value(value_str: &str, schema: &Schema) -> Result<Value, Error>
} }
} }
match string_schema.format { if let Some(ref format) = string_schema.format {
ApiStringFormat::None => { /* do nothing */ } match format.as_ref() {
ApiStringFormat::Pattern(ref regex) => { ApiStringFormat::Pattern(ref regex) => {
if !regex.is_match(&res) { if !regex.is_match(&res) {
bail!("value does not match the regex pattern"); bail!("value does not match the regex pattern");
@ -234,6 +252,10 @@ fn parse_simple_value(value_str: &str, schema: &Schema) -> Result<Value, Error>
ApiStringFormat::Complex(ref _subschema) => { ApiStringFormat::Complex(ref _subschema) => {
bail!("implement me!"); bail!("implement me!");
} }
ApiStringFormat::VerifyFn(verify_fn) => {
verify_fn(&res)?;
}
}
} }
Value::String(res) Value::String(res)
@ -390,7 +412,7 @@ fn test_query_string() {
let schema = parameter!{name => Arc::new(ApiString!{ let schema = parameter!{name => Arc::new(ApiString!{
optional => false, 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); let res = parse_query_string("name=abcd", &schema, true);
@ -401,7 +423,7 @@ fn test_query_string() {
let schema = parameter!{name => Arc::new(ApiString!{ let schema = parameter!{name => Arc::new(ApiString!{
optional => false, 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); let res = parse_query_string("name=ateststring", &schema, true);
@ -414,7 +436,7 @@ fn test_query_string() {
let schema = parameter!{name => Arc::new(ApiString!{ let schema = parameter!{name => Arc::new(ApiString!{
optional => false, 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); let res = parse_query_string("name=noenum", &schema, true);

View File

@ -5,6 +5,7 @@ pub mod api {
#[macro_use] #[macro_use]
pub mod schema; pub mod schema;
pub mod registry;
#[macro_use] #[macro_use]
pub mod router; pub mod router;
pub mod config; pub mod config;

View File

@ -20,8 +20,10 @@ use hyper;
fn main() { fn main() {
println!("Proxmox REST Server example."); println!("Proxmox REST Server example.");
let prop = Arc::new(ApiString!{ optional => true });
let schema = parameter!{ let schema = parameter!{
name => Arc::new(ApiString!{ optional => true }) name1 => prop.clone(),
name2 => prop.clone()
}; };
let args: Vec<String> = std::env::args().skip(1).collect(); let args: Vec<String> = std::env::args().skip(1).collect();