1use std::fmt;
2
3use crate::executor::Family;
4
5#[derive(Debug)]
12#[non_exhaustive]
13pub enum ExecError {
14 Build(keelson_core::Error),
16
17 Decode {
21 column: String,
23 source: keelson_core::Error,
26 },
27
28 MissingColumn {
30 column: String,
32 available: Vec<String>,
35 },
36
37 RowNotFound,
39
40 TooManyRows,
45
46 UnsupportedValue {
52 type_name: &'static str,
54 family: Family,
56 },
57
58 Driver(Box<dyn std::error::Error + Send + Sync>),
60
61 Other(String),
63}
64
65impl ExecError {
66 pub fn driver(e: impl std::error::Error + Send + Sync + 'static) -> Self {
68 ExecError::Driver(Box::new(e))
69 }
70
71 pub fn other(msg: impl Into<String>) -> Self {
73 ExecError::Other(msg.into())
74 }
75}
76
77impl From<keelson_core::Error> for ExecError {
78 fn from(e: keelson_core::Error) -> Self {
79 ExecError::Build(e)
80 }
81}
82
83impl fmt::Display for ExecError {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 match self {
86 ExecError::Build(e) => write!(f, "query failed to build: {e}"),
87 ExecError::Decode { column, source } => write!(f, "column \"{column}\": {source}"),
88 ExecError::MissingColumn { column, available } => write!(
89 f,
90 "no column \"{column}\" in result set (columns: {})",
91 available.join(", ")
92 ),
93 ExecError::RowNotFound => {
94 f.write_str("no rows returned where exactly one was expected")
95 }
96 ExecError::TooManyRows => {
97 f.write_str("more than one row returned where at most one was expected")
98 }
99 ExecError::UnsupportedValue { type_name, family } => {
100 write!(f, "cannot bind a {type_name} value on {family}")
101 }
102 ExecError::Driver(e) => write!(f, "driver error: {e}"),
103 ExecError::Other(msg) => f.write_str(msg),
104 }
105 }
106}
107
108impl std::error::Error for ExecError {
109 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
110 match self {
111 ExecError::Build(e) | ExecError::Decode { source: e, .. } => Some(e),
112 ExecError::Driver(e) => Some(e.as_ref()),
113 _ => None,
114 }
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn decode_errors_name_the_column() {
124 let e = ExecError::Decode {
125 column: "email".into(),
126 source: keelson_core::Error::type_mismatch("String", "NULL"),
127 };
128 assert_eq!(
129 e.to_string(),
130 "column \"email\": cannot read NULL as String"
131 );
132 }
133
134 #[test]
135 fn missing_column_lists_what_was_there() {
136 let e = ExecError::MissingColumn {
137 column: "emial".into(),
138 available: vec!["id".into(), "name".into(), "email".into()],
139 };
140 assert_eq!(
141 e.to_string(),
142 "no column \"emial\" in result set (columns: id, name, email)"
143 );
144 }
145
146 #[test]
147 fn is_a_std_error_with_a_source() {
148 let e = ExecError::Build(keelson_core::Error::Incomplete("a table"));
149 assert!(std::error::Error::source(&e).is_some());
150 }
151}