Skip to main content

qubit_value/multi_values/
multi_values.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//! # Multiple Values Container
9//!
10//! Provides type-safe storage and access functionality for multiple values.
11// qubit-style: allow source-test-pair
12// Tests are intentionally distributed across behavior-specific files under
13// tests/multi_values/ rather than collected in multi_values_tests.rs.
14// qubit-style: allow multiple-public-types
15use qubit_datatype::DataType;
16use std::fmt;
17
18use super::multi_values_ref::MultiValuesRef;
19
20/// Defines the private storage representation for the public multi-value
21/// container from the shared value-type table.
22macro_rules! define_multi_values_enum {
23    (
24        ;
25        $(
26            (
27                [$($cfg:meta),*],
28                $variant:ident,
29                $type:ty,
30                $data_type:expr,
31                $materialization:ident,
32                $json_class:ident,
33                $number_projection:ident,
34                $value_doc:literal,
35                $multi_doc:literal
36            )
37        ),+ $(,)?
38    ) => {
39        /// Internal multiple-values representation.
40        ///
41        /// Uses an enum to represent multiple values of different types,
42        /// providing type-safe storage and access for multiple values.
43        ///
44        /// This representation is private; downstream code uses
45        /// [`MultiValues`] constructors and [`MultiValuesRef`] semantic views
46        /// instead of matching storage details.
47        ///
48        /// # Behavior
49        ///
50        /// - Stores a homogeneous collection from the closed [`DataType`]
51        ///   family.
52        /// - Provides strict getters and, with `converter`, option-controlled
53        ///   conversion methods.
54        /// - Distinguishes an unset container from a concrete empty vector.
55        ///
56        /// # Equality and hashing
57        ///
58        /// Equality preserves the collection variant and element order. Float
59        /// elements use canonical signed-zero and NaN identity, while map-like
60        /// elements hash structurally. Standard hash output is suitable for in-memory
61        /// keys but is not a stable persistent fingerprint.
62        ///
63        /// # Examples
64        ///
65        /// ```rust
66        /// use qubit_value::MultiValues;
67        ///
68        /// let mut values = MultiValues::Int32(vec![1, 2, 3]);
69        /// assert_eq!(values.len(), 3);
70        /// assert_eq!(values.get_first_int32().unwrap(), 1);
71        ///
72        /// let all = values.get_int32s().unwrap();
73        /// assert_eq!(all, &[1, 2, 3]);
74        ///
75        /// values.add(4).unwrap();
76        /// assert_eq!(values.len(), 4);
77        /// ```
78        #[derive(Debug, Clone)]
79        pub(crate) enum MultiValuesRepr {
80            /// Unset collection with a declared element data type.
81            Unset(
82                /// Declared element type retained while the collection is unset.
83                DataType,
84            ),
85            $(
86                $(#[$cfg])*
87                #[doc = $multi_doc]
88                $variant(
89                    #[doc = concat!("Stored ", $multi_doc, " payload.")]
90                    Vec<$type>,
91                ),
92            )+
93        }
94    };
95}
96
97for_each_value_type!(define_multi_values_enum);
98
99/// Multiple typed runtime values with private storage representation.
100#[must_use]
101#[derive(Clone)]
102pub struct MultiValues {
103    pub(crate) repr: MultiValuesRepr,
104}
105
106impl fmt::Debug for MultiValues {
107    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
108        self.view().fmt(formatter)
109    }
110}
111
112macro_rules! impl_multi_values_constructors {
113    (
114        ;
115        $(
116            (
117                [$($cfg:meta),*],
118                $variant:ident,
119                $type:ty,
120                $data_type:expr,
121                $materialization:ident,
122                $json_class:ident,
123                $number_projection:ident,
124                $value_doc:literal,
125                $multi_doc:literal
126            )
127        ),+ $(,)?
128    ) => {
129        impl MultiValues {
130            /// Creates an unset collection with an explicit element type.
131            #[allow(non_snake_case)]
132            #[inline(always)]
133            pub const fn Unset(data_type: DataType) -> Self {
134                Self::new_unset(data_type)
135            }
136
137            /// Creates an unset collection with an explicit element type.
138            #[inline(always)]
139            pub const fn new_unset(data_type: DataType) -> Self {
140                Self { repr: MultiValuesRepr::Unset(data_type) }
141            }
142
143            $(
144                $(#[$cfg])*
145                #[allow(non_snake_case)]
146                #[doc = concat!("Creates a collection of ", $multi_doc, ".")]
147                #[inline(always)]
148                pub fn $variant(values: Vec<$type>) -> Self {
149                    Self { repr: MultiValuesRepr::$variant(values) }
150                }
151            )+
152        }
153    };
154}
155
156for_each_value_type!(impl_multi_values_constructors);
157
158impl MultiValues {
159    /// Borrows the stable semantic view of this collection.
160    #[inline(always)]
161    pub fn view(&self) -> MultiValuesRef<'_> {
162        match &self.repr {
163            MultiValuesRepr::Unset(data_type) => {
164                MultiValuesRef::Unset(*data_type)
165            }
166            MultiValuesRepr::Bool(values) => MultiValuesRef::Bool(values),
167            MultiValuesRepr::Char(values) => MultiValuesRef::Char(values),
168            MultiValuesRepr::Int8(values) => MultiValuesRef::Int8(values),
169            MultiValuesRepr::Int16(values) => MultiValuesRef::Int16(values),
170            MultiValuesRepr::Int32(values) => MultiValuesRef::Int32(values),
171            MultiValuesRepr::Int64(values) => MultiValuesRef::Int64(values),
172            MultiValuesRepr::Int128(values) => MultiValuesRef::Int128(values),
173            MultiValuesRepr::UInt8(values) => MultiValuesRef::UInt8(values),
174            MultiValuesRepr::UInt16(values) => MultiValuesRef::UInt16(values),
175            MultiValuesRepr::UInt32(values) => MultiValuesRef::UInt32(values),
176            MultiValuesRepr::UInt64(values) => MultiValuesRef::UInt64(values),
177            MultiValuesRepr::UInt128(values) => MultiValuesRef::UInt128(values),
178            MultiValuesRepr::Float32(values) => MultiValuesRef::Float32(values),
179            MultiValuesRepr::Float64(values) => MultiValuesRef::Float64(values),
180            #[cfg(feature = "big-integer")]
181            MultiValuesRepr::BigInteger(values) => {
182                MultiValuesRef::BigInteger(values)
183            }
184            #[cfg(feature = "big-decimal")]
185            MultiValuesRepr::BigDecimal(values) => {
186                MultiValuesRef::BigDecimal(values)
187            }
188            MultiValuesRepr::String(values) => MultiValuesRef::String(values),
189            #[cfg(feature = "chrono")]
190            MultiValuesRepr::Date(values) => MultiValuesRef::Date(values),
191            #[cfg(feature = "chrono")]
192            MultiValuesRepr::Time(values) => MultiValuesRef::Time(values),
193            #[cfg(feature = "chrono")]
194            MultiValuesRepr::DateTime(values) => {
195                MultiValuesRef::DateTime(values)
196            }
197            #[cfg(feature = "chrono")]
198            MultiValuesRepr::Instant(values) => MultiValuesRef::Instant(values),
199            MultiValuesRepr::Duration(values) => {
200                MultiValuesRef::Duration(values)
201            }
202            #[cfg(feature = "url")]
203            MultiValuesRepr::Url(values) => MultiValuesRef::Url(values),
204            MultiValuesRepr::StringMap(values) => {
205                MultiValuesRef::StringMap(values)
206            }
207            #[cfg(feature = "json")]
208            MultiValuesRepr::Json(values) => MultiValuesRef::Json(values),
209        }
210    }
211}
212
213// ============================================================================
214// Getter method generation macros
215// ============================================================================
216
217/// Unified multiple values getter generation macro
218///
219/// Generates `get_[xxx]s` methods for `MultiValues`, returning a reference to
220/// value slices.
221///
222/// # Documentation Comment Support
223///
224/// The macro automatically extracts preceding documentation comments, so you
225/// can add `///` comments before macro invocations.
226macro_rules! impl_get_multi_values {
227    // Simple type: return slice reference
228    ($(#[$attr:meta])* slice: $method:ident, $variant:ident, $type:ty, $data_type:expr) => {
229        $(#[$attr])*
230        #[doc = ""]
231        #[doc = "# Errors"]
232        #[doc = ""]
233        #[doc = "Returns [`ValueError::Missing`] when the container is unset"]
234        #[doc = "with the requested type, or [`ValueError::TypeMismatch`] when"]
235        #[doc = "the stored data type differs. A concrete empty vector returns"]
236        #[doc = "an empty slice."]
237        #[inline(always)]
238        pub fn $method(&self) -> ValueResult<&[$type]> {
239            match &self.repr {
240                MultiValuesRepr::$variant(v) => Ok(v),
241                MultiValuesRepr::Unset(dt) if *dt == $data_type => {
242                    Err(ValueError::Missing($crate::ValueMissing::UnsetCollection {
243                        data_type: *dt,
244                    }))
245                }
246                _ => Err(ValueError::TypeMismatch {
247                    expected: $data_type,
248                    actual: self.data_type(),
249                }),
250            }
251        }
252    };
253
254    // Complex type: return Vec reference (e.g., Vec<String>, Vec<Vec<u8>>)
255    ($(#[$attr:meta])* vec: $method:ident, $variant:ident, $type:ty, $data_type:expr) => {
256        $(#[$attr])*
257        #[doc = ""]
258        #[doc = "# Errors"]
259        #[doc = ""]
260        #[doc = "Returns [`ValueError::Missing`] when the container is unset"]
261        #[doc = "with the requested type, or [`ValueError::TypeMismatch`] when"]
262        #[doc = "the stored data type differs. A concrete empty vector returns"]
263        #[doc = "an empty slice."]
264        #[inline(always)]
265        pub fn $method(&self) -> ValueResult<&[$type]> {
266            match &self.repr {
267                MultiValuesRepr::$variant(v) => Ok(v.as_slice()),
268                MultiValuesRepr::Unset(dt) if *dt == $data_type => {
269                    Err(ValueError::Missing($crate::ValueMissing::UnsetCollection {
270                        data_type: *dt,
271                    }))
272                }
273                _ => Err(ValueError::TypeMismatch {
274                    expected: $data_type,
275                    actual: self.data_type(),
276                }),
277            }
278        }
279    };
280}
281
282/// Unified multiple values get_first method generation macro
283///
284/// Generates `get_first_[xxx]` methods for `MultiValues`, used to get the first
285/// value.
286///
287/// # Documentation Comment Support
288///
289/// The macro automatically extracts preceding documentation comments, so you
290/// can add `///` comments before macro invocations.
291macro_rules! impl_get_first_value {
292    // Copy type: directly return value
293    ($(#[$attr:meta])* copy: $method:ident, $variant:ident, $type:ty, $data_type:expr) => {
294        $(#[$attr])*
295        #[doc = ""]
296        #[doc = "# Errors"]
297        #[doc = ""]
298        #[doc = "Returns [`ValueError::Missing`] when the requested type matches"]
299        #[doc = "but no value is stored, or [`ValueError::TypeMismatch`] when"]
300        #[doc = "the stored data type differs."]
301        #[inline(always)]
302        pub fn $method(&self) -> ValueResult<$type> {
303            match &self.repr {
304                MultiValuesRepr::$variant(v) if !v.is_empty() => Ok(v[0]),
305                MultiValuesRepr::$variant(_) => {
306                    Err(ValueError::Missing($crate::ValueMissing::EmptyCollection {
307                        data_type: $data_type,
308                    }))
309                }
310                MultiValuesRepr::Unset(dt) if *dt == $data_type => {
311                    Err(ValueError::Missing($crate::ValueMissing::UnsetCollection {
312                        data_type: *dt,
313                    }))
314                }
315                _ => Err(ValueError::TypeMismatch {
316                    expected: $data_type,
317                    actual: self.data_type(),
318                }),
319            }
320        }
321    };
322
323    // Reference type: return reference
324    ($(#[$attr:meta])* ref: $method:ident, $variant:ident, $ret_type:ty, $data_type:expr, $conversion:expr) => {
325        $(#[$attr])*
326        #[doc = ""]
327        #[doc = "# Errors"]
328        #[doc = ""]
329        #[doc = "Returns [`ValueError::Missing`] when the requested type matches"]
330        #[doc = "but no value is stored, or [`ValueError::TypeMismatch`] when"]
331        #[doc = "the stored data type differs."]
332        #[inline(always)]
333        pub fn $method(&self) -> ValueResult<$ret_type> {
334            match &self.repr {
335                MultiValuesRepr::$variant(v) if !v.is_empty() => {
336                    let conv_fn: fn(&_) -> $ret_type = $conversion;
337                    Ok(conv_fn(&v[0]))
338                },
339                MultiValuesRepr::$variant(_) => {
340                    Err(ValueError::Missing($crate::ValueMissing::EmptyCollection {
341                        data_type: $data_type,
342                    }))
343                }
344                MultiValuesRepr::Unset(dt) if *dt == $data_type => {
345                    Err(ValueError::Missing($crate::ValueMissing::UnsetCollection {
346                        data_type: *dt,
347                    }))
348                }
349                _ => Err(ValueError::TypeMismatch {
350                    expected: $data_type,
351                    actual: self.data_type(),
352                }),
353            }
354        }
355    };
356}