api_schema: allow generic api handler functions

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2019-04-16 10:36:04 +02:00
parent 286f0d4099
commit 062d4916ff
13 changed files with 176 additions and 26 deletions

View File

@ -35,7 +35,7 @@ pub fn router() -> Router {
let route = Router::new() let route = Router::new()
.get(ApiMethod::new( .get(ApiMethod::new(
|_,_,_| Ok(json!([ || Ok(json!([
{"subdir": "access"}, {"subdir": "access"},
{"subdir": "admin"}, {"subdir": "admin"},
{"subdir": "config"}, {"subdir": "config"},

View File

@ -70,7 +70,7 @@ pub fn router() -> Router {
let route = Router::new() let route = Router::new()
.get(ApiMethod::new( .get(ApiMethod::new(
|_,_,_| Ok(json!([ || Ok(json!([
{"subdir": "ticket"} {"subdir": "ticket"}
])), ])),
ObjectSchema::new("Directory index."))) ObjectSchema::new("Directory index.")))

View File

@ -8,7 +8,7 @@ pub fn router() -> Router {
let route = Router::new() let route = Router::new()
.get(ApiMethod::new( .get(ApiMethod::new(
|_,_,_| Ok(json!([ || Ok(json!([
{"subdir": "datastore"} {"subdir": "datastore"}
])), ])),
ObjectSchema::new("Directory index."))) ObjectSchema::new("Directory index.")))

View File

@ -388,7 +388,7 @@ pub fn router() -> Router {
let datastore_info = Router::new() let datastore_info = Router::new()
.get(ApiMethod::new( .get(ApiMethod::new(
|_,_,_| Ok(json!([ || Ok(json!([
{"subdir": "backups" }, {"subdir": "backups" },
{"subdir": "pxar" }, {"subdir": "pxar" },
{"subdir": "gc" }, {"subdir": "gc" },

View File

@ -11,7 +11,7 @@ pub fn router() -> Router {
let route = Router::new() let route = Router::new()
.get(ApiMethod::new( .get(ApiMethod::new(
|_,_,_| Ok(json!([ || Ok(json!([
{"subdir": "datastore"}, {"subdir": "datastore"},
])), ])),
ObjectSchema::new("Directory index."))) ObjectSchema::new("Directory index.")))

View File

@ -35,7 +35,7 @@ pub fn router() -> Router {
let route = Router::new() let route = Router::new()
.get(ApiMethod::new( .get(ApiMethod::new(
|_,_,_| Ok(json!([ || Ok(json!([
{"subdir": "dns"}, {"subdir": "dns"},
{"subdir": "network"}, {"subdir": "network"},
{"subdir": "services"}, {"subdir": "services"},

View File

@ -224,7 +224,7 @@ pub fn router() -> Router {
let service_api = Router::new() let service_api = Router::new()
.get(ApiMethod::new( .get(ApiMethod::new(
|_,_,_| { || {
let mut result = vec![]; let mut result = vec![];
for cmd in &["state", "start", "stop", "restart", "reload"] { for cmd in &["state", "start", "stop", "restart", "reload"] {
result.push(json!({"subdir": cmd })); result.push(json!({"subdir": cmd }));

View File

@ -175,7 +175,7 @@ pub fn router() -> Router {
let upid_api = Router::new() let upid_api = Router::new()
.get(ApiMethod::new( .get(ApiMethod::new(
|_,_,_| { || {
let mut result = vec![]; let mut result = vec![];
for cmd in &["log", "status"] { for cmd in &["log", "status"] {
result.push(json!({"subdir": cmd })); result.push(json!({"subdir": cmd }));

View File

@ -12,6 +12,7 @@
mod schema; mod schema;
pub use schema::*; pub use schema::*;
pub mod api_handler;
pub mod registry; pub mod registry;
#[macro_use] #[macro_use]
pub mod router; pub mod router;

View File

@ -0,0 +1,135 @@
use failure::Error;
use serde_json::Value;
use super::router::{ApiMethod, RpcEnvironment};
pub type ApiHandlerFn = Box<
dyn Fn(Value, &ApiMethod, &mut dyn RpcEnvironment) -> Result<Value, Error>
+ Send + Sync + 'static
>;
pub trait WrapApiHandler<Args, R, MetaArgs> {
fn wrap(self) -> ApiHandlerFn;
}
// fn()
impl<F, R> WrapApiHandler<(), R, ()> for F
where
F: Fn() -> Result<R, Error> + Send + Sync + 'static,
R: serde::Serialize,
{
fn wrap(self) -> ApiHandlerFn {
Box::new(move |_value, _method, _rpc_env| {
Ok(serde_json::to_value((self)()?)?)
})
}
}
// fn(Arg)
impl<F, A, R> WrapApiHandler<(A,), R, ()> for F
where
F: Fn(A) -> Result<R, Error> + Send + Sync + 'static,
A: serde::de::DeserializeOwned,
R: serde::Serialize,
{
fn wrap(self) -> ApiHandlerFn {
Box::new(move |value, _method, _rpc_env| {
Ok(serde_json::to_value((self)(serde_json::from_value(value)?)?)?)
})
}
}
// fn(&ApiMethod)
impl<F, R> WrapApiHandler<(), R, (ApiMethod,)> for F
where
F: Fn(&ApiMethod) -> Result<R, Error> + Send + Sync + 'static,
R: serde::Serialize,
{
fn wrap(self) -> ApiHandlerFn {
Box::new(move |_value, method, _rpc_env| {
Ok(serde_json::to_value((self)(method)?)?)
})
}
}
// fn(Arg, &ApiMethod)
impl<F, A, R> WrapApiHandler<(A,), R, (ApiMethod,)> for F
where
F: Fn(A, &ApiMethod) -> Result<R, Error> + Send + Sync + 'static,
A: serde::de::DeserializeOwned,
R: serde::Serialize,
{
fn wrap(self) -> ApiHandlerFn {
Box::new(move |value, method, _rpc_env| {
Ok(serde_json::to_value((self)(
serde_json::from_value(value)?,
method,
)?)?)
})
}
}
// RpcEnvironment is a trait, so use a "marker" type for it instead:
pub struct RpcEnvArg();
// fn(&mut dyn RpcEnvironment)
impl<F, R> WrapApiHandler<(), R, (RpcEnvArg,)> for F
where
F: Fn(&mut dyn RpcEnvironment) -> Result<R, Error> + Send + Sync + 'static,
R: serde::Serialize,
{
fn wrap(self) -> ApiHandlerFn {
Box::new(move |_value, _method, rpc_env| {
Ok(serde_json::to_value((self)(rpc_env)?)?)
})
}
}
// fn(Arg, &mut dyn RpcEnvironment)
impl<F, A, R> WrapApiHandler<(A,), R, (RpcEnvArg,)> for F
where
F: Fn(A, &mut dyn RpcEnvironment) -> Result<R, Error> + Send + Sync + 'static,
A: serde::de::DeserializeOwned,
R: serde::Serialize,
{
fn wrap(self) -> ApiHandlerFn {
Box::new(move |value, _method, rpc_env| {
Ok(serde_json::to_value((self)(
serde_json::from_value(value)?,
rpc_env,
)?)?)
})
}
}
// fn(&ApiMethod, &mut dyn RpcEnvironment)
impl<F, R> WrapApiHandler<(), R, (ApiMethod, RpcEnvArg,)> for F
where
F: Fn(&ApiMethod, &mut dyn RpcEnvironment) -> Result<R, Error> + Send + Sync + 'static,
R: serde::Serialize,
{
fn wrap(self) -> ApiHandlerFn {
Box::new(move |_value, method, rpc_env| {
Ok(serde_json::to_value((self)(method, rpc_env)?)?)
})
}
}
// fn(Arg, &ApiMethod, &mut dyn RpcEnvironment)
impl<F, A, R> WrapApiHandler<(A,), R, (ApiMethod, RpcEnvArg,)> for F
where
F: Fn(A, &ApiMethod, &mut dyn RpcEnvironment) -> Result<R, Error> + Send + Sync + 'static,
A: serde::de::DeserializeOwned,
R: serde::Serialize,
{
fn wrap(self) -> ApiHandlerFn {
Box::new(move |value, method, rpc_env| {
Ok(serde_json::to_value((self)(
serde_json::from_value(value)?,
method,
rpc_env,
)?)?)
})
}
}

View File

@ -10,6 +10,8 @@ use hyper::{Body, Response, StatusCode};
use hyper::rt::Future; use hyper::rt::Future;
use hyper::http::request::Parts; use hyper::http::request::Parts;
use super::api_handler::*;
pub type BoxFut = Box<Future<Item = Response<Body>, Error = failure::Error> + Send>; pub type BoxFut = Box<Future<Item = Response<Body>, Error = failure::Error> + Send>;
/// Abstract Interface for API methods to interact with the environment /// Abstract Interface for API methods to interact with the environment
@ -72,9 +74,10 @@ macro_rules! http_err {
}} }}
} }
type ApiHandlerFn = fn(Value, &ApiMethod, &mut dyn RpcEnvironment) -> Result<Value, Error>; type ApiAsyncHandlerFn = Box<
dyn Fn(Parts, Body, Value, &ApiAsyncMethod, &mut dyn RpcEnvironment) -> Result<BoxFut, Error>
type ApiAsyncHandlerFn = fn(Parts, Body, Value, &ApiAsyncMethod, &mut dyn RpcEnvironment) -> Result<BoxFut, Error>; + Send + Sync + 'static
>;
/// This struct defines synchronous API call which returns the restulkt as json `Value` /// This struct defines synchronous API call which returns the restulkt as json `Value`
pub struct ApiMethod { pub struct ApiMethod {
@ -89,15 +92,28 @@ pub struct ApiMethod {
/// Return type Schema /// Return type Schema
pub returns: Arc<Schema>, pub returns: Arc<Schema>,
/// Handler function /// Handler function
pub handler: ApiHandlerFn, pub handler: Option<ApiHandlerFn>,
} }
impl ApiMethod { impl ApiMethod {
pub fn new(handler: ApiHandlerFn, parameters: ObjectSchema) -> Self { pub fn new<F, Args, R, MetaArgs>(func: F, parameters: ObjectSchema) -> Self
where
F: WrapApiHandler<Args, R, MetaArgs>,
{
Self { Self {
parameters, parameters,
handler, 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), returns: Arc::new(Schema::Null),
protected: false, protected: false,
reload_timezone: false, reload_timezone: false,
@ -134,10 +150,14 @@ pub struct ApiAsyncMethod {
impl ApiAsyncMethod { impl ApiAsyncMethod {
pub fn new(handler: ApiAsyncHandlerFn, parameters: ObjectSchema) -> Self { pub fn new<F>(handler: F, parameters: ObjectSchema) -> Self
where
F: Fn(Parts, Body, Value, &ApiAsyncMethod, &mut dyn RpcEnvironment) -> Result<BoxFut, Error>
+ Send + Sync + 'static,
{
Self { Self {
parameters, parameters,
handler, handler: Box::new(handler),
returns: Arc::new(Schema::Null), returns: Arc::new(Schema::Null),
} }
} }

View File

@ -1,7 +1,6 @@
use failure::*; use failure::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use serde_json::Value;
use crate::api_schema::*; use crate::api_schema::*;
use crate::api_schema::router::*; use crate::api_schema::router::*;
@ -268,7 +267,7 @@ fn handle_simple_command(
} }
}; };
if (cli_cmd.info.handler as *const fn()) == (dummy_help as *const fn()) { if cli_cmd.info.handler.is_none() {
let prefix = prefix.split(' ').next().unwrap().to_string(); let prefix = prefix.split(' ').next().unwrap().to_string();
print_help(top_def, prefix, &rest, params["verbose"].as_bool()); print_help(top_def, prefix, &rest, params["verbose"].as_bool());
return; return;
@ -282,7 +281,7 @@ fn handle_simple_command(
let mut rpcenv = CliEnvironment::new(); let mut rpcenv = CliEnvironment::new();
match (cli_cmd.info.handler)(params, &cli_cmd.info, &mut rpcenv) { match (cli_cmd.info.handler.as_ref().unwrap())(params, &cli_cmd.info, &mut rpcenv) {
Ok(value) => { Ok(value) => {
println!("Result: {}", serde_json::to_string_pretty(&value).unwrap()); println!("Result: {}", serde_json::to_string_pretty(&value).unwrap());
} }
@ -603,8 +602,7 @@ pub fn print_bash_completion(def: &CommandLineInterface) {
fn help_command_def() -> CliCommand { fn help_command_def() -> CliCommand {
CliCommand::new( CliCommand::new(
ApiMethod::new( ApiMethod::new_dummy(
dummy_help,
ObjectSchema::new("Get help about specified command.") ObjectSchema::new("Get help about specified command.")
.optional("verbose", BooleanSchema::new("Verbose help.")) .optional("verbose", BooleanSchema::new("Verbose help."))
) )
@ -693,10 +691,6 @@ pub struct CliCommandMap {
pub commands: HashMap<String, CommandLineInterface>, pub commands: HashMap<String, CommandLineInterface>,
} }
fn dummy_help(_param: Value, _info: &ApiMethod, _rpcenv: &mut RpcEnvironment) -> Result<Value, Error> {
panic!("internal error"); // this is just a place holder - never call this
}
impl CliCommandMap { impl CliCommandMap {
pub fn new() -> Self { pub fn new() -> Self {

View File

@ -239,7 +239,7 @@ fn handle_sync_api_request(
let resp = params let resp = params
.and_then(move |params| { .and_then(move |params| {
let mut delay = false; let mut delay = false;
let resp = match (info.handler)(params, info, &mut rpcenv) { let resp = match (info.handler.as_ref().unwrap())(params, info, &mut rpcenv) {
Ok(data) => (formatter.format_result)(data, &rpcenv), Ok(data) => (formatter.format_result)(data, &rpcenv),
Err(err) => { Err(err) => {
if let Some(httperr) = err.downcast_ref::<HttpError>() { if let Some(httperr) = err.downcast_ref::<HttpError>() {