api: split ApiHandler out of ApiMethodInfo

The method info part is not generic after all.
(Makes it easier to test different representations of
ApiHandler without having to adapt all the other methods as
well.)

Signed-off-by: Wolfgang Bumiller <wry.git@bumiller.com>
This commit is contained in:
Wolfgang Bumiller 2019-06-23 12:52:04 +02:00 committed by Wolfgang Bumiller
parent 8a2d7a43e0
commit 205a9fc2aa
4 changed files with 47 additions and 31 deletions

View File

@ -194,20 +194,20 @@ fn handle_function(
use std::iter::FromIterator;
let arg_extraction = TokenStream::from_iter(arg_extraction.into_iter());
// The router expects an ApiMethod, or more accurately, an object implementing ApiMethodInfo.
// The router expects an ApiMethod, or more accurately, an object implementing ApiHandler.
// This is because we need access to a bunch of additional attributes of the functions both at
// runtime and when doing command line parsing/completion/help output.
//
// When manually implementing methods, we usually just write them out as an `ApiMethod` which
// is a type requiring all the info made available by the ApiMethodInfo trait as members.
// is a type requiring all the info made available by the ApiHandler trait as members.
//
// While we could just generate a `const ApiMethod` for our functions, we would like them to
// also be usable as functions simply because the syntax we use to create them makes them
// *look* like functions, so it would be nice if they also *behaved* like real functions.
//
// Therefore all the fields of an ApiMethod are accessed via methods from the ApiMethodInfo
// trait and we perform the same trick lazy_static does: Create a new type implementing
// ApiMethodInfo, and make its instance Deref to an actual function.
// Therefore all the fields of an ApiMethod are accessed via methods from the ApiHandler trait
// and we perform the same trick lazy_static does: Create a new type implementing ApiHandler,
// and make its instance Deref to an actual function.
// This way the function can still be used normally. Validators for parameters will be
// executed, serialization happens only when coming from the method's `handler`.
@ -334,8 +334,6 @@ fn handle_function(
// Note that technically we don't need the `description` member in this trait, as this is
// mostly used at compile time for documentation!
impl ::proxmox::api::ApiMethodInfo for #struct_name {
type Body = #body_type;
fn description(&self) -> &'static str {
#fn_api_description
}
@ -356,10 +354,18 @@ fn handle_function(
fn reload_timezone(&self) -> bool {
#fn_api_reload_timezone
}
}
impl ::proxmox::api::ApiHandler for #struct_name {
type Body = #body_type;
fn call(&self, params: ::serde_json::Value) -> ::proxmox::api::ApiFuture<#body_type> {
#struct_name::wrapped_api_handler(params)
}
fn method_info(&self) -> &(dyn ::proxmox::api::ApiMethodInfo + Send + Sync) {
self as _
}
}
});

View File

