Skip to main content

qubit_value/
named_value.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 Single Value
9//!
10//! Provides a named container for single values, allowing readable identifiers
11//! to be added to individual values in complex configurations or structures.
12//!
13//! Suitable for scenarios such as log annotation, configuration item
14//! encapsulation, and preserving strongly typed values in key-value pairs.
15
16use serde::{
17    Deserialize,
18    Deserializer,
19    Serialize,
20    Serializer,
21};
22
23use crate::ValueWireRefV1;
24#[cfg(feature = "json")]
25use crate::{
26    ValueWireDecodeError,
27    WireLimits,
28};
29
30use super::value::Value;
31
32mod internal;
33
34use internal::{
35    NamedValueWireOwned,
36    NamedValueWireRef,
37};
38
39/// Named single value
40///
41/// Associates a human-readable name with a single [`Value`], facilitating
42/// identification, retrieval, and display in configurations, parameter passing,
43/// and complex data structures.
44///
45/// # Features
46///
47/// - Provides stable name identification for values
48/// - Exposes the inner [`Value`] through explicit accessors
49/// - Supports `serde` serialization and deserialization
50///
51/// # Use Cases
52///
53/// - Configuration item encapsulation (e.g., `"port"`, `"timeout"`, etc.)
54/// - Named output of key values in logs/monitoring
55/// - Quick location by name in collections
56///
57/// # Examples
58///
59/// ```rust
60/// use qubit_value::{NamedValue, Value};
61///
62/// let named = NamedValue::new("flag", Value::Bool(true));
63/// assert!(named.value().get_bool().unwrap());
64/// ```
65///
66/// The wrapper intentionally does not forward [`Value`] methods implicitly:
67///
68/// ```compile_fail
69/// use qubit_value::{NamedValue, Value};
70///
71/// let named = NamedValue::new("flag", Value::Bool(true));
72/// let _ = named.get_bool();
73/// ```
74#[must_use]
75#[derive(Debug, Clone, PartialEq, Eq, Hash)]
76pub struct NamedValue {
77    /// Name of the value
78    name: String,
79    /// Content of the value
80    value: Value,
81}
82
83impl NamedValue {
84    /// Create a new named value
85    ///
86    /// Creates a binding instance between a name and a value.
87    ///
88    /// # Parameters
89    ///
90    /// * `name` - Name of the value
91    /// * `value` - Content of the value
92    ///
93    /// # Returns
94    ///
95    /// Returns a newly created [`NamedValue`] instance
96    ///
97    /// # Examples
98    ///
99    /// ```rust
100    /// use qubit_value::{NamedValue, Value};
101    ///
102    /// let named = NamedValue::new("timeout", Value::Int32(30));
103    /// assert_eq!(named.name(), "timeout");
104    /// ```
105    #[inline]
106    pub fn new(name: impl Into<String>, value: Value) -> Self {
107        Self {
108            name: name.into(),
109            value,
110        }
111    }
112
113    /// Decodes a complete named scalar JSON document with default limits.
114    ///
115    /// # Parameters
116    ///
117    /// * `input` - Complete UTF-8 JSON document to decode.
118    ///
119    /// # Returns
120    ///
121    /// The decoded named scalar.
122    ///
123    /// # Errors
124    ///
125    /// Returns a JSON, wire-contract, or resource-limit error.
126    #[cfg(feature = "json")]
127    #[inline]
128    pub fn decode_json_slice(
129        input: &[u8],
130    ) -> Result<Self, ValueWireDecodeError> {
131        Self::decode_json_slice_with_limits(input, WireLimits::default())
132    }
133
134    /// Decodes a complete named scalar JSON document with explicit limits.
135    ///
136    /// The wrapper name and nested scalar share one accounting session.
137    ///
138    /// # Parameters
139    ///
140    /// * `input` - Complete UTF-8 JSON document to decode.
141    /// * `limits` - Input and decoded-resource limits.
142    ///
143    /// # Returns
144    ///
145    /// The decoded named scalar.
146    ///
147    /// # Errors
148    ///
149    /// Returns a JSON, wire-contract, or resource-limit error.
150    #[cfg(feature = "json")]
151    pub fn decode_json_slice_with_limits(
152        input: &[u8],
153        limits: WireLimits,
154    ) -> Result<Self, ValueWireDecodeError> {
155        let mut budget = limits.begin(input.len())?;
156        let value: Self = serde_json::from_slice(input)
157            .map_err(ValueWireDecodeError::from)?;
158        budget.check_named_value(&value)?;
159        Ok(value)
160    }
161
162    /// Get a reference to the name
163    ///
164    /// Returns a read-only name slice bound to this value.
165    ///
166    /// # Returns
167    ///
168    /// Returns a string slice `&str` of the name
169    ///
170    /// # Examples
171    ///
172    /// ```rust
173    /// use qubit_value::{NamedValue, Value};
174    ///
175    /// let named = NamedValue::new("host", Value::String("localhost".to_string()));
176    /// assert_eq!(named.name(), "host");
177    /// ```
178    #[inline(always)]
179    #[must_use = "the borrowed name should be used"]
180    pub fn name(&self) -> &str {
181        &self.name
182    }
183
184    /// Set a new name
185    ///
186    /// Updates the name bound to the current instance.
187    ///
188    /// # Parameters
189    ///
190    /// * `name` - The new name
191    ///
192    /// # Examples
193    ///
194    /// ```rust
195    /// use qubit_value::{NamedValue, Value};
196    ///
197    /// let mut named = NamedValue::new("old_name", Value::Bool(true));
198    /// named.set_name("new_name");
199    /// assert_eq!(named.name(), "new_name");
200    /// ```
201    #[inline(always)]
202    pub fn set_name(&mut self, name: impl Into<String>) {
203        self.name = name.into();
204    }
205
206    /// Borrows the contained value.
207    ///
208    /// # Returns
209    ///
210    /// A shared reference to the contained [`Value`].
211    #[inline(always)]
212    #[must_use = "the borrowed value should be used"]
213    pub fn value(&self) -> &Value {
214        &self.value
215    }
216
217    /// Mutably borrows the contained value.
218    ///
219    /// # Returns
220    ///
221    /// An exclusive reference to the contained [`Value`].
222    #[inline(always)]
223    #[must_use = "the mutable value reference should be used"]
224    pub fn value_mut(&mut self) -> &mut Value {
225        &mut self.value
226    }
227
228    /// Replaces the contained value.
229    ///
230    /// # Parameters
231    ///
232    /// * `value` - New value to store under the existing name.
233    #[inline(always)]
234    pub fn set_value(&mut self, value: Value) {
235        self.value = value;
236    }
237
238    /// Consumes this wrapper and returns its owned name and value.
239    ///
240    /// # Returns
241    ///
242    /// The `(name, value)` pair without cloning either component.
243    #[inline(always)]
244    #[must_use = "consuming NamedValue without using its parts loses both fields"]
245    pub fn into_parts(self) -> (String, Value) {
246        (self.name, self.value)
247    }
248}
249
250impl Serialize for NamedValue {
251    /// Serializes the name and its explicitly versioned scalar value.
252    #[inline]
253    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
254    where
255        S: Serializer,
256    {
257        let value = ValueWireRefV1::try_from(self.value())
258            .map_err(serde::ser::Error::custom)?;
259        NamedValueWireRef {
260            name: self.name(),
261            value,
262        }
263        .serialize(serializer)
264    }
265}
266
267impl<'de> Deserialize<'de> for NamedValue {
268    /// Deserializes a named scalar value from the V1 wire contract.
269    #[inline]
270    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
271    where
272        D: Deserializer<'de>,
273    {
274        let NamedValueWireOwned { name, value } =
275            NamedValueWireOwned::deserialize(deserializer)?;
276        let value = value.into_container().into_scalar().map_err(|_| {
277            serde::de::Error::custom(
278                "named value wire payload must contain a scalar",
279            )
280        })?;
281        Ok(Self::new(name, value))
282    }
283}