snowflake_connector_rs/error/
schema.rs1use std::{
2 borrow::Cow,
3 error::Error as StdError,
4 fmt::{self, Display, Formatter},
5};
6
7use crate::result_table::ColumnType;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum SchemaError {
12 MissingColumn(MissingColumnError),
13 AmbiguousColumn(AmbiguousColumnError),
14 InvalidColumnIndex(InvalidColumnIndexError),
15 DuplicateColumnName(DuplicateColumnNameError),
16 ColumnCountMismatch(ColumnCountMismatchError),
17 IncompatibleColumnType(IncompatibleColumnTypeError),
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct MissingColumnError {
22 name: Box<str>,
23}
24
25impl MissingColumnError {
26 pub(crate) fn new(name: impl Into<Box<str>>) -> Self {
27 Self { name: name.into() }
28 }
29
30 pub fn name(&self) -> &str {
31 &self.name
32 }
33}
34
35impl Display for MissingColumnError {
36 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
37 write!(f, "missing column: {}", self.name)
38 }
39}
40
41impl StdError for MissingColumnError {}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct AmbiguousColumnError {
45 name: Box<str>,
46 candidates: Box<[usize]>,
47}
48
49impl AmbiguousColumnError {
50 pub(crate) fn new(name: impl Into<Box<str>>, candidates: impl Into<Box<[usize]>>) -> Self {
51 Self {
52 name: name.into(),
53 candidates: candidates.into(),
54 }
55 }
56
57 pub fn name(&self) -> &str {
58 &self.name
59 }
60
61 pub fn candidates(&self) -> &[usize] {
63 &self.candidates
64 }
65}
66
67impl Display for AmbiguousColumnError {
68 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
69 write!(f, "ambiguous column: {}", self.name)
70 }
71}
72
73impl StdError for AmbiguousColumnError {}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct InvalidColumnIndexError {
77 index: usize,
78 column_count: usize,
79}
80
81impl InvalidColumnIndexError {
82 pub(crate) fn new(index: usize, column_count: usize) -> Self {
83 Self {
84 index,
85 column_count,
86 }
87 }
88
89 pub fn index(&self) -> usize {
90 self.index
91 }
92
93 pub fn column_count(&self) -> usize {
94 self.column_count
95 }
96}
97
98impl Display for InvalidColumnIndexError {
99 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
100 write!(
101 f,
102 "invalid column index {} for schema with {} columns",
103 self.index, self.column_count
104 )
105 }
106}
107
108impl StdError for InvalidColumnIndexError {}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct DuplicateColumnNameError {
112 name: Box<str>,
113}
114
115impl DuplicateColumnNameError {
116 pub(crate) fn new(name: impl Into<Box<str>>) -> Self {
117 Self { name: name.into() }
118 }
119
120 pub fn name(&self) -> &str {
121 &self.name
122 }
123}
124
125impl Display for DuplicateColumnNameError {
126 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
127 write!(f, "duplicate column name in result: {}", self.name)
128 }
129}
130
131impl StdError for DuplicateColumnNameError {}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub struct ColumnCountMismatchError {
135 expected: usize,
136 actual: usize,
137}
138
139impl ColumnCountMismatchError {
140 pub fn new(expected: usize, actual: usize) -> Self {
142 Self { expected, actual }
143 }
144
145 pub fn expected(&self) -> usize {
146 self.expected
147 }
148
149 pub fn actual(&self) -> usize {
150 self.actual
151 }
152}
153
154impl Display for ColumnCountMismatchError {
155 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
156 write!(
157 f,
158 "column count mismatch (expected {}, actual {})",
159 self.expected, self.actual
160 )
161 }
162}
163
164impl StdError for ColumnCountMismatchError {}
165
166#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct IncompatibleColumnTypeError {
172 column_index: usize,
173 column_name: Box<str>,
174 target_type_name: Cow<'static, str>,
175 actual_column_type: ColumnType,
176 detail: Option<Box<str>>,
177}
178
179impl IncompatibleColumnTypeError {
180 pub(crate) fn new(
181 column_index: usize,
182 column_name: impl Into<Box<str>>,
183 target_type_name: impl Into<Cow<'static, str>>,
184 actual_column_type: ColumnType,
185 detail: Option<Box<str>>,
186 ) -> Self {
187 Self {
188 column_index,
189 column_name: column_name.into(),
190 target_type_name: target_type_name.into(),
191 actual_column_type,
192 detail,
193 }
194 }
195
196 pub fn column_index(&self) -> usize {
197 self.column_index
198 }
199
200 pub fn column_name(&self) -> &str {
201 &self.column_name
202 }
203
204 pub fn target_type_name(&self) -> &str {
205 &self.target_type_name
206 }
207
208 pub fn actual_column_type(&self) -> &ColumnType {
209 &self.actual_column_type
210 }
211
212 pub fn detail(&self) -> Option<&str> {
214 self.detail.as_deref()
215 }
216}
217
218impl Display for IncompatibleColumnTypeError {
219 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
220 write!(
221 f,
222 "column {} ({}) of type {} cannot be decoded as {}",
223 self.column_index, self.column_name, self.actual_column_type, self.target_type_name
224 )?;
225 if let Some(detail) = &self.detail {
226 write!(f, " ({detail})")?;
227 }
228 Ok(())
229 }
230}
231
232impl StdError for IncompatibleColumnTypeError {}
233
234impl Display for SchemaError {
235 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
236 match self {
237 Self::MissingColumn(error) => error.fmt(f),
238 Self::AmbiguousColumn(error) => error.fmt(f),
239 Self::InvalidColumnIndex(error) => error.fmt(f),
240 Self::DuplicateColumnName(error) => error.fmt(f),
241 Self::ColumnCountMismatch(error) => error.fmt(f),
242 Self::IncompatibleColumnType(error) => error.fmt(f),
243 }
244 }
245}
246
247impl StdError for SchemaError {}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn schema_error_display_formats_missing_and_ambiguous_variants() {
255 assert_eq!(
256 SchemaError::MissingColumn(MissingColumnError::new("value")).to_string(),
257 "missing column: value"
258 );
259 assert_eq!(
260 SchemaError::AmbiguousColumn(AmbiguousColumnError::new(
261 "value",
262 vec![0, 1].into_boxed_slice(),
263 ))
264 .to_string(),
265 "ambiguous column: value"
266 );
267 }
268
269 #[test]
270 fn schema_error_accessors_expose_structured_fields() {
271 let ambiguous = AmbiguousColumnError::new("value", vec![0, 1].into_boxed_slice());
272 assert_eq!(ambiguous.name(), "value");
273 assert_eq!(ambiguous.candidates(), &[0, 1]);
274
275 let invalid = InvalidColumnIndexError::new(7, 3);
276 assert_eq!(invalid.index(), 7);
277 assert_eq!(invalid.column_count(), 3);
278
279 let duplicate = DuplicateColumnNameError::new("id");
280 assert_eq!(duplicate.name(), "id");
281
282 let mismatch = ColumnCountMismatchError::new(4, 2);
283 assert_eq!(mismatch.expected(), 4);
284 assert_eq!(mismatch.actual(), 2);
285 }
286
287 #[test]
288 fn schema_error_display_formats_remaining_variants() {
289 assert_eq!(
290 SchemaError::InvalidColumnIndex(InvalidColumnIndexError::new(7, 3)).to_string(),
291 "invalid column index 7 for schema with 3 columns"
292 );
293 assert_eq!(
294 SchemaError::DuplicateColumnName(DuplicateColumnNameError::new("id")).to_string(),
295 "duplicate column name in result: id"
296 );
297 assert_eq!(
298 SchemaError::ColumnCountMismatch(ColumnCountMismatchError::new(4, 2)).to_string(),
299 "column count mismatch (expected 4, actual 2)"
300 );
301 }
302
303 #[test]
304 fn incompatible_column_type_exposes_fields_and_appends_detail() {
305 let without_detail = IncompatibleColumnTypeError::new(
306 2,
307 "TS",
308 "chrono::NaiveDateTime",
309 ColumnType::Boolean,
310 None,
311 );
312 assert_eq!(without_detail.column_index(), 2);
313 assert_eq!(without_detail.column_name(), "TS");
314 assert_eq!(without_detail.target_type_name(), "chrono::NaiveDateTime");
315 assert_eq!(without_detail.actual_column_type(), &ColumnType::Boolean);
316 assert_eq!(without_detail.detail(), None);
317 assert_eq!(
318 without_detail.to_string(),
319 "column 2 (TS) of type boolean cannot be decoded as chrono::NaiveDateTime"
320 );
321
322 let with_detail = IncompatibleColumnTypeError::new(
323 0,
324 "T",
325 "chrono::NaiveTime",
326 ColumnType::Time { scale: Some(12) },
327 Some(Box::from("invalid time scale: 12")),
328 );
329 assert_eq!(with_detail.detail(), Some("invalid time scale: 12"));
330 assert!(
331 with_detail
332 .to_string()
333 .ends_with("(invalid time scale: 12)"),
334 "actual: {with_detail}"
335 );
336 }
337}