From ec0524f83a111fcee80c720b63403fda2af2a1d9 Mon Sep 17 00:00:00 2001 From: dal Date: Mon, 31 Mar 2025 15:23:33 -0600 Subject: [PATCH] fix on chart config object --- api/libs/database/src/types/metric_yml.rs | 133 +++++++++++++++++----- 1 file changed, 105 insertions(+), 28 deletions(-) diff --git a/api/libs/database/src/types/metric_yml.rs b/api/libs/database/src/types/metric_yml.rs index 8f9a424c4..c4f3e1302 100644 --- a/api/libs/database/src/types/metric_yml.rs +++ b/api/libs/database/src/types/metric_yml.rs @@ -152,35 +152,112 @@ impl<'de> Deserialize<'de> for ChartConfig { // Helper function to create detailed error messages for deserialization failures fn detailed_deserialization_error(chart_type: &str, err: &serde_json::Error) -> E { - match err.classify() { - serde_json::error::Category::Data => { - // Type mismatch or invalid value - let error_message = format!("{} config error: {}", chart_type, err); - - // Extract the field path from the error message if possible - if let Some(field_name) = extract_field_from_error(err) { - E::custom(format!("{} config error at field '{}': {}", chart_type, field_name, err)) - } else { - E::custom(error_message) - } - } - serde_json::error::Category::Syntax | serde_json::error::Category::Io | serde_json::error::Category::Eof => { - // Other errors (shouldn't happen in this context) - E::custom(format!("{} config parse error: {}", chart_type, err)) - } - } -} - -// Helper function to extract field name from error message -fn extract_field_from_error(err: &serde_json::Error) -> Option { // Get the error message as a string let err_msg = err.to_string(); - // Try to extract field path using common patterns in serde_json error messages - if let Some(start_idx) = err_msg.find("at key ") { - if let Some(key_start) = err_msg[start_idx + 8..].find('"') { - if let Some(key_end) = err_msg[start_idx + 8 + key_start + 1..].find('"') { - let field = &err_msg[start_idx + 8 + key_start + 1..start_idx + 8 + key_start + 1 + key_end]; + // Match different error patterns to extract field information + let detailed_error = match extract_detailed_error_info(&err_msg) { + Some((field_name, expected_type, found_type)) => { + // Complete type mismatch info available + format!( + "{} config error: field '{}' has invalid type: {}, expected {}", + chart_type, field_name, found_type, expected_type + ) + } + None => { + // Try to at least extract the field name + if let Some(field_name) = extract_field_from_error(&err_msg) { + format!("{} config error at field '{}': {}", chart_type, field_name, err) + } else { + format!("{} config error: {}", chart_type, err) + } + } + }; + + E::custom(detailed_error) +} + +// Helper function to extract detailed type mismatch information +fn extract_detailed_error_info(err_msg: &str) -> Option<(String, String, String)> { + // Pattern for serde "invalid type" errors: "invalid type: X, expected Y at ..." + if err_msg.starts_with("invalid type:") { + // Extract expected and found types + let parts: Vec<&str> = err_msg.split(", expected ").collect(); + if parts.len() >= 2 { + let found_type = parts[0].replace("invalid type: ", "").trim().to_string(); + + // Extract field path and expected type + let remaining = parts[1]; + let field_parts: Vec<&str> = remaining.split(" at ").collect(); + let expected_type = field_parts[0].trim().to_string(); + + // Try to extract field name + if field_parts.len() >= 2 { + if let Some(field_name) = extract_field_path(field_parts[1]) { + return Some((field_name, expected_type, found_type)); + } + } + + // If we couldn't extract field but have types, use a placeholder + return Some(("unknown_field".to_string(), expected_type, found_type)); + } + } + + None +} + +// Helper function to extract field path from location info +fn extract_field_path(location_info: &str) -> Option { + // Try to extract field name from different patterns + + // Pattern: key `field_name` at line X column Y + if let Some(start_idx) = location_info.find("key `") { + if let Some(end_idx) = location_info[start_idx + 5..].find('`') { + return Some(location_info[start_idx + 5..start_idx + 5 + end_idx].to_string()); + } + } + + // Pattern: at key "field_name" + if let Some(start_idx) = location_info.find("at key \"") { + if let Some(end_idx) = location_info[start_idx + 8..].find('"') { + return Some(location_info[start_idx + 8..start_idx + 8 + end_idx].to_string()); + } + } + + // Pattern: "field_name": + if let Some(start_idx) = location_info.find('"') { + if let Some(end_idx) = location_info[start_idx + 1..].find('"') { + return Some(location_info[start_idx + 1..start_idx + 1 + end_idx].to_string()); + } + } + + None +} + +// Helper function to extract field name from error message +fn extract_field_from_error(err_msg: &str) -> Option { + // Try multiple patterns to extract field names + + // Pattern: key `field_name` at line X column Y + if let Some(start_idx) = err_msg.find("key `") { + if let Some(end_idx) = err_msg[start_idx + 5..].find('`') { + return Some(err_msg[start_idx + 5..start_idx + 5 + end_idx].to_string()); + } + } + + // Pattern: at key "field_name" + if let Some(start_idx) = err_msg.find("at key \"") { + if let Some(end_idx) = err_msg[start_idx + 8..].find('"') { + return Some(err_msg[start_idx + 8..start_idx + 8 + end_idx].to_string()); + } + } + + // Pattern: "field_name": + if let Some(start_idx) = err_msg.find('"') { + if let Some(end_idx) = err_msg[start_idx + 1..].find('"') { + let field = &err_msg[start_idx + 1..start_idx + 1 + end_idx]; + // Avoid capturing things that aren't likely field names + if !field.contains(' ') && field.len() > 0 && field.len() < 50 { return Some(field.to_string()); } } @@ -208,7 +285,7 @@ pub struct BaseChartConfig { pub grid_lines: Option, #[serde(skip_serializing_if = "Option::is_none")] #[serde(alias = "show_legend_headline")] - pub show_legend_headline: Option, + pub show_legend_headline: Option, #[serde(skip_serializing_if = "Option::is_none")] #[serde(alias = "goal_lines")] pub goal_lines: Option>, @@ -367,7 +444,7 @@ pub struct BarLineChartConfig { pub bar_layout: Option, #[serde(skip_serializing_if = "Option::is_none")] #[serde(alias = "bar_sort_by")] - pub bar_sort_by: Option, + pub bar_sort_by: Option>, #[serde(skip_serializing_if = "Option::is_none")] #[serde(alias = "bar_group_type")] pub bar_group_type: Option,