]> git.proxmox.com Git - proxmox-backup.git/blob - src/api_schema/router.rs
c449de0b5c8c142275dccb78cea50f48f9d645e8
[proxmox-backup.git] / src / api_schema / router.rs
1 use failure::*;
2
3 use crate::api_schema::*;
4 use serde_json::{json, Value};
5 use std::collections::HashMap;
6 use std::sync::Arc;
7 use std::fmt;
8
9 use hyper::{Body, Method, Response, StatusCode};
10 use hyper::rt::Future;
11 use hyper::http::request::Parts;
12
13 use super::api_handler::*;
14
15 pub type BoxFut = Box<dyn Future<Output = Result<Response<Body>, failure::Error>> + Send>;
16
17 /// Abstract Interface for API methods to interact with the environment
18 pub trait RpcEnvironment: std::any::Any + crate::tools::AsAny + Send {
19
20 /// Use this to pass additional result data. It is up to the environment
21 /// how the data is used.
22 fn set_result_attrib(&mut self, name: &str, value: Value);
23
24 /// Query additional result data.
25 fn get_result_attrib(&self, name: &str) -> Option<&Value>;
26
27 /// The environment type
28 fn env_type(&self) -> RpcEnvironmentType;
29
30 /// Set user name
31 fn set_user(&mut self, user: Option<String>);
32
33 /// Get user name
34 fn get_user(&self) -> Option<String>;
35 }
36
37
38 /// Environment Type
39 ///
40 /// We use this to enumerate the different environment types. Some methods
41 /// needs to do different things when started from the command line interface,
42 /// or when executed from a privileged server running as root.
43 #[derive(PartialEq, Copy, Clone)]
44 pub enum RpcEnvironmentType {
45 /// Command started from command line
46 CLI,
47 /// Access from public accessible server
48 PUBLIC,
49 /// Access from privileged server (run as root)
50 PRIVILEGED,
51 }
52
53 #[derive(Debug, Fail)]
54 pub struct HttpError {
55 pub code: StatusCode,
56 pub message: String,
57 }
58
59 impl HttpError {
60 pub fn new(code: StatusCode, message: String) -> Self {
61 HttpError { code, message }
62 }
63 }
64
65 impl fmt::Display for HttpError {
66 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67 write!(f, "{}", self.message)
68 }
69 }
70
71 macro_rules! http_err {
72 ($status:ident, $msg:expr) => {{
73 Error::from(HttpError::new(StatusCode::$status, $msg))
74 }}
75 }
76
77 type ApiAsyncHandlerFn = Box<
78 dyn Fn(Parts, Body, Value, &ApiAsyncMethod, Box<dyn RpcEnvironment>) -> Result<BoxFut, Error>
79 + Send + Sync + 'static
80 >;
81
82 /// This struct defines synchronous API call which returns the restulkt as json `Value`
83 pub struct ApiMethod {
84 /// The protected flag indicates that the provides function should be forwarded
85 /// to the deaemon running in priviledged mode.
86 pub protected: bool,
87 /// This flag indicates that the provided method may change the local timezone, so the server
88 /// should do a tzset afterwards
89 pub reload_timezone: bool,
90 /// Parameter type Schema
91 pub parameters: ObjectSchema,
92 /// Return type Schema
93 pub returns: Arc<Schema>,
94 /// Handler function
95 pub handler: Option<ApiHandlerFn>,
96 }
97
98 impl ApiMethod {
99
100 pub fn new<F, Args, R, MetaArgs>(func: F, parameters: ObjectSchema) -> Self
101 where
102 F: WrapApiHandler<Args, R, MetaArgs>,
103 {
104 Self {
105 parameters,
106 handler: Some(func.wrap()),
107 returns: Arc::new(Schema::Null),
108 protected: false,
109 reload_timezone: false,
110 }
111 }
112
113 pub fn new_dummy(parameters: ObjectSchema) -> Self {
114 Self {
115 parameters,
116 handler: None,
117 returns: Arc::new(Schema::Null),
118 protected: false,
119 reload_timezone: false,
120 }
121 }
122
123 pub fn returns<S: Into<Arc<Schema>>>(mut self, schema: S) -> Self {
124
125 self.returns = schema.into();
126
127 self
128 }
129
130 pub fn protected(mut self, protected: bool) -> Self {
131
132 self.protected = protected;
133
134 self
135 }
136
137 pub fn reload_timezone(mut self, reload_timezone: bool) -> Self {
138
139 self.reload_timezone = reload_timezone;
140
141 self
142 }
143 }
144
145 pub struct ApiAsyncMethod {
146 pub parameters: ObjectSchema,
147 pub returns: Arc<Schema>,
148 pub handler: ApiAsyncHandlerFn,
149 }
150
151 impl ApiAsyncMethod {
152
153 pub fn new<F>(handler: F, parameters: ObjectSchema) -> Self
154 where
155 F: Fn(Parts, Body, Value, &ApiAsyncMethod, Box<dyn RpcEnvironment>) -> Result<BoxFut, Error>
156 + Send + Sync + 'static,
157 {
158 Self {
159 parameters,
160 handler: Box::new(handler),
161 returns: Arc::new(Schema::Null),
162 }
163 }
164
165 pub fn returns<S: Into<Arc<Schema>>>(mut self, schema: S) -> Self {
166
167 self.returns = schema.into();
168
169 self
170 }
171 }
172
173 pub enum SubRoute {
174 None,
175 Hash(HashMap<String, Router>),
176 MatchAll { router: Box<Router>, param_name: String },
177 }
178
179 pub enum MethodDefinition {
180 None,
181 Simple(ApiMethod),
182 Async(ApiAsyncMethod),
183 }
184
185 pub struct Router {
186 pub get: MethodDefinition,
187 pub put: MethodDefinition,
188 pub post: MethodDefinition,
189 pub delete: MethodDefinition,
190 pub subroute: SubRoute,
191 }
192
193 impl Router {
194
195 pub fn new() -> Self {
196 Self {
197 get: MethodDefinition::None,
198 put: MethodDefinition::None,
199 post: MethodDefinition::None,
200 delete: MethodDefinition::None,
201 subroute: SubRoute::None
202 }
203 }
204
205 pub fn subdir<S: Into<String>>(mut self, subdir: S, router: Router) -> Self {
206 if let SubRoute::None = self.subroute {
207 self.subroute = SubRoute::Hash(HashMap::new());
208 }
209 match self.subroute {
210 SubRoute::Hash(ref mut map) => {
211 map.insert(subdir.into(), router);
212 }
213 _ => panic!("unexpected subroute type"),
214 }
215 self
216 }
217
218 pub fn subdirs(mut self, map: HashMap<String, Router>) -> Self {
219 self.subroute = SubRoute::Hash(map);
220 self
221 }
222
223 pub fn match_all<S: Into<String>>(mut self, param_name: S, router: Router) -> Self {
224 if let SubRoute::None = self.subroute {
225 self.subroute = SubRoute::MatchAll { router: Box::new(router), param_name: param_name.into() };
226 } else {
227 panic!("unexpected subroute type");
228 }
229 self
230 }
231
232 pub fn list_subdirs(self) -> Self {
233 match self.get {
234 MethodDefinition::None => {},
235 _ => panic!("cannot create directory index - method get already in use"),
236 }
237 match self.subroute {
238 SubRoute::Hash(ref map) => {
239 let index = json!(map.keys().map(|s| json!({ "subdir": s}))
240 .collect::<Vec<Value>>());
241 self.get(ApiMethod::new(
242 move || { Ok(index.clone()) },
243 ObjectSchema::new("Directory index.").additional_properties(true))
244 )
245 }
246 _ => panic!("cannot create directory index (no SubRoute::Hash)"),
247 }
248 }
249
250 pub fn get(mut self, m: ApiMethod) -> Self {
251 self.get = MethodDefinition::Simple(m);
252 self
253 }
254
255 pub fn put(mut self, m: ApiMethod) -> Self {
256 self.put = MethodDefinition::Simple(m);
257 self
258 }
259
260 pub fn post(mut self, m: ApiMethod) -> Self {
261 self.post = MethodDefinition::Simple(m);
262 self
263 }
264
265 pub fn upload(mut self, m: ApiAsyncMethod) -> Self {
266 self.post = MethodDefinition::Async(m);
267 self
268 }
269
270 pub fn download(mut self, m: ApiAsyncMethod) -> Self {
271 self.get = MethodDefinition::Async(m);
272 self
273 }
274
275 pub fn upgrade(mut self, m: ApiAsyncMethod) -> Self {
276 self.get = MethodDefinition::Async(m);
277 self
278 }
279
280 pub fn delete(mut self, m: ApiMethod) -> Self {
281 self.delete = MethodDefinition::Simple(m);
282 self
283 }
284
285 pub fn find_route(&self, components: &[&str], uri_param: &mut HashMap<String, String>) -> Option<&Router> {
286
287 if components.is_empty() { return Some(self); };
288
289 let (dir, rest) = (components[0], &components[1..]);
290
291 match self.subroute {
292 SubRoute::None => {},
293 SubRoute::Hash(ref dirmap) => {
294 if let Some(ref router) = dirmap.get(dir) {
295 //println!("FOUND SUBDIR {}", dir);
296 return router.find_route(rest, uri_param);
297 }
298 }
299 SubRoute::MatchAll { ref router, ref param_name } => {
300 //println!("URI PARAM {} = {}", param_name, dir); // fixme: store somewhere
301 uri_param.insert(param_name.clone(), dir.into());
302 return router.find_route(rest, uri_param);
303 },
304 }
305
306 None
307 }
308
309 pub fn find_method(
310 &self,
311 components: &[&str],
312 method: Method,
313 uri_param: &mut HashMap<String, String>
314 ) -> &MethodDefinition {
315
316 if let Some(info) = self.find_route(components, uri_param) {
317 return match method {
318 Method::GET => &info.get,
319 Method::PUT => &info.put,
320 Method::POST => &info.post,
321 Method::DELETE => &info.delete,
322 _ => &MethodDefinition::None,
323 };
324 }
325 &MethodDefinition::None
326 }
327 }
328
329 impl Default for Router {
330 fn default() -> Self {
331 Self::new()
332 }
333 }