@ -8,17 +8,21 @@ use serde_json::{json, Value};
/// This contains all the info required to call, document, or command-line-complete parameters for
/// a method.
pub trait ApiMethodInfo {
type Body;
fn description(&self) -> &'static str;
fn parameters(&self) -> &'static [Parameter];
fn return_type(&self) -> &'static TypeInfo;
fn protected(&self) -> bool;
fn reload_timezone(&self) -> bool;
fn call(&self, params: Value) -> super::ApiFuture<Self::Body>;
}
impl<Body: 'static> dyn ApiMethodInfo<Body = Body> {
pub trait ApiHandler: ApiMethodInfo + Send + Sync {
type Body;
fn call(&self, params: Value) -> super::ApiFuture<Self::Body>;
fn method_info(&self) -> &(dyn ApiMethodInfo + Send + Sync);
}
impl<Body: 'static> dyn ApiHandler<Body = Body> {
pub fn call_as<ToBody>(&self, params: Value) -> super::ApiFuture<ToBody>
where
Body: Into<ToBody>,
@ -97,8 +101,6 @@ pub struct ApiMethod<Body> {
}
impl<Body> ApiMethodInfo for ApiMethod<Body> {
type Body = Body;
fn description(&self) -> &'static str {
self.description
}
@ -118,13 +120,21 @@ impl<Body> ApiMethodInfo for ApiMethod<Body> {
fn reload_timezone(&self) -> bool {
self.reload_timezone
}
}
impl<Body> ApiHandler for ApiMethod<Body> {
type Body = Body;
fn call(&self, params: Value) -> super::ApiFuture<Body> {
(self.handler)(params)
}
fn method_info(&self) -> &(dyn ApiMethodInfo + Send + Sync) {
self as _
}
}
impl<Body> dyn ApiMethodInfo<Body = Body> + Send + Sync {
impl dyn ApiMethodInfo + Send + Sync {
pub fn api_dump(&self) -> Value {
let parameters = Value::Object(std::iter::FromIterator::from_iter(
self.parameters()
@ -311,7 +321,7 @@ pub trait UnifiedApiMethod<Body>: Send + Sync {
impl<T: Send + Sync + 'static, Body> UnifiedApiMethod<Body> for T
where
T: ApiMethodInfo,
T: ApiHandler,
T::Body: 'static + Into<Body>,
{
fn parameters(&self) -> &'static [Parameter] {
@ -319,6 +329,6 @@ where
}
fn call(&self, params: Value) -> super::ApiFuture<Body> {
(self as &dyn ApiMethodInfo<Body = T::Body>).call_as(params)
(self as &dyn ApiHandler<Body = T::Body>).call_as(params)
}
}

View File

@ -8,7 +8,7 @@ use failure::{bail, format_err, Error};
use serde::Serialize;
use serde_json::Value;
use super::{ApiMethodInfo, ApiOutput, Parameter, UnifiedApiMethod};
use super::{ApiHandler, ApiOutput, Parameter, UnifiedApiMethod};
type MethodInfoRef = &'static dyn UnifiedApiMethod<Bytes>;
@ -87,7 +87,7 @@ impl Command {
positional_args: &'static [&'static str],
) -> Self
where
T: ApiMethodInfo,
T: ApiHandler,
T::Body: 'static + Into<Bytes>,
{
Command::Method(Method::new(method, positional_args))

View File

@ -4,7 +4,7 @@ use std::collections::HashMap;
use serde_json::{json, Value};
use super::ApiMethodInfo;
use super::ApiHandler;
/// This enum specifies what to do when a subdirectory is requested from the current router.
///
@ -32,16 +32,16 @@ pub enum SubRoute<Body: 'static> {
#[derive(Default)]
pub struct Router<Body: 'static> {
/// The `GET` http method.
pub get: Option<&'static (dyn ApiMethodInfo<Body = Body> + Send + Sync)>,
pub get: Option<&'static (dyn ApiHandler<Body = Body> + Send + Sync)>,
/// The `PUT` http method.
pub put: Option<&'static (dyn ApiMethodInfo<Body = Body> + Send + Sync)>,
pub put: Option<&'static (dyn ApiHandler<Body = Body> + Send + Sync)>,
/// The `POST` http method.
pub post: Option<&'static (dyn ApiMethodInfo<Body = Body> + Send + Sync)>,
pub post: Option<&'static (dyn ApiHandler<Body = Body> + Send + Sync)>,
/// The `DELETE` http method.
pub delete: Option<&'static (dyn ApiMethodInfo<Body = Body> + Send + Sync)>,
pub delete: Option<&'static (dyn ApiHandler<Body = Body> + Send + Sync)>,
/// Specifies the behavior of sub directories. See [`SubRoute`].
pub subroute: Option<SubRoute<Body>>,
@ -114,19 +114,19 @@ where
let mut this = serde_json::Map::<String, Value>::new();
if let Some(get) = self.get {
this.insert("GET".to_string(), get.api_dump());
this.insert("GET".to_string(), get.method_info().api_dump());
}
if let Some(put) = self.put {
this.insert("PUT".to_string(), put.api_dump());
this.insert("PUT".to_string(), put.method_info().api_dump());
}
if let Some(post) = self.post {
this.insert("POST".to_string(), post.api_dump());
this.insert("POST".to_string(), post.method_info().api_dump());
}
if let Some(delete) = self.delete {
this.insert("DELETE".to_string(), delete.api_dump());
this.insert("DELETE".to_string(), delete.method_info().api_dump());
}
match &self.subroute {
@ -165,7 +165,7 @@ where
/// Builder method to provide a `GET` method info.
pub fn get<I>(mut self, method: &'static I) -> Self
where
I: ApiMethodInfo<Body = Body> + Send + Sync,
I: ApiHandler<Body = Body> + Send + Sync,
{
self.get = Some(method);
self
@ -174,7 +174,7 @@ where
/// Builder method to provide a `PUT` method info.
pub fn put<I>(mut self, method: &'static I) -> Self
where
I: ApiMethodInfo<Body = Body> + Send + Sync,
I: ApiHandler<Body = Body> + Send + Sync,
{
self.put = Some(method);
self
@ -183,7 +183,7 @@ where
/// Builder method to provide a `POST` method info.
pub fn post<I>(mut self, method: &'static I) -> Self
where
I: ApiMethodInfo<Body = Body> + Send + Sync,
I: ApiHandler<Body = Body> + Send + Sync,
{
self.post = Some(method);
self
@ -192,7 +192,7 @@ where
/// Builder method to provide a `DELETE` method info.
pub fn delete<I>(mut self, method: &'static I) -> Self
where
I: ApiMethodInfo<Body = Body> + Send + Sync,
I: ApiHandler<Body = Body> + Send + Sync,
{
self.delete = Some(method);
self