]> git.proxmox.com Git - proxmox-backup.git/commitdiff
rename src/api to src/api_schema
authorDietmar Maurer <dietmar@proxmox.com>
Sun, 17 Feb 2019 08:59:20 +0000 (09:59 +0100)
committerDietmar Maurer <dietmar@proxmox.com>
Sun, 17 Feb 2019 08:59:20 +0000 (09:59 +0100)
39 files changed:
src/api/config.rs [deleted file]
src/api/registry.rs [deleted file]
src/api/router.rs [deleted file]
src/api/schema.rs [deleted file]
src/api2.rs
src/api2/access.rs
src/api2/admin.rs
src/api2/admin/datastore.rs
src/api2/admin/datastore/catar.rs
src/api2/config.rs
src/api2/config/datastore.rs
src/api2/node.rs
src/api2/node/dns.rs
src/api2/node/network.rs
src/api2/node/services.rs
src/api2/node/syslog.rs
src/api2/node/time.rs
src/api2/subscription.rs
src/api2/version.rs
src/api_schema.rs [new file with mode: 0644]
src/api_schema/config.rs [new file with mode: 0644]
src/api_schema/registry.rs [new file with mode: 0644]
src/api_schema/router.rs [new file with mode: 0644]
src/api_schema/schema.rs [new file with mode: 0644]
src/bin/catar.rs
src/bin/proxmox-backup-api.rs
src/bin/proxmox-backup-client.rs
src/bin/proxmox-backup-proxy.rs
src/cli/command.rs
src/cli/environment.rs
src/client/backup_repo.rs
src/config/datastore.rs
src/getopts.rs
src/lib.rs
src/section_config.rs
src/server/environment.rs
src/server/formatter.rs
src/server/rest.rs
src/storage/config.rs

