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    #[error("JSON projection limit for {data_type} at collection index {source_index:?}: {source}")]
57    JsonProjectionLimit {
58        /// Runtime scalar or collection element type being projected.
59        data_type: DataType,
60        /// Collection element index, or `None` for a scalar or outer shape.
61        source_index: Option<usize>,
62        /// Exact rejected resource measurement, including its configured bound.
63        #[source]
64        source: MeasuredBudgetError<ConversionResource, u64>,
65    },
66
67    /// No concrete item is available from typed runtime storage or conversion.
68    #[error("Missing value: {0}")]
69    Missing(
70        /// Structured typed storage state that caused the missing value.
71        #[source]
72        ValueMissing,
73    ),
74
75    /// Type mismatch
76    #[error("Type mismatch: expected {expected}, actual {actual}")]
77    TypeMismatch {
78        /// Expected data type
79        expected: DataType,
80        /// Actual data type
81        actual: DataType,
82    },
83
84    /// Error returned by the shared single-value conversion layer.
85    #[cfg(feature = "converter")]
86    #[error("Conversion error: {0}")]
87    Conversion(
88        /// Structured conversion failure from `qubit-datatype`.
89        #[source]
90        DataConversionError,
91    ),
92
93    /// Error returned by the shared list conversion layer.
94    #[cfg(feature = "converter")]
95    #[error("List conversion error: {0}")]
96    ListConversion(
97        /// Structured list conversion failure, including the source index.
98        #[source]
99        DataListConversionError,
100    ),
101}
102
103impl PartialEq for ValueError {
104    /// Compares structured error facts without depending on diagnostic text.
105    fn eq(&self, other: &Self) -> bool {
106        match (self, other) {
107            (Self::Missing(left), Self::Missing(right)) => left == right,
108            (
109                Self::TypeMismatch {
110                    expected: le,
111                    actual: la,
112                },
113                Self::TypeMismatch {
114                    expected: re,
115                    actual: ra,
116                },
117            ) => le == re && la == ra,
118            #[cfg(feature = "converter")]
119            (Self::Conversion(left), Self::Conversion(right)) => left == right,
120            #[cfg(feature = "converter")]
121            (Self::ListConversion(left), Self::ListConversion(right)) => left == right,
122            #[cfg(all(feature = "converter", feature = "json"))]
123            (
124                Self::JsonProjectionLimit {
125                    data_type: lt,
126                    source_index: li,
127                    source: ls,
128                },
129                Self::JsonProjectionLimit {
130                    data_type: rt,
131                    source_index: ri,
132                    source: rs,
133                },
134            ) => {
135                lt == rt
136                    && li == ri
137                    && ls.resource() == rs.resource()
138                    && ls.budget_error() == rs.budget_error()
139                    && ls.quantity_error() == rs.quantity_error()
140            }
141            _ => false,
142        }
143    }
144}
145
146impl Eq for ValueError {}
147
148impl ValueError {
149    /// Reports whether this error describes a missing value.
150    ///
151    /// # Returns
152    ///
153    /// `true` only for [`Self::Missing`].
154    #[must_use]
155    #[inline(always)]
156    pub const fn is_missing(&self) -> bool {
157        matches!(self, Self::Missing(_))
158    }
159
160    /// Returns the structured missing-value reason, when present.
161    ///
162    /// # Returns
163    ///
164    /// `Some(reason)` for [`Self::Missing`] and `None` for every other variant.
165    #[must_use]
166    #[inline(always)]
167    pub const fn missing(&self) -> Option<&ValueMissing> {
168        match self {
169            Self::Missing(missing) => Some(missing),
170            Self::TypeMismatch { .. } => None,
171            #[cfg(all(feature = "converter", feature = "json"))]
172            Self::JsonProjectionLimit { .. } => None,
173            #[cfg(feature = "converter")]
174            Self::Conversion(_) | Self::ListConversion(_) => None,
175        }
176    }
177}
178
179#[cfg(feature = "converter")]
180impl From<DataConversionError> for ValueError {
181    fn from(error: DataConversionError) -> Self {
182        if error.is_missing() || error.kind() == DataConversionErrorKind::EmptyCollection {
183            return Self::Missing(ValueMissing::from_conversion(error, None));
184        }
185        Self::Conversion(error)
186    }
187}
188
189#[cfg(feature = "converter")]
190impl From<DataListConversionError> for ValueError {
191    fn from(error: DataListConversionError) -> Self {
192        let (source_index, source) = error.into_parts();
193        if source.is_missing() || source.kind() == DataConversionErrorKind::EmptyCollection {
194            return Self::Missing(ValueMissing::from_conversion(source, Some(source_index)));
195        }
196        Self::ListConversion(DataListConversionError::new(source_index, source))
197    }
198}
199
200/// Result returned by value processing operations.
201///
202/// # Type Parameters
203///
204/// * `T` - Successful value returned by the operation.
205///
206/// # Examples
207///
208/// ```
209/// use qubit_value::{Value, ValueResult};
210///
211/// fn read_int(value: &Value) -> ValueResult<i32> {
212///     value.get_int32()
213/// }
214///
215/// assert_eq!(read_int(&Value::from(42_i32)).unwrap(), 42);
216/// ```
217pub type ValueResult<T> = Result<T, ValueError>;