qubit-value 0.13.1

Type-safe containers for single, multi-valued, and named runtime values
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
// =============================================================================
//    Copyright (c) 2025 - 2026 Haixing Hu.
//
//    SPDX-License-Identifier: Apache-2.0
//
//    Licensed under the Apache License, Version 2.0.
// =============================================================================
//! Missing-value facts retained across storage and conversion boundaries.

use std::fmt;

#[cfg(feature = "converter")]
use qubit_datatype::DataConversionError;
#[cfg(feature = "converter")]
use qubit_datatype::DataConversionErrorKind;
use qubit_datatype::DataType;

use crate::ValueMissingReason;

/// Describes the storage state, requested type and source of a missing read.
///
/// The fields are private so callers cannot manufacture an incomplete fact.
/// Use the constructors for storage states and the accessors for the optional
/// facts. `source_type` and `target_type` are `None` only when the caller has
/// no type information (for example, a generic empty iterator); `source_index`
/// is set only when a collection item caused the failure. A preserved
/// `conversion_error` is available only when the `converter` feature is on.
///
/// Strict fallback is allowed only for an unset value after type admission.
/// Conversion fallback additionally accepts a policy-classified missing scalar
/// but never a concrete empty collection or a missing collection item.
///
/// # Examples
///
/// ```
/// use qubit_datatype::DataType;
/// use qubit_value::{Value, ValueMissingReason};
///
/// let error = Value::new_unset(DataType::Int32).get::<i32>().unwrap_err();
/// let missing = error.missing().unwrap();
/// assert_eq!(missing.reason(), ValueMissingReason::UnsetScalar);
/// assert_eq!(missing.source_type(), Some(DataType::Int32));
/// assert_eq!(missing.target_type(), Some(DataType::Int32));
/// assert!(missing.is_defaultable_for_strict_read());
/// ```
///
/// Strict reads record both source and target. Conversion failures additionally
/// retain their original error and, for collection items, source index.
/// Inspect [`Self::reason`] and the accessors instead of matching storage
/// fields.
#[must_use]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValueMissing {
    /// Storage state or conversion-policy classification of the missing read.
    reason: ValueMissingReason,
    /// Runtime source type, absent when the originating operation did not
    /// retain it.
    source_type: Option<DataType>,
    /// Requested target type, absent when no target information is available.
    target_type: Option<DataType>,
    /// Zero-based original collection position, absent for scalar or outer
    /// failures.
    source_index: Option<usize>,
    /// Owned conversion cause, absent for descriptors created by strict storage
    /// reads.
    #[cfg(feature = "converter")]
    conversion_error: Option<DataConversionError>,
}

impl ValueMissing {
    /// Creates an unset scalar descriptor for a read from `source` to `target`.
    ///
    /// # Parameters
    ///
    /// * `source` - Runtime type of the unset scalar storage.
    /// * `target` - Type requested by the read operation.
    ///
    /// # Returns
    ///
    /// A descriptor classified as [`ValueMissingReason::UnsetScalar`].
    #[inline(always)]
    pub const fn unset_scalar(source: DataType, target: DataType) -> Self {
        Self::storage(ValueMissingReason::UnsetScalar, source, target)
    }

    /// Creates an unset collection descriptor for the requested element type.
    ///
    /// # Parameters
    ///
    /// * `source` - Runtime element type of the unset collection storage.
    /// * `target` - Element type requested by the read operation.
    ///
    /// # Returns
    ///
    /// A descriptor classified as [`ValueMissingReason::UnsetCollection`].
    #[inline(always)]
    pub const fn unset_collection(source: DataType, target: DataType) -> Self {
        Self::storage(ValueMissingReason::UnsetCollection, source, target)
    }

    /// Creates a descriptor for a first-item read from a concrete empty
    /// collection.
    ///
    /// # Parameters
    ///
    /// * `source` - Runtime element type of the empty collection.
    /// * `target` - Element type requested by the read operation.
    ///
    /// # Returns
    ///
    /// A descriptor classified as [`ValueMissingReason::EmptyCollection`].
    #[inline(always)]
    pub const fn empty_collection(source: DataType, target: DataType) -> Self {
        Self::storage(ValueMissingReason::EmptyCollection, source, target)
    }

