Skip to main content

chopin_orm/
error.rs

1use chopin_pg::error::PgError;
2
3/// Error type for the Chopin ORM.
4///
5/// All variants are `Send + Sync`, making this safe to use across thread boundaries.
6#[derive(Debug)]
7pub enum OrmError {
8    /// Error from the underlying PostgreSQL driver.
9    Database(PgError),
10    /// No records were found for a query that expected at least one.
11    RecordNotFound,
12    /// Multiple records were found for a query that expected exactly one.
13    MultipleRecordsFound,
14    /// Error during data extraction or type conversion from a row.
15    Extraction(String),
16    /// Model-specific configuration or logic error.
17    ModelError(String),
18    /// One or more validation rules failed.
19    Validation(Vec<String>),
20}
21
22impl std::fmt::Display for OrmError {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        match self {
25            OrmError::Database(e) => write!(f, "Database error: {}", e),
26            OrmError::RecordNotFound => write!(f, "Record not found"),
27            OrmError::MultipleRecordsFound => write!(f, "Multiple records found"),
28            OrmError::Extraction(msg) => write!(f, "Extraction error: {}", msg),
29            OrmError::ModelError(msg) => write!(f, "Model error: {}", msg),
30            OrmError::Validation(errors) => {
31                write!(f, "Validation failed: {}", errors.join(", "))
32            }
33        }
34    }
35}
36
37impl std::error::Error for OrmError {
38    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
39        match self {
40            OrmError::Database(e) => Some(e),
41            _ => None,
42        }
43    }
44}
45
46impl From<PgError> for OrmError {
47    fn from(e: PgError) -> Self {
48        OrmError::Database(e)
49    }
50}
51
52impl From<String> for OrmError {
53    fn from(msg: String) -> Self {
54        OrmError::ModelError(msg)
55    }
56}
57
58impl From<&str> for OrmError {
59    fn from(msg: &str) -> Self {
60        OrmError::ModelError(msg.to_string())
61    }
62}
63
64pub type OrmResult<T> = Result<T, OrmError>;
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use chopin_pg::error::PgError;
70
71    // ─── Display ─────────────────────────────────────────────────────────────
72
73    #[test]
74    fn test_display_record_not_found() {
75        let s = OrmError::RecordNotFound.to_string();
76        assert!(
77            s.contains("not found") || s.contains("Record"),
78            "unexpected: {}",
79            s
80        );
81    }
82
83    #[test]
84    fn test_display_multiple_records_found() {
85        let s = OrmError::MultipleRecordsFound.to_string();
86        assert!(
87            s.contains("Multiple") || s.contains("multiple"),
88            "unexpected: {}",
89            s
90        );
91    }
92
93    #[test]
94    fn test_display_extraction() {
95        let s = OrmError::Extraction("bad type".to_string()).to_string();
96        assert!(s.contains("bad type"), "unexpected: {}", s);
97    }
98
99    #[test]
100    fn test_display_model_error() {
101        let s = OrmError::ModelError("invalid field".to_string()).to_string();
102        assert!(s.contains("invalid field"), "unexpected: {}", s);
103    }
104
105    #[test]
106    fn test_display_database() {
107        let pg_err = PgError::Protocol("test error".to_string());
108        let s = OrmError::Database(pg_err).to_string();
109        assert!(
110            s.contains("Database") || s.contains("test error"),
111            "unexpected: {}",
112            s
113        );
114    }
115
116    // ─── Error::source() ─────────────────────────────────────────────────────
117
118    #[test]
119    fn test_source_database_is_some() {
120        use std::error::Error;
121        let e = OrmError::Database(PgError::Protocol("x".to_string()));
122        assert!(e.source().is_some());
123    }
124
125    #[test]
126    fn test_source_non_database_is_none() {
127        use std::error::Error;
128        assert!(OrmError::RecordNotFound.source().is_none());
129        assert!(OrmError::MultipleRecordsFound.source().is_none());
130        assert!(OrmError::Extraction("e".into()).source().is_none());
131        assert!(OrmError::ModelError("m".into()).source().is_none());
132        assert!(OrmError::Validation(vec!["v".into()]).source().is_none());
133    }
134
135    // ─── From<PgError> ───────────────────────────────────────────────────────
136
137    #[test]
138    fn test_from_pgerror() {
139        let pg_err = PgError::Protocol("from-test".to_string());
140        let orm_err: OrmError = pg_err.into();
141        assert!(matches!(orm_err, OrmError::Database(_)));
142    }
143
144    #[test]
145    fn test_from_string() {
146        let orm_err: OrmError = "test error".into();
147        assert!(matches!(orm_err, OrmError::ModelError(_)));
148        let orm_err: OrmError = String::from("test error").into();
149        assert!(matches!(orm_err, OrmError::ModelError(_)));
150    }
151
152    // ─── Debug ───────────────────────────────────────────────────────────────
153
154    #[test]
155    fn test_debug_does_not_panic() {
156        let _ = format!("{:?}", OrmError::RecordNotFound);
157        let _ = format!("{:?}", OrmError::MultipleRecordsFound);
158        let _ = format!("{:?}", OrmError::Extraction("e".into()));
159        let _ = format!("{:?}", OrmError::ModelError("m".into()));
160        let _ = format!("{:?}", OrmError::Database(PgError::Protocol("x".into())));
161        let _ = format!("{:?}", OrmError::Validation(vec!["v".into()]));
162    }
163
164    // ─── Validation variant ──────────────────────────────────────────────────
165
166    #[test]
167    fn test_display_validation() {
168        let s = OrmError::Validation(vec!["field required".into(), "too short".into()]).to_string();
169        assert!(s.contains("field required"), "unexpected: {}", s);
170        assert!(s.contains("too short"), "unexpected: {}", s);
171    }
172
173    #[test]
174    fn test_validation_empty() {
175        let s = OrmError::Validation(vec![]).to_string();
176        assert!(s.contains("Validation failed"), "unexpected: {}", s);
177    }
178
179    // ─── OrmResult type alias ─────────────────────────────────────────────────
180
181    #[test]
182    fn test_orm_result_ok() {
183        let r: OrmResult<i32> = Ok(7);
184        assert!(matches!(r, Ok(7)));
185    }
186
187    #[test]
188    fn test_orm_result_err() {
189        let r: OrmResult<i32> = Err(OrmError::RecordNotFound);
190        assert!(r.is_err());
191    }
192}