Skip to main content

sqry_core/query/builder/
error.rs

1//! Error types for the Query Builder API
2
3use crate::query::Operator;
4use std::fmt;
5
6/// Errors that can occur when building a query
7#[derive(Debug, Clone)]
8pub enum BuildError {
9    /// Unknown field name (not in registry)
10    UnknownField {
11        /// The field name that was not found
12        field: String,
13        /// List of available field names
14        available: String,
15    },
16
17    /// Operator not valid for the field type
18    InvalidOperator {
19        /// The field name
20        field: String,
21        /// The operator that was used
22        operator: Operator,
23        /// The field type that doesn't support this operator
24        field_type: String,
25    },
26
27    /// Value type doesn't match field type
28    ValueTypeMismatch {
29        /// The field name
30        field: String,
31        /// The expected value type
32        expected: String,
33        /// The actual value type provided
34        actual: String,
35    },
36
37    /// Invalid enum value for an enum field
38    InvalidEnumValue {
39        /// The field name
40        field: String,
41        /// The invalid value provided
42        value: String,
43        /// List of valid enum values
44        valid: String,
45    },
46
47    /// Invalid regex pattern (from standard regex crate)
48    InvalidRegex(regex::Error),
49
50    /// Invalid regex pattern with lookaround (from fancy-regex crate)
51    InvalidFancyRegex {
52        /// The invalid pattern
53        pattern: String,
54        /// The error message
55        error: String,
56    },
57
58    /// Empty query (no conditions specified)
59    EmptyQuery,
60
61    /// Multiple validation errors
62    Multiple(Vec<BuildError>),
63}
64
65impl fmt::Display for BuildError {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        match self {
68            BuildError::UnknownField { field, available } => {
69                write!(f, "unknown field '{field}', available fields: {available}")
70            }
71            BuildError::InvalidOperator {
72                field,
73                operator,
74                field_type,
75            } => {
76                write!(
77                    f,
78                    "operator '{operator:?}' not valid for field '{field}' (type: {field_type})"
79                )
80            }
81            BuildError::ValueTypeMismatch {
82                field,
83                expected,
84                actual,
85            } => {
86                write!(
87                    f,
88                    "value type mismatch for field '{field}': expected {expected}, got {actual}"
89                )
90            }
91            BuildError::InvalidEnumValue {
92                field,
93                value,
94                valid,
95            } => {
96                write!(
97                    f,
98                    "invalid enum value '{value}' for field '{field}', valid values: {valid}"
99                )
100            }
101            BuildError::InvalidRegex(e) => {
102                write!(f, "invalid regex pattern: {e}")
103            }
104            BuildError::InvalidFancyRegex { pattern, error } => {
105                write!(f, "invalid regex pattern '{pattern}': {error}")
106            }
107            BuildError::EmptyQuery => {
108                write!(f, "empty query: no conditions specified")
109            }
110            BuildError::Multiple(errors) => {
111                write!(f, "multiple validation errors:")?;
112                for (i, err) in errors.iter().enumerate() {
113                    write!(f, "\n  {}: {err}", i + 1)?;
114                }
115                Ok(())
116            }
117        }
118    }
119}
120
121impl std::error::Error for BuildError {
122    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
123        match self {
124            BuildError::InvalidRegex(e) => Some(e),
125            _ => None,
126        }
127    }
128}
129
130impl From<regex::Error> for BuildError {
131    fn from(err: regex::Error) -> Self {
132        BuildError::InvalidRegex(err)
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn test_unknown_field_display() {
142        let err = BuildError::UnknownField {
143            field: "foo".to_string(),
144            available: "kind, name, path".to_string(),
145        };
146        assert_eq!(
147            err.to_string(),
148            "unknown field 'foo', available fields: kind, name, path"
149        );
150    }
151
152    #[test]
153    fn test_invalid_operator_display() {
154        let err = BuildError::InvalidOperator {
155            field: "kind".to_string(),
156            operator: Operator::Greater,
157            field_type: "Enum".to_string(),
158        };
159        assert!(err.to_string().contains("operator"));
160        assert!(err.to_string().contains("kind"));
161    }
162
163    #[test]
164    fn test_value_type_mismatch_display() {
165        let err = BuildError::ValueTypeMismatch {
166            field: "name".to_string(),
167            expected: "String".to_string(),
168            actual: "Number".to_string(),
169        };
170        assert!(err.to_string().contains("value type mismatch"));
171        assert!(err.to_string().contains("expected String"));
172    }
173
174    #[test]
175    fn test_invalid_enum_value_display() {
176        let err = BuildError::InvalidEnumValue {
177            field: "kind".to_string(),
178            value: "invalid".to_string(),
179            valid: "function, class, method".to_string(),
180        };
181        assert!(err.to_string().contains("invalid enum value"));
182        assert!(err.to_string().contains("function, class, method"));
183    }
184
185    #[test]
186    fn test_empty_query_display() {
187        let err = BuildError::EmptyQuery;
188        assert_eq!(err.to_string(), "empty query: no conditions specified");
189    }
190
191    #[test]
192    fn test_multiple_errors_display() {
193        let err = BuildError::Multiple(vec![
194            BuildError::EmptyQuery,
195            BuildError::UnknownField {
196                field: "foo".to_string(),
197                available: "kind".to_string(),
198            },
199        ]);
200        let msg = err.to_string();
201        assert!(msg.contains("multiple validation errors"));
202        assert!(msg.contains("empty query"));
203        assert!(msg.contains("unknown field"));
204    }
205
206    #[test]
207    #[allow(clippy::invalid_regex)] // Intentionally invalid regex to test error conversion
208    fn test_regex_error_conversion() {
209        let regex_err = regex::Regex::new("[invalid").unwrap_err();
210        let build_err: BuildError = regex_err.into();
211        assert!(matches!(build_err, BuildError::InvalidRegex(_)));
212        assert!(build_err.to_string().contains("invalid regex pattern"));
213    }
214}