1use thiserror::Error;
7
8#[derive(Error, Debug)]
28#[error("{kind}")]
29pub struct ArrowConversionError {
30 kind: ErrorKind,
31}
32
33#[derive(Error, Debug)]
38#[non_exhaustive]
39pub(crate) enum ErrorKind {
40 #[error("unsupported HANA type: {type_id:?}")]
42 UnsupportedType { type_id: i16 },
43
44 #[error("schema mismatch: expected {expected} columns, got {actual}")]
46 SchemaMismatch { expected: usize, actual: usize },
47
48 #[error("value conversion failed for column '{column}': {message}")]
50 ValueConversion {
51 column: String,
52 message: String,
53 #[source]
54 source: Option<Box<dyn std::error::Error + Send + Sync>>,
55 },
56
57 #[error("decimal overflow: precision {precision}, scale {scale}")]
59 DecimalOverflow { precision: u8, scale: i8 },
60
61 #[error("arrow error")]
63 Arrow(
64 #[source]
65 #[from]
66 arrow_schema::ArrowError,
67 ),
68
69 #[error("hdbconnect error: {message}")]
71 Hdbconnect {
72 message: String,
73 #[source]
74 source: Option<Box<dyn std::error::Error + Send + Sync>>,
75 },
76
77 #[error("LOB streaming error: {message}")]
79 LobStreaming { message: String },
80
81 #[error("invalid precision: {0}")]
83 InvalidPrecision(String),
84
85 #[error("invalid scale: {0}")]
87 InvalidScale(String),
88}
89
90impl ArrowConversionError {
91 #[must_use]
97 pub const fn unsupported_type(type_id: i16) -> Self {
98 Self {
99 kind: ErrorKind::UnsupportedType { type_id },
100 }
101 }
102
103 #[must_use]
105 pub const fn schema_mismatch(expected: usize, actual: usize) -> Self {
106 Self {
107 kind: ErrorKind::SchemaMismatch { expected, actual },
108 }
109 }
110
111 #[must_use]
113 pub fn value_conversion(column: impl Into<String>, message: impl Into<String>) -> Self {
114 Self {
115 kind: ErrorKind::ValueConversion {
116 column: column.into(),
117 message: message.into(),
118 source: None,
119 },
120 }
121 }
122
123 #[must_use]
125 pub fn value_conversion_with_source<E>(
126 column: impl Into<String>,
127 message: impl Into<String>,
128 source: E,
129 ) -> Self
130 where
131 E: std::error::Error + Send + Sync + 'static,
132 {
133 Self {
134 kind: ErrorKind::ValueConversion {
135 column: column.into(),
136 message: message.into(),
137 source: Some(Box::new(source)),
138 },
139 }
140 }
141
142 #[must_use]
144 pub const fn decimal_overflow(precision: u8, scale: i8) -> Self {
145 Self {
146 kind: ErrorKind::DecimalOverflow { precision, scale },
147 }
148 }
149
150 #[must_use]
152 pub fn lob_streaming(message: impl Into<String>) -> Self {
153 Self {
154 kind: ErrorKind::LobStreaming {
155 message: message.into(),
156 },
157 }
158 }
159
160 #[must_use]
162 pub fn invalid_precision(message: impl Into<String>) -> Self {
163 Self {
164 kind: ErrorKind::InvalidPrecision(message.into()),
165 }
166 }
167
168 #[must_use]
170 pub fn invalid_scale(message: impl Into<String>) -> Self {
171 Self {
172 kind: ErrorKind::InvalidScale(message.into()),
173 }
174 }
175
176 #[must_use]
182 pub const fn is_unsupported_type(&self) -> bool {
183 matches!(self.kind, ErrorKind::UnsupportedType { .. })
184 }
185
186 #[must_use]
188 pub const fn is_schema_mismatch(&self) -> bool {
189 matches!(self.kind, ErrorKind::SchemaMismatch { .. })
190 }
191
192 #[must_use]
194 pub const fn is_value_conversion(&self) -> bool {
195 matches!(self.kind, ErrorKind::ValueConversion { .. })
196 }
197
198 #[must_use]
200 pub const fn is_decimal_overflow(&self) -> bool {
201 matches!(self.kind, ErrorKind::DecimalOverflow { .. })
202 }
203
204 #[must_use]
206 pub const fn is_arrow_error(&self) -> bool {
207 matches!(self.kind, ErrorKind::Arrow(_))
208 }
209
210 #[must_use]
212 pub const fn is_hdbconnect_error(&self) -> bool {
213 matches!(self.kind, ErrorKind::Hdbconnect { .. })
214 }
215
216 #[must_use]
218 pub const fn is_lob_streaming(&self) -> bool {
219 matches!(self.kind, ErrorKind::LobStreaming { .. })
220 }
221
222 #[must_use]
224 pub const fn is_invalid_precision(&self) -> bool {
225 matches!(self.kind, ErrorKind::InvalidPrecision(_))
226 }
227
228 #[must_use]
230 pub const fn is_invalid_scale(&self) -> bool {
231 matches!(self.kind, ErrorKind::InvalidScale(_))
232 }
233
234 #[must_use]
261 #[allow(clippy::match_same_arms)]
262 pub const fn is_recoverable(&self) -> bool {
263 match &self.kind {
264 ErrorKind::UnsupportedType { .. } => false,
266 ErrorKind::SchemaMismatch { .. } => false,
267 ErrorKind::ValueConversion { .. } => false,
268 ErrorKind::DecimalOverflow { .. } => false,
269 ErrorKind::InvalidPrecision(_) => false,
270 ErrorKind::InvalidScale(_) => false,
271
272 ErrorKind::Arrow(_) => false,
274
275 ErrorKind::LobStreaming { .. } => true,
277
278 ErrorKind::Hdbconnect { .. } => true,
281 }
282 }
283
284 #[must_use]
289 pub const fn is_configuration_error(&self) -> bool {
290 matches!(
291 &self.kind,
292 ErrorKind::UnsupportedType { .. }
293 | ErrorKind::SchemaMismatch { .. }
294 | ErrorKind::InvalidPrecision(_)
295 | ErrorKind::InvalidScale(_)
296 )
297 }
298
299 #[must_use]
303 pub const fn is_data_error(&self) -> bool {
304 matches!(
305 &self.kind,
306 ErrorKind::ValueConversion { .. } | ErrorKind::DecimalOverflow { .. }
307 )
308 }
309}
310
311impl From<hdbconnect::HdbError> for ArrowConversionError {
312 fn from(err: hdbconnect::HdbError) -> Self {
313 Self {
314 kind: ErrorKind::Hdbconnect {
315 message: err.to_string(),
316 source: Some(Box::new(err)),
317 },
318 }
319 }
320}
321
322impl From<arrow_schema::ArrowError> for ArrowConversionError {
323 fn from(err: arrow_schema::ArrowError) -> Self {
324 Self {
325 kind: ErrorKind::Arrow(err),
326 }
327 }
328}
329
330pub type Result<T> = std::result::Result<T, ArrowConversionError>;
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 #[test]
342 fn test_unsupported_type_creation() {
343 let err = ArrowConversionError::unsupported_type(42);
344 assert!(err.is_unsupported_type());
345 assert!(!err.is_schema_mismatch());
346 assert!(!err.is_value_conversion());
347 assert!(!err.is_decimal_overflow());
348 assert!(!err.is_arrow_error());
349 assert!(!err.is_hdbconnect_error());
350 assert!(!err.is_lob_streaming());
351 assert!(!err.is_invalid_precision());
352 assert!(!err.is_invalid_scale());
353 }
354
355 #[test]
356 fn test_schema_mismatch_creation() {
357 let err = ArrowConversionError::schema_mismatch(5, 3);
358 assert!(err.is_schema_mismatch());
359 assert!(!err.is_unsupported_type());
360 assert!(err.to_string().contains("expected 5 columns, got 3"));
361 }
362
363 #[test]
364 fn test_value_conversion_creation() {
365 let err = ArrowConversionError::value_conversion("col1", "invalid integer");
366 assert!(err.is_value_conversion());
367 assert!(!err.is_unsupported_type());
368 assert!(err.to_string().contains("col1"));
369 assert!(err.to_string().contains("invalid integer"));
370 }
371
372 #[test]
373 fn test_decimal_overflow_creation() {
374 let err = ArrowConversionError::decimal_overflow(38, 10);
375 assert!(err.is_decimal_overflow());
376 assert!(!err.is_unsupported_type());
377 assert!(err.to_string().contains("precision 38"));
378 assert!(err.to_string().contains("scale 10"));
379 }
380
381 #[test]
382 fn test_lob_streaming_creation() {
383 let err = ArrowConversionError::lob_streaming("connection lost");
384 assert!(err.is_lob_streaming());
385 assert!(!err.is_unsupported_type());
386 assert!(err.to_string().contains("LOB streaming error"));
387 assert!(err.to_string().contains("connection lost"));
388 }
389
390 #[test]
391 fn test_invalid_precision_creation() {
392 let err = ArrowConversionError::invalid_precision("precision must be positive");
393 assert!(err.is_invalid_precision());
394 assert!(!err.is_unsupported_type());
395 assert!(err.to_string().contains("invalid precision"));
396 }
397
398 #[test]
399 fn test_invalid_scale_creation() {
400 let err = ArrowConversionError::invalid_scale("scale exceeds precision");
401 assert!(err.is_invalid_scale());
402 assert!(!err.is_unsupported_type());
403 assert!(err.to_string().contains("invalid scale"));
404 }
405
406 #[test]
411 fn test_from_arrow_error() {
412 let arrow_err = arrow_schema::ArrowError::SchemaError("test error".to_string());
413 let err: ArrowConversionError = arrow_err.into();
414 assert!(err.is_arrow_error());
415 assert!(!err.is_unsupported_type());
416 assert!(err.to_string().contains("arrow error"));
417 }
418
419 #[test]
424 fn test_error_debug() {
425 let err = ArrowConversionError::unsupported_type(99);
426 let debug_str = format!("{err:?}");
427 assert!(debug_str.contains("ArrowConversionError"));
428 assert!(debug_str.contains("UnsupportedType"));
429 }
430
431 #[test]
432 fn test_unsupported_type_display() {
433 let err = ArrowConversionError::unsupported_type(127);
434 let display = err.to_string();
435 assert!(display.contains("unsupported HANA type"));
436 assert!(display.contains("127"));
437 }
438
439 #[test]
440 fn test_schema_mismatch_display() {
441 let err = ArrowConversionError::schema_mismatch(10, 5);
442 let display = err.to_string();
443 assert!(display.contains("schema mismatch"));
444 assert!(display.contains("expected 10 columns"));
445 assert!(display.contains("got 5"));
446 }
447
448 #[test]
449 fn test_value_conversion_display() {
450 let err = ArrowConversionError::value_conversion("my_column", "parse error");
451 let display = err.to_string();
452 assert!(display.contains("value conversion failed"));
453 assert!(display.contains("my_column"));
454 assert!(display.contains("parse error"));
455 }
456
457 #[test]
458 fn test_decimal_overflow_display() {
459 let err = ArrowConversionError::decimal_overflow(50, 20);
460 let display = err.to_string();
461 assert!(display.contains("decimal overflow"));
462 assert!(display.contains("precision 50"));
463 assert!(display.contains("scale 20"));
464 }
465
466 #[test]
471 fn test_all_predicates_false_for_unsupported_type() {
472 let err = ArrowConversionError::unsupported_type(1);
473 assert!(err.is_unsupported_type());
474 assert!(!err.is_schema_mismatch());
475 assert!(!err.is_value_conversion());
476 assert!(!err.is_decimal_overflow());
477 assert!(!err.is_arrow_error());
478 assert!(!err.is_hdbconnect_error());
479 assert!(!err.is_lob_streaming());
480 assert!(!err.is_invalid_precision());
481 assert!(!err.is_invalid_scale());
482 }
483
484 #[test]
485 fn test_all_predicates_false_for_schema_mismatch() {
486 let err = ArrowConversionError::schema_mismatch(1, 2);
487 assert!(!err.is_unsupported_type());
488 assert!(err.is_schema_mismatch());
489 assert!(!err.is_value_conversion());
490 assert!(!err.is_decimal_overflow());
491 assert!(!err.is_arrow_error());
492 assert!(!err.is_hdbconnect_error());
493 assert!(!err.is_lob_streaming());
494 assert!(!err.is_invalid_precision());
495 assert!(!err.is_invalid_scale());
496 }
497
498 #[test]
499 fn test_all_predicates_false_for_lob_streaming() {
500 let err = ArrowConversionError::lob_streaming("test");
501 assert!(!err.is_unsupported_type());
502 assert!(!err.is_schema_mismatch());
503 assert!(!err.is_value_conversion());
504 assert!(!err.is_decimal_overflow());
505 assert!(!err.is_arrow_error());
506 assert!(!err.is_hdbconnect_error());
507 assert!(err.is_lob_streaming());
508 assert!(!err.is_invalid_precision());
509 assert!(!err.is_invalid_scale());
510 }
511
512 #[test]
517 fn test_empty_column_name() {
518 let err = ArrowConversionError::value_conversion("", "error");
519 assert!(err.is_value_conversion());
520 }
521
522 #[test]
523 fn test_empty_message() {
524 let err = ArrowConversionError::lob_streaming("");
525 assert!(err.is_lob_streaming());
526 }
527
528 #[test]
529 fn test_unicode_in_messages() {
530 let err = ArrowConversionError::value_conversion("列名", "无效数据");
531 assert!(err.is_value_conversion());
532 assert!(err.to_string().contains("列名"));
533 }
534
535 #[test]
536 fn test_zero_schema_mismatch() {
537 let err = ArrowConversionError::schema_mismatch(0, 0);
538 assert!(err.is_schema_mismatch());
539 }
540
541 #[test]
542 fn test_negative_type_id() {
543 let err = ArrowConversionError::unsupported_type(-1);
544 assert!(err.is_unsupported_type());
545 }
546
547 #[test]
548 fn test_negative_scale() {
549 let err = ArrowConversionError::decimal_overflow(10, -5);
550 assert!(err.is_decimal_overflow());
551 assert!(err.to_string().contains("scale -5"));
552 }
553
554 #[test]
559 fn test_is_recoverable() {
560 assert!(!ArrowConversionError::unsupported_type(42).is_recoverable());
562 assert!(!ArrowConversionError::schema_mismatch(1, 2).is_recoverable());
563 assert!(!ArrowConversionError::value_conversion("col", "msg").is_recoverable());
564 assert!(!ArrowConversionError::decimal_overflow(38, 10).is_recoverable());
565 assert!(!ArrowConversionError::invalid_precision("msg").is_recoverable());
566 assert!(!ArrowConversionError::invalid_scale("msg").is_recoverable());
567
568 assert!(ArrowConversionError::lob_streaming("network timeout").is_recoverable());
570 }
571
572 #[test]
573 fn test_is_configuration_error() {
574 assert!(ArrowConversionError::unsupported_type(42).is_configuration_error());
576 assert!(ArrowConversionError::schema_mismatch(1, 2).is_configuration_error());
577 assert!(ArrowConversionError::invalid_precision("msg").is_configuration_error());
578 assert!(ArrowConversionError::invalid_scale("msg").is_configuration_error());
579
580 assert!(!ArrowConversionError::value_conversion("col", "msg").is_configuration_error());
582 assert!(!ArrowConversionError::decimal_overflow(38, 10).is_configuration_error());
583 assert!(!ArrowConversionError::lob_streaming("msg").is_configuration_error());
584 }
585
586 #[test]
587 fn test_is_data_error() {
588 assert!(ArrowConversionError::value_conversion("col", "msg").is_data_error());
590 assert!(ArrowConversionError::decimal_overflow(38, 10).is_data_error());
591
592 assert!(!ArrowConversionError::unsupported_type(42).is_data_error());
594 assert!(!ArrowConversionError::schema_mismatch(1, 2).is_data_error());
595 assert!(!ArrowConversionError::lob_streaming("msg").is_data_error());
596 assert!(!ArrowConversionError::invalid_precision("msg").is_data_error());
597 }
598
599 #[test]
600 fn test_error_classification_mutual_exclusivity() {
601 let config_err = ArrowConversionError::unsupported_type(42);
603 assert!(config_err.is_configuration_error());
604 assert!(!config_err.is_data_error());
605 assert!(!config_err.is_recoverable());
606
607 let data_err = ArrowConversionError::value_conversion("col", "msg");
608 assert!(!data_err.is_configuration_error());
609 assert!(data_err.is_data_error());
610 assert!(!data_err.is_recoverable());
611
612 let recoverable = ArrowConversionError::lob_streaming("timeout");
613 assert!(!recoverable.is_configuration_error());
614 assert!(!recoverable.is_data_error());
615 assert!(recoverable.is_recoverable());
616 }
617
618 #[test]
619 fn test_value_conversion_with_source() {
620 let source = std::io::Error::new(std::io::ErrorKind::Other, "parse failed");
621 let err = ArrowConversionError::value_conversion_with_source("col1", "failed", source);
622 assert!(err.is_value_conversion());
623 assert!(err.is_data_error());
624 assert!(err.to_string().contains("col1"));
625 }
626}