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}