diff --git a/proxmox-rest-server/src/daemon.rs b/proxmox-rest-server/src/daemon.rs
index 5401e30c..789ba47c 100644
--- a/proxmox-rest-server/src/daemon.rs
+++ b/proxmox-rest-server/src/daemon.rs
@@ -1,4 +1,4 @@
-//! Helpers for daemons/services.
+//! Helpers to implement restartable daemons/services.
use std::ffi::CString;
use std::future::Future;
@@ -351,6 +351,7 @@ extern "C" {
fn sd_notify(unset_environment: c_int, state: *const c_char) -> c_int;
}
+/// Systemd sercice startup states (see: ``man sd_notify``)
pub enum SystemdNotify {
Ready,
Reloading,
@@ -359,6 +360,7 @@ pub enum SystemdNotify {
MainPid(nix::unistd::Pid),
}
+/// Tells systemd the startup state of the service (see: ``man sd_notify``)
pub fn systemd_notify(state: SystemdNotify) -> Result<(), Error> {
let message = match state {
SystemdNotify::Ready => CString::new("READY=1"),
diff --git a/proxmox-rest-server/src/formatter.rs b/proxmox-rest-server/src/formatter.rs
index 8818e671..7f41b382 100644
--- a/proxmox-rest-server/src/formatter.rs
+++ b/proxmox-rest-server/src/formatter.rs
@@ -1,3 +1,5 @@
+//! Helpers to format response data
+
use anyhow::{Error};
use serde_json::{json, Value};
@@ -7,25 +9,28 @@ use hyper::header;
use proxmox::api::{HttpError, RpcEnvironment};
/// Extension to set error message for server side logging
-pub struct ErrorMessageExtension(pub String);
+pub(crate) struct ErrorMessageExtension(pub String);
-pub struct OutputFormatter {
+/// Methods to format data and errors
+pub trait OutputFormatter: Send + Sync {
+ /// Transform json data into a http response
+ fn format_data(&self, data: Value, rpcenv: &dyn RpcEnvironment) -> Response
;
- pub format_data: fn(data: Value, rpcenv: &dyn RpcEnvironment) -> Response,
+ /// Transform errors into a http response
+ fn format_error(&self, err: Error) -> Response;
- pub format_error: fn(err: Error) -> Response,
+ /// Transform a [Result] into a http response
+ fn format_result(&self, result: Result, rpcenv: &dyn RpcEnvironment) -> Response {
+ match result {
+ Ok(data) => self.format_data(data, rpcenv),
+ Err(err) => self.format_error(err),
+ }
+ }
}
static JSON_CONTENT_TYPE: &str = "application/json;charset=UTF-8";
-pub fn json_response(result: Result) -> Response {
- match result {
- Ok(data) => json_data_response(data),
- Err(err) => json_error_response(err),
- }
-}
-
-pub fn json_data_response(data: Value) -> Response {
+fn json_data_response(data: Value) -> Response {
let json_str = data.to_string();
@@ -51,76 +56,101 @@ fn add_result_attributes(result: &mut Value, rpcenv: &dyn RpcEnvironment)
}
}
-fn json_format_data(data: Value, rpcenv: &dyn RpcEnvironment) -> Response {
- let mut result = json!({
- "data": data
- });
+struct JsonFormatter();
- add_result_attributes(&mut result, rpcenv);
+/// Format data as ``application/json``
+///
+/// Errors generates a BAD_REQUEST containing the error
+/// message as string.
+pub static JSON_FORMATTER: &'static dyn OutputFormatter = &JsonFormatter();
- json_data_response(result)
+impl OutputFormatter for JsonFormatter {
+
+ fn format_data(&self, data: Value, rpcenv: &dyn RpcEnvironment) -> Response {
+
+ let mut result = json!({
+ "data": data
+ });
+
+ add_result_attributes(&mut result, rpcenv);
+
+ json_data_response(result)
+ }
+
+ fn format_error(&self, err: Error) -> Response {
+
+ let mut response = if let Some(apierr) = err.downcast_ref::() {
+ let mut resp = Response::new(Body::from(apierr.message.clone()));
+ *resp.status_mut() = apierr.code;
+ resp
+ } else {
+ let mut resp = Response::new(Body::from(err.to_string()));
+ *resp.status_mut() = StatusCode::BAD_REQUEST;
+ resp
+ };
+
+ response.headers_mut().insert(
+ header::CONTENT_TYPE,
+ header::HeaderValue::from_static(JSON_CONTENT_TYPE));
+
+ response.extensions_mut().insert(ErrorMessageExtension(err.to_string()));
+
+ response
+ }
}
-pub fn json_error_response(err: Error) -> Response {
+/// Format data as ExtJS compatible ``application/json``
+///
+/// The returned json object contains the following properties:
+///
+/// * ``success``: boolean attribute indicating the success.
+///
+/// * ``data``: The result data (on success)
+///
+/// * ``message``: The error message (on failure)
+///
+/// * ``errors``: detailed list of errors (if available)
+///
+/// Any result attributes set on ``rpcenv`` are also added to the object.
+///
+/// Please note that errors return status code OK, but setting success
+/// to false.
+pub static EXTJS_FORMATTER: &'static dyn OutputFormatter = &ExtJsFormatter();
- let mut response = if let Some(apierr) = err.downcast_ref::() {
- let mut resp = Response::new(Body::from(apierr.message.clone()));
- *resp.status_mut() = apierr.code;
- resp
- } else {
- let mut resp = Response::new(Body::from(err.to_string()));
- *resp.status_mut() = StatusCode::BAD_REQUEST;
- resp
- };
+struct ExtJsFormatter();
- response.headers_mut().insert(
- header::CONTENT_TYPE,
- header::HeaderValue::from_static(JSON_CONTENT_TYPE));
+impl OutputFormatter for ExtJsFormatter {
- response.extensions_mut().insert(ErrorMessageExtension(err.to_string()));
+ fn format_data(&self, data: Value, rpcenv: &dyn RpcEnvironment) -> Response {
- response
+ let mut result = json!({
+ "data": data,
+ "success": true
+ });
+
+ add_result_attributes(&mut result, rpcenv);
+
+ json_data_response(result)
+ }
+
+ fn format_error(&self, err: Error) -> Response {
+
+ let mut errors = vec![];
+
+ let message = err.to_string();
+ errors.push(&message);
+
+ let result = json!({
+ "message": message,
+ "errors": errors,
+ "success": false
+ });
+
+ let mut response = json_data_response(result);
+
+ response.extensions_mut().insert(ErrorMessageExtension(message));
+
+ response
+ }
}
-
-pub static JSON_FORMATTER: OutputFormatter = OutputFormatter {
- format_data: json_format_data,
- format_error: json_error_response,
-};
-
-fn extjs_format_data(data: Value, rpcenv: &dyn RpcEnvironment) -> Response {
-
- let mut result = json!({
- "data": data,
- "success": true
- });
-
- add_result_attributes(&mut result, rpcenv);
-
- json_data_response(result)
-}
-
-fn extjs_format_error(err: Error) -> Response {
-
- let mut errors = vec![];
-
- let message = err.to_string();
- errors.push(&message);
-
- let result = json!({
- "message": message,
- "errors": errors,
- "success": false
- });
-
- let mut response = json_data_response(result);
-
- response.extensions_mut().insert(ErrorMessageExtension(message));
-
- response
-}
-
-pub static EXTJS_FORMATTER: OutputFormatter = OutputFormatter {
- format_data: extjs_format_data,
- format_error: extjs_format_error,
-};
diff --git a/proxmox-rest-server/src/h2service.rs b/proxmox-rest-server/src/h2service.rs
index fba9714c..f7c7b978 100644
--- a/proxmox-rest-server/src/h2service.rs
+++ b/proxmox-rest-server/src/h2service.rs
@@ -51,12 +51,12 @@ impl H2Service {
let mut uri_param = HashMap::new();
- let formatter = &JSON_FORMATTER;
+ let formatter = JSON_FORMATTER;
match self.router.find_method(&components, method, &mut uri_param) {
None => {
let err = http_err!(NOT_FOUND, "Path '{}' not found.", path);
- future::ok((formatter.format_error)(err)).boxed()
+ future::ok(formatter.format_error(err)).boxed()
}
Some(api_method) => {
crate::rest::handle_api_request(
diff --git a/proxmox-rest-server/src/lib.rs b/proxmox-rest-server/src/lib.rs
index 5ba72ce0..697e2242 100644
--- a/proxmox-rest-server/src/lib.rs
+++ b/proxmox-rest-server/src/lib.rs
@@ -12,6 +12,7 @@ mod compression;
pub use compression::*;
pub mod daemon;
+
pub mod formatter;
mod environment;
diff --git a/proxmox-rest-server/src/rest.rs b/proxmox-rest-server/src/rest.rs
index 929e4043..ba2edf11 100644
--- a/proxmox-rest-server/src/rest.rs
+++ b/proxmox-rest-server/src/rest.rs
@@ -391,7 +391,7 @@ async fn proxy_protected_request(
pub(crate) async fn handle_api_request(
mut rpcenv: Env,
info: &'static ApiMethod,
- formatter: &'static OutputFormatter,
+ formatter: &'static dyn OutputFormatter,
parts: Parts,
req_body: Body,
uri_param: HashMap,
@@ -407,14 +407,14 @@ pub(crate) async fn handle_api_request {
let params =
get_request_parameters(info.parameters, parts, req_body, uri_param).await?;
- (handler)(params, info, &mut rpcenv).map(|data| (formatter.format_data)(data, &rpcenv))
+ (handler)(params, info, &mut rpcenv).map(|data| formatter.format_data(data, &rpcenv))
}
ApiHandler::Async(handler) => {
let params =
get_request_parameters(info.parameters, parts, req_body, uri_param).await?;
(handler)(params, info, &mut rpcenv)
.await
- .map(|data| (formatter.format_data)(data, &rpcenv))
+ .map(|data| formatter.format_data(data, &rpcenv))
}
};
@@ -426,7 +426,7 @@ pub(crate) async fn handle_api_request= 2 {
let format = components[1];
- let formatter = match format {
- "json" => &JSON_FORMATTER,
- "extjs" => &EXTJS_FORMATTER,
+ let formatter: &dyn OutputFormatter = match format {
+ "json" => JSON_FORMATTER,
+ "extjs" => EXTJS_FORMATTER,
_ => bail!("Unsupported output format '{}'.", format),
};
@@ -664,7 +664,7 @@ async fn handle_request(
// always delay unauthorized calls by 3 seconds (from start of request)
let err = http_err!(UNAUTHORIZED, "authentication failed - {}", err);
tokio::time::sleep_until(Instant::from_std(delay_unauth_time)).await;
- return Ok((formatter.format_error)(err));
+ return Ok(formatter.format_error(err));
}
}
}
@@ -672,7 +672,7 @@ async fn handle_request(
match api_method {
None => {
let err = http_err!(NOT_FOUND, "Path '{}' not found.", path);
- return Ok((formatter.format_error)(err));
+ return Ok(formatter.format_error(err));
}
Some(api_method) => {
let auth_id = rpcenv.get_auth_id();
@@ -686,7 +686,7 @@ async fn handle_request(
) {
let err = http_err!(FORBIDDEN, "permission check failed");
tokio::time::sleep_until(Instant::from_std(access_forbidden_time)).await;
- return Ok((formatter.format_error)(err));
+ return Ok(formatter.format_error(err));
}
let result = if api_method.protected && env_type == RpcEnvironmentType::PUBLIC {
@@ -698,7 +698,7 @@ async fn handle_request(
let mut response = match result {
Ok(resp) => resp,
- Err(err) => (formatter.format_error)(err),
+ Err(err) => formatter.format_error(err),
};
if let Some(auth_id) = auth_id {