Skip to main content

oxisql_core/
warning.rs

1//! SQL warning types surfaced by database backends.
2//!
3//! MySQL (and some other databases) return a warning count in every `OkPacket`.
4//! Callers can retrieve the full list of warnings via
5//! [`Connection::last_warnings`][crate::Connection::last_warnings] after any
6//! `execute` or `query` call.
7
8use std::fmt;
9
10// ── SqlWarningLevel ──────────────────────────────────────────────────────────
11
12/// Severity level of a SQL warning.
13///
14/// Maps directly to MySQL's `Level` column in `SHOW WARNINGS`.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum SqlWarningLevel {
17    /// Informational message — no data modification occurred.
18    Note,
19    /// A value was adjusted or truncated; data was still stored.
20    Warning,
21    /// The operation failed with an error surfaced as a warning
22    /// (seen inside stored procedures or `IGNORE`-mode DML).
23    Error,
24}
25
26impl fmt::Display for SqlWarningLevel {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        match self {
29            Self::Note => write!(f, "Note"),
30            Self::Warning => write!(f, "Warning"),
31            Self::Error => write!(f, "Error"),
32        }
33    }
34}
35
36// ── SqlWarning ───────────────────────────────────────────────────────────────
37
38/// A SQL warning returned by the database server after a statement.
39///
40/// Obtain a `Vec<SqlWarning>` by calling
41/// [`Connection::last_warnings`][crate::Connection::last_warnings] immediately
42/// after an `execute` or `query` call.  The list is cleared before each
43/// statement and repopulated afterwards (backends that support warnings).
44///
45/// # Example
46///
47/// ```rust
48/// use oxisql_core::{SqlWarning, SqlWarningLevel};
49///
50/// let w = SqlWarning {
51///     code: 1292,
52///     level: SqlWarningLevel::Warning,
53///     message: "Incorrect date value: '2024-13-01'".to_string(),
54/// };
55/// assert!(w.to_string().contains("1292"));
56/// assert!(w.to_string().contains("Warning"));
57/// ```
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct SqlWarning {
60    /// MySQL server error/warning code (e.g. 1292 for `ER_TRUNCATED_WRONG_VALUE`).
61    pub code: u16,
62    /// Severity level of the warning.
63    pub level: SqlWarningLevel,
64    /// Human-readable message from the server.
65    pub message: String,
66}
67
68impl fmt::Display for SqlWarning {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        write!(f, "[{}] {}: {}", self.level, self.code, self.message)
71    }
72}
73
74// ── parse helper ─────────────────────────────────────────────────────────────
75
76/// Parse a MySQL warning level string into a [`SqlWarningLevel`].
77///
78/// Comparison is case-insensitive.  Any unrecognised string maps to
79/// [`SqlWarningLevel::Warning`] as a safe default.
80pub fn parse_warning_level(s: &str) -> SqlWarningLevel {
81    match s.to_ascii_lowercase().as_str() {
82        "note" => SqlWarningLevel::Note,
83        "error" => SqlWarningLevel::Error,
84        _ => SqlWarningLevel::Warning,
85    }
86}
87
88// ── tests ────────────────────────────────────────────────────────────────────
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn sql_warning_display() {
96        let w = SqlWarning {
97            code: 1292,
98            level: SqlWarningLevel::Warning,
99            message: "Incorrect date value".to_string(),
100        };
101        let s = w.to_string();
102        assert!(s.contains("1292"), "code missing: {s}");
103        assert!(s.contains("Incorrect date"), "message missing: {s}");
104        assert!(s.contains("Warning"), "level missing: {s}");
105    }
106
107    #[test]
108    fn sql_warning_level_display() {
109        assert_eq!(SqlWarningLevel::Note.to_string(), "Note");
110        assert_eq!(SqlWarningLevel::Warning.to_string(), "Warning");
111        assert_eq!(SqlWarningLevel::Error.to_string(), "Error");
112    }
113
114    #[test]
115    fn parse_warning_level_variants() {
116        assert_eq!(parse_warning_level("note"), SqlWarningLevel::Note);
117        assert_eq!(parse_warning_level("Note"), SqlWarningLevel::Note);
118        assert_eq!(parse_warning_level("NOTE"), SqlWarningLevel::Note);
119        assert_eq!(parse_warning_level("warning"), SqlWarningLevel::Warning);
120        assert_eq!(parse_warning_level("Warning"), SqlWarningLevel::Warning);
121        assert_eq!(parse_warning_level("WARNING"), SqlWarningLevel::Warning);
122        assert_eq!(parse_warning_level("error"), SqlWarningLevel::Error);
123        assert_eq!(parse_warning_level("Error"), SqlWarningLevel::Error);
124        assert_eq!(parse_warning_level("ERROR"), SqlWarningLevel::Error);
125        // Unknown defaults to Warning
126        assert_eq!(parse_warning_level("unknown"), SqlWarningLevel::Warning);
127        assert_eq!(parse_warning_level(""), SqlWarningLevel::Warning);
128    }
129
130    #[test]
131    fn sql_warning_debug_clone_eq() {
132        let w = SqlWarning {
133            code: 1000,
134            level: SqlWarningLevel::Note,
135            message: "test note".to_string(),
136        };
137        let w2 = w.clone();
138        assert_eq!(w, w2);
139        // Debug doesn't panic
140        let _ = format!("{:?}", w);
141    }
142}