proxmox-rest-server: cleanup formatter, improve docs
Use trait for OutputFormatter. This is functionally equivalent, but more rust-like...
This commit is contained in:
parent
8a23ea4656
commit
53daae8e89
@ -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"),
|
||||
|
@ -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<Body>;
|
||||
|
||||
pub format_data: fn(data: Value, rpcenv: &dyn RpcEnvironment) -> Response<Body>,
|
||||
/// Transform errors into a http response
|
||||
fn format_error(&self, err: Error) -> Response<Body>;
|
||||
|
||||
pub format_error: fn(err: Error) -> Response<Body>,
|
||||
/// Transform a [Result] into a http response
|
||||
fn format_result(&self, result: Result<Value, Error>, rpcenv: &dyn RpcEnvironment) -> Response<Body> {
|
||||
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<Value, Error>) -> Response<Body> {
|
||||
match result {
|
||||
Ok(data) => json_data_response(data),
|
||||
Err(err) => json_error_response(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn json_data_response(data: Value) -> Response<Body> {
|
||||
fn json_data_response(data: Value) -> Response<Body> {
|
||||
|
||||
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<Body> {
|
||||
|
||||
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<Body> {
|
||||
|
||||
let mut result = json!({
|
||||
"data": data
|
||||
});
|
||||
|
||||
add_result_attributes(&mut result, rpcenv);
|
||||
|
||||
json_data_response(result)
|
||||
}
|
||||
|
||||
fn format_error(&self, err: Error) -> Response<Body> {
|
||||
|
||||
let mut response = if let Some(apierr) = err.downcast_ref::<HttpError>() {
|
||||
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<Body> {
|
||||
/// 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::<HttpError>() {
|
||||
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<Body> {
|
||||
|
||||
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<Body> {
|
||||
|
||||
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<Body> {
|
||||
|
||||
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<Body> {
|
||||
|
||||
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,
|
||||
};
|
||||
|
@ -51,12 +51,12 @@ impl <E: RpcEnvironment + Clone> H2Service<E> {
|
||||
|
||||
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(
|
||||
|
@ -12,6 +12,7 @@ mod compression;
|
||||
pub use compression::*;
|
||||
|
||||
pub mod daemon;
|
||||
|
||||
pub mod formatter;
|
||||
|
||||
mod environment;
|
||||
|
@ -391,7 +391,7 @@ async fn proxy_protected_request(
|
||||
pub(crate) async fn handle_api_request<Env: RpcEnvironment, S: 'static + BuildHasher + Send>(
|
||||
mut rpcenv: Env,
|
||||
info: &'static ApiMethod,
|
||||
formatter: &'static OutputFormatter,
|
||||
formatter: &'static dyn OutputFormatter,
|
||||
parts: Parts,
|
||||
req_body: Body,
|
||||
uri_param: HashMap<String, String, S>,
|
||||
@ -407,14 +407,14 @@ pub(crate) async fn handle_api_request<Env: RpcEnvironment, S: 'static + BuildHa
|
||||
ApiHandler::Sync(handler) => {
|
||||
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<Env: RpcEnvironment, S: 'static + BuildHa
|
||||
tokio::time::sleep_until(Instant::from_std(delay_unauth_time)).await;
|
||||
}
|
||||
}
|
||||
(formatter.format_error)(err)
|
||||
formatter.format_error(err)
|
||||
}
|
||||
};
|
||||
|
||||
@ -627,9 +627,9 @@ async fn handle_request(
|
||||
if comp_len >= 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 {
|
||||
|
@ -1332,7 +1332,7 @@ pub fn upload_backup_log(
|
||||
replace_file(&path, blob.raw_data(), CreateOptions::new())?;
|
||||
|
||||
// fixme: use correct formatter
|
||||
Ok(formatter::json_response(Ok(Value::Null)))
|
||||
Ok(formatter::JSON_FORMATTER.format_data(Value::Null, &*rpcenv))
|
||||
}.boxed()
|
||||
}
|
||||
|
||||
|
@ -111,7 +111,7 @@ pub struct BackupEnvironment {
|
||||
result_attributes: Value,
|
||||
auth_id: Authid,
|
||||
pub debug: bool,
|
||||
pub formatter: &'static OutputFormatter,
|
||||
pub formatter: &'static dyn OutputFormatter,
|
||||
pub worker: Arc<WorkerTask>,
|
||||
pub datastore: Arc<DataStore>,
|
||||
pub backup_dir: BackupDir,
|
||||
@ -146,7 +146,7 @@ impl BackupEnvironment {
|
||||
worker,
|
||||
datastore,
|
||||
debug: false,
|
||||
formatter: &JSON_FORMATTER,
|
||||
formatter: JSON_FORMATTER,
|
||||
backup_dir,
|
||||
last_backup: None,
|
||||
state: Arc::new(Mutex::new(state)),
|
||||
@ -556,10 +556,7 @@ impl BackupEnvironment {
|
||||
}
|
||||
|
||||
pub fn format_response(&self, result: Result<Value, Error>) -> Response<Body> {
|
||||
match result {
|
||||
Ok(data) => (self.formatter.format_data)(data, self),
|
||||
Err(err) => (self.formatter.format_error)(err),
|
||||
}
|
||||
self.formatter.format_result(result, self)
|
||||
}
|
||||
|
||||
/// Raise error if finished flag is not set
|
||||
|
@ -18,7 +18,7 @@ pub struct ReaderEnvironment {
|
||||
result_attributes: Value,
|
||||
auth_id: Authid,
|
||||
pub debug: bool,
|
||||
pub formatter: &'static OutputFormatter,
|
||||
pub formatter: &'static dyn OutputFormatter,
|
||||
pub worker: Arc<WorkerTask>,
|
||||
pub datastore: Arc<DataStore>,
|
||||
pub backup_dir: BackupDir,
|
||||
@ -42,7 +42,7 @@ impl ReaderEnvironment {
|
||||
worker,
|
||||
datastore,
|
||||
debug: false,
|
||||
formatter: &JSON_FORMATTER,
|
||||
formatter: JSON_FORMATTER,
|
||||
backup_dir,
|
||||
allowed_chunks: Arc::new(RwLock::new(HashSet::new())),
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user