Skip to main content

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}