diff --git a/src/api/config.rs b/src/api/config.rs
deleted file mode 100644 (file)
index 2bb9e86..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-use crate::api::router::*;
-
-use std::collections::HashMap;
-use std::path::{PathBuf};
-
-use hyper::Method;
-
-pub struct ApiConfig {
-    basedir: PathBuf,
-    router: &'static Router,
-    aliases: HashMap<String, PathBuf>,
-    env_type: RpcEnvironmentType,
-}
-
-impl ApiConfig {
-
-    pub fn new<B: Into<PathBuf>>(basedir: B, router: &'static Router, env_type: RpcEnvironmentType) -> Self {
-        Self {
-            basedir: basedir.into(),
-            router: router,
-            aliases: HashMap::new(),
-            env_type,
-        }
-    }
-
-    pub fn find_method(&self, components: &[&str], method: Method, uri_param: &mut HashMap<String, String>) -> &'static MethodDefinition {
-
-        if let Some(info) = self.router.find_route(components, uri_param) {
-            return match method {
-                Method::GET => &info.get,
-                Method::PUT => &info.put,
-                Method::POST => &info.post,
-                Method::DELETE => &info.delete,
-                _ => &MethodDefinition::None,
-            };
-        }
-        &MethodDefinition::None
-    }
-
-    pub fn find_alias(&self, components: &[&str]) -> PathBuf {
-
-        let mut prefix = String::new();
-        let mut filename = self.basedir.clone();
-        let comp_len = components.len();
-        if comp_len >= 1 {
-            prefix.push_str(components[0]);
-            if let Some(subdir) = self.aliases.get(&prefix) {
-                filename.push(subdir);
-                for i in 1..comp_len { filename.push(components[i]) }
-            } else {
-                for i in 0..comp_len { filename.push(components[i]) }
-            }
-        }
-        filename
-    }
-
-    pub fn add_alias<S, P>(&mut self, alias: S, path: P)
-        where S: Into<String>,
-              P: Into<PathBuf>,
-    {
-        self.aliases.insert(alias.into(), path.into());
-    }
-
-    pub fn env_type(&self) -> RpcEnvironmentType {
-        self.env_type
-    }
-}
diff --git a/src/api/registry.rs b/src/api/registry.rs
deleted file mode 100644 (file)
index a58562d..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-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<S: Into<Schema>>(&mut self, name: &'static str, schema: S) {
-
-        if let Some(_schema) = self.options.get(name) {
-            panic!("standard option '{}' already registered.", name); // fixme: really panic?
-        }
-
-        self.options.insert(name, Arc::new(schema.into()));
-    }
-
-    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",
-            IntegerSchema::new("The (unique) ID of the VM.")
-                .minimum(1)
-        );
-
-        self.register_option(
-            "pve-node",
-            StringSchema::new("The cluster node name.")
-                .format(self.lookup_format("pve-node").unwrap()) //fixme: unwrap?
-        );
-     }
-
-}
-
-fn verify_pve_node(_value: &str) -> Result<(), Error> {
-
-    // fixme: ??
-    Ok(())
-}
diff --git a/src/api/router.rs b/src/api/router.rs
deleted file mode 100644 (file)
index 01a71db..0000000
+++ /dev/null
@@ -1,253 +0,0 @@
-use failure::*;
-
-use crate::api::schema::*;
-use serde_json::{Value};
-use std::collections::HashMap;
-use std::sync::Arc;
-use std::fmt;
-
-use hyper::{Body, Response, StatusCode};
-use hyper::rt::Future;
-use hyper::http::request::Parts;
-
-pub type BoxFut = Box<Future<Item = Response<Body>, Error = failure::Error> + Send>;
-
-pub trait RpcEnvironment {
-
-    fn set_result_attrib(&mut self, name: &str, value: Value);
-
-    fn get_result_attrib(&self, name: &str) -> Option<&Value>;
-
-    fn env_type(&self) -> RpcEnvironmentType;
-
-    fn set_user(&mut self, user: Option<String>);
-
-    fn get_user(&self) -> Option<String>;
-}
-
-#[derive(PartialEq, Copy, Clone)]
-pub enum RpcEnvironmentType {
-    ///  command started from command line
-    CLI,
-    /// access from public acessable server
-    PUBLIC,
-    /// ... access from priviledged server (run as root)
-    PRIVILEDGED,
-}
-
-#[derive(Debug, Fail)]
-pub struct HttpError {
-    pub code: StatusCode,
-    pub message: String,
-}
-
-impl HttpError {
-    pub fn new(code: StatusCode, message: String) -> Self {
-        HttpError { code, message }
-    }
-}
-
-impl fmt::Display for HttpError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "Error {}: {}", self.code, self.message)
-    }
-}
-
-macro_rules! http_err {
-    ($status:ident, $msg:expr) => {{
-        Error::from(HttpError::new(StatusCode::$status, $msg))
-    }}
-}
-
-type ApiHandlerFn = fn(Value, &ApiMethod, &mut dyn RpcEnvironment) -> Result<Value, Error>;
-
-type ApiAsyncHandlerFn = fn(Parts, Body, Value, &ApiAsyncMethod, &mut dyn RpcEnvironment) -> Result<BoxFut, Error>;
-
-/// This struct defines synchronous API call which returns the restulkt as json `Value`
-pub struct ApiMethod {
-    /// The protected flag indicates that the provides function should be forwarded
-    /// to the deaemon running in priviledged mode.
-    pub protected: bool,
-    /// This flag indicates that the provided method may change the local timezone, so the server
-    /// should do a tzset afterwards
-    pub reload_timezone: bool,
-    /// Parameter type Schema
-    pub parameters: ObjectSchema,
-    /// Return type Schema
-    pub returns: Arc<Schema>,
-    /// Handler function
-    pub handler: ApiHandlerFn,
-}
-
-impl ApiMethod {
-
-    pub fn new(handler: ApiHandlerFn, parameters: ObjectSchema) -> Self {
-        Self {
-            parameters,
-            handler,
-            returns: Arc::new(Schema::Null),
-            protected: false,
-            reload_timezone: false,
-        }
-    }
-
-    pub fn returns<S: Into<Arc<Schema>>>(mut self, schema: S) -> Self {
-
-        self.returns = schema.into();
-
-        self
-    }
-
-    pub fn protected(mut self, protected: bool) -> Self {
-
-        self.protected = protected;
-
-        self
-    }
-
-    pub fn reload_timezone(mut self, reload_timezone: bool) -> Self {
-
-        self.reload_timezone = reload_timezone;
-
-        self
-    }
-}
-
-pub struct ApiAsyncMethod {
-    pub parameters: ObjectSchema,
-    pub returns: Arc<Schema>,
-    pub handler: ApiAsyncHandlerFn,
-}
-
-impl ApiAsyncMethod {
-
-    pub fn new(handler: ApiAsyncHandlerFn, parameters: ObjectSchema) -> Self {
-        Self {
-            parameters,
-            handler,
-            returns: Arc::new(Schema::Null),
-        }
-    }
-
-    pub fn returns<S: Into<Arc<Schema>>>(mut self, schema: S) -> Self {
-
-        self.returns = schema.into();
-
-        self
-    }
-}
-
-pub enum SubRoute {
-    None,
-    Hash(HashMap<String, Router>),
-    MatchAll { router: Box<Router>, param_name: String },
-}
-
-pub enum MethodDefinition {
-    None,
-    Simple(ApiMethod),
-    Async(ApiAsyncMethod),
-}
-
-pub struct Router {
-    pub get: MethodDefinition,
-    pub put: MethodDefinition,
-    pub post: MethodDefinition,
-    pub delete: MethodDefinition,
-    pub subroute: SubRoute,
-}
-
-impl Router {
-
-    pub fn new() -> Self {
-        Self {
-            get: MethodDefinition::None,
-            put: MethodDefinition::None,
-            post: MethodDefinition::None,
-            delete: MethodDefinition::None,
-            subroute: SubRoute::None
-        }
-    }
-
-    pub fn subdir<S: Into<String>>(mut self, subdir: S, router: Router) -> Self {
-        if let SubRoute::None = self.subroute {
-            self.subroute = SubRoute::Hash(HashMap::new());
-        }
-        match self.subroute {
-            SubRoute::Hash(ref mut map) => {
-                map.insert(subdir.into(), router);
-            }
-            _ => panic!("unexpected subroute type"),
-        }
-        self
-    }
-
-    pub fn subdirs(mut self, map: HashMap<String, Router>) -> Self {
-        self.subroute = SubRoute::Hash(map);
-        self
-    }
-
-    pub fn match_all<S: Into<String>>(mut self, param_name: S, router: Router) -> Self {
-        if let SubRoute::None = self.subroute {
-            self.subroute = SubRoute::MatchAll { router: Box::new(router), param_name: param_name.into() };
-        } else {
-            panic!("unexpected subroute type");
-        }
-        self
-    }
-
-    pub fn get(mut self, m: ApiMethod) -> Self {
-        self.get = MethodDefinition::Simple(m);
-        self
-    }
-
-    pub fn put(mut self, m: ApiMethod) -> Self {
-        self.put = MethodDefinition::Simple(m);
-        self
-    }
-
-    pub fn post(mut self, m: ApiMethod) -> Self {
-        self.post = MethodDefinition::Simple(m);
-        self
-    }
-
-    pub fn upload(mut self, m: ApiAsyncMethod) -> Self {
-        self.post = MethodDefinition::Async(m);
-        self
-    }
-
-    pub fn download(mut self, m: ApiAsyncMethod) -> Self {
-        self.get = MethodDefinition::Async(m);
-        self
-    }
-
-
-    pub fn delete(mut self, m: ApiMethod) -> Self {
-        self.delete = MethodDefinition::Simple(m);
-        self
-    }
-
-    pub fn find_route(&self, components: &[&str], uri_param: &mut HashMap<String, String>) -> Option<&Router> {
-
-        if components.len() == 0 { return Some(self); };
-
-        let (dir, rest) = (components[0], &components[1..]);
-
-        match self.subroute {
-            SubRoute::None => {},
-            SubRoute::Hash(ref dirmap) => {
-                if let Some(ref router) = dirmap.get(dir) {
-                    println!("FOUND SUBDIR {}", dir);
-                    return router.find_route(rest, uri_param);
-                }
-            }
-            SubRoute::MatchAll { ref router, ref param_name } => {
-                println!("URI PARAM {} = {}", param_name, dir); // fixme: store somewhere
-                uri_param.insert(param_name.clone(), dir.into());
-                return router.find_route(rest, uri_param);
-            },
-        }
-
-        None
-    }
-}
diff --git a/src/api/schema.rs b/src/api/schema.rs
deleted file mode 100644 (file)
index 86e786d..0000000
+++ /dev/null
@@ -1,943 +0,0 @@
-use failure::*;
-use std::collections::HashMap;
-use serde_json::{json, Value};
-use url::form_urlencoded;
-use regex::Regex;
-use std::fmt;
-use std::sync::Arc;
-
-#[derive(Debug, Fail)]
-pub struct ParameterError {
-    error_list: Vec<Error>,
-}
-
-impl ParameterError {
-
-    pub fn new() -> Self {
-        Self { error_list: vec![] }
-    }
-
-    pub fn push(&mut self, value: Error) {
-        self.error_list.push(value);
-    }
-
-    pub fn len(&self) -> usize {
-        self.error_list.len()
-    }
-}
-
-impl fmt::Display for ParameterError {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let msg = self.error_list.iter().fold(String::from(""), |acc, item| {
-            acc + &item.to_string() + "\n"
-        });
-
-        write!(f, "{}", msg)
-    }
-}
-
-#[derive(Debug)]
-pub struct BooleanSchema {
-    pub description: &'static str,
-    pub default: Option<bool>,
-}
-
-impl BooleanSchema {
-
-    pub fn new(description: &'static str) -> Self {
-        BooleanSchema {
-            description: description,
-            default: None,
-        }
-    }
-
-    pub fn default(mut self, default: bool) -> Self {
-        self.default = Some(default);
-        self
-    }
-}
-
-#[derive(Debug)]
-pub struct IntegerSchema {
-    pub description: &'static str,
-    pub minimum: Option<isize>,
-    pub maximum: Option<isize>,
-    pub default: Option<isize>,
-}
-
-impl IntegerSchema {
-
-    pub fn new(description: &'static str) -> Self {
-        IntegerSchema {
-            description: description,
-            default: None,
-            minimum: None,
-            maximum: None,
-        }
-    }
-
-    pub fn default(mut self, default: isize) -> Self {
-        self.default = Some(default);
-        self
-    }
-
-    pub fn minimum(mut self, minimum: isize) -> Self {
-        self.minimum = Some(minimum);
-        self
-    }
-
-    pub fn maximum(mut self, maximium: isize) -> Self {
-        self.maximum = Some(maximium);
-        self
-    }
-
-    fn check_constraints(&self, value: isize) -> Result<(), Error> {
-
-        if let Some(minimum) = self.minimum {
-            if value < minimum {
-                bail!("value must have a minimum value of {}", minimum);
-            }
-        }
-
-        if let Some(maximum) = self.maximum {
-            if value > maximum {
-                bail!("value must have a maximum value of {}", maximum);
-            }
-        }
-
-        Ok(())
-    }
-}
-
-
-#[derive(Debug)]
-pub struct StringSchema {
-    pub description: &'static str,
-    pub default: Option<&'static str>,
-    pub min_length: Option<usize>,
-    pub max_length: Option<usize>,
-    pub format: Option<Arc<ApiStringFormat>>,
-}
-
-impl StringSchema {
-
-    pub fn new(description: &'static str) -> Self {
-        StringSchema {
-            description: description,
-            default: None,
-            min_length: None,
-            max_length: None,
-            format: None,
-        }
-    }
-
-    pub fn default(mut self, text: &'static str) -> Self {
-        self.default = Some(text);
-        self
-    }
-
-    pub fn format(mut self, format: Arc<ApiStringFormat>) -> Self {
-        self.format = Some(format);
-        self
-    }
-
-    pub fn min_length(mut self, min_length: usize) -> Self {
-        self.min_length = Some(min_length);
-        self
-    }
-
-    pub fn max_length(mut self, max_length: usize) -> Self {
-        self.max_length = Some(max_length);
-        self
-    }
-
-    fn check_length(&self, length: usize) -> Result<(), Error> {
-
-        if let Some(min_length) = self.min_length {
-            if length < min_length {
-                bail!("value must be at least {} characters long", min_length);
-            }
-        }
-
-        if let Some(max_length) = self.max_length {
-            if length > max_length {
-                bail!("value may only be {} characters long", max_length);
-            }
-        }
-
-        Ok(())
-    }
-
-    pub fn check_constraints(&self, value: &str) -> Result<(), Error> {
-
-        self.check_length(value.chars().count())?;
-
-        if let Some(ref format) = self.format {
-            match format.as_ref() {
-                ApiStringFormat::Pattern(ref regex) => {
-                    if !regex.is_match(value) {
-                        bail!("value does not match the regex pattern");
-                    }
-                }
-                ApiStringFormat::Enum(ref stringvec) => {
-                    if stringvec.iter().find(|&e| *e == value) == None {
-                        bail!("value '{}' is not defined in the enumeration.", value);
-                    }
-                }
-                ApiStringFormat::Complex(ref subschema) => {
-                    parse_property_string(value, subschema)?;
-                }
-                ApiStringFormat::VerifyFn(verify_fn) => {
-                    verify_fn(value)?;
-                }
-            }
-        }
-
-        Ok(())
-    }
-
-}
-
-#[derive(Debug)]
-pub struct ArraySchema {
-    pub description: &'static str,
-    pub items: Arc<Schema>,
-    pub min_length: Option<usize>,
-    pub max_length: Option<usize>,
-}
-
-impl ArraySchema {
-
-    pub fn new(description: &'static str, item_schema: Arc<Schema>) -> Self {
-        ArraySchema {
-            description: description,
-            items: item_schema,
-            min_length: None,
-            max_length: None,
-        }
-    }
-
-    pub fn min_length(mut self, min_length: usize) -> Self {
-        self.min_length = Some(min_length);
-        self
-    }
-
-    pub fn max_length(mut self, max_length: usize) -> Self {
-        self.max_length = Some(max_length);
-        self
-    }
-
-    fn check_length(&self, length: usize) -> Result<(), Error> {
-
-        if let Some(min_length) = self.min_length {
-            if length < min_length {
-                bail!("array must contain at least {} elements", min_length);
-            }
-        }
-
-        if let Some(max_length) = self.max_length {
-            if length > max_length {
-                bail!("array may only contain {} elements", max_length);
-            }
-        }
-
-        Ok(())
-    }
-}
-
-#[derive(Debug)]
-pub struct ObjectSchema {
-    pub description: &'static str,
-    pub additional_properties: bool,
-    pub properties: HashMap<&'static str, (bool, Arc<Schema>)>,
-    pub default_key: Option<&'static str>,
-}
-
-impl ObjectSchema {
-
-    pub fn new(description: &'static str) -> Self {
-        let properties = HashMap::new();
-        ObjectSchema {
-            description: description,
-            additional_properties: false,
-            properties: properties,
-            default_key: None,
-        }
-    }
-
-    pub fn additional_properties(mut self, additional_properties: bool) -> Self {
-        self.additional_properties = additional_properties;
-        self
-    }
-
-    pub fn default_key(mut self, key: &'static str) -> Self {
-        self.default_key = Some(key);
-        self
-    }
-
-    pub fn required<S: Into<Arc<Schema>>>(mut self, name: &'static str, schema: S) -> Self {
-        self.properties.insert(name, (false, schema.into()));
-        self
-    }
-
-    pub fn optional<S: Into<Arc<Schema>>>(mut self, name: &'static str, schema: S) -> Self {
-        self.properties.insert(name, (true, schema.into()));
-        self
-    }
-}
-
-#[derive(Debug)]
-pub enum Schema {
-    Null,
-    Boolean(BooleanSchema),
-    Integer(IntegerSchema),
-    String(StringSchema),
-    Object(ObjectSchema),
-    Array(ArraySchema),
-}
-
-impl From<StringSchema> for Schema {
-    fn from(string_schema: StringSchema) -> Self {
-        Schema::String(string_schema)
-    }
-}
-
-impl From<StringSchema> for Arc<Schema> {
-    fn from(string_schema: StringSchema) -> Self {
-        Arc::new(Schema::String(string_schema))
-    }
-}
-
-impl From<BooleanSchema> for Schema {
-    fn from(boolean_schema: BooleanSchema) -> Self {
-        Schema::Boolean(boolean_schema)
-    }
-}
-
-impl From<BooleanSchema> for Arc<Schema> {
-    fn from(boolean_schema: BooleanSchema) -> Self {
-        Arc::new(Schema::Boolean(boolean_schema))
-    }
-}
-
-impl From<IntegerSchema> for Schema {
-    fn from(integer_schema: IntegerSchema) -> Self {
-        Schema::Integer(integer_schema)
-    }
-}
-
-impl From<IntegerSchema> for Arc<Schema> {
-    fn from(integer_schema: IntegerSchema) -> Self {
-        Arc::new(Schema::Integer(integer_schema))
-    }
-}
-
-impl From<ObjectSchema> for Schema {
-    fn from(object_schema: ObjectSchema) -> Self {
-        Schema::Object(object_schema)
-    }
-}
-
-impl From<ObjectSchema> for Arc<Schema> {
-    fn from(object_schema: ObjectSchema) -> Self {
-        Arc::new(Schema::Object(object_schema))
-    }
-}
-
-impl From<ArraySchema> for Schema {
-    fn from(array_schema: ArraySchema) -> Self {
-        Schema::Array(array_schema)
-    }
-}
-
-impl From<ArraySchema> for Arc<Schema> {
-    fn from(array_schema: ArraySchema) -> Self {
-        Arc::new(Schema::Array(array_schema))
-    }
-}
-
-pub enum ApiStringFormat {
-    Enum(Vec<String>),
-    Pattern(&'static Regex),
-    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)
-            }
-        }
-    }
-}
-
-pub fn parse_boolean(value_str: &str) -> Result<bool, Error> {
-    match value_str.to_lowercase().as_str() {
-        "1" | "on" | "yes" | "true" => Ok(true),
-        "0" | "off" | "no" | "false" => Ok(false),
-        _ => bail!("Unable to parse boolean option."),
-    }
-}
-
-fn parse_property_string(value_str: &str, schema: &Schema) -> Result<Value, Error> {
-
-    println!("Parse property string: {}", value_str);
-
-    let mut param_list: Vec<(String, String)> = vec![];
-
-    match schema {
-        Schema::Object(object_schema) => {
-            for key_val in value_str.split(',').filter(|s| !s.is_empty()) {
-                let kv: Vec<&str> = key_val.splitn(2, '=').collect();
-                if kv.len() == 2 {
-                    param_list.push((kv[0].into(), kv[1].into()));
-                } else {
-                    if let Some(key) = object_schema.default_key {
-                        param_list.push((key.into(), kv[0].into()));
-                    } else {
-                        bail!("Value without key, but schema does not define a default key.");
-                    }
-                }
-            }
-
-            return parse_parameter_strings(&param_list, &object_schema, true)
-                .map_err(Error::from);
-
-        }
-        Schema::Array(array_schema) => {
-            let mut array : Vec<Value> = vec![];
-            for value in value_str.split(',').filter(|s| !s.is_empty()) {
-                match parse_simple_value(value, &array_schema.items) {
-                    Ok(res) => array.push(res),
-                    Err(err) => bail!("unable to parse array element: {}", err),
-                }
-            }
-            array_schema.check_length(array.len())?;
-
-            return Ok(array.into());
-        }
-        _ => {
-            bail!("Got unexpetec schema type.")
-        }
-    }
-
-}
-
-pub fn parse_simple_value(value_str: &str, schema: &Schema) -> Result<Value, Error> {
-
-    let value = match schema {
-        Schema::Null => {
-            bail!("internal error - found Null schema.");
-        }
-        Schema::Boolean(_boolean_schema) => {
-            let res = parse_boolean(value_str)?;
-            Value::Bool(res)
-        }
-        Schema::Integer(integer_schema) => {
-            let res: isize = value_str.parse()?;
-            integer_schema.check_constraints(res)?;
-            Value::Number(res.into())
-        }
-        Schema::String(string_schema) => {
-            string_schema.check_constraints(value_str)?;
-            Value::String(value_str.into())
-        }
-        _ => bail!("unable to parse complex (sub) objects."),
-    };
-    Ok(value)
-}
-
-pub fn parse_parameter_strings(data: &Vec<(String, String)>, schema: &ObjectSchema, test_required: bool) -> Result<Value, ParameterError> {
-
-    let mut params = json!({});
-
-    let mut errors = ParameterError::new();
-
-    let properties = &schema.properties;
-    let additional_properties = schema.additional_properties;
-
-    for (key, value) in data {
-        if let Some((_optional, prop_schema)) = properties.get::<str>(key) {
-            match prop_schema.as_ref() {
-                Schema::Array(array_schema) => {
-                    if params[key] == Value::Null {
-                        params[key] = json!([]);
-                    }
-                    match params[key] {
-                        Value::Array(ref mut array) => {
-                            match parse_simple_value(value, &array_schema.items) {
-                                Ok(res) => array.push(res), // fixme: check_length??
-                                Err(err) => errors.push(format_err!("parameter '{}': {}", key, err)),
-                            }
-                        }
-                        _ => errors.push(format_err!("parameter '{}': expected array - type missmatch", key)),
-                    }
-                }
-                _ => {
-                    match parse_simple_value(value, prop_schema) {
-                        Ok(res) => {
-                            if params[key] == Value::Null {
-                                params[key] = res;
-                            } else {
-                                errors.push(format_err!("parameter '{}': duplicate parameter.", key));
-                            }
-                        },
-                        Err(err) => errors.push(format_err!("parameter '{}': {}", key, err)),
-                    }
-                }
-            }
-        } else {
-            if additional_properties {
-                match params[key] {
-                    Value::Null => {
-                        params[key] = Value::String(value.to_owned());
-                    },
-                    Value::String(ref old) => {
-                        params[key] = Value::Array(
-                            vec![Value::String(old.to_owned()),  Value::String(value.to_owned())]);
-                    }
-                    Value::Array(ref mut array) => {
-                        array.push(Value::String(value.to_string()));
-                    }
-                    _ => errors.push(format_err!("parameter '{}': expected array - type missmatch", key)),
-                }
-            } else {
-                errors.push(format_err!("parameter '{}': schema does not allow additional properties.", key));
-            }
-        }
-    }
-
-    if test_required && errors.len() == 0 {
-        for (name, (optional, _prop_schema)) in properties {
-            if *optional == false && params[name] == Value::Null {
-                errors.push(format_err!("parameter '{}': parameter is missing and it is not optional.", name));
-            }
-        }
-    }
-
-    if errors.len() > 0 {
-        Err(errors)
-    } else {
-        Ok(params)
-    }
-}
-
-pub fn parse_query_string(query: &str, schema: &ObjectSchema, test_required: bool) -> Result<Value,  ParameterError> {
-
-    let param_list: Vec<(String, String)> =
-        form_urlencoded::parse(query.as_bytes()).into_owned().collect();
-
-    parse_parameter_strings(&param_list, schema, test_required)
-}
-
-pub fn verify_json(data: &Value, schema: &Schema) -> Result<(), Error> {
-
-    match schema {
-        Schema::Object(object_schema) => {
-            verify_json_object(data, &object_schema)?;
-        }
-        Schema::Array(array_schema) => {
-            verify_json_array(data, &array_schema)?;
-        }
-        Schema::Null => {
-            if !data.is_null() {
-                bail!("Expected Null, but value is not Null.");
-            }
-        }
-        Schema::Boolean(boolean_schema) => verify_json_boolean(data, &boolean_schema)?,
-        Schema::Integer(integer_schema) => verify_json_integer(data, &integer_schema)?,
-        Schema::String(string_schema) => verify_json_string(data, &string_schema)?,
-    }
-    Ok(())
-}
-
-pub fn verify_json_string(data: &Value, schema: &StringSchema) -> Result<(), Error> {
-    if let Some(value) = data.as_str() {
-        schema.check_constraints(value)
-    } else {
-        bail!("Expected string value.");
-    }
-}
-
-pub fn verify_json_boolean(data: &Value, _schema: &BooleanSchema) -> Result<(), Error> {
-    if !data.is_boolean() {
-        bail!("Expected boolean value.");
-    }
-    Ok(())
-}
-
-pub fn verify_json_integer(data: &Value, schema: &IntegerSchema) -> Result<(), Error> {
-    if let Some(value) = data.as_i64() {
-        schema.check_constraints(value as isize)
-    } else {
-        bail!("Expected integer value.");
-    }
-}
-
-pub fn verify_json_array(data: &Value, schema: &ArraySchema) -> Result<(), Error> {
-
-    let list = match data {
-        Value::Array(ref list) => list,
-        Value::Object(_) => bail!("Expected array - got object."),
-        _ => bail!("Expected array - got scalar value."),
-    };
-
-    schema.check_length(list.len())?;
-
-    for item in list {
-        verify_json(item, &schema.items)?;
-    }
-
-    Ok(())
-}
-
-pub fn verify_json_object(data: &Value, schema: &ObjectSchema) -> Result<(), Error> {
-
-    let map = match data {
-        Value::Object(ref map) => map,
-        Value::Array(_) => bail!("Expected object - got array."),
-        _ => bail!("Expected object - got scalar value."),
-    };
-
-    let properties = &schema.properties;
-    let additional_properties = schema.additional_properties;
-
-    for (key, value) in map {
-        if let Some((_optional, prop_schema)) = properties.get::<str>(key) {
-            match prop_schema.as_ref() {
-                Schema::Object(object_schema) => {
-                    verify_json_object(value, object_schema)?;
-                }
-                Schema::Array(array_schema) => {
-                    verify_json_array(value, array_schema)?;
-                }
-                _ => verify_json(value, prop_schema)?,
-            }
-        } else {
-            if !additional_properties {
-                bail!("property '{}': schema does not allow additional properties.", key);
-            }
-        }
-    }
-
-    for (name, (optional, _prop_schema)) in properties {
-        if *optional == false && data[name] == Value::Null {
-            bail!("property '{}': property is missing and it is not optional.", name);
-        }
-    }
-
-    Ok(())
-}
-
-#[test]
-fn test_schema1() {
-    let schema = Schema::Object(ObjectSchema {
-        description: "TEST",
-        additional_properties: false,
-        properties: {
-            let map = HashMap::new();
-
-            map
-        },
-        default_key: None,
-    });
-
-    println!("TEST Schema: {:?}", schema);
-}
-
-#[test]
-fn test_query_string() {
-
-    let schema = ObjectSchema::new("Parameters.")
-        .required("name", StringSchema::new("Name."));
-
-    let res = parse_query_string("", &schema, true);
-    assert!(res.is_err());
-
-    let schema = ObjectSchema::new("Parameters.")
-        .optional("name", StringSchema::new("Name."));
-
-    let res = parse_query_string("", &schema, true);
-    assert!(res.is_ok());
-
-    // TEST min_length and max_length
-
-    let schema = ObjectSchema::new("Parameters.")
-        .required(
-            "name", StringSchema::new("Name.")
-                .min_length(5)
-                .max_length(10)
-        );
-
-    let res = parse_query_string("name=abcd", &schema, true);
-    assert!(res.is_err());
-
-    let res = parse_query_string("name=abcde", &schema, true);
-    assert!(res.is_ok());
-
-    let res = parse_query_string("name=abcdefghijk", &schema, true);
-    assert!(res.is_err());
-
-    let res = parse_query_string("name=abcdefghij", &schema, true);
-    assert!(res.is_ok());
-
-    // TEST regex pattern
-
-    use lazy_static::lazy_static;
-    lazy_static! {
-        static ref TEST_REGEX: Regex = Regex::new("test").unwrap();
-        static ref TEST2_REGEX: Regex = Regex::new("^test$").unwrap();
-    }
-
-    let schema = ObjectSchema::new("Parameters.")
-        .required(
-            "name", StringSchema::new("Name.")
-                .format(Arc::new(ApiStringFormat::Pattern(&TEST_REGEX)))
-        );
-
-    let res = parse_query_string("name=abcd", &schema, true);
-    assert!(res.is_err());
-
-    let res = parse_query_string("name=ateststring", &schema, true);
-    assert!(res.is_ok());
-
-    let schema = ObjectSchema::new("Parameters.")
-        .required(
-            "name", StringSchema::new("Name.")
-                .format(Arc::new(ApiStringFormat::Pattern(&TEST2_REGEX)))
-        );
-
-    let res = parse_query_string("name=ateststring", &schema, true);
-    assert!(res.is_err());
-
-    let res = parse_query_string("name=test", &schema, true);
-    assert!(res.is_ok());
-
-    // TEST string enums
-
-    let schema = ObjectSchema::new("Parameters.")
-        .required(
-            "name", StringSchema::new("Name.")
-                .format(Arc::new(ApiStringFormat::Enum(vec!["ev1".into(), "ev2".into()])))
-        );
-
-    let res = parse_query_string("name=noenum", &schema, true);
-    assert!(res.is_err());
-
-    let res = parse_query_string("name=ev1", &schema, true);
-    assert!(res.is_ok());
-
-    let res = parse_query_string("name=ev2", &schema, true);
-    assert!(res.is_ok());
-
-    let res = parse_query_string("name=ev3", &schema, true);
-    assert!(res.is_err());
-
-}
-
-#[test]
-fn test_query_integer() {
-
-    let schema = ObjectSchema::new("Parameters.")
-        .required(
-            "count" , IntegerSchema::new("Count.")
-        );
-
-    let res = parse_query_string("", &schema, true);
-    assert!(res.is_err());
-
-    let schema = ObjectSchema::new("Parameters.")
-        .optional(
-            "count", IntegerSchema::new("Count.")
-                .minimum(-3)
-                .maximum(50)
-        );
-
-    let res = parse_query_string("", &schema, true);
-    assert!(res.is_ok());
-
-    let res = parse_query_string("count=abc", &schema, false);
-    assert!(res.is_err());
-
-    let res = parse_query_string("count=30", &schema, false);
-    assert!(res.is_ok());
-
-    let res = parse_query_string("count=-1", &schema, false);
-    assert!(res.is_ok());
-
-    let res = parse_query_string("count=300", &schema, false);
-    assert!(res.is_err());
-
-    let res = parse_query_string("count=-30", &schema, false);
-    assert!(res.is_err());
-
-    let res = parse_query_string("count=50", &schema, false);
-    assert!(res.is_ok());
-
-    let res = parse_query_string("count=-3", &schema, false);
-    assert!(res.is_ok());
-}
-
-#[test]
-fn test_query_boolean() {
-
-    let schema = ObjectSchema::new("Parameters.")
-        .required(
-            "force", BooleanSchema::new("Force.")
-        );
-
-    let res = parse_query_string("", &schema, true);
-    assert!(res.is_err());
-
-    let schema = ObjectSchema::new("Parameters.")
-        .optional(
-            "force", BooleanSchema::new("Force.")
-        );
-
-    let res = parse_query_string("", &schema, true);
-    assert!(res.is_ok());
-
-    let res = parse_query_string("a=b", &schema, true);
-    assert!(res.is_err());
-
-
-    let res = parse_query_string("force", &schema, true);
-    assert!(res.is_err());
-
-    let res = parse_query_string("force=yes", &schema, true);
-    assert!(res.is_ok());
-    let res = parse_query_string("force=1", &schema, true);
-    assert!(res.is_ok());
-    let res = parse_query_string("force=On", &schema, true);
-    assert!(res.is_ok());
-    let res = parse_query_string("force=TRUE", &schema, true);
-    assert!(res.is_ok());
-    let res = parse_query_string("force=TREU", &schema, true);
-    assert!(res.is_err());
-
-    let res = parse_query_string("force=NO", &schema, true);
-    assert!(res.is_ok());
-    let res = parse_query_string("force=0", &schema, true);
-    assert!(res.is_ok());
-    let res = parse_query_string("force=off", &schema, true);
-    assert!(res.is_ok());
-    let res = parse_query_string("force=False", &schema, true);
-    assert!(res.is_ok());
-}
-
-#[test]
-fn test_verify_function() {
-
-    let schema = ObjectSchema::new("Parameters.")
-        .required(
-            "p1", StringSchema::new("P1")
-                .format(ApiStringFormat::VerifyFn(|value| {
-                    if value == "test" { return Ok(()) };
-                    bail!("format error");
-                }).into())
-        );
-
-    let res = parse_query_string("p1=tes", &schema, true);
-    assert!(res.is_err());
-    let res = parse_query_string("p1=test", &schema, true);
-    assert!(res.is_ok());
-}
-
-#[test]
-fn test_verify_complex_object() {
-
-    let nic_models = Arc::new(ApiStringFormat::Enum(
-        vec!["e1000".into(), "virtio".into()]));
-
-    let param_schema: Arc<Schema> = ObjectSchema::new("Properties.")
-        .default_key("model")
-        .required("model", StringSchema::new("Ethernet device Model.")
-                  .format(nic_models))
-        .optional("enable", BooleanSchema::new("Enable device."))
-        .into();
-
-    let schema = ObjectSchema::new("Parameters.")
-        .required(
-            "net0", StringSchema::new("First Network device.")
-                .format(ApiStringFormat::Complex(param_schema).into())
-        );
-
-    let res = parse_query_string("", &schema, true);
-    assert!(res.is_err());
-
-    let res = parse_query_string("test=abc", &schema, true);
-    assert!(res.is_err());
-
-    let res = parse_query_string("net0=model=abc", &schema, true);
-    assert!(res.is_err());
-
-    let res = parse_query_string("net0=model=virtio", &schema, true);
-     assert!(res.is_ok());
-
-    let res = parse_query_string("net0=model=virtio,enable=1", &schema, true);
-    assert!(res.is_ok());
-
-    let res = parse_query_string("net0=virtio,enable=no", &schema, true);
-    assert!(res.is_ok());
-}
-
-#[test]
-fn test_verify_complex_array() {
-
-    let param_schema: Arc<Schema> = ArraySchema::new(
-        "Integer List.", Arc::new(IntegerSchema::new("Soemething").into()))
-        .into();
-
-    let schema = ObjectSchema::new("Parameters.")
-        .required(
-            "list", StringSchema::new("A list on integers, comma separated.")
-                .format(ApiStringFormat::Complex(param_schema).into())
-        );
-
-    let res = parse_query_string("", &schema, true);
-    assert!(res.is_err());
-
-    let res = parse_query_string("list=", &schema, true);
-    assert!(res.is_ok());
-
-    let res = parse_query_string("list=abc", &schema, true);
-    assert!(res.is_err());
-
-    let res = parse_query_string("list=1", &schema, true);
-    assert!(res.is_ok());
-
-    let res = parse_query_string("list=2,3,4,5", &schema, true);
-    assert!(res.is_ok());
-
-    let param_schema: Arc<Schema> = ArraySchema::new(
-        "Integer List.", Arc::new(IntegerSchema::new("Soemething").into()))
-        .min_length(1)
-        .max_length(3)
-        .into();
-
-    let schema = ObjectSchema::new("Parameters.")
-        .required(
-            "list", StringSchema::new("A list on integers, comma separated.")
-                .format(ApiStringFormat::Complex(param_schema).into())
-        );
-
-    let res = parse_query_string("list=", &schema, true);
-    assert!(res.is_err());
-
-    let res = parse_query_string("list=1,2,3", &schema, true);
-    assert!(res.is_ok());
-
-    let res = parse_query_string("list=2,3,4,5", &schema, true);
-    assert!(res.is_err());
-}
index 5cd2bce6bae87c4f4199a5b71bbe59fd53eedb31..7993a60867710d4b63775e5d2566c5d87961bb36 100644 (file)
@@ -1,7 +1,7 @@
 //use failure::*;
 
