Skip to main content

qubit_metadata/
metadata.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Provides the [`Metadata`] type — a structured, ordered, typed key-value store.
11
12use std::collections::BTreeMap;
13
14use qubit_datatype::{DataType, DataTypeOf};
15use qubit_value::{Value, ValueConstructor, ValueConverter};
16use serde::{Deserialize, Serialize};
17
18use crate::{MetadataError, MetadataResult, MetadataSchema};
19
20/// A structured, ordered, typed key-value store for metadata fields.
21///
22/// `Metadata` stores values as [`qubit_value::Value`], preserving concrete Rust
23/// scalar types such as `i64`, `u32`, `f64`, `String`, and `bool`.  This avoids
24/// the ambiguity of a single JSON number type while still allowing callers to
25/// store explicit [`Value::Json`] values when they really need JSON payloads.
26///
27/// Use [`Metadata::with`] for fluent construction and [`Metadata::set`] when
28/// mutating an existing object.
29#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
30pub struct Metadata(BTreeMap<String, Value>);
31
32impl Metadata {
33    /// Creates an empty metadata object.
34    #[inline]
35    #[must_use]
36    pub fn new() -> Self {
37        Self(BTreeMap::new())
38    }
39
40    /// Returns `true` if there are no entries.
41    #[inline]
42    #[must_use]
43    pub fn is_empty(&self) -> bool {
44        self.0.is_empty()
45    }
46
47    /// Returns the number of key-value pairs.
48    #[inline]
49    #[must_use]
50    pub fn len(&self) -> usize {
51        self.0.len()
52    }
53
54    /// Returns `true` if the given key exists.
55    #[inline]
56    #[must_use]
57    pub fn contains_key(&self, key: &str) -> bool {
58        self.0.contains_key(key)
59    }
60
61    /// Retrieves the value associated with `key` and converts it to `T`.
62    ///
63    /// This convenience method returns `None` when the key is absent or when the
64    /// stored [`Value`] cannot be converted to `T`.
65    #[inline]
66    pub fn get<T>(&self, key: &str) -> Option<T>
67    where
68        T: DataTypeOf,
69        Value: ValueConverter<T>,
70    {
71        self.try_get(key).ok()
72    }
73
74    /// Retrieves the value associated with `key` and converts it to `T`.
75    ///
76    /// # Errors
77    ///
78    /// Returns [`MetadataError::MissingKey`] when the key is absent, or
79    /// [`MetadataError::TypeMismatch`] when the stored value cannot be converted
80    /// to the requested type.
81    pub fn try_get<T>(&self, key: &str) -> MetadataResult<T>
82    where
83        T: DataTypeOf,
84        Value: ValueConverter<T>,
85    {
86        let value = self
87            .0
88            .get(key)
89            .ok_or_else(|| MetadataError::MissingKey(key.to_string()))?;
90        value
91            .to::<T>()
92            .map_err(|error| MetadataError::conversion_error(key, T::DATA_TYPE, value, error))
93    }
94
95    /// Returns a reference to the stored [`Value`] for `key`, or `None` if absent.
96    #[inline]
97    #[must_use]
98    pub fn get_raw(&self, key: &str) -> Option<&Value> {
99        self.0.get(key)
100    }
101
102    /// Returns the concrete data type of the value stored under `key`.
103    #[inline]
104    #[must_use]
105    pub fn data_type(&self, key: &str) -> Option<DataType> {
106        self.0.get(key).map(Value::data_type)
107    }
108
109    /// Retrieves and converts the value associated with `key`, or returns
110    /// `default` if lookup or conversion fails.
111    #[inline]
112    #[must_use]
113    pub fn get_or<T>(&self, key: &str, default: T) -> T
114    where
115        T: DataTypeOf,
116        Value: ValueConverter<T>,
117    {
118        self.try_get(key).unwrap_or(default)
119    }
120
121    /// Inserts a typed value under `key` and returns the previous value if present.
122    #[inline]
123    pub fn set<T>(&mut self, key: &str, value: T) -> Option<Value>
124    where
125        Value: ValueConstructor<T>,
126    {
127        self.0.insert(key.to_string(), to_value(value))
128    }
129
130    /// Inserts a typed value after validating it against `schema`.
131    ///
132    /// # Errors
133    ///
134    /// Returns [`MetadataError::UnknownField`] when `key` is rejected by the
135    /// schema, or [`MetadataError::TypeMismatch`] when the constructed value's
136    /// concrete type does not match the schema field type.
137    #[inline]
138    pub fn set_checked<T>(
139        &mut self,
140        schema: &MetadataSchema,
141        key: &str,
142        value: T,
143    ) -> MetadataResult<Option<Value>>
144    where
145        Value: ValueConstructor<T>,
146    {
147        let value = to_value(value);
148        schema.validate_entry(key, &value)?;
149        Ok(self.set_raw(key, value))
150    }
151
152    /// Returns a new metadata object with a typed value validated and inserted.
153    ///
154    /// # Errors
155    ///
156    /// Returns [`MetadataError::UnknownField`] when `key` is rejected by the
157    /// schema, or [`MetadataError::TypeMismatch`] when the constructed value's
158    /// concrete type does not match the schema field type.
159    #[inline]
160    pub fn with_checked<T>(
161        mut self,
162        schema: &MetadataSchema,
163        key: &str,
164        value: T,
165    ) -> MetadataResult<Self>
166    where
167        Value: ValueConstructor<T>,
168    {
169        self.set_checked(schema, key, value)?;
170        Ok(self)
171    }
172
173    /// Returns a new metadata object with `key` set to `value`.
174    #[inline]
175    #[must_use]
176    pub fn with<T>(mut self, key: &str, value: T) -> Self
177    where
178        Value: ValueConstructor<T>,
179    {
180        self.set(key, value);
181        self
182    }
183
184    /// Inserts a raw [`Value`] directly and returns the previous value if present.
185    #[inline]
186    pub fn set_raw(&mut self, key: &str, value: Value) -> Option<Value> {
187        self.0.insert(key.to_string(), value)
188    }
189
190    /// Returns a new metadata object with a raw [`Value`] inserted.
191    #[inline]
192    #[must_use]
193    pub fn with_raw(mut self, key: &str, value: Value) -> Self {
194        self.set_raw(key, value);
195        self
196    }
197
198    /// Removes the entry for `key` and returns the stored [`Value`] if it existed.
199    #[inline]
200    pub fn remove(&mut self, key: &str) -> Option<Value> {
201        self.0.remove(key)
202    }
203
204    /// Removes all entries.
205    #[inline]
206    pub fn clear(&mut self) {
207        self.0.clear();
208    }
209
210    /// Returns an iterator over `(&str, &Value)` pairs in key-sorted order.
211    #[inline]
212    pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
213        self.0.iter().map(|(key, value)| (key.as_str(), value))
214    }
215
216    /// Returns an iterator over the keys in sorted order.
217    #[inline]
218    pub fn keys(&self) -> impl Iterator<Item = &str> {
219        self.0.keys().map(String::as_str)
220    }
221
222    /// Returns an iterator over the values in key-sorted order.
223    #[inline]
224    pub fn values(&self) -> impl Iterator<Item = &Value> {
225        self.0.values()
226    }
227
228    /// Merges all entries from `other` into `self`, overwriting existing keys.
229    pub fn merge(&mut self, other: Metadata) {
230        for (key, value) in other.0 {
231            self.0.insert(key, value);
232        }
233    }
234
235    /// Returns a new `Metadata` that contains entries from `self` and `other`.
236    ///
237    /// Entries from `other` take precedence on key conflicts.
238    #[must_use]
239    pub fn merged(&self, other: &Metadata) -> Metadata {
240        let mut result = self.clone();
241        for (key, value) in &other.0 {
242            result.0.insert(key.clone(), value.clone());
243        }
244        result
245    }
246
247    /// Retains only the entries for which `predicate` returns `true`.
248    #[inline]
249    pub fn retain<F>(&mut self, mut predicate: F)
250    where
251        F: FnMut(&str, &Value) -> bool,
252    {
253        self.0.retain(|key, value| predicate(key.as_str(), value));
254    }
255
256    /// Converts this metadata object into its underlying map.
257    #[inline]
258    #[must_use]
259    pub fn into_inner(self) -> BTreeMap<String, Value> {
260        self.0
261    }
262}
263
264#[inline]
265fn to_value<T>(value: T) -> Value
266where
267    Value: ValueConstructor<T>,
268{
269    <Value as ValueConstructor<T>>::from_type(value)
270}
271
272impl From<BTreeMap<String, Value>> for Metadata {
273    #[inline]
274    fn from(map: BTreeMap<String, Value>) -> Self {
275        Self(map)
276    }
277}
278
279impl From<Metadata> for BTreeMap<String, Value> {
280    #[inline]
281    fn from(meta: Metadata) -> Self {
282        meta.0
283    }
284}
285
286impl FromIterator<(String, Value)> for Metadata {
287    #[inline]
288    fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Self {
289        Self(iter.into_iter().collect())
290    }
291}
292
293impl IntoIterator for Metadata {
294    type IntoIter = std::collections::btree_map::IntoIter<String, Value>;
295    type Item = (String, Value);
296
297    #[inline]
298    fn into_iter(self) -> Self::IntoIter {
299        self.0.into_iter()
300    }
301}
302
303impl<'a> IntoIterator for &'a Metadata {
304    type IntoIter = std::collections::btree_map::Iter<'a, String, Value>;
305    type Item = (&'a String, &'a Value);
306
307    #[inline]
308    fn into_iter(self) -> Self::IntoIter {
309        self.0.iter()
310    }
311}
312
313impl Extend<(String, Value)> for Metadata {
314    #[inline]
315    fn extend<I: IntoIterator<Item = (String, Value)>>(&mut self, iter: I) {
316        self.0.extend(iter);
317    }
318}