From 7d2c13da95f9f274afb4e471c1f052b91db63996 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Sun, 9 Jun 2019 14:04:53 +0200 Subject: [PATCH] api: add generic Body parameter Since we already know we'll want to be using hyper::Body and bytes::Bytes as API output, we need to allow making routers for each kind. Signed-off-by: Wolfgang Bumiller --- proxmox-api/Cargo.toml | 2 +- proxmox-api/src/api_output.rs | 23 ++++++++++++++--------- proxmox-api/src/api_type.rs | 28 ++++++++++++++++++++-------- proxmox-api/src/lib.rs | 5 ++--- proxmox-api/src/router.rs | 35 +++++++++++++++++++---------------- proxmox-api/tests/router.rs | 23 +++++++++++++---------- 6 files changed, 69 insertions(+), 47 deletions(-) diff --git a/proxmox-api/Cargo.toml b/proxmox-api/Cargo.toml index 59160b17..b9e2cf32 100644 --- a/proxmox-api/Cargo.toml +++ b/proxmox-api/Cargo.toml @@ -5,7 +5,6 @@ version = "0.1.0" authors = [ "Wolfgang Bumiller " ] [dependencies] -bytes = "0.4" failure = "0.1" futures-preview = "0.3.0-alpha" http = "0.1" @@ -14,4 +13,5 @@ serde_derive = "1.0" serde_json = "1.0" [dev-dependencies] +bytes = "0.4" lazy_static = "1.3" diff --git a/proxmox-api/src/api_output.rs b/proxmox-api/src/api_output.rs index 11cbca6c..6b1a03f8 100644 --- a/proxmox-api/src/api_output.rs +++ b/proxmox-api/src/api_output.rs @@ -10,36 +10,41 @@ use super::{ApiOutput, ApiType}; /// wrapped in a `http::Response` with a status code of `200`, but if an API method returns a /// `http::Response`, we don't want that, our wrappers produced by the `#[api]` macro simply call /// `output.into_api_output()`, and the trait implementation decides how to proceed. -pub trait IntoApiOutput { - fn into_api_output(self) -> ApiOutput; +pub trait IntoApiOutput { + fn into_api_output(self) -> ApiOutput; } -impl IntoApiOutput<()> for T { +impl IntoApiOutput for T +where + Body: 'static, + T: ApiType + serde::Serialize, + Body: From, +{ /// By default, any serializable type is serialized into a `{"data": output}` json structure, /// and returned as http status 200. - fn into_api_output(self) -> ApiOutput { + fn into_api_output(self) -> ApiOutput { let output = serde_json::to_value(self)?; let res = json!({ "data": output }); let output = serde_json::to_string(&res)?; Ok(http::Response::builder() .status(200) .header("content-type", "application/json") - .body(bytes::Bytes::from(output))?) + .body(Body::from(output))?) } } /// Methods returning `ApiOutput` (which is a `Result, Error>`) don't need /// anything to happen to the value anymore, return the result as is: -impl IntoApiOutput for ApiOutput { - fn into_api_output(self) -> ApiOutput { +impl IntoApiOutput> for ApiOutput { + fn into_api_output(self) -> ApiOutput { self } } /// Methods returning a `http::Response` (without the `Result<_, Error>` around it) need to be /// wrapped in a `Result`, as we do apply a `?` operator on our methods. -impl IntoApiOutput for http::Response { - fn into_api_output(self) -> ApiOutput { +impl IntoApiOutput> for http::Response { + fn into_api_output(self) -> ApiOutput { Ok(self) } } diff --git a/proxmox-api/src/api_type.rs b/proxmox-api/src/api_type.rs index 58d773f5..06132084 100644 --- a/proxmox-api/src/api_type.rs +++ b/proxmox-api/src/api_type.rs @@ -3,7 +3,6 @@ use std::cell::Cell; use std::sync::Once; -use bytes::Bytes; use failure::Error; use http::Response; use serde_json::Value; @@ -11,13 +10,13 @@ use serde_json::Value; /// Method entries in a `Router` are actually just `&dyn ApiMethodInfo` trait objects. /// This contains all the info required to call, document, or command-line-complete parameters for /// a method. -pub trait ApiMethodInfo { +pub trait ApiMethodInfo { 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 handler(&self) -> fn(Value) -> super::ApiFuture; + fn handler(&self) -> fn(Value) -> super::ApiFuture; } /// Shortcut to not having to type it out. This function signature is just a dummy and not yet @@ -46,16 +45,16 @@ pub struct TypeInfo { /// Otherwise this is mostly there so we can run the tests in the tests subdirectory without /// depending on the api-macro crate. Tests using the macros belong into the api-macro crate itself /// after all! -pub struct ApiMethod { +pub struct ApiMethod { pub description: &'static str, pub parameters: &'static [Parameter], pub return_type: &'static TypeInfo, pub protected: bool, pub reload_timezone: bool, - pub handler: fn(Value) -> super::ApiFuture, + pub handler: fn(Value) -> super::ApiFuture, } -impl ApiMethodInfo for ApiMethod { +impl ApiMethodInfo for ApiMethod { fn description(&self) -> &'static str { self.description } @@ -76,7 +75,7 @@ impl ApiMethodInfo for ApiMethod { self.reload_timezone } - fn handler(&self) -> fn(Value) -> super::ApiFuture { + fn handler(&self) -> fn(Value) -> super::ApiFuture { self.handler } } @@ -222,7 +221,20 @@ unconstrained_api_type! {String, isize, usize, i64, u64, i32, u32, i16, u16, i8, unconstrained_api_type! {Vec} // Raw return types are also okay: -unconstrained_api_type! {Response} +impl ApiType for Response { + fn verify(&self) -> Result<(), Error> { + Ok(()) + } + + fn type_info() -> &'static TypeInfo { + const INFO: TypeInfo = TypeInfo { + name: "http::Response<>", + description: "A raw http response", + complete_fn: None, + }; + &INFO + } +} // FIXME: make const once feature(const_fn) is stable! pub fn get_type_info() -> &'static TypeInfo { diff --git a/proxmox-api/src/lib.rs b/proxmox-api/src/lib.rs index cb9e57b0..b7e75fe0 100644 --- a/proxmox-api/src/lib.rs +++ b/proxmox-api/src/lib.rs @@ -9,7 +9,6 @@ use std::future::Future; use std::pin::Pin; -use bytes::Bytes; use failure::Error; use http::Response; @@ -23,7 +22,7 @@ mod router; pub use router::*; /// Return type of an API method. -pub type ApiOutput = Result, Error>; +pub type ApiOutput = Result, Error>; /// Future type of an API method. In order to support `async fn` this is a pinned box. -pub type ApiFuture = Pin>>; +pub type ApiFuture = Pin>>>; diff --git a/proxmox-api/src/router.rs b/proxmox-api/src/router.rs index 4f4520eb..ff9b637f 100644 --- a/proxmox-api/src/router.rs +++ b/proxmox-api/src/router.rs @@ -13,16 +13,16 @@ use super::ApiMethodInfo; /// When subdirectories are supposed to be passed as a `String` parameter to methods beneath the /// current directory, a `Parameter` entry is used. Note that the parameter name is fixed at this /// point, so all method calls beneath will receive a parameter ot that particular name. -pub enum SubRoute { +pub enum SubRoute { /// Call this router for any further subdirectory paths, and provide the relative path via the /// given parameter. Wildcard(&'static str), /// This is used for plain subdirectories. - Directories(HashMap<&'static str, Router>), + Directories(HashMap<&'static str, Router>), /// Match subdirectories as the given parameter name to the underlying router. - Parameter(&'static str, Box), + Parameter(&'static str, Box>), } /// A router is a nested structure. On the one hand it contains HTTP method entries (`GET`, `PUT`, @@ -30,24 +30,27 @@ pub enum SubRoute { /// sub directories as parameters, so the nesting uses a `SubRoute` `enum` representing which of /// the two is the case. #[derive(Default)] -pub struct Router { +pub struct Router { /// The `GET` http method. - pub get: Option<&'static dyn ApiMethodInfo>, + pub get: Option<&'static dyn ApiMethodInfo>, /// The `PUT` http method. - pub put: Option<&'static dyn ApiMethodInfo>, + pub put: Option<&'static dyn ApiMethodInfo>, /// The `POST` http method. - pub post: Option<&'static dyn ApiMethodInfo>, + pub post: Option<&'static dyn ApiMethodInfo>, /// The `DELETE` http method. - pub delete: Option<&'static dyn ApiMethodInfo>, + pub delete: Option<&'static dyn ApiMethodInfo>, /// Specifies the behavior of sub directories. See [`SubRoute`]. - pub subroute: Option, + pub subroute: Option>, } -impl Router { +impl Router +where + Self: Default, +{ /// Create a new empty router. pub fn new() -> Self { Self::default() @@ -110,7 +113,7 @@ impl Router { /// Builder method to provide a `GET` method info. pub fn get(mut self, method: &'static I) -> Self where - I: ApiMethodInfo, + I: ApiMethodInfo, { self.get = Some(method); self @@ -119,7 +122,7 @@ impl Router { /// Builder method to provide a `PUT` method info. pub fn put(mut self, method: &'static I) -> Self where - I: ApiMethodInfo, + I: ApiMethodInfo, { self.put = Some(method); self @@ -128,7 +131,7 @@ impl Router { /// Builder method to provide a `POST` method info. pub fn post(mut self, method: &'static I) -> Self where - I: ApiMethodInfo, + I: ApiMethodInfo, { self.post = Some(method); self @@ -137,7 +140,7 @@ impl Router { /// Builder method to provide a `DELETE` method info. pub fn delete(mut self, method: &'static I) -> Self where - I: ApiMethodInfo, + I: ApiMethodInfo, { self.delete = Some(method); self @@ -147,7 +150,7 @@ impl Router { /// /// This is supposed to be used statically (via `lazy_static!), therefore we panic if we /// already have a subdir entry! - pub fn parameter_subdir(mut self, parameter_name: &'static str, router: Router) -> Self { + pub fn parameter_subdir(mut self, parameter_name: &'static str, router: Router) -> Self { if self.subroute.is_some() { panic!("match_parameter can only be used once and without sub directories"); } @@ -159,7 +162,7 @@ impl Router { /// /// This is supposed to be used statically (via `lazy_static!), therefore we panic if we /// already have a subdir entry! - pub fn subdir(mut self, dir_name: &'static str, router: Router) -> Self { + pub fn subdir(mut self, dir_name: &'static str, router: Router) -> Self { let previous = match self.subroute { Some(SubRoute::Directories(ref mut map)) => map.insert(dir_name, router), None => { diff --git a/proxmox-api/tests/router.rs b/proxmox-api/tests/router.rs index c870bcf2..b33ccab5 100644 --- a/proxmox-api/tests/router.rs +++ b/proxmox-api/tests/router.rs @@ -2,13 +2,15 @@ use std::pin::Pin; +use bytes::Bytes; + use proxmox_api::Router; #[test] fn basic() { - let info: &proxmox_api::ApiMethod = &methods::GET_PEOPLE; - let get_subpath: &proxmox_api::ApiMethod = &methods::GET_SUBPATH; - let router = Router::new() + let info: &proxmox_api::ApiMethod = &methods::GET_PEOPLE; + let get_subpath: &proxmox_api::ApiMethod = &methods::GET_SUBPATH; + let router: Router = Router::new() .subdir( "people", Router::new().parameter_subdir("person", Router::new().get(info)), @@ -35,7 +37,7 @@ fn basic() { } fn check_with_matched_params( - router: &Router, + router: &Router, path: &str, param_name: &str, param_value: &str, @@ -84,6 +86,7 @@ fn check_with_matched_params( #[cfg(test)] mod methods { + use bytes::Bytes; use failure::{bail, Error}; use http::Response; use lazy_static::lazy_static; @@ -94,14 +97,14 @@ mod methods { get_type_info, ApiFuture, ApiMethod, ApiOutput, ApiType, Parameter, TypeInfo, }; - pub async fn get_people(value: Value) -> ApiOutput { + pub async fn get_people(value: Value) -> ApiOutput { Ok(Response::builder() .status(200) .header("content-type", "application/json") .body(value["person"].as_str().unwrap().into())?) } - pub async fn get_subpath(value: Value) -> ApiOutput { + pub async fn get_subpath(value: Value) -> ApiOutput { Ok(Response::builder() .status(200) .header("content-type", "application/json") @@ -116,14 +119,14 @@ mod methods { type_info: get_type_info::(), }] }; - pub static ref GET_PEOPLE: ApiMethod = { + pub static ref GET_PEOPLE: ApiMethod = { ApiMethod { description: "get some people", parameters: &GET_PEOPLE_PARAMS, return_type: get_type_info::(), protected: false, reload_timezone: false, - handler: |value: Value| -> ApiFuture { Box::pin(get_people(value)) }, + handler: |value: Value| -> ApiFuture { Box::pin(get_people(value)) }, } }; static ref GET_SUBPATH_PARAMS: Vec = { @@ -133,14 +136,14 @@ mod methods { type_info: get_type_info::(), }] }; - pub static ref GET_SUBPATH: ApiMethod = { + pub static ref GET_SUBPATH: ApiMethod = { ApiMethod { description: "get the 'subpath' parameter returned back", parameters: &GET_SUBPATH_PARAMS, return_type: get_type_info::(), protected: false, reload_timezone: false, - handler: |value: Value| -> ApiFuture { Box::pin(get_subpath(value)) }, + handler: |value: Value| -> ApiFuture { Box::pin(get_subpath(value)) }, } }; }