-use crate::api::schema::*;
-use crate::api::router::*;
+use crate::api_schema::schema::*;
+use crate::api_schema::router::*;
 use serde_json::{json};
 use std::sync::Arc;
 
index 24811fb1da613df9a301939c8693527a761a94ba..851248d8933b7b908907ac6ed98a48f1fab1eb86 100644 (file)
@@ -1,8 +1,8 @@
 use failure::*;
 
 use crate::tools;
-use crate::api::schema::*;
-use crate::api::router::*;
+use crate::api_schema::schema::*;
+use crate::api_schema::router::*;
 use crate::tools::ticket::*;
 use crate::auth_helpers::*;
 
index 4f42d900f6259d124db614a4e9f04b235ad275fb..a89d43730cbbdccdf28450f8c51cb57fe33697ec 100644 (file)
@@ -1,5 +1,5 @@
-use crate::api::schema::*;
-use crate::api::router::*;
+use crate::api_schema::schema::*;
+use crate::api_schema::router::*;
 use serde_json::{json};
 
 pub mod datastore;
index 6c7276d7a5eded9e868ce55089332e917764e2eb..d706797ec98e37deacecbf4c080296ae3e6c812a 100644 (file)
@@ -1,7 +1,7 @@
 use failure::*;
 
-use crate::api::schema::*;
-use crate::api::router::*;
+use crate::api_schema::schema::*;
+use crate::api_schema::router::*;
 //use crate::server::rest::*;
 use serde_json::{json, Value};
 
