qubit_value/value_error.rs
1// =============================================================================
2// Copyright (c) 2025 - 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! # Value Processing Error Types
9//!
10//! Defines various errors that may occur during value processing.
11
12#[cfg(all(feature = "converter", feature = "json"))]
13use qubit_budget::MeasuredBudgetError;
14#[cfg(all(feature = "converter", feature = "json"))]
15use qubit_datatype::ConversionResource;
16#[cfg(feature = "converter")]
17use qubit_datatype::DataConversionError;
18#[cfg(feature = "converter")]
19use qubit_datatype::DataConversionErrorKind;
20#[cfg(feature = "converter")]
21use qubit_datatype::DataListConversionError;
22use qubit_datatype::DataType;
23use thiserror::Error;
24
25use crate::ValueMissing;
26
27/// Value processing error type
28///
29/// Defines various error conditions that may occur during value operations.
30/// Downstream matches must include a wildcard arm because this enum is
31/// non-exhaustive and may gain new error variants.
32///
33/// # Features
34///
35/// - Type mismatch error
36/// - Structured missing-value errors
37/// - Structured single-value conversion errors when `converter` is enabled
38/// - Structured list conversion errors, including the failing item index, when
39/// `converter` is enabled
40///
41/// # Examples
42///
43/// ```rust
44/// use qubit_datatype::DataType;
45/// use qubit_value::{ValueError, ValueMissing};
46///
47/// let error = ValueError::Missing(ValueMissing::unset_scalar(DataType::String, DataType::String));
48/// assert_eq!(error.missing().unwrap().target_type(), Some(DataType::String));
49/// ```
50#[non_exhaustive]
51#[must_use]
52#[derive(Debug, Clone, Error)]
53pub enum ValueError {
54 /// Resource rejection before materializing a natural JSON projection.
55 #[cfg(all(feature = "converter", feature = "json"))]
56 #[cfg_attr(docsrs, doc(cfg(all(feature = "converter", feature = "json"))))]
57 #[error("JSON projection limit for {data_type} at collection index {source_index:?}: {source}")]
58 JsonProjectionLimit {
59 /// Runtime scalar or collection element type being projected.
60 data_type: DataType,
61 /// Collection element index, or `None` for a scalar or outer shape.
62 source_index: Option<usize>,
63 /// Exact rejected resource measurement, including its configured bound.
64 #[source]
65 source: MeasuredBudgetError<ConversionResource, u64>,
66 },
67
68 /// No concrete item is available from typed runtime storage or conversion.
69 #[error("Missing value: {0}")]
70 Missing(
71 /// Structured typed storage state that caused the missing value.
72 #[source]
73 ValueMissing,
74 ),
75
76 /// Type mismatch
77 #[error("Type mismatch: expected {expected}, actual {actual}")]
78 TypeMismatch {
79 /// Expected data type
80 expected: DataType,
81 /// Actual data type
82 actual: DataType,
83 },
84
85 /// Error returned by the shared single-value conversion layer.
86 #[cfg(feature = "converter")]
87 #[cfg_attr(docsrs, doc(cfg(feature = "converter")))]
88 #[error("Conversion error: {0}")]
89 Conversion(
90 /// Structured conversion failure from `qubit-datatype`.
91 #[source]
92 DataConversionError,
93 ),
94
95 /// Error returned by the shared list conversion layer.
96 #[cfg(feature = "converter")]
97 #[cfg_attr(docsrs, doc(cfg(feature = "converter")))]
98 #[error("List conversion error: {0}")]
99 ListConversion(
100 /// Structured list conversion failure, including the source index.
101 #[source]
102 DataListConversionError,
103 ),
104}
105
106impl PartialEq for ValueError {
107 /// Compares structured error facts without depending on diagnostic text.
108 ///
109 /// # Parameters
110 ///
111 /// * `other` - Error whose variant and structured payload are compared.
112 ///
113 /// # Returns
114 ///
115 /// `true` when both errors have the same variant and equal structured
116 /// facts; otherwise `false`.
117 fn eq(&self, other: &Self) -> bool {
118 match (self, other) {
119 (Self::Missing(left), Self::Missing(right)) => left == right,
120 (
121 Self::TypeMismatch {
122 expected: le,
123 actual: la,
124 },
125 Self::TypeMismatch {
126 expected: re,
127 actual: ra,
128 },
129 ) => le == re && la == ra,
130 #[cfg(feature = "converter")]
131 (Self::Conversion(left), Self::Conversion(right)) => left == right,
132 #[cfg(feature = "converter")]
133 (Self::ListConversion(left), Self::ListConversion(right)) => left == right,
134 #[cfg(all(feature = "converter", feature = "json"))]
135 (
136 Self::JsonProjectionLimit {
137 data_type: lt,
138 source_index: li,
139 source: ls,
140 },
141 Self::JsonProjectionLimit {
142 data_type: rt,
143 source_index: ri,
144 source: rs,
145 },
146 ) => {
147 lt == rt
148 && li == ri
149 && ls.resource() == rs.resource()
150 && ls.budget_error() == rs.budget_error()
151 && ls.quantity_error() == rs.quantity_error()
152 }
153 _ => false,
154 }
155 }
156}
157
158impl Eq for ValueError {}
159
160impl ValueError {
161 /// Reports whether this error describes a missing value.
162 ///
163 /// # Returns
164 ///
165 /// `true` only for [`Self::Missing`].
166 #[must_use]
167 #[inline(always)]
168 pub const fn is_missing(&self) -> bool {
169 matches!(self, Self::Missing(_))
170 }
171
172 /// Returns the structured missing-value reason, when present.
173 ///
174 /// # Returns
175 ///
176 /// `Some(reason)` for [`Self::Missing`] and `None` for every other variant.
177 #[must_use]
178 #[inline(always)]
179 pub const fn missing(&self) -> Option<&ValueMissing> {
180 match self {
181 Self::Missing(missing) => Some(missing),
182 Self::TypeMismatch { .. } => None,
183 #[cfg(all(feature = "converter", feature = "json"))]
184 Self::JsonProjectionLimit { .. } => None,
185 #[cfg(feature = "converter")]
186 Self::Conversion(_) | Self::ListConversion(_) => None,
187 }
188 }
189}
190
191#[cfg(feature = "converter")]
192impl From<DataConversionError> for ValueError {
193 /// Preserves a single-value conversion failure, promoting missing states.
194 ///
195 /// # Parameters
196 ///
197 /// * `error` - Owned conversion failure retained without cloning.
198 ///
199 /// # Returns
200 ///
201 /// [`Self::Missing`] for missing or empty-collection failures; otherwise
202 /// [`Self::Conversion`] with the original error.
203 #[inline]
204 fn from(error: DataConversionError) -> Self {
205 if error.is_missing() || error.kind() == DataConversionErrorKind::EmptyCollection {
206 return Self::Missing(ValueMissing::from_conversion(error, None));
207 }
208 Self::Conversion(error)
209 }
210}
211
212#[cfg(feature = "converter")]
213impl From<DataListConversionError> for ValueError {
214 /// Preserves a failed list item's original index and conversion cause.
215 ///
216 /// # Parameters
217 ///
218 /// * `error` - Owned list conversion failure consumed without cloning.
219 ///
220 /// # Returns
221 ///
222 /// [`Self::Missing`] for missing or empty-collection causes, retaining the
223 /// source index; otherwise [`Self::ListConversion`] with the original
224 /// facts.
225 #[inline]
226 fn from(error: DataListConversionError) -> Self {
227 let (source_index, source) = error.into_parts();
228 if source.is_missing() || source.kind() == DataConversionErrorKind::EmptyCollection {
229 return Self::Missing(ValueMissing::from_conversion(source, Some(source_index)));
230 }
231 Self::ListConversion(DataListConversionError::new(source_index, source))
232 }
233}
234
235/// Result returned by value processing operations.
236///
237/// # Type Parameters
238///
239/// * `T` - Successful value returned by the operation.
240///
241/// # Examples
242///
243/// ```
244/// use qubit_value::{Value, ValueResult};
245///
246/// fn read_int(value: &Value) -> ValueResult<i32> {
247/// value.get_int32()
248/// }
249///
250/// assert_eq!(read_int(&Value::from(42_i32)).unwrap(), 42);
251/// ```
252pub type ValueResult<T> = Result<T, ValueError>;