Skip to main content

qubit_metadata/
metadata.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9//! Provides the [`Metadata`] type — a structured, ordered, type-safe key-value
10//! store backed by [`serde_json::Value`].
11
12use std::collections::BTreeMap;
13
14use serde::{
15    de::DeserializeOwned,
16    Serialize,
17};
18use serde_json::Value;
19
20use crate::{
21    MetadataError,
22    MetadataResult,
23    MetadataValueKind,
24};
25
26/// A structured, ordered, type-safe key-value store for attaching arbitrary
27/// annotations to domain objects.
28///
29/// `Metadata` is backed by a [`BTreeMap<String, Value>`] (ordered by key) and
30/// provides two layers of typed access:
31///
32/// - Convenience accessors like [`Metadata::get`] and [`Metadata::set`] keep the
33///   API terse and ergonomic.
34/// - Explicit accessors like [`Metadata::try_get`] and [`Metadata::try_set`]
35///   preserve failure reasons, which is useful for debugging and validation.
36///
37/// The type model intentionally stays JSON-shaped rather than closed over a
38/// fixed enum of Rust scalar types. This keeps the crate interoperable with
39/// `serde_json`, nested objects, and external JSON-based APIs.
40///
41/// # Examples
42///
43/// ```rust
44/// use qubit_metadata::Metadata;
45///
46/// let mut meta = Metadata::new();
47/// meta.set("author", "alice");
48/// meta.set("priority", 3_i64);
49/// meta.set("reviewed", true);
50///
51/// // Convenience API
52/// let author: Option<String> = meta.get("author");
53/// assert_eq!(author.as_deref(), Some("alice"));
54///
55/// // Explicit API
56/// let priority = meta.try_get::<i64>("priority").unwrap();
57/// assert_eq!(priority, 3);
58/// ```
59#[derive(Debug, Clone, PartialEq, Default, Serialize, serde::Deserialize)]
60pub struct Metadata(BTreeMap<String, Value>);
61
62impl Metadata {
63    /// Creates an empty `Metadata` instance.
64    #[inline]
65    pub fn new() -> Self {
66        Self(BTreeMap::new())
67    }
68
69    /// Returns `true` if there are no entries.
70    #[inline]
71    pub fn is_empty(&self) -> bool {
72        self.0.is_empty()
73    }
74
75    /// Returns the number of key-value pairs.
76    #[inline]
77    pub fn len(&self) -> usize {
78        self.0.len()
79    }
80
81    /// Returns `true` if the given key exists.
82    #[inline]
83    pub fn contains_key(&self, key: &str) -> bool {
84        self.0.contains_key(key)
85    }
86
87    /// Retrieves and deserializes the value associated with `key`.
88    ///
89    /// This is the convenience version of [`Metadata::try_get`]. It returns
90    /// `None` when the key is absent or when deserialization into `T` fails.
91    ///
92    /// Use this when a concise, best-effort lookup is preferred over detailed
93    /// diagnostics.
94    pub fn get<T>(&self, key: &str) -> Option<T>
95    where
96        T: DeserializeOwned,
97    {
98        self.try_get(key).ok()
99    }
100
101    /// Retrieves and deserializes the value associated with `key`, preserving
102    /// the reason when retrieval fails.
103    ///
104    /// # Errors
105    ///
106    /// - [`MetadataError::MissingKey`] if `key` does not exist
107    /// - [`MetadataError::DeserializationError`] if the stored JSON value cannot
108    ///   be deserialized into `T`
109    pub fn try_get<T>(&self, key: &str) -> MetadataResult<T>
110    where
111        T: DeserializeOwned,
112    {
113        let value = self
114            .0
115            .get(key)
116            .ok_or_else(|| MetadataError::MissingKey(key.to_string()))?;
117        serde_json::from_value(value.clone())
118            .map_err(|error| MetadataError::deserialization_error::<T>(key, value, error))
119    }
120
121    /// Returns a reference to the raw [`Value`] for `key`, or `None` if absent.
122    #[inline]
123    pub fn get_raw(&self, key: &str) -> Option<&Value> {
124        self.0.get(key)
125    }
126
127    /// Returns the coarse JSON kind of the value stored under `key`.
128    ///
129    /// This is a lightweight inspection API inspired by the stricter type
130    /// introspection facilities in `qubit-value`, adapted to `Metadata`'s
131    /// open-ended JSON storage model.
132    #[inline]
133    pub fn value_kind(&self, key: &str) -> Option<MetadataValueKind> {
134        self.0.get(key).map(MetadataValueKind::of)
135    }
136
137    /// Retrieves and deserializes the value associated with `key`, or returns
138    /// `default` if lookup fails for any reason.
139    ///
140    /// This mirrors the forgiving default-value style used by `qubit-config`.
141    /// It is intentionally convenience-oriented: both missing keys and type
142    /// mismatches fall back to the supplied default.
143    #[must_use]
144    pub fn get_or<T>(&self, key: &str, default: T) -> T
145    where
146        T: DeserializeOwned,
147    {
148        self.try_get(key).unwrap_or(default)
149    }
150
151    /// Serializes `value` and inserts it under `key`.
152    ///
153    /// This is the convenience version of [`Metadata::try_set`]. It preserves
154    /// the current ergonomic API and panics if serialization fails.
155    pub fn set<T>(&mut self, key: impl Into<String>, value: T) -> Option<Value>
156    where
157        T: Serialize,
158    {
159        self.try_set(key, value)
160            .expect("Metadata::set: value must be serializable to serde_json::Value")
161    }
162
163    /// Serializes `value` and inserts it under `key`, preserving serialization
164    /// failures instead of panicking.
165    ///
166    /// # Errors
167    ///
168    /// Returns [`MetadataError::SerializationError`] when `value` fails to
169    /// serialize into [`serde_json::Value`].
170    pub fn try_set<T>(&mut self, key: impl Into<String>, value: T) -> MetadataResult<Option<Value>>
171    where
172        T: Serialize,
173    {
174        let key = key.into();
175        let json = serde_json::to_value(value)
176            .map_err(|error| MetadataError::serialization_error(key.clone(), error))?;
177        Ok(self.0.insert(key, json))
178    }
179
180    /// Inserts a raw [`Value`] directly, bypassing serialization.
181    ///
182    /// Returns the previous value if present.
183    #[inline]
184    pub fn set_raw(&mut self, key: impl Into<String>, value: Value) -> Option<Value> {
185        self.0.insert(key.into(), value)
186    }
187
188    /// Removes the entry for `key` and returns the raw [`Value`] if it existed.
189    #[inline]
190    pub fn remove(&mut self, key: &str) -> Option<Value> {
191        self.0.remove(key)
192    }
193
194    /// Removes all entries.
195    #[inline]
196    pub fn clear(&mut self) {
197        self.0.clear();
198    }
199
200    /// Returns an iterator over `(&str, &Value)` pairs in key-sorted order.
201    #[inline]
202    pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
203        self.0.iter().map(|(k, v)| (k.as_str(), v))
204    }
205
206    /// Returns an iterator over the keys in sorted order.
207    #[inline]
208    pub fn keys(&self) -> impl Iterator<Item = &str> {
209        self.0.keys().map(String::as_str)
210    }
211
212    /// Returns an iterator over the raw values in key-sorted order.
213    #[inline]
214    pub fn values(&self) -> impl Iterator<Item = &Value> {
215        self.0.values()
216    }
217
218    /// Merges all entries from `other` into `self`, overwriting existing keys.
219    pub fn merge(&mut self, other: Metadata) {
220        for (k, v) in other.0 {
221            self.0.insert(k, v);
222        }
223    }
224
225    /// Returns a new `Metadata` that contains all entries from both `self` and
226    /// `other`.  Entries in `other` take precedence on key conflicts.
227    #[must_use]
228    pub fn merged(&self, other: &Metadata) -> Metadata {
229        let mut result = self.clone();
230        for (k, v) in &other.0 {
231            result.0.insert(k.clone(), v.clone());
232        }
233        result
234    }
235
236    /// Retains only the entries for which `predicate` returns `true`.
237    pub fn retain<F>(&mut self, mut predicate: F)
238    where
239        F: FnMut(&str, &Value) -> bool,
240    {
241        self.0.retain(|k, v| predicate(k.as_str(), v));
242    }
243
244    /// Converts this `Metadata` into its underlying [`BTreeMap`].
245    #[inline]
246    pub fn into_inner(self) -> BTreeMap<String, Value> {
247        self.0
248    }
249}
250
251impl From<BTreeMap<String, Value>> for Metadata {
252    #[inline]
253    fn from(map: BTreeMap<String, Value>) -> Self {
254        Self(map)
255    }
256}
257
258impl From<Metadata> for BTreeMap<String, Value> {
259    #[inline]
260    fn from(meta: Metadata) -> Self {
261        meta.0
262    }
263}
264
265impl FromIterator<(String, Value)> for Metadata {
266    fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Self {
267        Self(iter.into_iter().collect())
268    }
269}
270
271impl IntoIterator for Metadata {
272    type Item = (String, Value);
273    type IntoIter = std::collections::btree_map::IntoIter<String, Value>;
274
275    #[inline]
276    fn into_iter(self) -> Self::IntoIter {
277        self.0.into_iter()
278    }
279}
280
281impl<'a> IntoIterator for &'a Metadata {
282    type Item = (&'a String, &'a Value);
283    type IntoIter = std::collections::btree_map::Iter<'a, String, Value>;
284
285    #[inline]
286    fn into_iter(self) -> Self::IntoIter {
287        self.0.iter()
288    }
289}
290
291impl Extend<(String, Value)> for Metadata {
292    fn extend<I: IntoIterator<Item = (String, Value)>>(&mut self, iter: I) {
293        self.0.extend(iter);
294    }
295}