index 3f235cad3671a0eef84b2812481c3c1b4f414ed6..bf910ef000109ea87d878a3c378d7fd33c28c6be 100644 (file)
@@ -4,8 +4,8 @@ use crate::tools;
 use crate::tools::wrapped_reader_stream::*;
 use crate::backup::*;
 //use crate::server::rest::*;
-use crate::api::schema::*;
-use crate::api::router::*;
+use crate::api_schema::schema::*;
+use crate::api_schema::router::*;
 
 use chrono::{Utc, TimeZone};
 
index 30eca969017495caf0b1627a8665e400e301f693..10d00ef0813feba9056e9088c0d3eacc9c8b7927 100644 (file)
@@ -1,8 +1,8 @@
 //use failure::*;
 //use std::collections::HashMap;
 
-use crate::api::schema::*;
-use crate::api::router::*;
+use crate::api_schema::schema::*;
+use crate::api_schema::router::*;
 use serde_json::{json};
 
 pub mod datastore;
index cbde50be0e8568e21df2939be161be08f8fce0b8..ba096400b075cc48ed8671dbe172c251082cf96c 100644 (file)
@@ -1,8 +1,8 @@
 use failure::*;
 //use std::collections::HashMap;
 
-use crate::api::schema::*;
-use crate::api::router::*;
+use crate::api_schema::schema::*;
+use crate::api_schema::router::*;
 use crate::backup::*;
 use serde_json::{json, Value};
 use std::path::PathBuf;
index 6c3b13fc09fa306543c039589c79dce229b4ff69..a002bf727ab6804a09949891cd878157604dc6e9 100644 (file)
@@ -1,5 +1,5 @@
-use crate::api::schema::*;
-use crate::api::router::*;
+use crate::api_schema::schema::*;
+use crate::api_schema::router::*;
 use serde_json::{json};
 
 mod time;
index 5acec54ab3effa3e3b20bba2e456d4cda3c29fd6..e6a630c81068b4263fd4296087a906178ec57233 100644 (file)
@@ -3,8 +3,8 @@ use failure::*;
 
 use crate::tools;
 use crate::api2::*;
-//use crate::api::schema::*;
-//use crate::api::router::*;
+//use crate::api_schema::schema::*;
+//use crate::api_schema::router::*;
 
 use lazy_static::lazy_static;
 
index 3f22fe8df48eade89c9786b4d7cb0886cf7db8c7..799ad1df3c4486ee29d28337e9d53328841af46f 100644 (file)
@@ -1,8 +1,8 @@
 use failure::*;
 
 //use crate::tools;
-use crate::api::schema::*;
-use crate::api::router::*;
+use crate::api_schema::schema::*;
+use crate::api_schema::router::*;
 use serde_json::{json, Value};
 
 
index a648f4fb9863ae9b453aa42f25b420cc472a851a..2330ec0a0abe4898d4d838c6941648dc0c1cc10c 100644 (file)
@@ -1,8 +1,8 @@
 use failure::*;
 
 use crate::tools;
-use crate::api::schema::*;
-use crate::api::router::*;
+use crate::api_schema::schema::*;
+use crate::api_schema::router::*;
 use serde_json::{json, Value};
 
 use std::sync::Arc;
index 1391686fa28e8d7c5d2a5dc2ee892f1c619c95bd..3fc6882463162fa7a149adda9616a255d8524647 100644 (file)
@@ -1,7 +1,7 @@
 use failure::*;
 
-use crate::api::schema::*;
-use crate::api::router::*;
+use crate::api_schema::schema::*;
+use crate::api_schema::router::*;
 use serde_json::{json, Value};
 
 use std::sync::Arc;
index 0643d6d0282572286e0265786c94c05f64fed6fd..048091f99a29b409b28f2f9e278de4f98050e886 100644 (file)
@@ -1,8 +1,8 @@
 use failure::*;
 
 use crate::tools;
-use crate::api::schema::*;
-use crate::api::router::*;
+use crate::api_schema::schema::*;
+use crate::api_schema::router::*;
 use serde_json::{json, Value};
 
 use chrono::prelude::*;
index ce9c1d21a1cf8fc69dec6527128c47ff8641e514..2421a892c4224d87468825f378ccd7f7b427ccb9 100644 (file)
@@ -1,8 +1,8 @@
 use failure::*;
 
 use crate::tools;
-use crate::api::schema::*;
-use crate::api::router::*;
+use crate::api_schema::schema::*;
+use crate::api_schema::router::*;
 use serde_json::{json, Value};
 
 
index 161bc1ae90618ae5f1f90aeb57c6529558f67cec..4079dcc27a101d5a79a0f39326461ec0a0805dde 100644 (file)
@@ -1,7 +1,7 @@
 use failure::*;
 
