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    MetadataValueType,
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    #[inline]
95    pub fn get<T>(&self, key: &str) -> Option<T>
96    where
97        T: DeserializeOwned,
98    {
99        self.try_get(key).ok()
100    }
101
102    /// Retrieves and deserializes the value associated with `key`, preserving
103    /// the reason when retrieval fails.
104    ///
105    /// # Errors
106    ///
107    /// - [`MetadataError::MissingKey`] if `key` does not exist
108    /// - [`MetadataError::DeserializationError`] if the stored JSON value cannot
109    ///   be deserialized into `T`
110    pub fn try_get<T>(&self, key: &str) -> MetadataResult<T>
111    where
112        T: DeserializeOwned,
113    {
114        let value = self
115            .0
116            .get(key)
117            .ok_or_else(|| MetadataError::MissingKey(key.to_string()))?;
118        serde_json::from_value(value.clone())
119            .map_err(|error| MetadataError::deserialization_error::<T>(key, value, error))
120    }
121
122    /// Returns a reference to the raw [`Value`] for `key`, or `None` if absent.
123    #[inline]
124    pub fn get_raw(&self, key: &str) -> Option<&Value> {
125        self.0.get(key)
126    }
127
128    /// Returns the coarse JSON value type of the value stored under `key`.
129    ///
130    /// This is a lightweight inspection API inspired by the stricter type
131    /// introspection facilities in `qubit-value`, adapted to `Metadata`'s
132    /// open-ended JSON storage model.
133    #[inline]
134    pub fn value_type(&self, key: &str) -> Option<MetadataValueType> {
135        self.0.get(key).map(MetadataValueType::of)
136    }
137
138    /// Retrieves and deserializes the value associated with `key`, or returns
139    /// `default` if lookup fails for any reason.
140    ///
141    /// This mirrors the forgiving default-value style used by `qubit-config`.
142    /// It is intentionally convenience-oriented: both missing keys and type
143    /// mismatches fall back to the supplied default.
144    #[inline]
145    #[must_use]
146    pub fn get_or<T>(&self, key: &str, default: T) -> T
147    where
148        T: DeserializeOwned,
149    {
150        self.try_get(key).unwrap_or(default)
151    }
152
153    /// Serializes `value` and inserts it under `key`.
154    ///
155    /// This is the convenience version of [`Metadata::try_set`]. It preserves
156    /// the current ergonomic API and panics if serialization fails.
157    #[inline]
158    pub fn set<T>(&mut self, key: impl Into<String>, value: T) -> Option<Value>
159    where
160        T: Serialize,
161    {
162        self.try_set(key, value)
163            .expect("Metadata::set: value must be serializable to serde_json::Value")
164    }
165
166    /// Serializes `value` and inserts it under `key`, preserving serialization
167    /// failures instead of panicking.
168    ///
169    /// # Errors
170    ///
171    /// Returns [`MetadataError::SerializationError`] when `value` fails to
172    /// serialize into [`serde_json::Value`].
173    pub fn try_set<T>(&mut self, key: impl Into<String>, value: T) -> MetadataResult<Option<Value>>
174    where
175        T: Serialize,
176    {
177        let key = key.into();
178        let json = serde_json::to_value(value)
179            .map_err(|error| MetadataError::serialization_error(key.clone(), error))?;
180        Ok(self.0.insert(key, json))
181    }
182
183    /// Inserts a raw [`Value`] directly, bypassing serialization.
184    ///
185    /// Returns the previous value if present.
186    #[inline]
187    pub fn set_raw(&mut self, key: impl Into<String>, value: Value) -> Option<Value> {
188        self.0.insert(key.into(), value)
189    }
190
191    /// Removes the entry for `key` and returns the raw [`Value`] if it existed.
192    #[inline]
193    pub fn remove(&mut self, key: &str) -> Option<Value> {
194        self.0.remove(key)
195    }
196
197    /// Removes all entries.
198    #[inline]
199    pub fn clear(&mut self) {
200        self.0.clear();
201    }
202
203    /// Returns an iterator over `(&str, &Value)` pairs in key-sorted order.
204    #[inline]
205    pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
206        self.0.iter().map(|(k, v)| (k.as_str(), v))
207    }
208
209    /// Returns an iterator over the keys in sorted order.
210    #[inline]
211    pub fn keys(&self) -> impl Iterator<Item = &str> {
212        self.0.keys().map(String::as_str)
213    }
214
215    /// Returns an iterator over the raw values in key-sorted order.
216    #[inline]
217    pub fn values(&self) -> impl Iterator<Item = &Value> {
218        self.0.values()
219    }
220
221    /// Merges all entries from `other` into `self`, overwriting existing keys.
222    pub fn merge(&mut self, other: Metadata) {
223        for (k, v) in other.0 {
224            self.0.insert(k, v);
225        }
226    }
227
228    /// Returns a new `Metadata` that contains all entries from both `self` and
229    /// `other`.  Entries in `other` take precedence on key conflicts.
230    #[must_use]
231    pub fn merged(&self, other: &Metadata) -> Metadata {
232        let mut result = self.clone();
233        for (k, v) in &other.0 {
234            result.0.insert(k.clone(), v.clone());
235        }
236        result
237    }
238
239    /// Retains only the entries for which `predicate` returns `true`.
240    #[inline]
241    pub fn retain<F>(&mut self, mut predicate: F)
242    where
243        F: FnMut(&str, &Value) -> bool,
244    {
245        self.0.retain(|k, v| predicate(k.as_str(), v));
246    }
247
248    /// Converts this `Metadata` into its underlying [`BTreeMap`].
249    #[inline]
250    pub fn into_inner(self) -> BTreeMap<String, Value> {
251        self.0
252    }
253}
254
255impl From<BTreeMap<String, Value>> for Metadata {
256    #[inline]
257    fn from(map: BTreeMap<String, Value>) -> Self {
258        Self(map)
259    }
260}
261
262impl From<Metadata> for BTreeMap<String, Value> {
263    #[inline]
264    fn from(meta: Metadata) -> Self {
265        meta.0
266    }
267}
268
269impl FromIterator<(String, Value)> for Metadata {
270    #[inline]
271    fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Self {
272        Self(iter.into_iter().collect())
273    }
274}
275
276impl IntoIterator for Metadata {
277    type Item = (String, Value);
278    type IntoIter = std::collections::btree_map::IntoIter<String, Value>;
279
280    #[inline]
281    fn into_iter(self) -> Self::IntoIter {
282        self.0.into_iter()
283    }
284}
285
286impl<'a> IntoIterator for &'a Metadata {
287    type Item = (&'a String, &'a Value);
288    type IntoIter = std::collections::btree_map::Iter<'a, String, Value>;
289
290    #[inline]
291    fn into_iter(self) -> Self::IntoIter {
292        self.0.iter()
293    }
294}
295
296impl Extend<(String, Value)> for Metadata {
297    #[inline]
298    fn extend<I: IntoIterator<Item = (String, Value)>>(&mut self, iter: I) {
299        self.0.extend(iter);
300    }
301}