Skip to main content

qubit_fs/metadata/
non_sensitive_metadata.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Validated metadata that is safe for automatic structural formatting.
9
10use crate::metadata::UserMetadata;
11
12/// Flat metadata whose keys have passed credential-sensitivity checks.
13///
14/// [`UserMetadata`] stores ordered string pairs and rejects credential-like
15/// keys when each pair is added. This type's [`Debug`] implementation prints
16/// keys only and never automatically exposes values.
17///
18/// The inner [`UserMetadata`] is not mutably exposed, so every value of this
19/// type retains the wrapper invariant after construction.
20///
21/// # Examples
22///
23/// ```rust
24/// use qubit_fs::metadata::{NonSensitiveMetadata, UserMetadata};
25///
26/// let safe = NonSensitiveMetadata::from(
27///     UserMetadata::new().with("content-language", "en")?,
28/// );
29/// assert_eq!(Some("en"), safe.get("content-language"));
30/// # Ok::<(), qubit_fs::FsError>(())
31/// ```
32#[derive(Clone, Debug, PartialEq, Default)]
33pub struct NonSensitiveMetadata(
34    /// Validated metadata whose keys do not resemble credential material.
35    UserMetadata,
36);
37
38impl NonSensitiveMetadata {
39    /// Creates empty validated metadata.
40    #[inline]
41    #[must_use]
42    pub fn new() -> Self {
43        Self(UserMetadata::new())
44    }
45
46    /// Returns the validated metadata without mutable access.
47    #[inline]
48    #[must_use]
49    pub const fn as_metadata(&self) -> &UserMetadata {
50        &self.0
51    }
52
53    /// Consumes this wrapper and returns the underlying metadata.
54    #[inline]
55    #[must_use]
56    pub fn into_metadata(self) -> UserMetadata {
57        self.0
58    }
59
60    /// Returns whether the wrapped map contains no metadata pairs.
61    #[must_use]
62    #[inline]
63    pub fn is_empty(&self) -> bool {
64        self.0.is_empty()
65    }
66
67    /// Returns whether a metadata key is present.
68    #[must_use]
69    #[inline]
70    pub fn contains_key(&self, key: &str) -> bool {
71        self.0.contains_key(key)
72    }
73
74    /// Returns the value associated with a metadata key.
75    ///
76    /// # Returns
77    /// `Some` with the value when `key` is present, or `None` otherwise.
78    #[must_use]
79    #[inline]
80    pub fn get(&self, key: &str) -> Option<&str> {
81        self.0.get(key)
82    }
83}
84
85impl From<UserMetadata> for NonSensitiveMetadata {
86    /// Wraps arbitrary user metadata.
87    #[inline]
88    fn from(metadata: UserMetadata) -> Self {
89        Self(metadata)
90    }
91}
92
93impl AsRef<UserMetadata> for NonSensitiveMetadata {
94    #[inline]
95    fn as_ref(&self) -> &UserMetadata {
96        self.as_metadata()
97    }
98}
99
100impl From<NonSensitiveMetadata> for UserMetadata {
101    #[inline]
102    fn from(metadata: NonSensitiveMetadata) -> Self {
103        metadata.into_metadata()
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use std::hint::black_box;
110
111    use super::NonSensitiveMetadata;
112    use crate::metadata::UserMetadata;
113
114    #[test]
115    fn metadata_wrapper_contract_is_executed_at_runtime() {
116        let constructor: fn() -> NonSensitiveMetadata = black_box(NonSensitiveMetadata::new);
117        let as_metadata: fn(&NonSensitiveMetadata) -> &UserMetadata = black_box(NonSensitiveMetadata::as_metadata);
118        let into_metadata: fn(NonSensitiveMetadata) -> UserMetadata = black_box(NonSensitiveMetadata::into_metadata);
119        let is_empty: fn(&NonSensitiveMetadata) -> bool = black_box(NonSensitiveMetadata::is_empty);
120        let contains_key: fn(&NonSensitiveMetadata, &str) -> bool = black_box(NonSensitiveMetadata::contains_key);
121        let get: for<'a, 'b> fn(&'a NonSensitiveMetadata, &'b str) -> Option<&'a str> =
122            black_box(NonSensitiveMetadata::get);
123
124        let empty = constructor();
125        assert!(is_empty(&empty));
126        assert!(!contains_key(&empty, "owner"));
127        assert_eq!(None, get(&empty, "owner"));
128        assert!(as_metadata(&empty).is_empty());
129
130        let user_metadata = UserMetadata::new()
131            .with("owner", "storage")
132            .expect("safe metadata key must be accepted");
133        let wrapped: NonSensitiveMetadata = black_box(user_metadata.into());
134        assert!(contains_key(&wrapped, "owner"));
135        assert_eq!(Some("storage"), get(&wrapped, "owner"));
136        assert_eq!("storage", as_metadata(&wrapped).get("owner").unwrap());
137        assert_eq!(Some("storage"), into_metadata(wrapped).get("owner"));
138    }
139}