ch-remote: also pretty-print remote server errors

Remote server errors are transferred as raw HTTP body. This way,
we lose the nested structured error information.

This is an attempt to retrieve the errors from the HTTP response
and to align the output with the normal error output.

For example, this produces the following chain of errors. Note
that everything after level 0 was retrieved from the HTTP server
response:

```
Error: ch-remote exited with the following chain of errors:
  0: http client error
  1: Server responded with InternalServerError
  2: Error from API
  3: The disk could not be added to the VM
  4: Failed to validate config
  5: Identifier disk1 is not unique

Debug Info: HttpApiClient(ServerResponse(InternalServerError, Some("Error from API<br>The disk could not be added to the VM<br>Failed to validate config<br>Identifier disk1 is not unique")))
```

In case the JSON can't be parsed properly, ch-remote will print:

```
Error: ch-remote exited with the following chain of errors:
  0: http client error
  X: Can't get remote's error messages from JSON response: EOF while parsing a value at line 1 column 0: body=''

Debug Info: HttpApiClient(ServerResponse(InternalServerError, Some("")))
```

Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
This commit is contained in:
Philipp Schuster
2025-06-10 10:23:41 +02:00
committed by Rob Bradford
parent 6ea132708c
commit 190a11f212
5 changed files with 108 additions and 15 deletions

View File

@@ -4,6 +4,7 @@
//
use std::collections::BTreeMap;
use std::error::Error;
use std::fs::File;
use std::os::unix::io::{IntoRawFd, RawFd};
use std::os::unix::net::UnixListener;
@@ -69,20 +70,29 @@ pub enum HttpError {
const HTTP_ROOT: &str = "/api/v1";
/// Creates the error response's body meant to be sent back to an API client.
/// Creates the error response's JSON body meant to be sent back to an API client.
///
/// The error message contained in the response is supposed to be user-facing,
/// thus insightful and helpful while balancing technical accuracy and
/// simplicity.
pub fn error_response(error: HttpError, status: StatusCode) -> Response {
let mut response = Response::new(Version::Http11, status);
// We must use debug output here without `#`, as it is currently the only
// feasible option to get all relevant error details to the receiver,
// i.e., ch-remote, in a balanced form. The Display impl is not guaranteed
// to hold all relevant or helpful data.
//
// TODO: We might print a nice error chain here as well and send it to the
// remote, similar to the normal error reporting?
response.set_body(Body::new(format!("{error:?}")));
let error: &dyn Error = &error;
// Write the Display::display() output all errors (from top to root).
let error_messages = std::iter::successors(Some(error), |sub_error| {
// Dereference necessary to mitigate rustc compiler bug.
// See <https://github.com/rust-lang/rust/issues/141673>
(*sub_error).source()
})
.map(|error| format!("{error}"))
.collect::<Vec<_>>();
// TODO: Move `api` module from `vmm` to dedicated crate and use a common type definition
let json = serde_json::to_string(&error_messages).unwrap();
let body = Body::new(json);
response.set_body(body);
response
}