hdbconnect_arrow/
error.rs

1//! Error hierarchy for hdbconnect-arrow.
2//!
3//! Follows the "canonical error struct" pattern from Microsoft Rust Guidelines.
4//! Exposes `is_xxx()` methods rather than internal `ErrorKind` for future-proofing.
5
6use thiserror::Error;
7
8/// Root error type for hdbconnect-arrow crate.
9///
10/// This error type captures all possible failure modes during HANA to Arrow
11/// conversion. Exposes predicate methods (`is_xxx()`) for error classification
12/// without exposing internals.
13///
14/// # Example
15///
16/// ```rust,ignore
17/// use hdbconnect_arrow::ArrowConversionError;
18///
19/// fn handle_error(err: ArrowConversionError) {
20///     if err.is_unsupported_type() {
21///         eprintln!("Unsupported HANA type encountered");
22///     } else if err.is_schema_mismatch() {
23///         eprintln!("Schema mismatch detected");
24///     }
25/// }
26/// ```
27#[derive(Error, Debug)]
28#[error("{kind}")]
29pub struct ArrowConversionError {
30    kind: ErrorKind,
31}
32
33/// Internal error classification.
34///
35/// This enum is `pub(crate)` to allow adding variants without breaking changes.
36/// External code should use the `is_xxx()` predicate methods instead.
37#[derive(Error, Debug)]
38#[non_exhaustive]
39pub(crate) enum ErrorKind {
40    /// A HANA type that cannot be mapped to Arrow.
41    #[error("unsupported HANA type: {type_id:?}")]
42    UnsupportedType { type_id: i16 },
43
44    /// Column count mismatch between expected and actual.
45    #[error("schema mismatch: expected {expected} columns, got {actual}")]
46    SchemaMismatch { expected: usize, actual: usize },
47
48    /// Value conversion failure for a specific column.
49    #[error("value conversion failed for column '{column}': {message}")]
50    ValueConversion { column: String, message: String },
51
52    /// Decimal value exceeds Arrow Decimal128 capacity.
53    #[error("decimal overflow: precision {precision}, scale {scale}")]
54    DecimalOverflow { precision: u8, scale: i8 },
55
56    /// Error from Arrow library operations.
57    #[error("arrow error: {0}")]
58    Arrow(#[from] arrow_schema::ArrowError),
59
60    /// Error from hdbconnect library.
61    #[error("hdbconnect error: {0}")]
62    Hdbconnect(String),
63
64    /// Error during LOB streaming operations.
65    #[error("LOB streaming error: {message}")]
66    LobStreaming { message: String },
67
68    /// Invalid precision value for DECIMAL type.
69    #[error("invalid precision: {0}")]
70    InvalidPrecision(String),
71
72    /// Invalid scale value for DECIMAL type.
73    #[error("invalid scale: {0}")]
74    InvalidScale(String),
75}
76
77impl ArrowConversionError {
78    // ═══════════════════════════════════════════════════════════════════════
79    // Constructors
80    // ═══════════════════════════════════════════════════════════════════════
81
82    /// Create error for unsupported HANA type.
83    #[must_use]
84    pub const fn unsupported_type(type_id: i16) -> Self {
85        Self {
86            kind: ErrorKind::UnsupportedType { type_id },
87        }
88    }
89
90    /// Create error for schema mismatch.
91    #[must_use]
92    pub const fn schema_mismatch(expected: usize, actual: usize) -> Self {
93        Self {
94            kind: ErrorKind::SchemaMismatch { expected, actual },
95        }
96    }
97
98    /// Create error for value conversion failure.
99    #[must_use]
100    pub fn value_conversion(column: impl Into<String>, message: impl Into<String>) -> Self {
101        Self {
102            kind: ErrorKind::ValueConversion {
103                column: column.into(),
104                message: message.into(),
105            },
106        }
107    }
108
109    /// Create error for decimal overflow.
110    #[must_use]
111    pub const fn decimal_overflow(precision: u8, scale: i8) -> Self {
112        Self {
113            kind: ErrorKind::DecimalOverflow { precision, scale },
114        }
115    }
116
117    /// Create error for LOB streaming failure.
118    #[must_use]
119    pub fn lob_streaming(message: impl Into<String>) -> Self {
120        Self {
121            kind: ErrorKind::LobStreaming {
122                message: message.into(),
123            },
124        }
125    }
126
127    /// Create error for invalid precision.
128    #[must_use]
129    pub fn invalid_precision(message: impl Into<String>) -> Self {
130        Self {
131            kind: ErrorKind::InvalidPrecision(message.into()),
132        }
133    }
134
135    /// Create error for invalid scale.
136    #[must_use]
137    pub fn invalid_scale(message: impl Into<String>) -> Self {
138        Self {
139            kind: ErrorKind::InvalidScale(message.into()),
140        }
141    }
142
143    // ═══════════════════════════════════════════════════════════════════════
144    // Predicate Methods (is_xxx)
145    // ═══════════════════════════════════════════════════════════════════════
146
147    /// Returns true if this is an unsupported type error.
148    #[must_use]
149    pub const fn is_unsupported_type(&self) -> bool {
150        matches!(self.kind, ErrorKind::UnsupportedType { .. })
151    }
152
153    /// Returns true if this is a schema mismatch error.
154    #[must_use]
155    pub const fn is_schema_mismatch(&self) -> bool {
156        matches!(self.kind, ErrorKind::SchemaMismatch { .. })
157    }
158
159    /// Returns true if this is a value conversion error.
160    #[must_use]
161    pub const fn is_value_conversion(&self) -> bool {
162        matches!(self.kind, ErrorKind::ValueConversion { .. })
163    }
164
165    /// Returns true if this is a decimal overflow error.
166    #[must_use]
167    pub const fn is_decimal_overflow(&self) -> bool {
168        matches!(self.kind, ErrorKind::DecimalOverflow { .. })
169    }
170
171    /// Returns true if this is an Arrow library error.
172    #[must_use]
173    pub const fn is_arrow_error(&self) -> bool {
174        matches!(self.kind, ErrorKind::Arrow(_))
175    }
176
177    /// Returns true if this is an hdbconnect error.
178    #[must_use]
179    pub const fn is_hdbconnect_error(&self) -> bool {
180        matches!(self.kind, ErrorKind::Hdbconnect(_))
181    }
182
183    /// Returns true if this is a LOB streaming error.
184    #[must_use]
185    pub const fn is_lob_streaming(&self) -> bool {
186        matches!(self.kind, ErrorKind::LobStreaming { .. })
187    }
188
189    /// Returns true if this is an invalid precision error.
190    #[must_use]
191    pub const fn is_invalid_precision(&self) -> bool {
192        matches!(self.kind, ErrorKind::InvalidPrecision(_))
193    }
194
195    /// Returns true if this is an invalid scale error.
196    #[must_use]
197    pub const fn is_invalid_scale(&self) -> bool {
198        matches!(self.kind, ErrorKind::InvalidScale(_))
199    }
200}
201
202impl From<hdbconnect::HdbError> for ArrowConversionError {
203    fn from(err: hdbconnect::HdbError) -> Self {
204        Self {
205            kind: ErrorKind::Hdbconnect(err.to_string()),
206        }
207    }
208}
209
210impl From<arrow_schema::ArrowError> for ArrowConversionError {
211    fn from(err: arrow_schema::ArrowError) -> Self {
212        Self {
213            kind: ErrorKind::Arrow(err),
214        }
215    }
216}
217
218/// Result type alias for Arrow conversion operations.
219pub type Result<T> = std::result::Result<T, ArrowConversionError>;
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224
225    #[test]
226    fn test_error_creation() {
227        let err = ArrowConversionError::unsupported_type(42);
228        assert!(err.is_unsupported_type());
229        assert!(!err.is_schema_mismatch());
230    }
231
232    #[test]
233    fn test_schema_mismatch() {
234        let err = ArrowConversionError::schema_mismatch(5, 3);
235        assert!(err.is_schema_mismatch());
236        assert!(err.to_string().contains("expected 5 columns, got 3"));
237    }
238
239    #[test]
240    fn test_value_conversion() {
241        let err = ArrowConversionError::value_conversion("col1", "invalid integer");
242        assert!(err.is_value_conversion());
243        assert!(err.to_string().contains("col1"));
244    }
245
246    #[test]
247    fn test_decimal_overflow() {
248        let err = ArrowConversionError::decimal_overflow(38, 10);
249        assert!(err.is_decimal_overflow());
250    }
251
252    #[test]
253    fn test_error_debug() {
254        let err = ArrowConversionError::unsupported_type(99);
255        // Debug should be implemented
256        let debug_str = format!("{err:?}");
257        assert!(debug_str.contains("ArrowConversionError"));
258    }
259
260    #[test]
261    fn test_error_display() {
262        let err = ArrowConversionError::lob_streaming("connection lost");
263        let display = err.to_string();
264        assert!(display.contains("LOB streaming error"));
265        assert!(display.contains("connection lost"));
266    }
267}