use failure::*; use crate::api_schema::*; use serde_json::{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; use super::api_handler::*; pub type BoxFut = Box, Error = failure::Error> + Send>; /// Abstract Interface for API methods to interact with the environment pub trait RpcEnvironment { /// Use this to pass additional result data. It is up to the environment /// how the data is used. fn set_result_attrib(&mut self, name: &str, value: Value); /// Query additional result data. fn get_result_attrib(&self, name: &str) -> Option<&Value>; /// The environment type fn env_type(&self) -> RpcEnvironmentType; /// Set user name fn set_user(&mut self, user: Option); /// Get user name fn get_user(&self) -> Option; } /// Environment Type /// /// We use this to enumerate the different environment types. Some methods /// needs to do different things when started from the command line interface, /// or when executed from a privileged server running as root. #[derive(PartialEq, Copy, Clone)] pub enum RpcEnvironmentType { /// Command started from command line CLI, /// Access from public accessible server PUBLIC, /// Access from privileged server (run as root) PRIVILEGED, } #[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, "{}", self.message) } } macro_rules! http_err { ($status:ident, $msg:expr) => {{ Error::from(HttpError::new(StatusCode::$status, $msg)) }} } type ApiAsyncHandlerFn = Box< dyn Fn(Parts, Body, Value, &ApiAsyncMethod, &mut dyn RpcEnvironment) -> Result + Send + Sync + 'static >; /// 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, /// Handler function pub handler: Option, } impl ApiMethod { pub fn new(func: F, parameters: ObjectSchema) -> Self where F: WrapApiHandler, { Self { parameters, handler: Some(func.wrap()), returns: Arc::new(Schema::Null), protected: false, reload_timezone: false, } } pub fn new_dummy(parameters: ObjectSchema) -> Self { Self { parameters, handler: None, returns: Arc::new(Schema::Null), protected: false, reload_timezone: false, } } pub fn returns>>(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, pub handler: ApiAsyncHandlerFn, } impl ApiAsyncMethod { pub fn new(handler: F, parameters: ObjectSchema) -> Self where F: Fn(Parts, Body, Value, &ApiAsyncMethod, &mut dyn RpcEnvironment) -> Result + Send + Sync + 'static, { Self { parameters, handler: Box::new(handler), returns: Arc::new(Schema::Null), } } pub fn returns>>(mut self, schema: S) -> Self { self.returns = schema.into(); self } } pub enum SubRoute { None, Hash(HashMap), MatchAll { router: Box, 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>(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) -> Self { self.subroute = SubRoute::Hash(map); self } pub fn match_all>(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 list_subdirs(self) -> Self { match self.get { MethodDefinition::None => {}, _ => panic!("cannot create directory index - method get already in use"), } match self.subroute { SubRoute::Hash(ref map) => { let index = json!(map.keys().map(|s| json!({ "subdir": s})) .collect::>()); self.get(ApiMethod::new( move || { Ok(index.clone()) }, ObjectSchema::new("Directory index.").additional_properties(true)) ) } _ => panic!("cannot create directory index (no SubRoute::Hash)"), } } 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 upgrade(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) -> 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 } }