oximedia_ml/error.rs
1//! Error types for `oximedia-ml`.
2//!
3//! Every fallible operation in the crate returns [`MlResult<T>`], which
4//! is an alias for `Result<T, MlError>`. The variants are designed so
5//! call sites never need to reach into backend-specific error types:
6//! backend failures are flattened into [`MlError::OnnxRuntime`] or
7//! [`MlError::ModelLoad`], and user-supplied data problems surface as
8//! [`MlError::InvalidInput`] / [`MlError::Preprocess`] /
9//! [`MlError::Postprocess`].
10//!
11//! ## Pattern
12//!
13//! ```no_run
14//! use oximedia_ml::{MlError, MlResult};
15//!
16//! fn check_top_k(k: usize) -> MlResult<()> {
17//! if k == 0 {
18//! return Err(MlError::invalid_input("top_k must be >= 1"));
19//! }
20//! Ok(())
21//! }
22//! ```
23//!
24//! The [`MlError::pipeline`], [`MlError::invalid_input`],
25//! [`MlError::preprocess`], and [`MlError::postprocess`] constructors
26//! accept anything `Into<String>` so integrators can propagate rich
27//! messages without allocating at call sites that already have owned
28//! strings.
29
30use std::path::PathBuf;
31use thiserror::Error;
32
33/// Result alias for ML operations.
34///
35/// All fallible APIs in this crate return `MlResult<T>`; pattern-match
36/// on [`MlError`] to handle the different failure categories.
37pub type MlResult<T> = Result<T, MlError>;
38
39/// Errors surfaced by oximedia-ml.
40///
41/// Construct user-facing failures with [`MlError::invalid_input`],
42/// [`MlError::preprocess`], [`MlError::postprocess`], or
43/// [`MlError::pipeline`]; the remaining variants are usually produced by
44/// the crate itself and matched on at call sites.
45#[derive(Debug, Error)]
46pub enum MlError {
47 /// The requested device is either unknown, unavailable, or built without its feature flag.
48 #[error("device '{0}' is not available in this build")]
49 DeviceUnavailable(String),
50
51 /// A feature required to perform the operation is disabled.
52 #[error("required feature '{0}' is not enabled (re-build oximedia-ml with --features {0})")]
53 FeatureDisabled(&'static str),
54
55 /// The model file could not be loaded from disk.
56 #[error("failed to load model at {path}: {reason}")]
57 ModelLoad {
58 /// Model path that failed to load.
59 path: PathBuf,
60 /// Human-readable reason provided by the backend.
61 reason: String,
62 },
63
64 /// Input tensor(s) did not match the model's contract.
65 #[error("invalid input: {0}")]
66 InvalidInput(String),
67
68 /// Pre-processing failed (e.g. image dimensions don't fit).
69 #[error("preprocess error: {0}")]
70 Preprocess(String),
71
72 /// Post-processing failed (e.g. reading an output tensor).
73 #[error("postprocess error: {0}")]
74 Postprocess(String),
75
76 /// A pipeline sub-component failed to execute.
77 #[error("pipeline error ({stage}): {message}")]
78 Pipeline {
79 /// Pipeline stage that raised the error.
80 stage: &'static str,
81 /// Descriptive error message.
82 message: String,
83 },
84
85 /// The cache capacity was zero, which is invalid.
86 #[error("model cache capacity must be at least 1")]
87 CacheCapacityZero,
88
89 /// Underlying ONNX runtime error (feature-gated).
90 #[error("onnx runtime error: {0}")]
91 OnnxRuntime(String),
92}
93
94impl MlError {
95 /// Convenience constructor for pipeline errors.
96 #[must_use]
97 pub fn pipeline(stage: &'static str, message: impl Into<String>) -> Self {
98 Self::Pipeline {
99 stage,
100 message: message.into(),
101 }
102 }
103
104 /// Convenience constructor for invalid input errors.
105 #[must_use]
106 pub fn invalid_input(message: impl Into<String>) -> Self {
107 Self::InvalidInput(message.into())
108 }
109
110 /// Convenience constructor for preprocess errors.
111 #[must_use]
112 pub fn preprocess(message: impl Into<String>) -> Self {
113 Self::Preprocess(message.into())
114 }
115
116 /// Convenience constructor for postprocess errors.
117 #[must_use]
118 pub fn postprocess(message: impl Into<String>) -> Self {
119 Self::Postprocess(message.into())
120 }
121}