    /// Preserves an already classified missing conversion and its original
    /// index.
    ///
    /// # Parameters
    ///
    /// * `error` - Conversion error carrying the source and target facts.
    /// * `source_index` - Original item index, or `None` for scalar conversion.
    ///
    /// # Returns
    ///
    /// A descriptor retaining the conversion error and collection position.
    #[cfg(feature = "converter")]
    #[inline]
    pub(crate) fn from_conversion(error: DataConversionError, source_index: Option<usize>) -> Self {
        Self {
            reason: if error.kind() == DataConversionErrorKind::EmptyCollection {
                ValueMissingReason::EmptyCollection
            } else {
                ValueMissingReason::Conversion
            },
            source_type: error.from_type(),
            target_type: Some(error.to_type()),
            source_index,
            conversion_error: Some(error),
        }
    }

    /// Records a known storage state without inventing a conversion source.
    ///
    /// # Parameters
    ///
    /// * `reason` - Storage-state classification for the missing read.
    /// * `source` - Runtime type recorded by the source container.
    /// * `target` - Type requested by the read operation.
    ///
    /// # Returns
    ///
    /// A descriptor with known source and target types and no item index.
    #[inline(always)]
    const fn storage(reason: ValueMissingReason, source: DataType, target: DataType) -> Self {
        Self {
            reason,
            source_type: Some(source),
            target_type: Some(target),
            source_index: None,
            #[cfg(feature = "converter")]
            conversion_error: None,
        }
    }

    /// Returns the storage or policy reason for this missing result.
    ///
    /// This value is always present, including when the source or target type
    /// is unknown.
    ///
    /// # Returns
    ///
    /// The storage or conversion-policy classification.
    #[must_use]
    #[inline(always)]
    pub const fn reason(&self) -> ValueMissingReason {
        self.reason
    }

    /// Returns the known source type, or `None` when conversion did not retain
    /// one (for example, a generic empty iterator).
    ///
    /// # Returns
    ///
    /// `Some` with the stored source type when known; otherwise `None`.
    #[must_use]
    #[inline(always)]
    pub const fn source_type(&self) -> Option<DataType> {
        self.source_type
    }

    /// Returns the requested target type when known. Strict reads normally
    /// provide it; a low-level conversion failure may leave it absent.
    ///
    /// # Returns
    ///
    /// `Some` with the requested type when known; otherwise `None`.
    #[must_use]
    #[inline(always)]
    pub const fn target_type(&self) -> Option<DataType> {
        self.target_type
    }

    /// Returns the original collection item index, or `None` for an outer
    /// failure or a scalar conversion.
    ///
    /// # Returns
    ///
    /// `Some` with the zero-based source index for an item failure; otherwise
    /// `None`.
    #[must_use]
    #[inline(always)]
    pub const fn source_index(&self) -> Option<usize> {
        self.source_index
    }

    /// Returns the original conversion error, absent for a strict storage
    /// read. The error is the source for [`std::error::Error::source`].
    ///
    /// # Returns
    ///
    /// `Some` with the preserved conversion error for conversion failures;
    /// otherwise `None`.
    #[cfg(feature = "converter")]
    #[must_use]
    #[inline(always)]
    #[cfg_attr(docsrs, doc(cfg(feature = "converter")))]
    pub const fn conversion_error(&self) -> Option<&DataConversionError> {
        self.conversion_error.as_ref()
    }

    /// Reports whether scalar or collection storage is unset.
    ///
    /// This predicate is the condition used by strict fallback helpers.
    ///
    /// # Returns
    ///
    /// `true` for unset scalar or collection storage; otherwise `false`.
    #[must_use]
    #[inline(always)]
    pub const fn is_unset(&self) -> bool {
        matches!(
            self.reason,
            ValueMissingReason::UnsetScalar | ValueMissingReason::UnsetCollection
        )
    }

    /// Reports whether a first-item read failed because a concrete collection
    /// is empty.
    ///
    /// # Returns
    ///
    /// `true` only for a concrete empty collection; otherwise `false`.
    #[must_use]
    #[inline(always)]
    pub const fn is_empty_collection(&self) -> bool {
        matches!(self.reason, ValueMissingReason::EmptyCollection)
    }

