3 use crate::api_schema
::*;
4 use serde_json
::{json, Value}
;
5 use std
::collections
::HashMap
;
9 use hyper
::{Body, Method, Response, StatusCode}
;
10 use hyper
::rt
::Future
;
11 use hyper
::http
::request
::Parts
;
13 use super::api_handler
::*;
15 pub type BoxFut
= Box
<dyn Future
<Output
= Result
<Response
<Body
>, failure
::Error
>> + Send
>;
17 /// Abstract Interface for API methods to interact with the environment
18 pub trait RpcEnvironment
: std
::any
::Any
+ crate::tools
::AsAny
+ Send
{
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
);
24 /// Query additional result data.
25 fn get_result_attrib(&self, name
: &str) -> Option
<&Value
>;
27 /// The environment type
28 fn env_type(&self) -> RpcEnvironmentType
;
31 fn set_user(&mut self, user
: Option
<String
>);
34 fn get_user(&self) -> Option
<String
>;
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
47 /// Access from public accessible server
49 /// Access from privileged server (run as root)
53 #[derive(Debug, Fail)]
54 pub struct HttpError
{
60 pub fn new(code
: StatusCode
, message
: String
) -> Self {
61 HttpError { code, message }
65 impl fmt
::Display
for HttpError
{
66 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
67 write
!(f
, "{}", self.message
)
71 macro_rules
! http_err
{
72 ($status
:ident
, $msg
:expr
) => {{
73 Error
::from(HttpError
::new(StatusCode
::$status
, $msg
))
77 type ApiAsyncHandlerFn
= Box
<
78 dyn Fn(Parts
, Body
, Value
, &ApiAsyncMethod
, Box
<dyn RpcEnvironment
>) -> Result
<BoxFut
, Error
>
79 + Send
+ Sync
+ '
static
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.
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
>,
95 pub handler
: Option
<ApiHandlerFn
>,
100 pub fn new
<F
, Args
, R
, MetaArgs
>(func
: F
, parameters
: ObjectSchema
) -> Self
102 F
: WrapApiHandler
<Args
, R
, MetaArgs
>,
106 handler
: Some(func
.wrap()),
107 returns
: Arc
::new(Schema
::Null
),
109 reload_timezone
: false,
113 pub fn new_dummy(parameters
: ObjectSchema
) -> Self {
117 returns
: Arc
::new(Schema
::Null
),
119 reload_timezone
: false,
123 pub fn returns
<S
: Into
<Arc
<Schema
>>>(mut self, schema
: S
) -> Self {
125 self.returns
= schema
.into();
130 pub fn protected(mut self, protected
: bool
) -> Self {
132 self.protected
= protected
;
137 pub fn reload_timezone(mut self, reload_timezone
: bool
) -> Self {
139 self.reload_timezone
= reload_timezone
;
145 pub struct ApiAsyncMethod
{
146 pub parameters
: ObjectSchema
,
147 pub returns
: Arc
<Schema
>,
148 pub handler
: ApiAsyncHandlerFn
,
151 impl ApiAsyncMethod
{
153 pub fn new
<F
>(handler
: F
, parameters
: ObjectSchema
) -> Self
155 F
: Fn(Parts
, Body
, Value
, &ApiAsyncMethod
, Box
<dyn RpcEnvironment
>) -> Result
<BoxFut
, Error
>
156 + Send
+ Sync
+ '
static,
160 handler
: Box
::new(handler
),
161 returns
: Arc
::new(Schema
::Null
),
165 pub fn returns
<S
: Into
<Arc
<Schema
>>>(mut self, schema
: S
) -> Self {
167 self.returns
= schema
.into();
175 Hash(HashMap
<String
, Router
>),
176 MatchAll { router: Box<Router>, param_name: String }
,
179 pub enum MethodDefinition
{
182 Async(ApiAsyncMethod
),
186 pub get
: MethodDefinition
,
187 pub put
: MethodDefinition
,
188 pub post
: MethodDefinition
,
189 pub delete
: MethodDefinition
,
190 pub subroute
: SubRoute
,
195 pub fn new() -> Self {
197 get
: MethodDefinition
::None
,
198 put
: MethodDefinition
::None
,
199 post
: MethodDefinition
::None
,
200 delete
: MethodDefinition
::None
,
201 subroute
: SubRoute
::None
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());
209 match self.subroute
{
210 SubRoute
::Hash(ref mut map
) => {
211 map
.insert(subdir
.into(), router
);
213 _
=> panic
!("unexpected subroute type"),
218 pub fn subdirs(mut self, map
: HashMap
<String
, Router
>) -> Self {
219 self.subroute
= SubRoute
::Hash(map
);
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() }
;
227 panic
!("unexpected subroute type");
232 pub fn list_subdirs(self) -> Self {
234 MethodDefinition
::None
=> {}
,
235 _
=> panic
!("cannot create directory index - method get already in use"),
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))
246 _
=> panic
!("cannot create directory index (no SubRoute::Hash)"),
250 pub fn get(mut self, m
: ApiMethod
) -> Self {
251 self.get
= MethodDefinition
::Simple(m
);
255 pub fn put(mut self, m
: ApiMethod
) -> Self {
256 self.put
= MethodDefinition
::Simple(m
);
260 pub fn post(mut self, m
: ApiMethod
) -> Self {
261 self.post
= MethodDefinition
::Simple(m
);
265 pub fn upload(mut self, m
: ApiAsyncMethod
) -> Self {
266 self.post
= MethodDefinition
::Async(m
);
270 pub fn download(mut self, m
: ApiAsyncMethod
) -> Self {
271 self.get
= MethodDefinition
::Async(m
);
275 pub fn upgrade(mut self, m
: ApiAsyncMethod
) -> Self {
276 self.get
= MethodDefinition
::Async(m
);
280 pub fn delete(mut self, m
: ApiMethod
) -> Self {
281 self.delete
= MethodDefinition
::Simple(m
);
285 pub fn find_route(&self, components
: &[&str], uri_param
: &mut HashMap
<String
, String
>) -> Option
<&Router
> {
287 if components
.is_empty() { return Some(self); }
;
289 let (dir
, rest
) = (components
[0], &components
[1..]);
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
);
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
);
313 uri_param
: &mut HashMap
<String
, String
>
314 ) -> &MethodDefinition
{
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
,
325 &MethodDefinition
::None
329 impl Default
for Router
{
330 fn default() -> Self {