use crate::query::Operator;
use std::fmt;
#[derive(Debug, Clone)]
pub enum BuildError {
UnknownField {
field: String,
available: String,
},
InvalidOperator {
field: String,
operator: Operator,
field_type: String,
},
ValueTypeMismatch {
field: String,
expected: String,
actual: String,
},
InvalidEnumValue {
field: String,
value: String,
valid: String,
},
InvalidRegex(regex::Error),
InvalidFancyRegex {
pattern: String,
error: String,
},
EmptyQuery,
Multiple(Vec<BuildError>),
}
impl fmt::Display for BuildError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BuildError::UnknownField { field, available } => {
write!(f, "unknown field '{field}', available fields: {available}")
}
BuildError::InvalidOperator {
field,
operator,
field_type,
} => {
write!(
f,
"operator '{operator:?}' not valid for field '{field}' (type: {field_type})"
)
}
BuildError::ValueTypeMismatch {
field,
expected,
actual,
} => {
write!(
f,
"value type mismatch for field '{field}': expected {expected}, got {actual}"
)
}
BuildError::InvalidEnumValue {
field,
value,
valid,
} => {
write!(
f,
"invalid enum value '{value}' for field '{field}', valid values: {valid}"
)
}
BuildError::InvalidRegex(e) => {
write!(f, "invalid regex pattern: {e}")
}
BuildError::InvalidFancyRegex { pattern, error } => {
write!(f, "invalid regex pattern '{pattern}': {error}")
}
BuildError::EmptyQuery => {
write!(f, "empty query: no conditions specified")
}
BuildError::Multiple(errors) => {
write!(f, "multiple validation errors:")?;
for (i, err) in errors.iter().enumerate() {
write!(f, "\n {}: {err}", i + 1)?;
}
Ok(())
}
}
}
}
impl std::error::Error for BuildError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
BuildError::InvalidRegex(e) => Some(e),
_ => None,
}
}
}
impl From<regex::Error> for BuildError {
fn from(err: regex::Error) -> Self {
BuildError::InvalidRegex(err)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unknown_field_display() {
let err = BuildError::UnknownField {
field: "foo".to_string(),
available: "kind, name, path".to_string(),
};
assert_eq!(
err.to_string(),
"unknown field 'foo', available fields: kind, name, path"
);
}
#[test]
fn test_invalid_operator_display() {
let err = BuildError::InvalidOperator {
field: "kind".to_string(),
operator: Operator::Greater,
field_type: "Enum".to_string(),
};
assert!(err.to_string().contains("operator"));
assert!(err.to_string().contains("kind"));
}
#[test]
fn test_value_type_mismatch_display() {
let err = BuildError::ValueTypeMismatch {
field: "name".to_string(),
expected: "String".to_string(),
actual: "Number".to_string(),
};
assert!(err.to_string().contains("value type mismatch"));
assert!(err.to_string().contains("expected String"));
}
#[test]
fn test_invalid_enum_value_display() {
let err = BuildError::InvalidEnumValue {
field: "kind".to_string(),
value: "invalid".to_string(),
valid: "function, class, method".to_string(),
};
assert!(err.to_string().contains("invalid enum value"));
assert!(err.to_string().contains("function, class, method"));
}
#[test]
fn test_empty_query_display() {
let err = BuildError::EmptyQuery;
assert_eq!(err.to_string(), "empty query: no conditions specified");
}
#[test]
fn test_multiple_errors_display() {
let err = BuildError::Multiple(vec![
BuildError::EmptyQuery,
BuildError::UnknownField {
field: "foo".to_string(),
available: "kind".to_string(),
},
]);
let msg = err.to_string();
assert!(msg.contains("multiple validation errors"));
assert!(msg.contains("empty query"));
assert!(msg.contains("unknown field"));
}
#[test]
#[allow(clippy::invalid_regex)] fn test_regex_error_conversion() {
let regex_err = regex::Regex::new("[invalid").unwrap_err();
let build_err: BuildError = regex_err.into();
assert!(matches!(build_err, BuildError::InvalidRegex(_)));
assert!(build_err.to_string().contains("invalid regex pattern"));
}
}