api: add 'router' and 'cli' features

Move API types into the router module and make router and
cli optional.

This way the 'router' module is the only thing with a
dependency on 'hyper', allowing the use of the schema alone
without pulling in the entire hyper stack.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2019-12-05 13:36:05 +01:00
parent 2d12f81235
commit d00a096851
4 changed files with 192 additions and 178 deletions

View File

@ -9,7 +9,6 @@ bytes = "0.4"
failure = "0.1"
futures-preview = "0.3.0-alpha"
http = "0.1"
hyper = { version = "0.13.0-alpha.1" }
proxmox-tools = { version = "0.1", path = "../proxmox-tools" }
regex = "1.0"
rustyline = "5.0.4"
@ -19,5 +18,12 @@ serde_json = "1.0"
textwrap = "0.11"
url = "1.7"
hyper = { version = "0.13.0-alpha.1", optional = true }
[dev-dependencies]
lazy_static = "1.3"
[features]
default = [ "router", "cli" ]
router = [ "hyper" ]
cli = [ "router", "hyper" ]

View File

@ -1,11 +1,11 @@
//! Module to generate and format API Documenation
use failure::*;
use failure::Error;
use std::io::Write;
use super::router::{Router, SubRoute};
use super::schema::*;
use super::{ApiHandler, ApiMethod};
use crate::schema::*;
use crate::{ApiHandler, ApiMethod};
/// Enumerate different styles to display parameters/properties.
#[derive(Copy, Clone)]
@ -256,10 +256,12 @@ fn dump_method_definition(method: &str, path: &str, def: Option<&ApiMethod>) ->
/// Generate ReST Documentaion for a complete API defined by a ``Router``.
pub fn dump_api(
output: &mut dyn Write,
router: &Router,
router: &crate::Router,
path: &str,
mut pos: usize,
) -> Result<(), Error> {
use crate::SubRoute;
let mut cond_print = |x| -> Result<_, Error> {
if let Some(text) = x {
if pos > 0 {

View File

@ -1,189 +1,35 @@
//! Proxmox API module. This provides utilities for HTTP and command line APIs.
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use failure::Error;
use http::request::Parts;
use http::Response;
use hyper::Body;
use serde_json::Value;
#[doc(hidden)]
pub mod const_regex;
#[doc(hidden)]
pub mod error;
pub mod format;
#[doc(hidden)]
pub mod router;
#[doc(hidden)]
pub mod rpc_environment;
pub mod schema;
pub mod cli;
use schema::{ObjectSchema, Schema};
#[doc(inline)]
pub use const_regex::ConstRegexPattern;
#[doc(inline)]
pub use rpc_environment::{RpcEnvironment, RpcEnvironmentType};
#[doc(inline)]
pub use router::{Router, SubRoute, SubdirMap};
#[doc(inline)]
pub use error::HttpError;
/// A synchronous API handler gets a json Value as input and returns a json Value as output.
///
/// Most API handler are synchronous. Use this to define such handler:
/// ```
/// # use failure::*;
/// # use serde_json::{json, Value};
/// # use proxmox_api::{*, schema::*};
/// #
/// fn hello(
/// param: Value,
/// info: &ApiMethod,
/// rpcenv: &mut dyn RpcEnvironment,
/// ) -> Result<Value, Error> {
/// Ok(json!("Hello world!"))
/// }
///
/// const API_METHOD_HELLO: ApiMethod = ApiMethod::new(
/// &ApiHandler::Sync(&hello),
/// &ObjectSchema::new("Hello World Example", &[])
/// );
/// ```
pub type ApiHandlerFn = &'static (dyn Fn(Value, &ApiMethod, &mut dyn RpcEnvironment) -> Result<Value, Error>
+ Send
+ Sync
+ 'static);
#[cfg(any(feature = "router", feature = "cli"))]
#[doc(hidden)]
pub mod rpc_environment;
/// Asynchronous HTTP API handlers
///
/// They get low level access to request and response data. Use this
/// to implement custom upload/download functions.
/// ```
/// # use failure::*;
/// # use serde_json::{json, Value};
/// # use proxmox_api::{*, schema::*};
/// #
/// use futures::*;
/// use hyper::{Body, Response, http::request::Parts};
///
/// fn low_level_hello(
/// parts: Parts,
/// req_body: Body,
/// param: Value,
/// info: &ApiMethod,
/// rpcenv: Box<dyn RpcEnvironment>,
/// ) -> ApiFuture {
/// async move {
/// let response = http::Response::builder()
/// .status(200)
/// .body(Body::from("Hello world!"))?;
/// Ok(response)
/// }.boxed()
/// }
///
/// const API_METHOD_LOW_LEVEL_HELLO: ApiMethod = ApiMethod::new(
/// &ApiHandler::AsyncHttp(&low_level_hello),
/// &ObjectSchema::new("Hello World Example (low level)", &[])
/// );
/// ```
pub type ApiAsyncHttpHandlerFn = &'static (dyn Fn(Parts, Body, Value, &'static ApiMethod, Box<dyn RpcEnvironment>) -> ApiFuture
+ Send
+ Sync
+ 'static);
#[cfg(any(feature = "router", feature = "cli"))]
#[doc(inline)]
pub use rpc_environment::{RpcEnvironment, RpcEnvironmentType};
/// The output of an asynchronous API handler is a futrue yielding a `Response`.
pub type ApiFuture = Pin<Box<dyn Future<Output = Result<Response<Body>, failure::Error>> + Send>>;
#[cfg(feature = "router")]
pub mod format;
/// Enum for different types of API handler functions.
pub enum ApiHandler {
Sync(ApiHandlerFn),
AsyncHttp(ApiAsyncHttpHandlerFn),
}
#[cfg(feature = "router")]
#[doc(hidden)]
pub mod router;
const NULL_SCHEMA: Schema = Schema::Null;
#[cfg(feature = "router")]
#[doc(inline)]
pub use router::{ApiFuture, ApiHandler, ApiMethod, Router, SubRoute, SubdirMap};
fn dummy_handler_fn(
_arg: Value,
_method: &ApiMethod,
_env: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
// do nothing
Ok(Value::Null)
}
const DUMMY_HANDLER: ApiHandler = ApiHandler::Sync(&dummy_handler_fn);
/// This struct defines synchronous API call which returns the restulkt as json `Value`
pub struct ApiMethod {
/// The protected flag indicates that the provides function should be forwarded
/// to the deaemon running in priviledged mode.
pub protected: bool,
/// This flag indicates that the provided method may change the local timezone, so the server
/// should do a tzset afterwards
pub reload_timezone: bool,
/// Parameter type Schema
pub parameters: &'static schema::ObjectSchema,
/// Return type Schema
pub returns: &'static schema::Schema,
/// Handler function
pub handler: &'static ApiHandler,
}
impl std::fmt::Debug for ApiMethod {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ApiMethod {{ ")?;
write!(f, " parameters: {:?}", self.parameters)?;
write!(f, " returns: {:?}", self.returns)?;
write!(f, " handler: {:p}", &self.handler)?;
write!(f, "}}")
}
}
impl ApiMethod {
pub const fn new(handler: &'static ApiHandler, parameters: &'static ObjectSchema) -> Self {
Self {
parameters,
handler,
returns: &NULL_SCHEMA,
protected: false,
reload_timezone: false,
}
}
pub const fn new_dummy(parameters: &'static ObjectSchema) -> Self {
Self {
parameters,
handler: &DUMMY_HANDLER,
returns: &NULL_SCHEMA,
protected: false,
reload_timezone: false,
}
}
pub const fn returns(mut self, schema: &'static Schema) -> Self {
self.returns = schema;
self
}
pub const fn protected(mut self, protected: bool) -> Self {
self.protected = protected;
self
}
pub const fn reload_timezone(mut self, reload_timezone: bool) -> Self {
self.reload_timezone = reload_timezone;
self
}
}
#[cfg(feature = "cli")]
pub mod cli;

View File

@ -1,8 +1,88 @@
use std::collections::HashMap;
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use http::Method;
use failure::Error;
use http::request::Parts;
use http::{Method, Response};
use hyper::Body;
use serde_json::Value;
use crate::ApiMethod;
use crate::schema::{self, ObjectSchema, Schema};
use crate::RpcEnvironment;
/// A synchronous API handler gets a json Value as input and returns a json Value as output.
///
/// Most API handler are synchronous. Use this to define such handler:
/// ```
/// # use failure::*;
/// # use serde_json::{json, Value};
/// # use proxmox_api::{*, schema::*};
/// #
/// fn hello(
/// param: Value,
/// info: &ApiMethod,
/// rpcenv: &mut dyn RpcEnvironment,
/// ) -> Result<Value, Error> {
/// Ok(json!("Hello world!"))
/// }
///
/// const API_METHOD_HELLO: ApiMethod = ApiMethod::new(
/// &ApiHandler::Sync(&hello),
/// &ObjectSchema::new("Hello World Example", &[])
/// );
/// ```
pub type ApiHandlerFn = &'static (dyn Fn(Value, &ApiMethod, &mut dyn RpcEnvironment) -> Result<Value, Error>
+ Send
+ Sync
+ 'static);
/// Asynchronous HTTP API handlers
///
/// They get low level access to request and response data. Use this
/// to implement custom upload/download functions.
/// ```
/// # use failure::*;
/// # use serde_json::{json, Value};
/// # use proxmox_api::{*, schema::*};
/// #
/// use futures::*;
/// use hyper::{Body, Response, http::request::Parts};
///
/// fn low_level_hello(
/// parts: Parts,
/// req_body: Body,
/// param: Value,
/// info: &ApiMethod,
/// rpcenv: Box<dyn RpcEnvironment>,
/// ) -> ApiFuture {
/// async move {
/// let response = http::Response::builder()
/// .status(200)
/// .body(Body::from("Hello world!"))?;
/// Ok(response)
/// }.boxed()
/// }
///
/// const API_METHOD_LOW_LEVEL_HELLO: ApiMethod = ApiMethod::new(
/// &ApiHandler::AsyncHttp(&low_level_hello),
/// &ObjectSchema::new("Hello World Example (low level)", &[])
/// );
/// ```
pub type ApiAsyncHttpHandlerFn = &'static (dyn Fn(Parts, Body, Value, &'static ApiMethod, Box<dyn RpcEnvironment>) -> ApiFuture
+ Send
+ Sync
+ 'static);
/// The output of an asynchronous API handler is a futrue yielding a `Response`.
pub type ApiFuture = Pin<Box<dyn Future<Output = Result<Response<Body>, failure::Error>> + Send>>;
/// Enum for different types of API handler functions.
pub enum ApiHandler {
Sync(ApiHandlerFn),
AsyncHttp(ApiAsyncHttpHandlerFn),
}
/// Lookup table to child `Router`s
///
@ -221,7 +301,87 @@ impl Router {
}
impl Default for Router {
#[inline]
fn default() -> Self {
Self::new()
}
}
const NULL_SCHEMA: Schema = Schema::Null;
fn dummy_handler_fn(
_arg: Value,
_method: &ApiMethod,
_env: &mut dyn RpcEnvironment,
) -> Result<Value, Error> {
// do nothing
Ok(Value::Null)
}
const DUMMY_HANDLER: ApiHandler = ApiHandler::Sync(&dummy_handler_fn);
/// This struct defines synchronous API call which returns the restulkt as json `Value`
pub struct ApiMethod {
/// The protected flag indicates that the provides function should be forwarded
/// to the deaemon running in priviledged mode.
pub protected: bool,
/// This flag indicates that the provided method may change the local timezone, so the server
/// should do a tzset afterwards
pub reload_timezone: bool,
/// Parameter type Schema
pub parameters: &'static schema::ObjectSchema,
/// Return type Schema
pub returns: &'static schema::Schema,
/// Handler function
pub handler: &'static ApiHandler,
}
impl std::fmt::Debug for ApiMethod {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ApiMethod {{ ")?;
write!(f, " parameters: {:?}", self.parameters)?;
write!(f, " returns: {:?}", self.returns)?;
write!(f, " handler: {:p}", &self.handler)?;
write!(f, "}}")
}
}
impl ApiMethod {
pub const fn new(handler: &'static ApiHandler, parameters: &'static ObjectSchema) -> Self {
Self {
parameters,
handler,
returns: &NULL_SCHEMA,
protected: false,
reload_timezone: false,
}
}
pub const fn new_dummy(parameters: &'static ObjectSchema) -> Self {
Self {
parameters,
handler: &DUMMY_HANDLER,
returns: &NULL_SCHEMA,
protected: false,
reload_timezone: false,
}
}
pub const fn returns(mut self, schema: &'static Schema) -> Self {
self.returns = schema;
self
}
pub const fn protected(mut self, protected: bool) -> Self {
self.protected = protected;
self
}
pub const fn reload_timezone(mut self, reload_timezone: bool) -> Self {
self.reload_timezone = reload_timezone;
self
}
}