1 //! Helpers to format response data
2 use std
::collections
::HashMap
;
5 use serde_json
::{json, Value}
;
8 use hyper
::{Body, Response, StatusCode}
;
10 use proxmox_router
::{HttpError, RpcEnvironment, SerializableReturn}
;
11 use proxmox_schema
::ParameterError
;
13 /// Extension to set error message for server side logging
14 pub(crate) struct ErrorMessageExtension(pub String
);
16 /// Methods to format data and errors
17 pub trait OutputFormatter
: Send
+ Sync
{
18 /// Transform json data into a http response
19 fn format_data(&self, data
: Value
, rpcenv
: &dyn RpcEnvironment
) -> Response
<Body
>;
21 /// Transform serializable data into a streaming http response
22 fn format_data_streaming(
24 data
: Box
<dyn SerializableReturn
+ Send
>,
25 rpcenv
: &dyn RpcEnvironment
,
26 ) -> Result
<Response
<Body
>, Error
>;
28 /// Transform errors into a http response
29 fn format_error(&self, err
: Error
) -> Response
<Body
>;
31 /// Transform a [Result] into a http response
34 result
: Result
<Value
, Error
>,
35 rpcenv
: &dyn RpcEnvironment
,
38 Ok(data
) => self.format_data(data
, rpcenv
),
39 Err(err
) => self.format_error(err
),
44 static JSON_CONTENT_TYPE
: &str = "application/json;charset=UTF-8";
46 fn json_data_response(data
: Value
) -> Response
<Body
> {
47 let json_str
= data
.to_string();
49 let raw
= json_str
.into_bytes();
51 let mut response
= Response
::new(raw
.into());
52 response
.headers_mut().insert(
54 header
::HeaderValue
::from_static(JSON_CONTENT_TYPE
),
60 fn json_data_response_streaming(body
: Body
) -> Result
<Response
<Body
>, Error
> {
61 let response
= Response
::builder()
64 header
::HeaderValue
::from_static(JSON_CONTENT_TYPE
),
70 fn add_result_attributes(result
: &mut Value
, rpcenv
: &dyn RpcEnvironment
) {
71 let attributes
= match rpcenv
.result_attrib().as_object() {
76 for (key
, value
) in attributes
{
77 result
[key
] = value
.clone();
81 fn start_data_streaming(
83 data
: Box
<dyn SerializableReturn
+ Send
>,
84 ) -> tokio
::sync
::mpsc
::Receiver
<Result
<Vec
<u8>, Error
>> {
85 let (writer
, reader
) = tokio
::sync
::mpsc
::channel(1);
87 tokio
::task
::spawn_blocking(move || {
88 let output
= proxmox_async
::blocking
::SenderWriter
::from_sender(writer
);
89 let mut output
= std
::io
::BufWriter
::new(output
);
90 let mut serializer
= serde_json
::Serializer
::new(&mut output
);
91 let _
= data
.sender_serialize(&mut serializer
, value
);
97 struct JsonFormatter();
99 /// Format data as ``application/json``
101 /// The returned json object contains the following properties:
103 /// * ``data``: The result data (on success)
105 /// Any result attributes set on ``rpcenv`` are also added to the object.
107 /// Errors generates a BAD_REQUEST containing the error
108 /// message as string.
109 pub static JSON_FORMATTER
: &'
static dyn OutputFormatter
= &JsonFormatter();
111 impl OutputFormatter
for JsonFormatter
{
112 fn format_data(&self, data
: Value
, rpcenv
: &dyn RpcEnvironment
) -> Response
<Body
> {
113 let mut result
= json
!({ "data": data }
);
115 add_result_attributes(&mut result
, rpcenv
);
117 json_data_response(result
)
120 fn format_data_streaming(
122 data
: Box
<dyn SerializableReturn
+ Send
>,
123 rpcenv
: &dyn RpcEnvironment
,
124 ) -> Result
<Response
<Body
>, Error
> {
125 let mut value
= json
!({}
);
127 add_result_attributes(&mut value
, rpcenv
);
129 let reader
= start_data_streaming(value
, data
);
130 let stream
= tokio_stream
::wrappers
::ReceiverStream
::new(reader
);
132 json_data_response_streaming(Body
::wrap_stream(stream
))
135 fn format_error(&self, err
: Error
) -> Response
<Body
> {
136 error_to_response(err
)
140 pub(crate) fn error_to_response(err
: Error
) -> Response
<Body
> {
141 let mut response
= if let Some(apierr
) = err
.downcast_ref
::<HttpError
>() {
142 let mut resp
= Response
::new(Body
::from(apierr
.message
.clone()));
143 *resp
.status_mut() = apierr
.code
;
146 let mut resp
= Response
::new(Body
::from(err
.to_string()));
147 *resp
.status_mut() = StatusCode
::BAD_REQUEST
;
151 response
.headers_mut().insert(
152 header
::CONTENT_TYPE
,
153 header
::HeaderValue
::from_static(JSON_CONTENT_TYPE
),
158 .insert(ErrorMessageExtension(err
.to_string()));
163 /// Format data as ExtJS compatible ``application/json``
165 /// The returned json object contains the following properties:
167 /// * ``success``: boolean attribute indicating the success.
169 /// * ``status``: api call status code.
171 /// * ``data``: The result data (on success)
173 /// * ``message``: The error message (on failure)
175 /// * ``errors``: detailed list of errors (if available)
177 /// Any result attributes set on ``rpcenv`` are also added to the object.
179 /// Please note that errors return a HTTP response with status code OK, but setting success
180 /// to false. The real status from the API call is encoded in the status
182 pub static EXTJS_FORMATTER
: &'
static dyn OutputFormatter
= &ExtJsFormatter();
184 struct ExtJsFormatter();
186 impl OutputFormatter
for ExtJsFormatter
{
187 fn format_data(&self, data
: Value
, rpcenv
: &dyn RpcEnvironment
) -> Response
<Body
> {
188 let mut result
= json
!({
191 "status": StatusCode
::OK
.as_u16(),
194 add_result_attributes(&mut result
, rpcenv
);
196 json_data_response(result
)
199 fn format_data_streaming(
201 data
: Box
<dyn SerializableReturn
+ Send
>,
202 rpcenv
: &dyn RpcEnvironment
,
203 ) -> Result
<Response
<Body
>, Error
> {
204 let mut value
= json
!({
206 "status": StatusCode
::OK
.as_u16(),
209 add_result_attributes(&mut value
, rpcenv
);
211 let reader
= start_data_streaming(value
, data
);
212 let stream
= tokio_stream
::wrappers
::ReceiverStream
::new(reader
);
214 json_data_response_streaming(Body
::wrap_stream(stream
))
217 fn format_error(&self, err
: Error
) -> Response
<Body
> {
218 let mut errors
= HashMap
::new();
220 let (message
, status
) = if err
.is
::<ParameterError
>() {
221 match err
.downcast
::<ParameterError
>() {
223 for (name
, err
) in param_err
{
224 errors
.insert(name
, err
.to_string());
227 String
::from("parameter verification errors"),
228 StatusCode
::BAD_REQUEST
,
231 Err(err
) => (err
.to_string(), StatusCode
::BAD_REQUEST
),
234 let status
= if let Some(apierr
) = err
.downcast_ref
::<HttpError
>() {
237 StatusCode
::BAD_REQUEST
239 (err
.to_string(), status
)
246 "status": status
.as_u16(),
249 let mut response
= json_data_response(result
);
253 .insert(ErrorMessageExtension(message
));