Skip to main content

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>;