typed_arrow/error.rs
1//! Error types for typed-arrow.
2
3use arrow_schema::DataType;
4use thiserror::Error;
5
6/// Error type for schema validation failures.
7#[derive(Debug, Clone, Error)]
8pub enum SchemaError {
9 /// Type mismatch between expected and actual schema
10 #[error("schema type mismatch: expected {expected}, got {actual}")]
11 TypeMismatch {
12 /// Expected Arrow DataType
13 expected: DataType,
14 /// Actual Arrow DataType
15 actual: DataType,
16 },
17 /// Missing required field
18 #[error("missing required field: {field_name}")]
19 MissingField {
20 /// Name of the missing field
21 field_name: String,
22 },
23 /// Invalid schema configuration
24 #[error("invalid schema: {message}")]
25 InvalidSchema {
26 /// Error message
27 message: String,
28 },
29}
30
31impl SchemaError {
32 /// Create a type mismatch error
33 pub fn type_mismatch(expected: DataType, actual: DataType) -> Self {
34 Self::TypeMismatch { expected, actual }
35 }
36
37 /// Create a missing field error
38 pub fn missing_field(field_name: impl Into<String>) -> Self {
39 Self::MissingField {
40 field_name: field_name.into(),
41 }
42 }
43
44 /// Create an invalid schema error
45 pub fn invalid(message: impl Into<String>) -> Self {
46 Self::InvalidSchema {
47 message: message.into(),
48 }
49 }
50}
51
52/// Error type for view access failures when reading from Arrow arrays.
53#[cfg(feature = "views")]
54#[derive(Debug, Error)]
55pub enum ViewAccessError {
56 /// Index out of bounds
57 #[error("index {index} out of bounds (len {len}){}", field_name.map(|n| format!(" for field '{n}'")).unwrap_or_default())]
58 OutOfBounds {
59 /// The invalid index
60 index: usize,
61 /// The array length
62 len: usize,
63 /// Optional field name for context
64 field_name: Option<&'static str>,
65 },
66 /// Unexpected null value
67 #[error("unexpected null at index {index}{}", field_name.map(|n| format!(" for field '{n}'")).unwrap_or_default())]
68 UnexpectedNull {
69 /// The index where null was found
70 index: usize,
71 /// Optional field name for context
72 field_name: Option<&'static str>,
73 },
74 /// Type mismatch during array downcast
75 #[error("type mismatch: expected {expected}, got {actual}{}", field_name.map(|n| format!(" for field '{n}'")).unwrap_or_default())]
76 TypeMismatch {
77 /// Expected Arrow DataType
78 expected: DataType,
79 /// Actual Arrow DataType
80 actual: DataType,
81 /// Optional field name for context
82 field_name: Option<&'static str>,
83 },
84 /// Custom user-defined error from domain-specific validation
85 ///
86 /// This variant allows custom types (newtypes) to wrap their own error types
87 /// while still using the common `ViewAccessError` type. The error can be
88 /// downcast to the specific type when needed.
89 #[error("custom validation error: {0}")]
90 Custom(Box<dyn std::error::Error + Send + Sync + 'static>),
91}
92
93/// Allows generic code to uniformly handle both infallible and fallible view-to-owned conversions.
94///
95/// When converting views to owned types, primitives and `String` never fail (`TryFrom<Primitive,
96/// Error = Infallible>`), while nested structs can fail (`TryFrom<StructView, Error =
97/// ViewAccessError>`). This blanket conversion allows generic code like `List<T>` or `Map<K, V>` to
98/// use a single implementation with `E: Into<ViewAccessError>` bounds that works for both cases.
99///
100/// The empty match is safe because `Infallible` is an uninhabited type that can never be
101/// constructed.
102#[cfg(feature = "views")]
103impl From<core::convert::Infallible> for ViewAccessError {
104 fn from(x: core::convert::Infallible) -> Self {
105 match x {}
106 }
107}