-use crate::api::schema::*;
-use crate::api::router::*;
+use crate::api_schema::schema::*;
+use crate::api_schema::router::*;
 use serde_json::{json, Value};
 
 const PROXMOX_PKG_VERSION: &'static str = env!("PROXMOX_PKG_VERSION");
diff --git a/src/api_schema.rs b/src/api_schema.rs
new file mode 100644 (file)
index 0000000..1b7b09b
--- /dev/null
@@ -0,0 +1,16 @@
+//! API definition helper
+//!
+//! This module contains helper classes to define REST APIs. Method
+//! parameters and return types are described using a
+//! [Schema](schema/enum.Schema.html).
+//!
+//! The [Router](router/struct.Router.html) is used to define a
+//! hierarchy of API entries, and provides ways to find an API
+//! definition by path.
+
+#[macro_use]
+pub mod schema;
+pub mod registry;
+#[macro_use]
+pub mod router;
+pub mod config;
diff --git a/src/api_schema/config.rs b/src/api_schema/config.rs
new file mode 100644 (file)
index 0000000..e8ac4a4
--- /dev/null
@@ -0,0 +1,67 @@
+use crate::api_schema::router::*;
+
+use std::collections::HashMap;
+use std::path::{PathBuf};
+
+use hyper::Method;
+
+pub struct ApiConfig {
+    basedir: PathBuf,
+    router: &'static Router,
+    aliases: HashMap<String, PathBuf>,
+    env_type: RpcEnvironmentType,
+}
+
+impl ApiConfig {
+
+    pub fn new<B: Into<PathBuf>>(basedir: B, router: &'static Router, env_type: RpcEnvironmentType) -> Self {
+        Self {
+            basedir: basedir.into(),
+            router: router,
+            aliases: HashMap::new(),
+            env_type,
+        }
+    }
+
+    pub fn find_method(&self, components: &[&str], method: Method, uri_param: &mut HashMap<String, String>) -> &'static MethodDefinition {
+
+        if let Some(info) = self.router.find_route(components, uri_param) {
+            return match method {
+                Method::GET => &info.get,
+                Method::PUT => &info.put,
+                Method::POST => &info.post,
+                Method::DELETE => &info.delete,
+                _ => &MethodDefinition::None,
+            };
+        }
+        &MethodDefinition::None
+    }
+
+    pub fn find_alias(&self, components: &[&str]) -> PathBuf {
+
+        let mut prefix = String::new();
+        let mut filename = self.basedir.clone();
+        let comp_len = components.len();
+        if comp_len >= 1 {
+            prefix.push_str(components[0]);
+            if let Some(subdir) = self.aliases.get(&prefix) {
+                filename.push(subdir);
+                for i in 1..comp_len { filename.push(components[i]) }
+            } else {
+                for i in 0..comp_len { filename.push(components[i]) }
+            }
+        }
+        filename
+    }
+
+    pub fn add_alias<S, P>(&mut self, alias: S, path: P)
+        where S: Into<String>,
+              P: Into<PathBuf>,
+    {
+        self.aliases.insert(alias.into(), path.into());
+    }
+
+    pub fn env_type(&self) -> RpcEnvironmentType {
+        self.env_type
+    }
+}
diff --git a/src/api_schema/registry.rs b/src/api_schema/registry.rs
new file mode 100644 (file)
index 0000000..4a4e922
--- /dev/null
@@ -0,0 +1,89 @@
+use crate::api_schema::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<S: Into<Schema>>(&mut self, name: &'static str, schema: S) {
+
+        if let Some(_schema) = self.options.get(name) {
+            panic!("standard option '{}' already registered.", name); // fixme: really panic?
+        }
+
+        self.options.insert(name, Arc::new(schema.into()));
+    }
+
+    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",
+            IntegerSchema::new("The (unique) ID of the VM.")
+                .minimum(1)
+        );
+
+        self.register_option(
+            "pve-node",
+            StringSchema::new("The cluster node name.")
+                .format(self.lookup_format("pve-node").unwrap()) //fixme: unwrap?
+        );
+     }
+
+}
+
+fn verify_pve_node(_value: &str) -> Result<(), Error> {
+
+    // fixme: ??
+    Ok(())
+}
diff --git a/src/api_schema/router.rs b/src/api_schema/router.rs
new file mode 100644 (file)
index 0000000..2c22606
--- /dev/null
@@ -0,0 +1,253 @@
+use failure::*;
+
+use crate::api_schema::schema::*;
+use serde_json::{Value};
+use std::collections::HashMap;
+use std::sync::Arc;
+use std::fmt;
+
+use hyper::{Body, Response, StatusCode};
+use hyper::rt::Future;
+use hyper::http::request::Parts;
+
+pub type BoxFut = Box<Future<Item = Response<Body>, Error = failure::Error> + Send>;
+
+pub trait RpcEnvironment {
+
+    fn set_result_attrib(&mut self, name: &str, value: Value);
+
+    fn get_result_attrib(&self, name: &str) -> Option<&Value>;
+
+    fn env_type(&self) -> RpcEnvironmentType;
+
+    fn set_user(&mut self, user: Option<String>);
+
+    fn get_user(&self) -> Option<String>;
+}
+
+#[derive(PartialEq, Copy, Clone)]
+pub enum RpcEnvironmentType {
+    ///  command started from command line
+    CLI,
+    /// access from public acessable server
+    PUBLIC,
+    /// ... access from priviledged server (run as root)
+    PRIVILEDGED,
+}
+
+#[derive(Debug, Fail)]
+pub struct HttpError {
+    pub code: StatusCode,
+    pub message: String,
+}
+
+impl HttpError {
+    pub fn new(code: StatusCode, message: String) -> Self {
+        HttpError { code, message }
+    }
+}
+
+impl fmt::Display for HttpError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "Error {}: {}", self.code, self.message)
+    }
+}
+
+macro_rules! http_err {
+    ($status:ident, $msg:expr) => {{
+        Error::from(HttpError::new(StatusCode::$status, $msg))
+    }}
+}
+
+type ApiHandlerFn = fn(Value, &ApiMethod, &mut dyn RpcEnvironment) -> Result<Value, Error>;
+
+type ApiAsyncHandlerFn = fn(Parts, Body, Value, &ApiAsyncMethod, &mut dyn RpcEnvironment) -> Result<BoxFut, Error>;
+
+/// This struct defines synchronous API call which returns the restulkt as json `Value`
+pub struct ApiMethod {
+    /// The protected flag indicates that the provides function should be forwarded
+    /// to the deaemon running in priviledged mode.
+    pub protected: bool,
+    /// This flag indicates that the provided method may change the local timezone, so the server
+    /// should do a tzset afterwards
+    pub reload_timezone: bool,
+    /// Parameter type Schema
+    pub parameters: ObjectSchema,
+    /// Return type Schema
+    pub returns: Arc<Schema>,
+    /// Handler function
+    pub handler: ApiHandlerFn,
+}
+
+impl ApiMethod {
+
+    pub fn new(handler: ApiHandlerFn, parameters: ObjectSchema) -> Self {
+        Self {
+            parameters,
+            handler,
+            returns: Arc::new(Schema::Null),
+            protected: false,
+            reload_timezone: false,
+        }
+    }
+
+    pub fn returns<S: Into<Arc<Schema>>>(mut self, schema: S) -> Self {
+
+        self.returns = schema.into();
+
+        self
+    }
+
+    pub fn protected(mut self, protected: bool) -> Self {
+
+        self.protected = protected;
+
+        self
+    }
+
+    pub fn reload_timezone(mut self, reload_timezone: bool) -> Self {
+
+        self.reload_timezone = reload_timezone;
+
+        self
+    }
+}
+
+pub struct ApiAsyncMethod {
+    pub parameters: ObjectSchema,
+    pub returns: Arc<Schema>,
+    pub handler: ApiAsyncHandlerFn,
+}
+
+impl ApiAsyncMethod {
+
+    pub fn new(handler: ApiAsyncHandlerFn, parameters: ObjectSchema) -> Self {
+        Self {
+            parameters,
+            handler,
+            returns: Arc::new(Schema::Null),
+        }
+    }
+
+    pub fn returns<S: Into<Arc<Schema>>>(mut self, schema: S) -> Self {
+
+        self.returns = schema.into();
+
+        self
+    }
+}
+
+pub enum SubRoute {
+    None,
+    Hash(HashMap<String, Router>),
+    MatchAll { router: Box<Router>, param_name: String },
+}
+
+pub enum MethodDefinition {
+    None,
+    Simple(ApiMethod),
+    Async(ApiAsyncMethod),
+}
+
+pub struct Router {
+    pub get: MethodDefinition,
+    pub put: MethodDefinition,
+    pub post: MethodDefinition,
+    pub delete: MethodDefinition,
+    pub subroute: SubRoute,
+}
+
+impl Router {
+
+    pub fn new() -> Self {
+        Self {
+            get: MethodDefinition::None,
+            put: MethodDefinition::None,
+            post: MethodDefinition::None,
+            delete: MethodDefinition::None,
+            subroute: SubRoute::None
+        }
+    }
+
+    pub fn subdir<S: Into<String>>(mut self, subdir: S, router: Router) -> Self {
+        if let SubRoute::None = self.subroute {
+            self.subroute = SubRoute::Hash(HashMap::new());
+        }
+        match self.subroute {
+            SubRoute::Hash(ref mut map) => {
+                map.insert(subdir.into(), router);
+            }
+            _ => panic!("unexpected subroute type"),
+        }
+        self
+    }
+
+    pub fn subdirs(mut self, map: HashMap<String, Router>) -> Self {
+        self.subroute = SubRoute::Hash(map);
+        self
+    }
+
+    pub fn match_all<S: Into<String>>(mut self, param_name: S, router: Router) -> Self {
+        if let SubRoute::None = self.subroute {
+            self.subroute = SubRoute::MatchAll { router: Box::new(router), param_name: param_name.into() };
+        } else {
+            panic!("unexpected subroute type");
+        }
+        self
+    }
+
+    pub fn get(mut self, m: ApiMethod) -> Self {
+        self.get = MethodDefinition::Simple(m);
+        self
+    }
+
+    pub fn put(mut self, m: ApiMethod) -> Self {
+        self.put = MethodDefinition::Simple(m);
+        self
+    }
+
+    pub fn post(mut self, m: ApiMethod) -> Self {
+        self.post = MethodDefinition::Simple(m);
+        self
+    }
+
+    pub fn upload(mut self, m: ApiAsyncMethod) -> Self {
+        self.post = MethodDefinition::Async(m);
+        self
+    }
+
+    pub fn download(mut self, m: ApiAsyncMethod) -> Self {
+        self.get = MethodDefinition::Async(m);
+        self
+    }
+
+
+    pub fn delete(mut self, m: ApiMethod) -> Self {
+        self.delete = MethodDefinition::Simple(m);
+        self
+    }
+
+    pub fn find_route(&self, components: &[&str], uri_param: &mut HashMap<String, String>) -> Option<&Router> {
+
+        if components.len() == 0 { return Some(self); };
+
+        let (dir, rest) = (components[0], &components[1..]);
+
+        match self.subroute {
+            SubRoute::None => {},
+            SubRoute::Hash(ref dirmap) => {
+                if let Some(ref router) = dirmap.get(dir) {
+                    println!("FOUND SUBDIR {}", dir);
+                    return router.find_route(rest, uri_param);
+                }
+            }
+            SubRoute::MatchAll { ref router, ref param_name } => {
+                println!("URI PARAM {} = {}", param_name, dir); // fixme: store somewhere
+                uri_param.insert(param_name.clone(), dir.into());
+                return router.find_route(rest, uri_param);
+            },
+        }
+
+        None
+    }
+}
diff --git a/src/api_schema/schema.rs b/src/api_schema/schema.rs
new file mode 100644 (file)
index 0000000..86e786d
--- /dev/null
@@ -0,0 +1,943 @@
+use failure::*;
+use std::collections::HashMap;
+use serde_json::{json, Value};
+use url::form_urlencoded;
+use regex::Regex;
+use std::fmt;
+use std::sync::Arc;
+
+#[derive(Debug, Fail)]
+pub struct ParameterError {
+    error_list: Vec<Error>,
+}
+
+impl ParameterError {
+
+    pub fn new() -> Self {
+        Self { error_list: vec![] }
+    }
+
+    pub fn push(&mut self, value: Error) {
+        self.error_list.push(value);
+    }
+
+    pub fn len(&self) -> usize {
+        self.error_list.len()
+    }
+}
+
+impl fmt::Display for ParameterError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let msg = self.error_list.iter().fold(String::from(""), |acc, item| {
+            acc + &item.to_string() + "\n"
+        });
+
+        write!(f, "{}", msg)
+    }
+}
+
+#[derive(Debug)]
+pub struct BooleanSchema {
+    pub description: &'static str,
+    pub default: Option<bool>,
+}
+
+impl BooleanSchema {
+
+    pub fn new(description: &'static str) -> Self {
+        BooleanSchema {
+            description: description,
+            default: None,
+        }
+    }
+
+    pub fn default(mut self, default: bool) -> Self {
+        self.default = Some(default);
+        self
+    }
+}
+
+#[derive(Debug)]
+pub struct IntegerSchema {
+    pub description: &'static str,
+    pub minimum: Option<isize>,
+    pub maximum: Option<isize>,
+    pub default: Option<isize>,
+}
+
+impl IntegerSchema {
+
+    pub fn new(description: &'static str) -> Self {
+        IntegerSchema {
+            description: description,
+            default: None,
+            minimum: None,
+            maximum: None,
+        }
+    }
+
+    pub fn default(mut self, default: isize) -> Self {
+        self.default = Some(default);
+        self
+    }
+
+    pub fn minimum(mut self, minimum: isize) -> Self {
+        self.minimum = Some(minimum);
+        self
+    }
+
+    pub fn maximum(mut self, maximium: isize) -> Self {
+        self.maximum = Some(maximium);
+        self
+    }
+
+    fn check_constraints(&self, value: isize) -> Result<(), Error> {
+
+        if let Some(minimum) = self.minimum {
+            if value < minimum {
+                bail!("value must have a minimum value of {}", minimum);
+            }
+        }
+
+        if let Some(maximum) = self.maximum {
+            if value > maximum {
+                bail!("value must have a maximum value of {}", maximum);
+            }
+        }
+
+        Ok(())
+    }
+}
+
+
+#[derive(Debug)]
+pub struct StringSchema {
+    pub description: &'static str,
+    pub default: Option<&'static str>,
+    pub min_length: Option<usize>,
+    pub max_length: Option<usize>,
+    pub format: Option<Arc<ApiStringFormat>>,
+}
+
+impl StringSchema {
+
+    pub fn new(description: &'static str) -> Self {
+        StringSchema {
+            description: description,
+            default: None,
+            min_length: None,
+            max_length: None,
+            format: None,
+        }
+    }
+
+    pub fn default(mut self, text: &'static str) -> Self {
+        self.default = Some(text);
+        self
+    }
+
+    pub fn format(mut self, format: Arc<ApiStringFormat>) -> Self {
+        self.format = Some(format);
+        self
+    }
+
+    pub fn min_length(mut self, min_length: usize) -> Self {
+        self.min_length = Some(min_length);
+        self
+    }
+
+    pub fn max_length(mut self, max_length: usize) -> Self {
+        self.max_length = Some(max_length);
+        self
+    }
+
+    fn check_length(&self, length: usize) -> Result<(), Error> {
+
+        if let Some(min_length) = self.min_length {
+            if length < min_length {
+                bail!("value must be at least {} characters long", min_length);
+            }
+        }
+
+        if let Some(max_length) = self.max_length {
+            if length > max_length {
+                bail!("value may only be {} characters long", max_length);
+            }
+        }
+
+        Ok(())
+    }
+
+    pub fn check_constraints(&self, value: &str) -> Result<(), Error> {
+
+        self.check_length(value.chars().count())?;
+
+        if let Some(ref format) = self.format {
+            match format.as_ref() {
+                ApiStringFormat::Pattern(ref regex) => {
+                    if !regex.is_match(value) {
+                        bail!("value does not match the regex pattern");
+                    }
+                }
+                ApiStringFormat::Enum(ref stringvec) => {
+                    if stringvec.iter().find(|&e| *e == value) == None {
+                        bail!("value '{}' is not defined in the enumeration.", value);
+                    }
+                }
+                ApiStringFormat::Complex(ref subschema) => {
+                    parse_property_string(value, subschema)?;
+                }
+                ApiStringFormat::VerifyFn(verify_fn) => {
+                    verify_fn(value)?;
+                }
+            }
+        }
+
+        Ok(())
+    }
+
+}
+
+#[derive(Debug)]
+pub struct ArraySchema {
+    pub description: &'static str,
+    pub items: Arc<Schema>,
+    pub min_length: Option<usize>,
+    pub max_length: Option<usize>,
+}
+
+impl ArraySchema {
+
+    pub fn new(description: &'static str, item_schema: Arc<Schema>) -> Self {
+        ArraySchema {
+            description: description,
+            items: item_schema,
+            min_length: None,
+            max_length: None,
+        }
+    }
+
+    pub fn min_length(mut self, min_length: usize) -> Self {
+        self.min_length = Some(min_length);
+        self
+    }
+
+    pub fn max_length(mut self, max_length: usize) -> Self {
+        self.max_length = Some(max_length);
+        self
+    }
+
+    fn check_length(&self, length: usize) -> Result<(), Error> {
+
+        if let Some(min_length) = self.min_length {
+            if length < min_length {
+                bail!("array must contain at least {} elements", min_length);
+            }
+        }
+
+        if let Some(max_length) = self.max_length {
+            if length > max_length {
+                bail!("array may only contain {} elements", max_length);
+            }
+        }
+
+        Ok(())
+    }
+}
+
+#[derive(Debug)]
+pub struct ObjectSchema {
+    pub description: &'static str,
+    pub additional_properties: bool,
+    pub properties: HashMap<&'static str, (bool, Arc<Schema>)>,
+    pub default_key: Option<&'static str>,
+}
+
+impl ObjectSchema {
+
+    pub fn new(description: &'static str) -> Self {
+        let properties = HashMap::new();
+        ObjectSchema {
+            description: description,
+            additional_properties: false,
+            properties: properties,
+            default_key: None,
+        }
+    }
+
+    pub fn additional_properties(mut self, additional_properties: bool) -> Self {
+        self.additional_properties = additional_properties;
+        self
+    }
+
+    pub fn default_key(mut self, key: &'static str) -> Self {
+        self.default_key = Some(key);
+        self
+    }
+
+    pub fn required<S: Into<Arc<Schema>>>(mut self, name: &'static str, schema: S) -> Self {
+        self.properties.insert(name, (false, schema.into()));
+        self
+    }
+
+    pub fn optional<S: Into<Arc<Schema>>>(mut self, name: &'static str, schema: S) -> Self {
+        self.properties.insert(name, (true, schema.into()));
+        self
+    }
+}
+
+#[derive(Debug)]
+pub enum Schema {
+    Null,
+    Boolean(BooleanSchema),
+    Integer(IntegerSchema),
+    String(StringSchema),
+    Object(ObjectSchema),
+    Array(ArraySchema),
+}
+
+impl From<StringSchema> for Schema {
+    fn from(string_schema: StringSchema) -> Self {
+        Schema::String(string_schema)
+    }
+}
+
+impl From<StringSchema> for Arc<Schema> {
+    fn from(string_schema: StringSchema) -> Self {
+        Arc::new(Schema::String(string_schema))
+    }
+}
+
+impl From<BooleanSchema> for Schema {
+    fn from(boolean_schema: BooleanSchema) -> Self {
+        Schema::Boolean(boolean_schema)
+    }
+}
+
+impl From<BooleanSchema> for Arc<Schema> {
+    fn from(boolean_schema: BooleanSchema) -> Self {
+        Arc::new(Schema::Boolean(boolean_schema))
+    }
+}
+
+impl From<IntegerSchema> for Schema {
+    fn from(integer_schema: IntegerSchema) -> Self {
+        Schema::Integer(integer_schema)
+    }
+}
+
+impl From<IntegerSchema> for Arc<Schema> {
+    fn from(integer_schema: IntegerSchema) -> Self {
+        Arc::new(Schema::Integer(integer_schema))
+    }
+}
+
+impl From<ObjectSchema> for Schema {
+    fn from(object_schema: ObjectSchema) -> Self {
+        Schema::Object(object_schema)
+    }
+}
+
+impl From<ObjectSchema> for Arc<Schema> {
+    fn from(object_schema: ObjectSchema) -> Self {
+        Arc::new(Schema::Object(object_schema))
+    }
+}
+
+impl From<ArraySchema> for Schema {
+    fn from(array_schema: ArraySchema) -> Self {
+        Schema::Array(array_schema)
+    }
+}
+
+impl From<ArraySchema> for Arc<Schema> {
+    fn from(array_schema: ArraySchema) -> Self {
+        Arc::new(Schema::Array(array_schema))
+    }
+}
+
+pub enum ApiStringFormat {
+    Enum(Vec<String>),
+    Pattern(&'static Regex),
+    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)
+            }
+        }
+    }
+}
+
+pub fn parse_boolean(value_str: &str) -> Result<bool, Error> {
+    match value_str.to_lowercase().as_str() {
+        "1" | "on" | "yes" | "true" => Ok(true),
+        "0" | "off" | "no" | "false" => Ok(false),
+        _ => bail!("Unable to parse boolean option."),
+    }
+}
+
+fn parse_property_string(value_str: &str, schema: &Schema) -> Result<Value, Error> {
+
+    println!("Parse property string: {}", value_str);
+
+    let mut param_list: Vec<(String, String)> = vec![];
+
+    match schema {
+        Schema::Object(object_schema) => {
+            for key_val in value_str.split(',').filter(|s| !s.is_empty()) {
+                let kv: Vec<&str> = key_val.splitn(2, '=').collect();
+                if kv.len() == 2 {
+                    param_list.push((kv[0].into(), kv[1].into()));
+                } else {
+                    if let Some(key) = object_schema.default_key {
+                        param_list.push((key.into(), kv[0].into()));
+                    } else {
+                        bail!("Value without key, but schema does not define a default key.");
+                    }
+                }
+            }
+
+            return parse_parameter_strings(&param_list, &object_schema, true)
+                .map_err(Error::from);
+
+        }
+        Schema::Array(array_schema) => {
+            let mut array : Vec<Value> = vec![];
+            for value in value_str.split(',').filter(|s| !s.is_empty()) {
+                match parse_simple_value(value, &array_schema.items) {
+                    Ok(res) => array.push(res),
+                    Err(err) => bail!("unable to parse array element: {}", err),
+                }
+            }
+            array_schema.check_length(array.len())?;
+
+            return Ok(array.into());
+        }
+        _ => {
+            bail!("Got unexpetec schema type.")
+        }
+    }
+
+}
+
+pub fn parse_simple_value(value_str: &str, schema: &Schema) -> Result<Value, Error> {
+
+    let value = match schema {
+        Schema::Null => {
+            bail!("internal error - found Null schema.");
+        }
+        Schema::Boolean(_boolean_schema) => {
+            let res = parse_boolean(value_str)?;
+            Value::Bool(res)
+        }
+        Schema::Integer(integer_schema) => {
+            let res: isize = value_str.parse()?;
+            integer_schema.check_constraints(res)?;
+            Value::Number(res.into())
+        }
+        Schema::String(string_schema) => {
+            string_schema.check_constraints(value_str)?;
+            Value::String(value_str.into())
+        }
+        _ => bail!("unable to parse complex (sub) objects."),
+    };
+    Ok(value)
+}
+
+pub fn parse_parameter_strings(data: &Vec<(String, String)>, schema: &ObjectSchema, test_required: bool) -> Result<Value, ParameterError> {
+
+    let mut params = json!({});
+
+    let mut errors = ParameterError::new();
+
+    let properties = &schema.properties;
+    let additional_properties = schema.additional_properties;
+
+    for (key, value) in data {
+        if let Some((_optional, prop_schema)) = properties.get::<str>(key) {
+            match prop_schema.as_ref() {
+                Schema::Array(array_schema) => {
+                    if params[key] == Value::Null {
+                        params[key] = json!([]);
+                    }
+                    match params[key] {
+                        Value::Array(ref mut array) => {
+                            match parse_simple_value(value, &array_schema.items) {
+                                Ok(res) => array.push(res), // fixme: check_length??
+                                Err(err) => errors.push(format_err!("parameter '{}': {}", key, err)),
+                            }
+                        }
+                        _ => errors.push(format_err!("parameter '{}': expected array - type missmatch", key)),
+                    }
+                }
+                _ => {
+                    match parse_simple_value(value, prop_schema) {
+                        Ok(res) => {
+                            if params[key] == Value::Null {
+                                params[key] = res;
+                            } else {
+                                errors.push(format_err!("parameter '{}': duplicate parameter.", key));
+                            }
+                        },
+                        Err(err) => errors.push(format_err!("parameter '{}': {}", key, err)),
+                    }
+                }
+            }
+        } else {
+            if additional_properties {
+                match params[key] {
+                    Value::Null => {
+                        params[key] = Value::String(value.to_owned());
+                    },
+                    Value::String(ref old) => {
+                        params[key] = Value::Array(
+                            vec![Value::String(old.to_owned()),  Value::String(value.to_owned())]);
+                    }
+                    Value::Array(ref mut array) => {
+                        array.push(Value::String(value.to_string()));
+                    }
+                    _ => errors.push(format_err!("parameter '{}': expected array - type missmatch", key)),
+                }
+            } else {
+                errors.push(format_err!("parameter '{}': schema does not allow additional properties.", key));
+            }
+        }
+    }
+
+    if test_required && errors.len() == 0 {
+        for (name, (optional, _prop_schema)) in properties {
+            if *optional == false && params[name] == Value::Null {
+                errors.push(format_err!("parameter '{}': parameter is missing and it is not optional.", name));
+            }
+        }
+    }
+
+    if errors.len() > 0 {
+        Err(errors)
+    } else {
+        Ok(params)
+    }
+}
+
+pub fn parse_query_string(query: &str, schema: &ObjectSchema, test_required: bool) -> Result<Value,  ParameterError> {
+
+    let param_list: Vec<(String, String)> =
+        form_urlencoded::parse(query.as_bytes()).into_owned().collect();
+
+    parse_parameter_strings(&param_list, schema, test_required)
+}
+
+pub fn verify_json(data: &Value, schema: &Schema) -> Result<(), Error> {
+
+    match schema {
+        Schema::Object(object_schema) => {
+            verify_json_object(data, &object_schema)?;
+        }
+        Schema::Array(array_schema) => {
+            verify_json_array(data, &array_schema)?;
+        }
+        Schema::Null => {
+            if !data.is_null() {
+                bail!("Expected Null, but value is not Null.");
+            }
+        }
+        Schema::Boolean(boolean_schema) => verify_json_boolean(data, &boolean_schema)?,
+        Schema::Integer(integer_schema) => verify_json_integer(data, &integer_schema)?,
+        Schema::String(string_schema) => verify_json_string(data, &string_schema)?,
+    }
+    Ok(())
+}
+
+pub fn verify_json_string(data: &Value, schema: &StringSchema) -> Result<(), Error> {
+    if let Some(value) = data.as_str() {
+        schema.check_constraints(value)
+    } else {
+        bail!("Expected string value.");
+    }
+}
+
+pub fn verify_json_boolean(data: &Value, _schema: &BooleanSchema) -> Result<(), Error> {
+    if !data.is_boolean() {
+        bail!("Expected boolean value.");
+    }
+    Ok(())
+}
+
+pub fn verify_json_integer(data: &Value, schema: &IntegerSchema) -> Result<(), Error> {
+    if let Some(value) = data.as_i64() {
+        schema.check_constraints(value as isize)
+    } else {
+        bail!("Expected integer value.");
+    }
+}
+
+pub fn verify_json_array(data: &Value, schema: &ArraySchema) -> Result<(), Error> {
+
+    let list = match data {
+        Value::Array(ref list) => list,
+        Value::Object(_) => bail!("Expected array - got object."),
+        _ => bail!("Expected array - got scalar value."),
+    };
+
+    schema.check_length(list.len())?;
+
+    for item in list {
+        verify_json(item, &schema.items)?;
+    }
+
+    Ok(())
+}
+
+pub fn verify_json_object(data: &Value, schema: &ObjectSchema) -> Result<(), Error> {
+
+    let map = match data {
+        Value::Object(ref map) => map,
+        Value::Array(_) => bail!("Expected object - got array."),
+        _ => bail!("Expected object - got scalar value."),
+    };
+
+    let properties = &schema.properties;
+    let additional_properties = schema.additional_properties;
+
+    for (key, value) in map {
+        if let Some((_optional, prop_schema)) = properties.get::<str>(key) {
+            match prop_schema.as_ref() {
+                Schema::Object(object_schema) => {
+                    verify_json_object(value, object_schema)?;
+                }
+                Schema::Array(array_schema) => {
+                    verify_json_array(value, array_schema)?;
+                }
+                _ => verify_json(value, prop_schema)?,
+            }
+        } else {
+            if !additional_properties {
+                bail!("property '{}': schema does not allow additional properties.", key);
+            }
+        }
+    }
+
+    for (name, (optional, _prop_schema)) in properties {
+        if *optional == false && data[name] == Value::Null {
+            bail!("property '{}': property is missing and it is not optional.", name);
+        }
+    }
+
+    Ok(())
+}
+
+#[test]
+fn test_schema1() {
+    let schema = Schema::Object(ObjectSchema {
+        description: "TEST",
+        additional_properties: false,
+        properties: {
+            let map = HashMap::new();
+
+            map
+        },
+        default_key: None,
+    });
+
+    println!("TEST Schema: {:?}", schema);
+}
+
+#[test]
+fn test_query_string() {
+
+    let schema = ObjectSchema::new("Parameters.")
+        .required("name", StringSchema::new("Name."));
+
+    let res = parse_query_string("", &schema, true);
+    assert!(res.is_err());
+
+    let schema = ObjectSchema::new("Parameters.")
+        .optional("name", StringSchema::new("Name."));
+
+    let res = parse_query_string("", &schema, true);
+    assert!(res.is_ok());
+
+    // TEST min_length and max_length
+
+    let schema = ObjectSchema::new("Parameters.")
+        .required(
+            "name", StringSchema::new("Name.")
+                .min_length(5)
+                .max_length(10)
+        );
+
+    let res = parse_query_string("name=abcd", &schema, true);
+    assert!(res.is_err());
+
+    let res = parse_query_string("name=abcde", &schema, true);
+    assert!(res.is_ok());
+
+    let res = parse_query_string("name=abcdefghijk", &schema, true);
+    assert!(res.is_err());
+
+    let res = parse_query_string("name=abcdefghij", &schema, true);
+    assert!(res.is_ok());
+
+    // TEST regex pattern
+
+    use lazy_static::lazy_static;
+    lazy_static! {
+        static ref TEST_REGEX: Regex = Regex::new("test").unwrap();
+        static ref TEST2_REGEX: Regex = Regex::new("^test$").unwrap();
+    }
+
+    let schema = ObjectSchema::new("Parameters.")
+        .required(
+            "name", StringSchema::new("Name.")
+                .format(Arc::new(ApiStringFormat::Pattern(&TEST_REGEX)))
+        );
+
+    let res = parse_query_string("name=abcd", &schema, true);
+    assert!(res.is_err());
+
+    let res = parse_query_string("name=ateststring", &schema, true);
+    assert!(res.is_ok());
+
+    let schema = ObjectSchema::new("Parameters.")
+        .required(
+            "name", StringSchema::new("Name.")
+                .format(Arc::new(ApiStringFormat::Pattern(&TEST2_REGEX)))
+        );
+
+    let res = parse_query_string("name=ateststring", &schema, true);
+    assert!(res.is_err());
+
+    let res = parse_query_string("name=test", &schema, true);
+    assert!(res.is_ok());
+
+    // TEST string enums
+
+    let schema = ObjectSchema::new("Parameters.")
+        .required(
+            "name", StringSchema::new("Name.")
+                .format(Arc::new(ApiStringFormat::Enum(vec!["ev1".into(), "ev2".into()])))
+        );
+
+    let res = parse_query_string("name=noenum", &schema, true);
+    assert!(res.is_err());
+
+    let res = parse_query_string("name=ev1", &schema, true);
+    assert!(res.is_ok());
+
+    let res = parse_query_string("name=ev2", &schema, true);
+    assert!(res.is_ok());
+
+    let res = parse_query_string("name=ev3", &schema, true);
+    assert!(res.is_err());
+
+}
+
+#[test]
+fn test_query_integer() {
+
+    let schema = ObjectSchema::new("Parameters.")
+        .required(
+            "count" , IntegerSchema::new("Count.")
+        );
+
+    let res = parse_query_string("", &schema, true);
+    assert!(res.is_err());
+
+    let schema = ObjectSchema::new("Parameters.")
+        .optional(
+            "count", IntegerSchema::new("Count.")
+                .minimum(-3)
+                .maximum(50)
+        );
+
+    let res = parse_query_string("", &schema, true);
+    assert!(res.is_ok());
+
+    let res = parse_query_string("count=abc", &schema, false);
+    assert!(res.is_err());
+
+    let res = parse_query_string("count=30", &schema, false);
+    assert!(res.is_ok());
+
+    let res = parse_query_string("count=-1", &schema, false);
+    assert!(res.is_ok());
+
+    let res = parse_query_string("count=300", &schema, false);
+    assert!(res.is_err());
+
+    let res = parse_query_string("count=-30", &schema, false);
+    assert!(res.is_err());
+
+    let res = parse_query_string("count=50", &schema, false);
+    assert!(res.is_ok());
+
+    let res = parse_query_string("count=-3", &schema, false);
+    assert!(res.is_ok());
+}
+
+#[test]
+fn test_query_boolean() {
+
+    let schema = ObjectSchema::new("Parameters.")
+        .required(
+            "force", BooleanSchema::new("Force.")
+        );
+
+    let res = parse_query_string("", &schema, true);
+    assert!(res.is_err());
+
+    let schema = ObjectSchema::new("Parameters.")
+        .optional(
+            "force", BooleanSchema::new("Force.")
+        );
+
+    let res = parse_query_string("", &schema, true);
+    assert!(res.is_ok());
+
+    let res = parse_query_string("a=b", &schema, true);
+    assert!(res.is_err());
+
+
+    let res = parse_query_string("force", &schema, true);
+    assert!(res.is_err());
+
+    let res = parse_query_string("force=yes", &schema, true);
+    assert!(res.is_ok());
+    let res = parse_query_string("force=1", &schema, true);
+    assert!(res.is_ok());
+    let res = parse_query_string("force=On", &schema, true);
+    assert!(res.is_ok());
+    let res = parse_query_string("force=TRUE", &schema, true);
+    assert!(res.is_ok());
+    let res = parse_query_string("force=TREU", &schema, true);
+    assert!(res.is_err());
+
+    let res = parse_query_string("force=NO", &schema, true);
+    assert!(res.is_ok());
+    let res = parse_query_string("force=0", &schema, true);
+    assert!(res.is_ok());
+    let res = parse_query_string("force=off", &schema, true);
+    assert!(res.is_ok());
+    let res = parse_query_string("force=False", &schema, true);
+    assert!(res.is_ok());
+}
+
+#[test]
+fn test_verify_function() {
+
+    let schema = ObjectSchema::new("Parameters.")
+        .required(
+            "p1", StringSchema::new("P1")
+                .format(ApiStringFormat::VerifyFn(|value| {
+                    if value == "test" { return Ok(()) };
+                    bail!("format error");
+                }).into())
+        );
+
+    let res = parse_query_string("p1=tes", &schema, true);
+    assert!(res.is_err());
+    let res = parse_query_string("p1=test", &schema, true);
+    assert!(res.is_ok());
+}
+
+#[test]
+fn test_verify_complex_object() {
+
+    let nic_models = Arc::new(ApiStringFormat::Enum(
+        vec!["e1000".into(), "virtio".into()]));
+
+    let param_schema: Arc<Schema> = ObjectSchema::new("Properties.")
+        .default_key("model")
+        .required("model", StringSchema::new("Ethernet device Model.")
+                  .format(nic_models))
+        .optional("enable", BooleanSchema::new("Enable device."))
+        .into();
+
+    let schema = ObjectSchema::new("Parameters.")
+        .required(
+            "net0", StringSchema::new("First Network device.")
+                .format(ApiStringFormat::Complex(param_schema).into())
+        );
+
+    let res = parse_query_string("", &schema, true);
+    assert!(res.is_err());
+
+    let res = parse_query_string("test=abc", &schema, true);
+    assert!(res.is_err());
+
+    let res = parse_query_string("net0=model=abc", &schema, true);
+    assert!(res.is_err());
+
+    let res = parse_query_string("net0=model=virtio", &schema, true);
+     assert!(res.is_ok());
+
+    let res = parse_query_string("net0=model=virtio,enable=1", &schema, true);
+    assert!(res.is_ok());
+
+    let res = parse_query_string("net0=virtio,enable=no", &schema, true);
+    assert!(res.is_ok());
+}
+
+#[test]
+fn test_verify_complex_array() {
+
+    let param_schema: Arc<Schema> = ArraySchema::new(
+        "Integer List.", Arc::new(IntegerSchema::new("Soemething").into()))
+        .into();
+
+    let schema = ObjectSchema::new("Parameters.")
+        .required(
+            "list", StringSchema::new("A list on integers, comma separated.")
+                .format(ApiStringFormat::Complex(param_schema).into())
+        );
+
+    let res = parse_query_string("", &schema, true);
+    assert!(res.is_err());
+
+    let res = parse_query_string("list=", &schema, true);
+    assert!(res.is_ok());
+
+    let res = parse_query_string("list=abc", &schema, true);
+    assert!(res.is_err());
+
+    let res = parse_query_string("list=1", &schema, true);
+    assert!(res.is_ok());
+
+    let res = parse_query_string("list=2,3,4,5", &schema, true);
+    assert!(res.is_ok());
+
+    let param_schema: Arc<Schema> = ArraySchema::new(
+        "Integer List.", Arc::new(IntegerSchema::new("Soemething").into()))
+        .min_length(1)
+        .max_length(3)
+        .into();
+
+    let schema = ObjectSchema::new("Parameters.")
+        .required(
+            "list", StringSchema::new("A list on integers, comma separated.")
+                .format(ApiStringFormat::Complex(param_schema).into())
+        );
+
+    let res = parse_query_string("list=", &schema, true);
+    assert!(res.is_err());
+
+    let res = parse_query_string("list=1,2,3", &schema, true);
+    assert!(res.is_ok());
+
+    let res = parse_query_string("list=2,3,4,5", &schema, true);
+    assert!(res.is_err());
+}
index 74d24d97393264b254ef9e2db966c5311a51bacb..534850764e247a8424b1c022efb55cebded3991a 100644 (file)
@@ -4,8 +4,8 @@ use failure::*;
 
 use proxmox_backup::tools;
 use proxmox_backup::cli::command::*;
