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;
16use serde::{Deserialize, Serialize};
17
18use crate::{FromMetadataValue, IntoMetadataValue, 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 + FromMetadataValue,
69    {
70        self.try_get(key).ok()
71    }
72
73    /// Retrieves the value associated with `key` and converts it to `T`.
74    ///
75    /// # Errors
76    ///
77    /// Returns [`MetadataError::MissingKey`] when the key is absent, or
78    /// [`MetadataError::TypeMismatch`] when the stored value cannot be converted
79    /// to the requested type.
80    pub fn try_get<T>(&self, key: &str) -> MetadataResult<T>
81    where
82        T: DataTypeOf + FromMetadataValue,
83    {
84        let value = self
85            .0
86            .get(key)
87            .ok_or_else(|| MetadataError::MissingKey(key.to_string()))?;
88        T::from_metadata_value(value)
89            .map_err(|error| MetadataError::conversion_error(key, T::DATA_TYPE, value, error))
90    }
91
92    /// Returns a reference to the stored [`Value`] for `key`, or `None` if absent.
93    #[inline]
94    #[must_use]
95    pub fn get_raw(&self, key: &str) -> Option<&Value> {
96        self.0.get(key)
97    }
98
99    /// Returns the concrete data type of the value stored under `key`.
100    #[inline]
101    #[must_use]
102    pub fn data_type(&self, key: &str) -> Option<DataType> {
103        self.0.get(key).map(Value::data_type)
104    }
105
106    /// Retrieves and converts the value associated with `key`, or returns
107    /// `default` if lookup or conversion fails.
108    #[inline]
109    #[must_use]
110    pub fn get_or<T>(&self, key: &str, default: T) -> T
111    where
112        T: DataTypeOf + FromMetadataValue,
113    {
114        self.try_get(key).unwrap_or(default)
115    }
116
117    /// Inserts a typed value under `key` and returns the previous value if present.
118    #[inline]
119    pub fn set<T>(&mut self, key: &str, value: T) -> Option<Value>
120    where
121        T: IntoMetadataValue,
122    {
123        self.0.insert(key.to_string(), value.into_metadata_value())
124    }
125
126    /// Inserts a typed value after validating it against `schema`.
127    ///
128    /// # Errors
129    ///
130    /// Returns [`MetadataError::UnknownField`] when `key` is rejected by the
131    /// schema, or [`MetadataError::TypeMismatch`] when the constructed value's
132    /// concrete type does not match the schema field type.
133    #[inline]
134    pub fn set_checked<T>(
135        &mut self,
136        schema: &MetadataSchema,
137        key: &str,
138        value: T,
139    ) -> MetadataResult<Option<Value>>
140    where
141        T: IntoMetadataValue,
142    {
143        let value = value.into_metadata_value();
144        schema.validate_entry(key, &value)?;
145        Ok(self.set_raw(key, value))
146    }
147
148    /// Returns a new metadata object with a typed value validated and inserted.
149    ///
150    /// # Errors
151    ///
152    /// Returns [`MetadataError::UnknownField`] when `key` is rejected by the
153    /// schema, or [`MetadataError::TypeMismatch`] when the constructed value's
154    /// concrete type does not match the schema field type.
155    #[inline]
156    pub fn with_checked<T>(
157        mut self,
158        schema: &MetadataSchema,
159        key: &str,
160        value: T,
161    ) -> MetadataResult<Self>
162    where
163        T: IntoMetadataValue,
164    {
165        self.set_checked(schema, key, value)?;
166        Ok(self)
167    }
168
169    /// Returns a new metadata object with `key` set to `value`.
170    #[inline]
171    #[must_use]
172    pub fn with<T>(mut self, key: &str, value: T) -> Self
173    where
174        T: IntoMetadataValue,
175    {
176        self.set(key, value);
177        self
178    }
179
180    /// Inserts a raw [`Value`] directly and returns the previous value if present.
181    #[inline]
182    pub fn set_raw(&mut self, key: &str, value: Value) -> Option<Value> {
183        self.0.insert(key.to_string(), value)
184    }
185
186    /// Returns a new metadata object with a raw [`Value`] inserted.
187    #[inline]
188    #[must_use]
189    pub fn with_raw(mut self, key: &str, value: Value) -> Self {
190        self.set_raw(key, value);
191        self
192    }
193
194    /// Removes the entry for `key` and returns the stored [`Value`] if it existed.
195    #[inline]
196    pub fn remove(&mut self, key: &str) -> Option<Value> {
197        self.0.remove(key)
198    }
199
200    /// Removes all entries.
201    #[inline]
202    pub fn clear(&mut self) {
203        self.0.clear();
204    }
205
206    /// Returns an iterator over `(&str, &Value)` pairs in key-sorted order.
207    #[inline]
208    pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
209        self.0.iter().map(|(key, value)| (key.as_str(), value))
210    }
211
212    /// Returns an iterator over the keys in sorted order.
213    #[inline]
214    pub fn keys(&self) -> impl Iterator<Item = &str> {
215        self.0.keys().map(String::as_str)
216    }
217
218    /// Returns an iterator over the values in key-sorted order.
219    #[inline]
220    pub fn values(&self) -> impl Iterator<Item = &Value> {
221        self.0.values()
222    }
223
224    /// Merges all entries from `other` into `self`, overwriting existing keys.
225    pub fn merge(&mut self, other: Metadata) {
226        for (key, value) in other.0 {
227            self.0.insert(key, value);
228        }
229    }
230
231    /// Returns a new `Metadata` that contains entries from `self` and `other`.
232    ///
233    /// Entries from `other` take precedence on key conflicts.
234    #[must_use]
235    pub fn merged(&self, other: &Metadata) -> Metadata {
236        let mut result = self.clone();
237        for (key, value) in &other.0 {
238            result.0.insert(key.clone(), value.clone());
239        }
240        result
241    }
242
243    /// Retains only the entries for which `predicate` returns `true`.
244    #[inline]
245    pub fn retain<F>(&mut self, mut predicate: F)
246    where
247        F: FnMut(&str, &Value) -> bool,
248    {
249        self.0.retain(|key, value| predicate(key.as_str(), value));
250    }
251
252    /// Converts this metadata object into its underlying map.
253    #[inline]
254    #[must_use]
255    pub fn into_inner(self) -> BTreeMap<String, Value> {
256        self.0
257    }
258}
259
260impl From<BTreeMap<String, Value>> for Metadata {
261    #[inline]
262    fn from(map: BTreeMap<String, Value>) -> Self {
263        Self(map)
264    }
265}
266
267impl From<Metadata> for BTreeMap<String, Value> {
268    #[inline]
269    fn from(meta: Metadata) -> Self {
270        meta.0
271    }
272}
273
274impl FromIterator<(String, Value)> for Metadata {
275    #[inline]
276    fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Self {
277        Self(iter.into_iter().collect())
278    }
279}
280
281impl IntoIterator for Metadata {
282    type IntoIter = std::collections::btree_map::IntoIter<String, Value>;
283    type Item = (String, Value);
284
285    #[inline]
286    fn into_iter(self) -> Self::IntoIter {
287        self.0.into_iter()
288    }
289}
290
291impl<'a> IntoIterator for &'a Metadata {
292    type IntoIter = std::collections::btree_map::Iter<'a, String, Value>;
293    type Item = (&'a String, &'a Value);
294
295    #[inline]
296    fn into_iter(self) -> Self::IntoIter {
297        self.0.iter()
298    }
299}
300
301impl Extend<(String, Value)> for Metadata {
302    #[inline]
303    fn extend<I: IntoIterator<Item = (String, Value)>>(&mut self, iter: I) {
304        self.0.extend(iter);
305    }
306}