2021-09-27 10:59:06 +00:00
|
|
|
//! Helpers to format response data
|
2021-10-19 11:09:27 +00:00
|
|
|
use std::collections::HashMap;
|
2021-09-27 10:59:06 +00:00
|
|
|
|
2022-04-06 14:55:39 +00:00
|
|
|
use anyhow::Error;
|
2018-12-05 11:42:25 +00:00
|
|
|
use serde_json::{json, Value};
|
|
|
|
|
2018-12-05 17:22:56 +00:00
|
|
|
use hyper::header;
|
2022-04-06 14:55:39 +00:00
|
|
|
use hyper::{Body, Response, StatusCode};
|
2018-12-05 17:22:56 +00:00
|
|
|
|
2022-04-12 14:15:08 +00:00
|
|
|
use proxmox_router::{HttpError, RpcEnvironment, SerializableReturn};
|
2021-10-08 09:19:37 +00:00
|
|
|
use proxmox_schema::ParameterError;
|
2019-11-21 13:36:28 +00:00
|
|
|
|
2019-02-15 08:55:12 +00:00
|
|
|
/// Extension to set error message for server side logging
|
2021-09-27 10:59:06 +00:00
|
|
|
pub(crate) struct ErrorMessageExtension(pub String);
|
|
|
|
|
|
|
|
/// 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>;
|
|
|
|
|
2022-04-12 14:15:08 +00:00
|
|
|
/// Transform serializable data into a streaming http response
|
|
|
|
fn format_data_streaming(
|
|
|
|
&self,
|
|
|
|
data: Box<dyn SerializableReturn + Send>,
|
|
|
|
rpcenv: &dyn RpcEnvironment,
|
|
|
|
) -> Result<Response<Body>, Error>;
|
|
|
|
|
2021-09-27 10:59:06 +00:00
|
|
|
/// Transform errors into a http response
|
|
|
|
fn format_error(&self, err: Error) -> Response<Body>;
|
|
|
|
|
|
|
|
/// Transform a [Result] into a http response
|
2022-04-06 14:55:39 +00:00
|
|
|
fn format_result(
|
|
|
|
&self,
|
|
|
|
result: Result<Value, Error>,
|
|
|
|
rpcenv: &dyn RpcEnvironment,
|
|
|
|
) -> Response<Body> {
|
2021-09-27 10:59:06 +00:00
|
|
|
match result {
|
|
|
|
Ok(data) => self.format_data(data, rpcenv),
|
|
|
|
Err(err) => self.format_error(err),
|
|
|
|
}
|
|
|
|
}
|
2018-12-05 11:42:25 +00:00
|
|
|
}
|
|
|
|
|
2019-01-26 14:08:02 +00:00
|
|
|
static JSON_CONTENT_TYPE: &str = "application/json;charset=UTF-8";
|
2019-01-26 13:50:37 +00:00
|
|
|
|
2021-09-27 10:59:06 +00:00
|
|
|
fn json_data_response(data: Value) -> Response<Body> {
|
2019-05-09 11:28:26 +00:00
|
|
|
let json_str = data.to_string();
|
2019-01-26 13:50:37 +00:00
|
|
|
|
|
|
|
let raw = json_str.into_bytes();
|
2018-12-05 11:42:25 +00:00
|
|
|
|
2019-01-26 13:50:37 +00:00
|
|
|
let mut response = Response::new(raw.into());
|
|
|
|
response.headers_mut().insert(
|
|
|
|
header::CONTENT_TYPE,
|
2022-04-06 14:55:39 +00:00
|
|
|
header::HeaderValue::from_static(JSON_CONTENT_TYPE),
|
|
|
|
);
|
2018-12-05 11:42:25 +00:00
|
|
|
|
2019-01-26 13:50:37 +00:00
|
|
|
response
|
|
|
|
}
|
2018-12-05 17:22:56 +00:00
|
|
|
|
2022-04-12 14:15:08 +00:00
|
|
|
fn json_data_response_streaming(body: Body) -> Result<Response<Body>, Error> {
|
|
|
|
let response = Response::builder()
|
|
|
|
.header(
|
|
|
|
header::CONTENT_TYPE,
|
|
|
|
header::HeaderValue::from_static(JSON_CONTENT_TYPE),
|
|
|
|
)
|
|
|
|
.body(body)?;
|
|
|
|
Ok(response)
|
|
|
|
}
|
|
|
|
|
2022-04-06 14:55:39 +00:00
|
|
|
fn add_result_attributes(result: &mut Value, rpcenv: &dyn RpcEnvironment) {
|
2020-05-18 07:57:35 +00:00
|
|
|
let attributes = match rpcenv.result_attrib().as_object() {
|
|
|
|
Some(attr) => attr,
|
|
|
|
None => return,
|
|
|
|
};
|
2019-12-07 14:29:42 +00:00
|
|
|
|
2020-05-18 07:57:35 +00:00
|
|
|
for (key, value) in attributes {
|
|
|
|
result[key] = value.clone();
|
2018-12-05 17:22:56 +00:00
|
|
|
}
|
2019-12-07 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
2022-04-12 14:15:08 +00:00
|
|
|
fn start_data_streaming(
|
|
|
|
value: Value,
|
|
|
|
data: Box<dyn SerializableReturn + Send>,
|
|
|
|
) -> tokio::sync::mpsc::Receiver<Result<Vec<u8>, Error>> {
|
|
|
|
let (writer, reader) = tokio::sync::mpsc::channel(1);
|
|
|
|
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
|
|
let output = proxmox_async::blocking::SenderWriter::from_sender(writer);
|
|
|
|
let mut output = std::io::BufWriter::new(output);
|
|
|
|
let mut serializer = serde_json::Serializer::new(&mut output);
|
|
|
|
let _ = data.sender_serialize(&mut serializer, value);
|
|
|
|
});
|
|
|
|
|
|
|
|
reader
|
|
|
|
}
|
|
|
|
|
2021-09-27 10:59:06 +00:00
|
|
|
struct JsonFormatter();
|
2019-12-07 14:29:42 +00:00
|
|
|
|
2021-09-27 10:59:06 +00:00
|
|
|
/// Format data as ``application/json``
|
|
|
|
///
|
2021-09-28 08:11:56 +00:00
|
|
|
/// The returned json object contains the following properties:
|
|
|
|
///
|
|
|
|
/// * ``data``: The result data (on success)
|
|
|
|
///
|
|
|
|
/// Any result attributes set on ``rpcenv`` are also added to the object.
|
|
|
|
///
|
2021-09-27 10:59:06 +00:00
|
|
|
/// Errors generates a BAD_REQUEST containing the error
|
|
|
|
/// message as string.
|
|
|
|
pub static JSON_FORMATTER: &'static dyn OutputFormatter = &JsonFormatter();
|
2019-01-26 13:50:37 +00:00
|
|
|
|
2022-04-06 14:55:39 +00:00
|
|
|
impl OutputFormatter for JsonFormatter {
|
2021-09-27 10:59:06 +00:00
|
|
|
fn format_data(&self, data: Value, rpcenv: &dyn RpcEnvironment) -> Response<Body> {
|
2022-04-06 14:55:39 +00:00
|
|
|
let mut result = json!({ "data": data });
|
2019-02-17 16:18:44 +00:00
|
|
|
|
2021-09-27 10:59:06 +00:00
|
|
|
add_result_attributes(&mut result, rpcenv);
|
2019-02-15 08:55:12 +00:00
|
|
|
|
2021-09-27 10:59:06 +00:00
|
|
|
json_data_response(result)
|
|
|
|
}
|
2018-12-05 11:42:25 +00:00
|
|
|
|
2022-04-12 14:15:08 +00:00
|
|
|
fn format_data_streaming(
|
|
|
|
&self,
|
|
|
|
data: Box<dyn SerializableReturn + Send>,
|
|
|
|
rpcenv: &dyn RpcEnvironment,
|
|
|
|
) -> Result<Response<Body>, Error> {
|
|
|
|
let mut value = json!({});
|
|
|
|
|
|
|
|
add_result_attributes(&mut value, rpcenv);
|
|
|
|
|
|
|
|
let reader = start_data_streaming(value, data);
|
|
|
|
let stream = tokio_stream::wrappers::ReceiverStream::new(reader);
|
|
|
|
|
|
|
|
json_data_response_streaming(Body::wrap_stream(stream))
|
|
|
|
}
|
|
|
|
|
2021-09-27 10:59:06 +00:00
|
|
|
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
|
|
|
|
};
|
2018-12-05 11:42:25 +00:00
|
|
|
|
2021-09-27 10:59:06 +00:00
|
|
|
response.headers_mut().insert(
|
|
|
|
header::CONTENT_TYPE,
|
2022-04-06 14:55:39 +00:00
|
|
|
header::HeaderValue::from_static(JSON_CONTENT_TYPE),
|
|
|
|
);
|
2018-12-05 11:42:25 +00:00
|
|
|
|
2022-04-06 14:55:39 +00:00
|
|
|
response
|
|
|
|
.extensions_mut()
|
|
|
|
.insert(ErrorMessageExtension(err.to_string()));
|
2018-12-05 17:22:56 +00:00
|
|
|
|
2021-09-27 10:59:06 +00:00
|
|
|
response
|
|
|
|
}
|
2019-01-26 13:50:37 +00:00
|
|
|
}
|
2018-12-05 11:42:25 +00:00
|
|
|
|
2021-09-27 10:59:06 +00:00
|
|
|
/// 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();
|
|
|
|
|
|
|
|
struct ExtJsFormatter();
|
|
|
|
|
2022-04-06 14:55:39 +00:00
|
|
|
impl OutputFormatter for ExtJsFormatter {
|
2021-09-27 10:59:06 +00:00
|
|
|
fn format_data(&self, 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)
|
|
|
|
}
|
2018-12-05 11:42:25 +00:00
|
|
|
|
2022-04-12 14:15:08 +00:00
|
|
|
fn format_data_streaming(
|
|
|
|
&self,
|
|
|
|
data: Box<dyn SerializableReturn + Send>,
|
|
|
|
rpcenv: &dyn RpcEnvironment,
|
|
|
|
) -> Result<Response<Body>, Error> {
|
|
|
|
let mut value = json!({
|
|
|
|
"success": true,
|
|
|
|
});
|
|
|
|
|
|
|
|
add_result_attributes(&mut value, rpcenv);
|
|
|
|
|
|
|
|
let reader = start_data_streaming(value, data);
|
|
|
|
let stream = tokio_stream::wrappers::ReceiverStream::new(reader);
|
|
|
|
|
|
|
|
json_data_response_streaming(Body::wrap_stream(stream))
|
|
|
|
}
|
|
|
|
|
2021-09-27 10:59:06 +00:00
|
|
|
fn format_error(&self, err: Error) -> Response<Body> {
|
2021-10-19 11:09:27 +00:00
|
|
|
let mut errors = HashMap::new();
|
2018-12-05 11:42:25 +00:00
|
|
|
|
2022-04-10 16:33:32 +00:00
|
|
|
let message: String = match err.downcast::<ParameterError>() {
|
2022-02-11 13:09:45 +00:00
|
|
|
Ok(param_err) => {
|
|
|
|
for (name, err) in param_err {
|
|
|
|
errors.insert(name, err.to_string());
|
|
|
|
}
|
2022-04-10 16:33:32 +00:00
|
|
|
String::from("parameter verification errors")
|
2021-10-19 11:09:27 +00:00
|
|
|
}
|
2022-04-10 16:33:32 +00:00
|
|
|
Err(err) => err.to_string(),
|
|
|
|
};
|
2018-12-05 11:42:25 +00:00
|
|
|
|
2021-09-27 10:59:06 +00:00
|
|
|
let result = json!({
|
|
|
|
"message": message,
|
|
|
|
"errors": errors,
|
|
|
|
"success": false
|
|
|
|
});
|
2019-02-15 08:55:12 +00:00
|
|
|
|
2021-09-27 10:59:06 +00:00
|
|
|
let mut response = json_data_response(result);
|
2019-02-15 08:55:12 +00:00
|
|
|
|
2022-04-06 14:55:39 +00:00
|
|
|
response
|
|
|
|
.extensions_mut()
|
|
|
|
.insert(ErrorMessageExtension(message));
|
2018-12-05 11:42:25 +00:00
|
|
|
|
2021-09-27 10:59:06 +00:00
|
|
|
response
|
|
|
|
}
|
|
|
|
}
|