-use proxmox_backup::api::schema::*;
-use proxmox_backup::api::router::*;
+use proxmox_backup::api_schema::schema::*;
+use proxmox_backup::api_schema::router::*;
 
 use serde_json::{Value};
 
index 29ab6d6b1fb8d65aae4bfad7c0fc1a8add7758b6..22e35b2e6afa15ddefd8b5658e4de1197e8354ff 100644 (file)
@@ -1,8 +1,8 @@
 extern crate proxmox_backup;
 
 //use proxmox_backup::tools;
-use proxmox_backup::api::router::*;
-use proxmox_backup::api::config::*;
+use proxmox_backup::api_schema::router::*;
+use proxmox_backup::api_schema::config::*;
 use proxmox_backup::server::rest::*;
 use proxmox_backup::auth_helpers::*;
 use proxmox_backup::config;
index 48030dfde6ecabf8196b829bd0199a55ea317af3..92dff6007362d714dd6f6e34eeb4e6d275d5860e 100644 (file)
@@ -5,8 +5,8 @@ use failure::*;
 
 use proxmox_backup::tools;
 use proxmox_backup::cli::command::*;
-use proxmox_backup::api::schema::*;
-use proxmox_backup::api::router::*;
+use proxmox_backup::api_schema::schema::*;
+use proxmox_backup::api_schema::router::*;
 use proxmox_backup::client::*;
 //use proxmox_backup::backup::chunk_store::*;
 //use proxmox_backup::backup::image_index::*;
