mirror of https://github.com/buster-so/buster.git
detailed errors on chart configs
This commit is contained in:
parent
b43b32fbde
commit
bbd326ebde
|
@ -57,7 +57,7 @@ impl<'de> Deserialize<'de> for ChartConfig {
|
||||||
// Deserialize into a generic JSON value
|
// Deserialize into a generic JSON value
|
||||||
let value: Value = Deserialize::deserialize(deserializer)?;
|
let value: Value = Deserialize::deserialize(deserializer)?;
|
||||||
|
|
||||||
// Ensure it’s an object
|
// Ensure it's an object
|
||||||
let obj = value
|
let obj = value
|
||||||
.as_object()
|
.as_object()
|
||||||
.ok_or_else(|| serde::de::Error::custom("expected a JSON object"))?;
|
.ok_or_else(|| serde::de::Error::custom("expected a JSON object"))?;
|
||||||
|
@ -80,48 +80,115 @@ impl<'de> Deserialize<'de> for ChartConfig {
|
||||||
// Match the tag to the appropriate variant
|
// Match the tag to the appropriate variant
|
||||||
match tag {
|
match tag {
|
||||||
"bar" => {
|
"bar" => {
|
||||||
let config: BarLineChartConfig =
|
let config = match serde_json::from_value::<BarLineChartConfig>(config_value) {
|
||||||
serde_json::from_value(config_value).map_err(serde::de::Error::custom)?;
|
Ok(config) => config,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(detailed_deserialization_error::<D::Error>("Bar chart", &e));
|
||||||
|
}
|
||||||
|
};
|
||||||
Ok(ChartConfig::Bar(config))
|
Ok(ChartConfig::Bar(config))
|
||||||
}
|
}
|
||||||
"line" => {
|
"line" => {
|
||||||
let config: BarLineChartConfig =
|
let config = match serde_json::from_value::<BarLineChartConfig>(config_value) {
|
||||||
serde_json::from_value(config_value).map_err(serde::de::Error::custom)?;
|
Ok(config) => config,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(detailed_deserialization_error::<D::Error>("Line chart", &e));
|
||||||
|
}
|
||||||
|
};
|
||||||
Ok(ChartConfig::Line(config))
|
Ok(ChartConfig::Line(config))
|
||||||
}
|
}
|
||||||
"scatter" => {
|
"scatter" => {
|
||||||
let config: ScatterChartConfig =
|
let config = match serde_json::from_value::<ScatterChartConfig>(config_value) {
|
||||||
serde_json::from_value(config_value).map_err(serde::de::Error::custom)?;
|
Ok(config) => config,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(detailed_deserialization_error::<D::Error>("Scatter chart", &e));
|
||||||
|
}
|
||||||
|
};
|
||||||
Ok(ChartConfig::Scatter(config))
|
Ok(ChartConfig::Scatter(config))
|
||||||
}
|
}
|
||||||
"pie" => {
|
"pie" => {
|
||||||
let config: PieChartConfig =
|
let config = match serde_json::from_value::<PieChartConfig>(config_value) {
|
||||||
serde_json::from_value(config_value).map_err(serde::de::Error::custom)?;
|
Ok(config) => config,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(detailed_deserialization_error::<D::Error>("Pie chart", &e));
|
||||||
|
}
|
||||||
|
};
|
||||||
Ok(ChartConfig::Pie(config))
|
Ok(ChartConfig::Pie(config))
|
||||||
}
|
}
|
||||||
"combo" => {
|
"combo" => {
|
||||||
let config: ComboChartConfig =
|
let config = match serde_json::from_value::<ComboChartConfig>(config_value) {
|
||||||
serde_json::from_value(config_value).map_err(serde::de::Error::custom)?;
|
Ok(config) => config,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(detailed_deserialization_error::<D::Error>("Combo chart", &e));
|
||||||
|
}
|
||||||
|
};
|
||||||
Ok(ChartConfig::Combo(config))
|
Ok(ChartConfig::Combo(config))
|
||||||
}
|
}
|
||||||
"metric" => {
|
"metric" => {
|
||||||
let config: MetricChartConfig =
|
let config = match serde_json::from_value::<MetricChartConfig>(config_value) {
|
||||||
serde_json::from_value(config_value).map_err(serde::de::Error::custom)?;
|
Ok(config) => config,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(detailed_deserialization_error::<D::Error>("Metric chart", &e));
|
||||||
|
}
|
||||||
|
};
|
||||||
Ok(ChartConfig::Metric(config))
|
Ok(ChartConfig::Metric(config))
|
||||||
}
|
}
|
||||||
"table" => {
|
"table" => {
|
||||||
let config: TableChartConfig =
|
let config = match serde_json::from_value::<TableChartConfig>(config_value) {
|
||||||
serde_json::from_value(config_value).map_err(serde::de::Error::custom)?;
|
Ok(config) => config,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(detailed_deserialization_error::<D::Error>("Table chart", &e));
|
||||||
|
}
|
||||||
|
};
|
||||||
Ok(ChartConfig::Table(config))
|
Ok(ChartConfig::Table(config))
|
||||||
}
|
}
|
||||||
unknown => Err(serde::de::Error::custom(format!(
|
unknown => Err(serde::de::Error::custom(format!(
|
||||||
"unknown variant: {}",
|
"unknown chart type: {}",
|
||||||
unknown
|
unknown
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to create detailed error messages for deserialization failures
|
||||||
|
fn detailed_deserialization_error<E: serde::de::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<String> {
|
||||||
|
// 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];
|
||||||
|
return Some(field.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
// Base chart config shared by all chart types
|
// Base chart config shared by all chart types
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
@ -460,12 +527,25 @@ impl MetricYml {
|
||||||
pub fn new(yml_content: String) -> Result<Self> {
|
pub fn new(yml_content: String) -> Result<Self> {
|
||||||
let file: MetricYml = match serde_yaml::from_str(&yml_content) {
|
let file: MetricYml = match serde_yaml::from_str(&yml_content) {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(e) => return Err(anyhow::anyhow!("Error parsing YAML: {}", e)),
|
Err(e) => {
|
||||||
|
// Extract field information from error message if possible
|
||||||
|
let error_message = format!("Error parsing YAML: {}", e);
|
||||||
|
|
||||||
|
// Try to extract field path from YAML error
|
||||||
|
let yaml_error_str = e.to_string();
|
||||||
|
let detailed_error = if yaml_error_str.contains("at line") && yaml_error_str.contains("column") {
|
||||||
|
format!("Error parsing YAML at {}", yaml_error_str)
|
||||||
|
} else {
|
||||||
|
error_message
|
||||||
|
};
|
||||||
|
|
||||||
|
return Err(anyhow::anyhow!(detailed_error));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match file.validate() {
|
match file.validate() {
|
||||||
Ok(_) => Ok(file),
|
Ok(_) => Ok(file),
|
||||||
Err(e) => Err(anyhow::anyhow!("Error compiling file: {}", e)),
|
Err(e) => Err(anyhow::anyhow!("Error validating metric: {}", e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue