Skip to main content

cudf/core/error/
code.rs

1//! Leaf-only `ErrorCode` taxonomy and pure projections to `ErrorKind`
2//! and `ErrorStatus`.
3//!
4//! - `ErrorCode` contains only leaf codes; it has no coarse family variants.
5//! - `ErrorKind` is the coarse category, derived from `code`.
6//! - `ErrorStatus` is the retry semantic, derived from `code`.
7//! - Neither `ErrorKind` nor `ErrorStatus` is stored on `Error`.
8
9use std::fmt;
10
11/// Stable, machine-readable failure identity.
12///
13/// Leaf error code variants. Coarse classification uses [`ErrorKind`].
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15#[non_exhaustive]
16pub enum ErrorCode {
17    // Domestic invalid-input leaves.
18    /// A DataFrame column name was expected but did not exist.
19    MissingColumn,
20    /// A DataFrame column name was duplicated in an input list.
21    DuplicateColumnName,
22    /// A lookup key was duplicated where uniqueness was required.
23    DuplicateKey,
24    /// Two columns expected to have matching row counts did not.
25    ColumnLengthMismatch,
26    /// A value's type did not match the expected type.
27    TypeMismatch,
28    /// A numeric, temporal, or fixed-point value could not fit in the
29    /// required target range or exact representation.
30    ValueOutOfRange,
31    /// An integer or decimal division, or an integer remainder, had a zero
32    /// divisor in a row where both operands were valid.
33    DivisionByZero,
34    /// A row, column, or buffer index was out of bounds.
35    IndexOutOfBounds,
36    /// A null was present where a non-null value was required.
37    ///
38    /// This leaf covers two physically distinct but contract-equivalent
39    /// conditions:
40    ///
41    /// - An element whose mask bit is null appeared in an API that does not
42    ///   accept nulls (for example, a reduction or join key column that
43    ///   required non-null values).
44    /// - A `&str` / `String` input contained a `\0` byte somewhere before its
45    ///   end. This typically surfaces when the value must be handed to a C shim
46    ///   as a null-terminated string, where the embedded `\0` would truncate the
47    ///   payload.
48    ///
49    /// Although the two sources differ (mask bit versus string byte), downstream
50    /// policy is the same. The input carries a null-ness the callee cannot
51    /// process, so the caller must clean or reject it. Keeping both under one
52    /// leaf avoids forcing downstreams to branch on an implementation detail
53    /// that does not change their response.
54    NullUnexpected,
55    /// A collection-valued input was empty where at least one element was
56    /// required.
57    EmptyInput,
58    /// A file input path pointed to a directory.
59    PathIsDirectory,
60
61    // Domestic execution-context leaves.
62    /// An empty-column construction path requires an explicit runtime.
63    EmptyConstructionRequiresRuntime,
64    /// Inputs reside on different CUDA devices where a single-device
65    /// invariant was required.
66    CrossDeviceInputs,
67    /// Inputs share a device but their runtime-provenance bundles conflict.
68    CrossProvenanceInputs,
69    /// A runtime-binding contract was violated at an execution boundary.
70    RuntimeBindingMismatch,
71    /// A readiness (stream/event) primitive was required but unavailable.
72    ReadinessUnavailable,
73    /// An execution binding (stream/memory-resource) was required but
74    /// unavailable.
75    ExecutionBindingUnavailable,
76
77    // Domestic unsupported leaves.
78    /// The requested dtype is not supported on this path.
79    UnsupportedDtype,
80    /// The requested physical layout is not supported on this path.
81    UnsupportedLayout,
82    /// The requested operation is not supported on this path.
83    UnsupportedOperation,
84
85    // Domestic dependency and resource leaves.
86    /// Device memory (or another bounded resource) was exhausted domestically.
87    OutOfMemory,
88    /// A byte-size or element-count computation exceeded the representable
89    /// platform range before allocation or copy.
90    SizeOverflow,
91    /// One unsplittable host result row exceeds a deterministic configured
92    /// materialization ceiling.
93    HostMaterializationLimitExceeded,
94    /// A CUDA runtime call returned a potentially transient error.
95    CudaRuntime,
96    /// A CUDA call reported a fatal, unrecoverable error.
97    FatalCuda,
98    /// An I/O path (file, Arrow reader) failed.
99    IoFailure,
100    /// I/O cleanup could not proceed because operations are still active.
101    IoBusy,
102
103    // Domestic soft-panic leaf.
104    /// An invariant in the crate's own code was violated (soft-panic replacement).
105    InvariantViolation,
106
107    // Foreign boundary leaves.
108    /// The foreign layer reported caller-supplied invalid input; classification
109    /// stopped at the boundary.
110    ForeignInvalidArgument,
111    /// The foreign layer reported a transient/runtime failure with a truthful
112    /// retry signal.
113    ForeignTransientDependencyFailure,
114    /// The foreign layer reported a hard/intervention-required failure with a
115    /// truthful non-retry signal.
116    ForeignPersistentDependencyFailure,
117    /// The foreign layer violated or failed to uphold its own declared
118    /// contract.
119    ForeignInvariantViolation,
120    /// The foreign layer reported resource exhaustion with opaque
121    /// classification.
122    ForeignResourceExhausted,
123}
124
125impl ErrorCode {
126    /// Every error code variant, in declaration order.
127    pub const ALL: [Self; 33] = [
128        Self::MissingColumn,
129        Self::DuplicateColumnName,
130        Self::DuplicateKey,
131        Self::ColumnLengthMismatch,
132        Self::TypeMismatch,
133        Self::ValueOutOfRange,
134        Self::DivisionByZero,
135        Self::IndexOutOfBounds,
136        Self::NullUnexpected,
137        Self::EmptyInput,
138        Self::PathIsDirectory,
139        Self::EmptyConstructionRequiresRuntime,
140        Self::CrossDeviceInputs,
141        Self::CrossProvenanceInputs,
142        Self::RuntimeBindingMismatch,
143        Self::ReadinessUnavailable,
144        Self::ExecutionBindingUnavailable,
145        Self::UnsupportedDtype,
146        Self::UnsupportedLayout,
147        Self::UnsupportedOperation,
148        Self::OutOfMemory,
149        Self::SizeOverflow,
150        Self::HostMaterializationLimitExceeded,
151        Self::CudaRuntime,
152        Self::FatalCuda,
153        Self::IoFailure,
154        Self::IoBusy,
155        Self::InvariantViolation,
156        Self::ForeignInvalidArgument,
157        Self::ForeignTransientDependencyFailure,
158        Self::ForeignPersistentDependencyFailure,
159        Self::ForeignInvariantViolation,
160        Self::ForeignResourceExhausted,
161    ];
162}
163
164impl fmt::Display for ErrorCode {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        let name = match self {
167            ErrorCode::MissingColumn => "MissingColumn",
168            ErrorCode::DuplicateColumnName => "DuplicateColumnName",
169            ErrorCode::DuplicateKey => "DuplicateKey",
170            ErrorCode::ColumnLengthMismatch => "ColumnLengthMismatch",
171            ErrorCode::TypeMismatch => "TypeMismatch",
172            ErrorCode::ValueOutOfRange => "ValueOutOfRange",
173            ErrorCode::DivisionByZero => "DivisionByZero",
174            ErrorCode::IndexOutOfBounds => "IndexOutOfBounds",
175            ErrorCode::NullUnexpected => "NullUnexpected",
176            ErrorCode::EmptyConstructionRequiresRuntime => "EmptyConstructionRequiresRuntime",
177            ErrorCode::EmptyInput => "EmptyInput",
178            ErrorCode::PathIsDirectory => "PathIsDirectory",
179            ErrorCode::CrossDeviceInputs => "CrossDeviceInputs",
180            ErrorCode::CrossProvenanceInputs => "CrossProvenanceInputs",
181            ErrorCode::RuntimeBindingMismatch => "RuntimeBindingMismatch",
182            ErrorCode::ReadinessUnavailable => "ReadinessUnavailable",
183            ErrorCode::ExecutionBindingUnavailable => "ExecutionBindingUnavailable",
184            ErrorCode::UnsupportedDtype => "UnsupportedDtype",
185            ErrorCode::UnsupportedLayout => "UnsupportedLayout",
186            ErrorCode::UnsupportedOperation => "UnsupportedOperation",
187            ErrorCode::OutOfMemory => "OutOfMemory",
188            ErrorCode::SizeOverflow => "SizeOverflow",
189            ErrorCode::HostMaterializationLimitExceeded => "HostMaterializationLimitExceeded",
190            ErrorCode::CudaRuntime => "CudaRuntime",
191            ErrorCode::FatalCuda => "FatalCuda",
192            ErrorCode::IoFailure => "IoFailure",
193            ErrorCode::IoBusy => "IoBusy",
194            ErrorCode::InvariantViolation => "InvariantViolation",
195            ErrorCode::ForeignInvalidArgument => "ForeignInvalidArgument",
196            ErrorCode::ForeignTransientDependencyFailure => "ForeignTransientDependencyFailure",
197            ErrorCode::ForeignPersistentDependencyFailure => "ForeignPersistentDependencyFailure",
198            ErrorCode::ForeignInvariantViolation => "ForeignInvariantViolation",
199            ErrorCode::ForeignResourceExhausted => "ForeignResourceExhausted",
200        };
201        f.write_str(name)
202    }
203}
204
205/// Coarse failure category, derived from [`ErrorCode`] via [`kind_for_code`].
206///
207/// `ErrorKind` exists for caller policy that needs only the broad category.
208/// `Error` does not store it; each call projects it from `code`.
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
210#[non_exhaustive]
211pub enum ErrorKind {
212    /// The caller provided invalid values, schema requests, or foreign inputs.
213    InvalidInput,
214    /// A named resource referenced by the caller does not exist.
215    NotFound,
216    /// The requested dtype, layout, or operation is not supported.
217    Unsupported,
218    /// A runtime, readiness, provenance, or execution binding contract failed.
219    ExecutionContext,
220    /// The operation ran out of memory or another bounded resource.
221    ResourceExhausted,
222    /// A lower-layer CUDA, I/O, or foreign dependency failed.
223    DependencyFailure,
224    /// An invariant was violated (domestic or foreign).
225    InvariantViolation,
226}
227
228impl fmt::Display for ErrorKind {
229    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230        let name = match self {
231            ErrorKind::InvalidInput => "InvalidInput",
232            ErrorKind::NotFound => "NotFound",
233            ErrorKind::Unsupported => "Unsupported",
234            ErrorKind::ExecutionContext => "ExecutionContext",
235            ErrorKind::ResourceExhausted => "ResourceExhausted",
236            ErrorKind::DependencyFailure => "DependencyFailure",
237            ErrorKind::InvariantViolation => "InvariantViolation",
238        };
239        f.write_str(name)
240    }
241}
242
243/// Retry semantic, derived from [`ErrorCode`] via [`status_for_code`].
244#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
245pub enum ErrorStatus {
246    /// Retrying with the same arguments will never succeed.
247    Permanent,
248    /// The failure may be transient; retrying could succeed.
249    Temporary,
250    /// The failure has been observed to recur; intervention is needed.
251    Persistent,
252}
253
254impl fmt::Display for ErrorStatus {
255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256        let name = match self {
257            ErrorStatus::Permanent => "permanent",
258            ErrorStatus::Temporary => "temporary",
259            ErrorStatus::Persistent => "persistent",
260        };
261        f.write_str(name)
262    }
263}
264
265/// Total, pure projection from [`ErrorCode`] to [`ErrorKind`].
266///
267/// Every variant is mapped here; adding a new code variant without extending
268/// this function is a compile error.
269pub const fn kind_for_code(code: ErrorCode) -> ErrorKind {
270    match code {
271        ErrorCode::MissingColumn => ErrorKind::NotFound,
272
273        ErrorCode::DuplicateColumnName
274        | ErrorCode::DuplicateKey
275        | ErrorCode::ColumnLengthMismatch
276        | ErrorCode::TypeMismatch
277        | ErrorCode::ValueOutOfRange
278        | ErrorCode::DivisionByZero
279        | ErrorCode::IndexOutOfBounds
280        | ErrorCode::NullUnexpected
281        | ErrorCode::PathIsDirectory
282        | ErrorCode::ForeignInvalidArgument => ErrorKind::InvalidInput,
283
284        ErrorCode::EmptyInput => ErrorKind::InvalidInput,
285
286        ErrorCode::EmptyConstructionRequiresRuntime
287        | ErrorCode::CrossDeviceInputs
288        | ErrorCode::CrossProvenanceInputs
289        | ErrorCode::RuntimeBindingMismatch
290        | ErrorCode::ReadinessUnavailable
291        | ErrorCode::ExecutionBindingUnavailable => ErrorKind::ExecutionContext,
292
293        ErrorCode::UnsupportedDtype | ErrorCode::UnsupportedLayout | ErrorCode::UnsupportedOperation => {
294            ErrorKind::Unsupported
295        },
296
297        ErrorCode::OutOfMemory
298        | ErrorCode::SizeOverflow
299        | ErrorCode::HostMaterializationLimitExceeded
300        | ErrorCode::IoBusy
301        | ErrorCode::ForeignResourceExhausted => ErrorKind::ResourceExhausted,
302
303        ErrorCode::CudaRuntime
304        | ErrorCode::FatalCuda
305        | ErrorCode::IoFailure
306        | ErrorCode::ForeignTransientDependencyFailure
307        | ErrorCode::ForeignPersistentDependencyFailure => ErrorKind::DependencyFailure,
308
309        ErrorCode::InvariantViolation | ErrorCode::ForeignInvariantViolation => ErrorKind::InvariantViolation,
310    }
311}
312
313/// Total, pure projection from [`ErrorCode`] to [`ErrorStatus`].
314///
315/// Every variant is mapped here; adding a new code variant without extending
316/// this function is a compile error.
317pub const fn status_for_code(code: ErrorCode) -> ErrorStatus {
318    match code {
319        ErrorCode::OutOfMemory
320        | ErrorCode::IoBusy
321        | ErrorCode::CudaRuntime
322        | ErrorCode::ForeignTransientDependencyFailure
323        | ErrorCode::ForeignResourceExhausted => ErrorStatus::Temporary,
324
325        ErrorCode::FatalCuda | ErrorCode::ForeignPersistentDependencyFailure => ErrorStatus::Persistent,
326
327        ErrorCode::MissingColumn
328        | ErrorCode::DuplicateColumnName
329        | ErrorCode::DuplicateKey
330        | ErrorCode::ColumnLengthMismatch
331        | ErrorCode::TypeMismatch
332        | ErrorCode::ValueOutOfRange
333        | ErrorCode::DivisionByZero
334        | ErrorCode::IndexOutOfBounds
335        | ErrorCode::NullUnexpected
336        | ErrorCode::EmptyConstructionRequiresRuntime
337        | ErrorCode::EmptyInput
338        | ErrorCode::PathIsDirectory
339        | ErrorCode::CrossDeviceInputs
340        | ErrorCode::CrossProvenanceInputs
341        | ErrorCode::RuntimeBindingMismatch
342        | ErrorCode::ReadinessUnavailable
343        | ErrorCode::ExecutionBindingUnavailable
344        | ErrorCode::UnsupportedDtype
345        | ErrorCode::UnsupportedLayout
346        | ErrorCode::UnsupportedOperation
347        | ErrorCode::SizeOverflow
348        | ErrorCode::HostMaterializationLimitExceeded
349        | ErrorCode::IoFailure
350        | ErrorCode::InvariantViolation
351        | ErrorCode::ForeignInvalidArgument
352        | ErrorCode::ForeignInvariantViolation => ErrorStatus::Permanent,
353    }
354}