index 7eca6f173eea88a1faed2331a6e1de15b6fb9516..38b9ac69c1714cbdd9288c8bdbc113d204c07935 100644 (file)
@@ -2,8 +2,8 @@
 extern crate proxmox_backup;
 
 use proxmox_backup::tools;
-use proxmox_backup::api::router::*;
-use proxmox_backup::api::config::*;
+use proxmox_backup::api_schema::router::*;
+use proxmox_backup::api_schema::config::*;
 use proxmox_backup::server::rest::*;
 use proxmox_backup::auth_helpers::*;
 
index e31bbeb82cd2706efa58050264b2ff2f3ce24c99..973f4eeda766cc794f1db76bd6087809fa7db6df 100644 (file)
@@ -4,9 +4,9 @@ use std::collections::HashSet;
 
 use serde_json::Value;
 
-use crate::api::schema::*;
-use crate::api::router::*;
-//use crate::api::config::*;
+use crate::api_schema::schema::*;
+use crate::api_schema::router::*;
+//use crate::api_schema::config::*;
 use super::environment::CliEnvironment;
 
 use crate::getopts;
index cd4179d8daa41f1db40797649570d7b97313180b..183c02dd0e35b810de4004b4a89b4ef45a3cdd18 100644 (file)
@@ -1,4 +1,4 @@
-use crate::api::router::*;
+use crate::api_schema::router::*;
 
 use std::collections::HashMap;
 use serde_json::Value;
