Skip to main content

qubit_value/
named_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//! # Named Multiple Values
9//!
10//! Provides a lightweight container for binding names to multiple value
11//! collections, facilitating human-readable identification of groups of values
12//! in configurations, serialization, logging, and other scenarios.
13
14use serde::{
15    Deserialize,
16    Deserializer,
17    Serialize,
18    Serializer,
19};
20
21use crate::ValueWireRefV1;
22#[cfg(feature = "json")]
23use crate::{
24    ValueWireDecodeError,
25    WireLimits,
26};
27
28use super::multi_values::MultiValues;
29use super::named_value::NamedValue;
30
31mod internal;
32
33use internal::{
34    NamedMultiValuesWireOwned,
35    NamedMultiValuesWireRef,
36};
37
38/// Named multiple values
39///
40/// A container that associates a readable name with a set of `MultiValues`,
41/// suitable for organizing data in key-value (name-multiple values) scenarios,
42/// such as configuration items, command-line parameter aggregation, structured
43/// log fields, etc.
44///
45/// # Features
46///
47/// - Provides clear name identification for multiple value collections
48/// - Exposes the inner [`MultiValues`] through explicit accessors
49/// - Supports `serde` serialization and deserialization
50///
51/// # Use Cases
52///
53/// - Aggregating a set of ports, hostnames, etc., as semantically meaningful
54///   fields
55/// - Outputting named multiple value lists in configurations/logs
56///
57/// # Examples
58///
59/// ```rust
60/// use qubit_value::{NamedMultiValues, MultiValues};
61///
62/// // Identify a group of ports with the name "ports"
63/// let named = NamedMultiValues::new(
64///     "ports",
65///     MultiValues::Int32(vec![8080, 8081, 8082])
66/// );
67///
68/// assert_eq!(named.name(), "ports");
69/// assert_eq!(named.values().len(), 3);
70/// ```
71///
72/// The wrapper intentionally does not forward [`MultiValues`] methods
73/// implicitly:
74///
75/// ```compile_fail
76/// use qubit_value::{MultiValues, NamedMultiValues};
77///
78/// let named = NamedMultiValues::new("ports", MultiValues::Int32(vec![8080]));
79/// let _ = named.len();
80/// ```
81#[must_use]
82#[derive(Debug, Clone, PartialEq, Eq, Hash)]
83pub struct NamedMultiValues {
84    /// Name of the values
85    name: String,
86    /// Content of the multiple values
87    value: MultiValues,
88}
89
90impl NamedMultiValues {
91    /// Create a new named multiple values
92    ///
93    /// Associates a given name with `MultiValues`, generating a container that
94    /// can be referenced by name.
95    ///
96    /// # Use Cases
97    ///
98    /// - Building configuration fields (e.g., `servers`, `ports`, etc.)
99    /// - Binding parsed multiple value results to semantic names
100    ///
101    /// # Parameters
102    ///
103    /// * `name` - Name of the multiple values
104    /// * `value` - Content of the multiple values
105    ///
106    /// # Returns
107    ///
108    /// Returns a newly created named multiple values
109    ///
110    /// # Examples
111    ///
112    /// ```rust
113    /// use qubit_value::{NamedMultiValues, MultiValues};
114    ///
115    /// let named = NamedMultiValues::new(
116    ///     "servers",
117    ///     MultiValues::String(vec!["s1".to_string(), "s2".to_string()])
118    /// );
119    /// assert_eq!(named.name(), "servers");
120    /// ```
121    #[inline]
122    pub fn new(name: impl Into<String>, value: MultiValues) -> Self {
123        Self {
124            name: name.into(),
125            value,
126        }
127    }
128
129    /// Decodes a complete named collection JSON document with default limits.
130    ///
131    /// # Parameters
132    ///
133    /// * `input` - Complete UTF-8 JSON document to decode.
134    ///
135    /// # Returns
136    ///
137    /// The decoded named collection.
138    ///
139    /// # Errors
140    ///
141    /// Returns a JSON, wire-contract, or resource-limit error.
142    #[cfg(feature = "json")]
143    #[inline]
144    pub fn decode_json_slice(
145        input: &[u8],
146    ) -> Result<Self, ValueWireDecodeError> {
147        Self::decode_json_slice_with_limits(input, WireLimits::default())
148    }
149
150    /// Decodes a complete named collection JSON document with explicit limits.
151    ///
152    /// The wrapper name and nested collection share one accounting session.
153    ///
154    /// # Parameters
155    ///
156    /// * `input` - Complete UTF-8 JSON document to decode.
157    /// * `limits` - Input and decoded-resource limits.
158    ///
159    /// # Returns
160    ///
161    /// The decoded named collection.
162    ///
163    /// # Errors
164    ///
165    /// Returns a JSON, wire-contract, or resource-limit error.
166    #[cfg(feature = "json")]
167    pub fn decode_json_slice_with_limits(
168        input: &[u8],
169        limits: WireLimits,
170    ) -> Result<Self, ValueWireDecodeError> {
171        let mut budget = limits.begin(input.len())?;
172        let value: Self = serde_json::from_slice(input)
173            .map_err(ValueWireDecodeError::from)?;
174        budget.check_named_multi_values(&value)?;
175        Ok(value)
176    }
177
178    /// Get a reference to the name
179    ///
180    /// # Returns
181    ///
182    /// Returns a string slice of the name
183    ///
184    /// # Examples
185    ///
186    /// ```rust
187    /// use qubit_value::{NamedMultiValues, MultiValues};
188    ///
189    /// let named = NamedMultiValues::new("items", MultiValues::Int32(vec![1, 2, 3]));
190    /// assert_eq!(named.name(), "items");
191    /// ```
192    #[inline(always)]
193    #[must_use = "the borrowed name should be used"]
194    pub fn name(&self) -> &str {
195        &self.name
196    }
197
198    /// Set a new name
199    ///
200    /// # Parameters
201    ///
202    /// * `name` - The new name
203    ///
204    /// # Returns
205    ///
206    /// No return value
207    ///
208    /// # Examples
209    ///
210    /// ```rust
211    /// use qubit_value::{NamedMultiValues, MultiValues};
212    ///
213    /// let mut named = NamedMultiValues::new("old", MultiValues::Bool(vec![true]));
214    /// named.set_name("new");
215    /// assert_eq!(named.name(), "new");
216    /// ```
217    #[inline(always)]
218    pub fn set_name(&mut self, name: impl Into<String>) {
219        self.name = name.into();
220    }
221
222    /// Borrows the contained values.
223    ///
224    /// # Returns
225    ///
226    /// A shared reference to the contained [`MultiValues`].
227    #[inline(always)]
228    #[must_use = "the borrowed values should be used"]
229    pub fn values(&self) -> &MultiValues {
230        &self.value
231    }
232
233    /// Mutably borrows the contained values.
234    ///
235    /// # Returns
236    ///
237    /// An exclusive reference to the contained [`MultiValues`].
238    #[inline(always)]
239    #[must_use = "the mutable values reference should be used"]
240    pub fn values_mut(&mut self) -> &mut MultiValues {
241        &mut self.value
242    }
243
244    /// Replaces the contained values.
245    ///
246    /// # Parameters
247    ///
248    /// * `values` - New collection to store under the existing name.
249    #[inline(always)]
250    pub fn set_values(&mut self, values: MultiValues) {
251        self.value = values;
252    }
253
254    /// Consumes this wrapper and returns its owned name and values.
255    ///
256    /// # Returns
257    ///
258    /// The `(name, values)` pair without cloning either component.
259    #[inline(always)]
260    #[must_use = "consuming NamedMultiValues without using its parts loses both fields"]
261    pub fn into_parts(self) -> (String, MultiValues) {
262        (self.name, self.value)
263    }
264
265    /// Convert this named multi-values into a named single value.
266    ///
267    /// The returned value keeps the same name and uses the first element from
268    /// the inner [`MultiValues`]. If there is no element, the returned value is
269    /// `Value::Unset` with the same data type.
270    ///
271    /// # Returns
272    ///
273    /// A named clone of the first item, or a named typed unset value.
274    #[inline]
275    pub fn first_named_value(&self) -> NamedValue {
276        NamedValue::new(self.name.as_str(), self.value.first_value())
277    }
278
279    /// Consumes this container and converts its first item to a named value.
280    ///
281    /// The owned name and first stored item are moved into the result. An empty
282    /// or unset collection produces [`crate::Value::Unset`] with the same data
283    /// type.
284    ///
285    /// # Returns
286    ///
287    /// A named owned first item, or a named typed unset value.
288    #[inline]
289    pub fn into_first_named_value(self) -> NamedValue {
290        let (name, values) = self.into_parts();
291        NamedValue::new(name, values.into_first_value())
292    }
293}
294
295impl From<NamedValue> for NamedMultiValues {
296    /// Construct `NamedMultiValues` from `NamedValue`
297    ///
298    /// Reuses the name and promotes the single value to a `MultiValues`
299    /// containing only one element.
300    #[inline]
301    fn from(named: NamedValue) -> Self {
302        let (name, value) = named.into_parts();
303        let value = MultiValues::from(value);
304        Self { name, value }
305    }
306}
307
308impl Serialize for NamedMultiValues {
309    /// Serializes the name and its explicitly versioned collection.
310    #[inline]
311    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
312    where
313        S: Serializer,
314    {
315        let value = ValueWireRefV1::try_from(self.values())
316            .map_err(serde::ser::Error::custom)?;
317        NamedMultiValuesWireRef {
318            name: self.name(),
319            value,
320        }
321        .serialize(serializer)
322    }
323}
324
325impl<'de> Deserialize<'de> for NamedMultiValues {
326    /// Deserializes a named collection from the V1 wire contract.
327    #[inline]
328    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
329    where
330        D: Deserializer<'de>,
331    {
332        let NamedMultiValuesWireOwned { name, value } =
333            NamedMultiValuesWireOwned::deserialize(deserializer)?;
334        let value = value.into_container().into_collection().map_err(|_| {
335            serde::de::Error::custom(
336                "named multi-values wire payload must contain a collection",
337            )
338        })?;
339        Ok(Self::new(name, value))
340    }
341}