vespertide_query/error.rs
1use thiserror::Error;
2
3use crate::sql::types::DatabaseBackend;
4
5/// Errors returned by SQL query generation.
6///
7/// `QueryError` is `#[non_exhaustive]`, so match arms must include a wildcard.
8///
9/// Matching variants:
10///
11/// ```rust
12/// use vespertide_query::{QueryError, DatabaseBackend};
13///
14/// fn report(err: &QueryError) -> String {
15/// match err {
16/// QueryError::SchemaError(msg) => format!("schema: {msg}"),
17/// QueryError::InvalidColumnType { backend, message } => {
18/// format!("type error for {backend:?}: {message}")
19/// }
20/// _ => format!("other: {err}"),
21/// }
22/// }
23///
24/// let schema_err = QueryError::SchemaError("missing table user".into());
25/// assert_eq!(report(&schema_err), "schema: missing table user");
26///
27/// let type_err = QueryError::InvalidColumnType {
28/// backend: DatabaseBackend::Postgres,
29/// message: "inet not supported".into(),
30/// };
31/// assert!(report(&type_err).contains("Postgres"));
32/// ```
33#[derive(Debug, Clone, Error)]
34#[non_exhaustive]
35pub enum QueryError {
36 /// The SQL backend doesn't support the requested table constraint variant.
37 #[error("unsupported table constraint")]
38 UnsupportedConstraint,
39
40 /// A column's type cannot be mapped to the target SQL backend.
41 ///
42 /// Constructor with named fields:
43 ///
44 /// ```rust
45 /// use vespertide_query::{QueryError, DatabaseBackend};
46 ///
47 /// let err = QueryError::InvalidColumnType {
48 /// backend: DatabaseBackend::MySql,
49 /// message: "inet is not supported by MySQL".into(),
50 /// };
51 /// assert!(err.to_string().contains("MySql"));
52 /// assert!(err.to_string().contains("inet is not supported by MySQL"));
53 /// ```
54 #[error("invalid column type for {backend:?}: {message}")]
55 InvalidColumnType {
56 backend: DatabaseBackend,
57 message: String,
58 },
59
60 /// A schema validation issue surfaced during query generation
61 /// (e.g., missing referenced table, malformed constraint).
62 ///
63 /// Constructor and `Display`:
64 ///
65 /// ```rust
66 /// use vespertide_query::QueryError;
67 ///
68 /// let err = QueryError::SchemaError("referenced table 'user' not found".into());
69 /// assert_eq!(err.to_string(), "schema error: referenced table 'user' not found");
70 /// ```
71 #[error("schema error: {0}")]
72 SchemaError(String),
73
74 /// Backend-specific SQL generation failed.
75 #[error("backend error ({backend:?}): {message}")]
76 BackendError {
77 backend: DatabaseBackend,
78 message: String,
79 },
80
81 /// The requested `MigrationAction` variant isn't yet supported by query generation.
82 #[error("unsupported action: {0}")]
83 UnsupportedAction(String),
84
85 /// Fallback variant for errors that don't fit a specific category yet.
86 /// Will be removed in a future major version — prefer specific variants when possible.
87 #[deprecated(
88 since = "0.2.0",
89 note = "Match a specific variant (InvalidColumnType, SchemaError, BackendError, UnsupportedAction)"
90 )]
91 #[error("{0}")]
92 Other(String),
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn invalid_column_type_message_includes_backend() {
101 let err = QueryError::InvalidColumnType {
102 backend: DatabaseBackend::Postgres,
103 message: "unknown type: ZZZ".into(),
104 };
105 let msg = err.to_string();
106 assert!(msg.contains("Postgres"));
107 assert!(msg.contains("ZZZ"));
108 }
109
110 #[test]
111 fn schema_error_displays_message() {
112 let err = QueryError::SchemaError("missing table foo".into());
113 assert_eq!(err.to_string(), "schema error: missing table foo");
114 }
115
116 #[test]
117 fn errors_can_be_cloned() {
118 let original = QueryError::UnsupportedConstraint;
119 let _clone = original.clone();
120 }
121
122 #[test]
123 #[expect(
124 deprecated,
125 reason = "0.2.0 G.3 explicit backward-compat test for deprecated QueryError::Other variant"
126 )]
127 fn other_variant_still_constructable_for_backward_compat() {
128 let err = QueryError::Other("legacy".into());
129 assert_eq!(err.to_string(), "legacy");
130 }
131}