    /// Reports whether conversion produced this failure, including enriched
    /// unset states.
    ///
    /// # Returns
    ///
    /// `true` when a conversion error is represented or retained; otherwise
    /// `false`.
    #[must_use]
    #[inline]
    pub const fn is_conversion(&self) -> bool {
        if matches!(self.reason, ValueMissingReason::Conversion) {
            return true;
        }
        #[cfg(feature = "converter")]
        {
            self.conversion_error.is_some()
        }
        #[cfg(not(feature = "converter"))]
        {
            false
        }
    }

    /// Reports whether the missing reason is unset storage.
    ///
    /// Strict readers must perform type admission before using this result
    /// to select a default. This predicate does not compare `source_type`
    /// with `target_type`; an unset descriptor constructed with different
    /// source and target types still returns `true`.
    /// Concrete empty collections and missing collection items return `false`.
    ///
    /// # Returns
    ///
    /// `true` for unset storage, allowing a strict fallback only after the
    /// caller has separately accepted the requested type.
    #[must_use]
    #[inline(always)]
    pub const fn is_defaultable_for_strict_read(&self) -> bool {
        self.is_unset()
    }

    /// Allows conversion fallback for unset storage or a policy-missing scalar.
    ///
    /// Empty collections and missing collection items never default.
    ///
    /// # Returns
    ///
    /// `true` when a conversion read may use its caller-supplied default.
    #[must_use]
    #[inline]
    pub const fn is_defaultable_for_conversion(&self) -> bool {
        self.is_unset()
            || (matches!(self.reason, ValueMissingReason::Conversion)
                && self.source_index.is_none())
    }

    /// Enriches a conversion failure with facts known by its owning container.
    ///
    /// Only called after conversion admission has produced a missing error.
    ///
    /// # Parameters
    ///
    /// * `source` - Runtime type supplied by the owning container.
    /// * `reason` - Storage classification supplied by that container.
    ///
    /// # Returns
    ///
    /// This descriptor with its source type and reason replaced.
    #[cfg(feature = "converter")]
    #[inline(always)]
    pub(crate) fn with_storage(mut self, source: DataType, reason: ValueMissingReason) -> Self {
        self.source_type = Some(source);
        self.reason = reason;
        self
    }

    /// Records the first collection item's original index when conversion lost
    /// it.
    ///
    /// # Returns
    ///
    /// This descriptor with index zero recorded when it represents an
    /// unindexed conversion failure; all other descriptors are unchanged.
    #[cfg(feature = "converter")]
    #[inline]
    pub(crate) fn with_first_index(mut self) -> Self {
        if self.reason == ValueMissingReason::Conversion && self.source_index.is_none() {
            self.source_index = Some(0);
        }
        self
    }
}

impl fmt::Display for ValueMissing {
    /// Formats diagnostic types and indices without exposing source payloads.
    ///
    /// # Parameters
    ///
    /// * `formatter` - Destination and formatting options supplied by the
    ///   caller.
    ///
    /// # Returns
    ///
    /// `Ok(())` when the diagnostic was written successfully.
    ///
    /// # Errors
    ///
    /// Returns an error if the destination rejects a formatted write.
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            formatter,
            "{:?}: source {:?}, target {:?}",
            self.reason, self.source_type, self.target_type
        )?;
        if let Some(index) = self.source_index {
            write!(formatter, ", collection index {index}")?;
        }
        Ok(())
    }
}

impl std::error::Error for ValueMissing {
    /// Exposes the preserved conversion error, or terminates a strict-read
    /// chain.
    ///
    /// # Returns
    ///
    /// `Some` borrowing the retained conversion cause when present; `None` for
    /// strict storage reads or when the `converter` feature is disabled.
    #[inline]
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        #[cfg(feature = "converter")]
        {
            self.conversion_error
                .as_ref()
                .map(|error| error as &dyn std::error::Error)
        }
        #[cfg(not(feature = "converter"))]
        {
            None
        }
    }
}