index 24d2738044ba807f8ec788ba5d8b650077899e60..caef0a66d337e168f7720f5ed6b4b5465ca6409c 100644 (file)
@@ -1,6 +1,6 @@
 use failure::*;
 
-use crate::api::schema::*;
+use crate::api_schema::schema::*;
 
 use std::sync::Arc;
 use lazy_static::lazy_static;
index 0098dca7905dfca3e737a6cb0952e5b6c83a1597..26dc9e4bdf1364614163d9305261a94ab17cc885 100644 (file)
@@ -5,7 +5,7 @@ use std::io::Read;
 
 //use std::sync::Arc;
 use crate::tools;
-use crate::api::schema::*;
+use crate::api_schema::schema::*;
 
 use crate::section_config::*;
 
index 1293deecec85ddae64e3aabec0d890fa562d8802..6e742fe71f0c617c632622aa981390f28d6df4a0 100644 (file)
@@ -1,4 +1,4 @@
-use crate::api::schema::*;
+use crate::api_schema::schema::*;
 
 use failure::*;
 
index b6f85f7b30dfeeb82f12a6b927bd26eb27831879..9bc6994a601c5a968cdf435296708a66aba69da4 100644 (file)
@@ -4,26 +4,8 @@ pub mod buildcfg;
 #[macro_use]
 pub mod tools;
 
-/// API definition helper
-///
-/// This module contains helper classes to define REST APIs. Method
-/// parameters and return types are described using a
-/// [Schema](schema/enum.Schema.html).
-///
-/// The [Router](router/struct.Router.html) is used to define a
-/// hierarchy of API entries, and provides ways to find an API
-/// definition by path.
-
 #[macro_use]
-pub mod api {
-
-    #[macro_use]
-    pub mod schema;
-    pub mod registry;
-    #[macro_use]
-    pub mod router;
-    pub mod config;
-}
+pub mod api_schema;
 
 #[macro_use]
 pub mod server {
index 8f5529770ffc2a733022c7bbb38eca49aa238202..a103185111fda2193690c2db4909fcce5fc51baa 100644 (file)
@@ -7,7 +7,7 @@ use std::collections::VecDeque;
 use serde_json::{json, Value};
 
 use std::sync::Arc;
-use crate::api::schema::*;
+use crate::api_schema::schema::*;
 
 pub struct SectionConfigPlugin {
     type_name: String,
index 2d6103f81bd299270ea2c2740791c87c8cf2b805..36bbcd9af593a224372a1243d6ff23b92ae9034c 100644 (file)
@@ -1,4 +1,4 @@
-use crate::api::router::*;
+use crate::api_schema::router::*;
 
 use std::collections::HashMap;
 use serde_json::Value;
index c46fc8223ac5710140212fbc4ac608d0e4a387e6..a4b898880a133dc5bc97767fe53250eb0c612784 100644 (file)
@@ -1,7 +1,7 @@
 use failure::*;
 use serde_json::{json, Value};
 
-use crate::api::router::RpcEnvironment;
+use crate::api_schema::router::RpcEnvironment;
 
 use hyper::{Body, Response, StatusCode};
 use hyper::header;
index 5d0f8249dc7ac7da7f851d5ca9946171b898c7eb..ec64824a6fe1d55d4c81d3b6c32c51bb3d3fead2 100644 (file)
@@ -1,7 +1,7 @@
 use crate::tools;
-use crate::api::schema::*;
-use crate::api::router::*;
-use crate::api::config::*;
+use crate::api_schema::schema::*;
+use crate::api_schema::router::*;
+use crate::api_schema::config::*;
 use crate::auth_helpers::*;
 use super::environment::RestEnvironment;
 use super::formatter::*;
index 1d294c0440d216a9405959632c70cab01689f6fe..9c1be18e3fa6cf627e3a016e173c35217b8573c5 100644 (file)
@@ -1,6 +1,6 @@
 use failure::*;
 
-use crate::api::schema::*;
+use crate::api_schema::schema::*;
 use crate::section_config::*;
 
 use lazy_static::lazy_static;