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