From efebdaeaf0014ca85b51ae425e6094813d535605 Mon Sep 17 00:00:00 2001 From: Konstantinos Stefanidis Vozikis Date: Tue, 19 Aug 2025 15:18:17 +0200 Subject: [PATCH 1/2] fix: don't assume 422 are validation errors --- crates/tower-cmd/src/output.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/tower-cmd/src/output.rs b/crates/tower-cmd/src/output.rs index 9b2f9831..55a96c37 100644 --- a/crates/tower-cmd/src/output.rs +++ b/crates/tower-cmd/src/output.rs @@ -151,8 +151,7 @@ fn output_response_content_error(err: ResponseContent) { }, StatusCode::UNPROCESSABLE_ENTITY => { // Show the main error message from the detail field - let line = format!("{} {}\n", "Validation error:".red(), "You need to fix one or more validation errors"); - io::stdout().write_all(line.as_bytes()).unwrap(); + error("The request was syntactically correct, but the Tower API couldn't process it."); // Show any additional error details from the errors field if let Some(errors) = &error_model.errors { From c27705176a1d5c4a37bcb63074a13fb84da1b593 Mon Sep 17 00:00:00 2001 From: Konstantinos Stefanidis Vozikis Date: Tue, 19 Aug 2025 18:43:01 +0200 Subject: [PATCH 2/2] fix: improve error message displaying --- crates/tower-cmd/src/output.rs | 71 +++++++++++++++---------------- crates/tower-cmd/src/util/apps.rs | 5 ++- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/crates/tower-cmd/src/output.rs b/crates/tower-cmd/src/output.rs index 55a96c37..2d8b6aa1 100644 --- a/crates/tower-cmd/src/output.rs +++ b/crates/tower-cmd/src/output.rs @@ -116,56 +116,55 @@ pub fn runtime_error(err: tower_runtime::errors::Error) { io::stdout().write_all(line.as_bytes()).unwrap(); } +// Outputs both the model.detail and the model.errors fields in a human readable format. +pub fn output_full_error_details(model: &ErrorModel) { + // Show the main detail message if available + if let Some(detail) = &model.detail { + writeln!(io::stdout(), "\n{}", "Error details:".yellow()).unwrap(); + writeln!(io::stdout(), "{}", detail.red()).unwrap(); + } + + // Show any additional error details from the errors field + if let Some(errors) = &model.errors { + if !errors.is_empty() { + if model.detail.is_none() { + writeln!(io::stdout(), "\n{}", "Error details:".yellow()).unwrap(); + } + for error in errors { + let msg = format!( + " • {}", + error.message.as_deref().unwrap_or("Unknown error") + ); + writeln!(io::stdout(), "{}", msg.red()).unwrap(); + } + } + } +} + fn output_response_content_error(err: ResponseContent) { // Attempt to deserialize the error content into an ErrorModel. - let error_model: ErrorModel = match serde_json::from_str(&err.content) { - Ok(model) => model, + let error_model = match serde_json::from_str::(&err.content) { + Ok(model) => { + debug!("Error model (status: {}): {:?}", err.status, model); + model + }, Err(e) => { debug!("Failed to parse error content as JSON: {}", e); debug!("Raw error content: {}", err.content); - error("An unexpected error occurred while processing the response."); + // Show the raw error content if JSON parsing fails + writeln!(io::stdout(), "\n{}", "API Error:".yellow()).unwrap(); + writeln!(io::stdout(), "{}", err.content.red()).unwrap(); return; } }; - debug!("Error model (status: {}): {:?}", err.status, error_model); match err.status { StatusCode::CONFLICT => { - // Show the main error message from the detail field error("There was a conflict while trying to do that!"); - - // Show any additional error details from the errors field - if let Some(errors) = &error_model.errors { - if !errors.is_empty() { - writeln!(io::stdout(), "\n{}", "Error details:".yellow()).unwrap(); - for error in errors { - let msg = format!( - " • {}", - error.message.as_deref().unwrap_or("Unknown error") - ); - writeln!(io::stdout(), "{}", msg.red()).unwrap(); - } - } - } - + output_full_error_details(&error_model); }, StatusCode::UNPROCESSABLE_ENTITY => { - // Show the main error message from the detail field - error("The request was syntactically correct, but the Tower API couldn't process it."); - - // Show any additional error details from the errors field - if let Some(errors) = &error_model.errors { - if !errors.is_empty() { - writeln!(io::stdout(), "\n{}", "Error details:".yellow()).unwrap(); - for error in errors { - let msg = format!( - " • {}", - error.message.as_deref().unwrap_or("Unknown error") - ); - writeln!(io::stdout(), "{}", msg.red()).unwrap(); - } - } - } + output_full_error_details(&error_model); }, StatusCode::INTERNAL_SERVER_ERROR => { error("The Tower API encountered an internal error. Maybe try again later on."); diff --git a/crates/tower-cmd/src/util/apps.rs b/crates/tower-cmd/src/util/apps.rs index 21d0e6c7..9c5e3d03 100644 --- a/crates/tower-cmd/src/util/apps.rs +++ b/crates/tower-cmd/src/util/apps.rs @@ -91,7 +91,10 @@ pub async fn ensure_app_exists( tower_api::apis::Error::ResponseError(resp) => resp.status, _ => StatusCode::INTERNAL_SERVER_ERROR, }, - content: create_err.to_string(), + content: match &create_err { + tower_api::apis::Error::ResponseError(resp) => resp.content.clone(), + _ => create_err.to_string(), + }, entity: None, }, ))