Skip to main content

qubit_metadata/
metadata.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Provides the [`Metadata`] type — a structured, key-sorted, typed key-value
9//! store.
10
11#[cfg(feature = "json")]
12use std::cell::RefCell;
13use std::collections::BTreeMap;
14use std::fmt;
15#[cfg(feature = "json")]
16use std::io::Write;
17#[cfg(feature = "json")]
18use std::rc::Rc;
19
20#[cfg(feature = "json")]
21use qubit_budget::json::JsonDecodeSession;
22#[cfg(feature = "json")]
23use qubit_budget::json::JsonEncodeLimits;
24#[cfg(feature = "json")]
25use qubit_budget::json::JsonEncodeSession;
26use qubit_datatype::ConversionLimits;
27use qubit_datatype::ConversionPolicy;
28use qubit_datatype::DataConversionTarget;
29use qubit_datatype::DataType;
30#[cfg(feature = "json")]
31use qubit_json::decode::JsonDecoder;
32#[cfg(feature = "json")]
33use qubit_json::encode::JsonEncoder;
34use qubit_redact::Redact;
35use qubit_redact::RedactionWriter;
36use qubit_redact::Redactor;
37use qubit_value::IntoValueDefault;
38use qubit_value::StrictValueRead;
39use qubit_value::Value;
40use qubit_value::ValueError;
41#[cfg(feature = "json")]
42use qubit_value::ValueWireEncodePreflight;
43use qubit_value::ValueWirePayloadV1;
44use qubit_value::ValueWirePayloadV1Seed;
45use serde::Deserialize;
46use serde::Deserializer;
47use serde::Serialize;
48use serde::Serializer;
49use serde::de;
50use serde::de::DeserializeSeed;
51#[cfg(feature = "json")]
52use serde::de::Error as DeError;
53use serde::ser::Error as SerError;
54
55use crate::MetadataError;
56use crate::MetadataResult;
57#[cfg(feature = "schema")]
58use crate::MetadataSchema;
59use crate::constants::STRICT_STRING_MAP_MAX_ENTRIES;
60use crate::constants::STRICT_STRING_MAP_MAX_KEY_BYTES;
61use crate::internal::MetadataValues;
62#[cfg(feature = "json")]
63use crate::metadata_limits::MetadataLimits;
64use crate::wire::METADATA_WIRE_VERSION_V1;
65use crate::wire::MetadataWireV1;
66use crate::wire::MetadataWireV1Seed;
67use crate::wire::MetadataWireValuesRef;
68use crate::wire::StrictStringMap;
69use crate::wire::StrictStringMapValueSeed;
70
71/// A structured, key-sorted, typed key-value store for metadata fields.
72///
73/// `Metadata` stores values as [`qubit_value::Value`], preserving concrete Rust
74/// scalar types such as `i64`, `u32`, `f64`, `String`, and `bool`.  This avoids
75/// the ambiguity of a single JSON number type while still allowing callers to
76/// store explicit `Value::Json` values when they really need JSON payloads.
77/// [`Value::Unset`] retains a declared type but represents no concrete metadata
78/// value: typed reads report [`MetadataError::ValueAccess`]. When the optional
79/// `schema` or `filter` features are enabled, their validation and matching
80/// APIs treat it as a missing concrete value.
81///
82/// Use [`Metadata::with`] for fluent construction and [`Metadata::set`] when
83/// mutating an existing object. The typed [`Metadata::get`] and
84/// [`Metadata::get_ref`] accessors read strictly; [`Metadata::convert`]
85/// explicitly converts stored values. Use [`Metadata::get_raw`] when
86/// the stored runtime [`qubit_value::Value`] must be inspected without
87/// conversion.
88///
89/// # Examples
90///
91/// ```
92/// use qubit_metadata::Metadata;
93///
94/// # fn main() -> qubit_metadata::MetadataResult<()> {
95/// let metadata = Metadata::new().with("tenant", "acme");
96/// assert_eq!(metadata.get_ref::<str>("tenant")?, "acme");
97/// # Ok(())
98/// # }
99/// ```
100#[derive(Clone, PartialEq, Default)]
101pub struct Metadata(
102    /// Stored values indexed by metadata key.
103    BTreeMap<String, Value>,
104);
105
106impl Metadata {
107    /// Creates an empty metadata object.
108    ///
109    /// # Returns
110    ///
111    /// An empty metadata object.
112    #[inline]
113    #[must_use]
114    pub fn new() -> Self {
115        Self(BTreeMap::new())
116    }
117
118    /// Decodes a strict metadata JSON envelope using the metadata profile.
119    ///
120    /// # Parameters
121    ///
122    /// * `input` - Complete untrusted JSON input.
123    ///
124    /// # Returns
125    ///
126    /// The decoded metadata object.
127    ///
128    /// # Errors
129    ///
130    /// Returns [`crate::MetadataWireDecodeError::Budget`] when the document
131    /// exceeds a shared JSON limit, or `InvalidJson` for syntax and envelope
132    /// failures. Domain limits return `Domain`; unsupported versions return
133    /// `UnsupportedVersion`.
134    #[cfg(feature = "json")]
135    #[inline]
136    pub fn decode_json_slice(input: &[u8]) -> Result<Self, crate::MetadataWireDecodeError> {
137        Self::decode_json_slice_with_limits(input, MetadataLimits::default())
138    }
139
140    /// Decodes a strict metadata JSON envelope after applying `limits`.
141    ///
142    /// # Parameters
143    ///
144    /// * `input` - Complete untrusted JSON input.
145    /// * `limits` - Shared JSON limits for this decoding session.
146    ///
147    /// # Returns
148    ///
149    /// The decoded metadata object.
150    ///
151    /// # Errors
152    ///
153    /// Returns a structured budget error before or during decoding, `Domain`
154    /// for entry/key limits, `UnsupportedVersion` for a version mismatch, or
155    /// redacted JSON errors for syntax, envelope, and scalar wire failures.
156    #[cfg(feature = "json")]
157    pub fn decode_json_slice_with_limits(
158        input: &[u8],
159        limits: MetadataLimits,
160    ) -> Result<Self, crate::MetadataWireDecodeError> {
161        limits
162            .validate()
163            .map_err(crate::MetadataWireDecodeError::InvalidLimits)?;
164        let mut decoder = JsonDecoder::new(JsonDecodeSession::from_limits(limits.json_decode()));
165        let error_slot = Rc::new(RefCell::new(None));
166        let wire = decoder
167            .decode_seed_utf8(
168                MetadataWireV1Seed::new(
169                    StrictStringMapValueSeed::new(
170                        limits.max_metadata_entries(),
171                        limits.max_key_bytes(),
172                        ValueWirePayloadV1Seed::new(),
173                    )
174                    .with_error_slot(Rc::clone(&error_slot)),
175                ),
176                input,
177            )
178            .map_err(|error| {
179                error_slot.borrow_mut().take().map_or_else(
180                    || Into::<crate::MetadataWireDecodeError>::into(error),
181                    crate::MetadataWireDecodeError::Domain,
182                )
183            })?;
184        if wire.version != METADATA_WIRE_VERSION_V1 {
185            return Err(crate::MetadataWireDecodeError::UnsupportedVersion {
186                expected: METADATA_WIRE_VERSION_V1,
187                actual: wire.version,
188            });
189        }
190        let metadata = Self(Self::from_wire(wire).map_err(|error| {
191            crate::MetadataWireDecodeError::InvalidJson(<serde_json::Error as DeError>::custom(error))
192        })?);
193        Ok(metadata)
194    }
195
196    /// Encodes this metadata object with the default JSON budget profile.
197    ///
198    /// # Returns
199    ///
200    /// Compact JSON bytes accepted by the strict metadata wire format.
201    ///
202    /// # Errors
203    ///
204    /// Returns [`crate::MetadataWireEncodeError`] when the JSON value or
205    /// output exceeds a configured budget, serialization fails, or the
206    /// destination writer rejects bytes.
207    #[cfg(feature = "json")]
208    pub fn to_json_vec(&self) -> Result<Vec<u8>, crate::MetadataWireEncodeError> {
209        self.to_json_vec_with_limits(crate::metadata_limits::default_json_encode_limits())
210    }
211
212    /// Encodes this metadata object with caller-provided JSON budgets.
213    ///
214    /// # Parameters
215    ///
216    /// * `limits` - Output and JSON-value budgets for this operation.
217    ///
218    /// # Returns
219    ///
220    /// Compact JSON bytes accepted by the strict metadata wire format.
221    ///
222    /// # Errors
223    ///
224    /// Returns [`crate::MetadataWireEncodeError`] when the JSON value or
225    /// output exceeds a configured budget, or serialization fails.
226    #[cfg(feature = "json")]
227    pub fn to_json_vec_with_limits(&self, limits: JsonEncodeLimits) -> Result<Vec<u8>, crate::MetadataWireEncodeError> {
228        let mut preflight = ValueWireEncodePreflight::new_value_limits(*limits.value_limits());
229        for value in self.0.values() {
230            preflight
231                .check_value(value)
232                .map_err(crate::MetadataWireEncodeError::from)?;
233        }
234        let session = JsonEncodeSession::from_limits(limits);
235        JsonEncoder::new(session).to_vec(self).map_err(Into::into)
236    }
237
238    /// Encodes this metadata object to a writer with the default JSON budget.
239    ///
240    /// # Parameters
241    ///
242    /// * `writer` - Destination receiving the complete compact JSON document.
243    ///
244    /// # Errors
245    ///
246    /// Returns [`crate::MetadataWireEncodeError`] when encoding exceeds a
247    /// budget, serialization fails, or `writer` rejects the output.
248    #[cfg(feature = "json")]
249    pub fn to_json_writer<W>(&self, writer: W) -> Result<(), crate::MetadataWireEncodeError>
250    where
251        W: Write,
252    {
253        self.to_json_writer_with_limits(writer, crate::metadata_limits::default_json_encode_limits())
254    }
255
256    /// Encodes this metadata object to a writer with caller-provided budgets.
257    ///
258    /// # Parameters
259    ///
260    /// * `writer` - Destination receiving the complete compact JSON document.
261    /// * `limits` - Output and JSON-value budgets for this operation.
262    ///
263    /// # Errors
264    ///
265    /// Returns [`crate::MetadataWireEncodeError`] when encoding exceeds a
266    /// budget, serialization fails, or `writer` rejects the output.
267    #[cfg(feature = "json")]
268    pub fn to_json_writer_with_limits<W>(
269        &self,
270        writer: W,
271        limits: JsonEncodeLimits,
272    ) -> Result<(), crate::MetadataWireEncodeError>
273    where
274        W: Write,
275    {
276        let mut preflight = ValueWireEncodePreflight::new_value_limits(*limits.value_limits());
277        for value in self.0.values() {
278            preflight
279                .check_value(value)
280                .map_err(crate::MetadataWireEncodeError::from)?;
281        }
282        let session = JsonEncodeSession::from_limits(limits);
283        JsonEncoder::new(session)
284            .write_buffered(writer, self)
285            .map_err(Into::into)
286    }
287
288    /// Returns `true` if there are no entries.
289    ///
290    /// # Returns
291    ///
292    /// `true` when this object contains no entries.
293    #[inline]
294    #[must_use]
295    pub fn is_empty(&self) -> bool {
296        self.0.is_empty()
297    }
298
299    /// Returns the number of key-value pairs.
300    ///
301    /// # Returns
302    ///
303    /// The number of stored entries.
304    #[inline]
305    #[must_use]
306    pub fn len(&self) -> usize {
307        self.0.len()
308    }
309
310    /// Returns `true` if the given key exists, including when it stores
311    /// [`Value::Unset`].
312    ///
313    /// # Parameters
314    ///
315    /// * `key` - Metadata key to inspect.
316    ///
317    /// # Returns
318    ///
319    /// `true` when an entry exists for `key`.
320    #[inline]
321    #[must_use]
322    pub fn contains_key(&self, key: &str) -> bool {
323        self.0.contains_key(key)
324    }
325
326    /// Strictly reads `key` as `T`, without coercing the stored runtime type.
327    ///
328    /// # Errors
329    /// Returns MissingKey for an absent key, or ValueAccess preserving type
330    /// mismatch and unset facts. Use [`Self::convert`] for coercing reads.
331    pub fn get<T: StrictValueRead>(&self, key: &str) -> MetadataResult<T> {
332        T::read_scalar(self.entry(key)?).map_err(|source| Self::map_access_error(key, source))
333    }
334
335    /// Borrows the concrete payload under `key` without copying it.
336    ///
337    /// # Errors
338    /// Returns MissingKey or ValueAccess for unset storage or a type mismatch.
339    /// The returned reference borrows this metadata object, not the key.
340    pub fn get_ref<'a, T: ?Sized>(&'a self, key: &str) -> MetadataResult<&'a T>
341    where
342        &'a T: TryFrom<&'a Value, Error = ValueError>,
343    {
344        self.entry(key)?
345            .get_ref::<T>()
346            .map_err(|source| Self::map_access_error(key, source))
347    }
348
349    /// Strictly reads `key`, returning None only for absent or matching unset
350    /// storage.
351    ///
352    /// # Errors
353    /// Preserves type mismatches, including unset storage of a different type.
354    pub fn get_optional<T: StrictValueRead>(&self, key: &str) -> MetadataResult<Option<T>> {
355        match self.get(key) {
356            Ok(value) => Ok(Some(value)),
357            Err(error) if Self::is_defaultable(&error, false) => Ok(None),
358            Err(error) => Err(error),
359        }
360    }
361
362    /// Strictly reads `key`, adapting `default` only for absent or matching
363    /// unset storage.
364    ///
365    /// # Errors
366    /// Returns the original read error for a type mismatch; never hides invalid
367    /// data.
368    pub fn get_or<T: StrictValueRead>(&self, key: &str, default: impl IntoValueDefault<T>) -> MetadataResult<T> {
369        self.get_optional(key)
370            .map(|value| value.unwrap_or_else(|| default.into_value_default()))
371    }
372
373    /// Converts `key` to `T` using the default conversion policy and limits.
374    ///
375    /// # Errors
376    /// Returns MissingKey or ValueAccess with the original missing, invalid,
377    /// unsupported, precision-loss or resource error and its source chain.
378    pub fn convert<T: DataConversionTarget>(&self, key: &str) -> MetadataResult<T> {
379        self.convert_with(key, ConversionPolicy::default_ref(), ConversionLimits::default_ref())
380    }
381
382    /// Converts `key` to `T` with explicit `policy` and `limits`.
383    ///
384    /// Each call owns one conversion budget and leaves stored data unchanged.
385    ///
386    /// # Errors
387    /// Returns MissingKey or ValueAccess preserving conversion facts and
388    /// limits.
389    pub fn convert_with<T: DataConversionTarget>(
390        &self,
391        key: &str,
392        policy: &ConversionPolicy,
393        limits: &ConversionLimits,
394    ) -> MetadataResult<T> {
395        self.entry(key)?
396            .to_with(policy, limits)
397            .map_err(|source| Self::map_access_error(key, source))
398    }
399
400    /// Converts `key`, returning None for absent, unset, or policy-missing
401    /// scalars.
402    ///
403    /// # Errors
404    /// All other conversion errors propagate with their source and key.
405    pub fn convert_optional_with<T: DataConversionTarget>(
406        &self,
407        key: &str,
408        policy: &ConversionPolicy,
409        limits: &ConversionLimits,
410    ) -> MetadataResult<Option<T>> {
411        match self.convert_with(key, policy, limits) {
412            Ok(value) => Ok(Some(value)),
413            Err(error) if Self::is_defaultable(&error, true) => Ok(None),
414            Err(error) => Err(error),
415        }
416    }
417
418    /// Converts `key`, adapting `default` only for a defaultable missing
419    /// scalar.
420    ///
421    /// # Errors
422    /// Invalid, unsupported, precision-loss and resource errors never default.
423    pub fn convert_or_with<T: DataConversionTarget>(
424        &self,
425        key: &str,
426        default: impl IntoValueDefault<T>,
427        policy: &ConversionPolicy,
428        limits: &ConversionLimits,
429    ) -> MetadataResult<T> {
430        self.convert_optional_with(key, policy, limits)
431            .map(|value| value.unwrap_or_else(|| default.into_value_default()))
432    }
433
434    /// Looks up storage, leaving unset and type classification to the value
435    /// layer.
436    fn entry(&self, key: &str) -> MetadataResult<&Value> {
437        self.get_raw(key)
438            .ok_or_else(|| MetadataError::MissingKey(key.to_owned()))
439    }
440
441    /// Attaches a key without flattening or replacing the original value error.
442    fn map_access_error(key: &str, source: ValueError) -> MetadataError {
443        MetadataError::ValueAccess {
444            key: key.to_owned(),
445            source: Box::new(source),
446        }
447    }
448
449    /// Applies the value layer's strict or conversion fallback classification.
450    fn is_defaultable(error: &MetadataError, conversion: bool) -> bool {
451        match error {
452            MetadataError::MissingKey(_) => true,
453            MetadataError::ValueAccess { source, .. } => source.missing().is_some_and(|missing| {
454                if conversion {
455                    missing.is_defaultable_for_conversion()
456                } else {
457                    missing.is_defaultable_for_strict_read()
458                }
459            }),
460            _ => false,
461        }
462    }
463
464    /// Returns a reference to the stored [`Value`] for `key`, or `None` if
465    /// absent.
466    ///
467    /// # Parameters
468    ///
469    /// * `key` - Metadata key to retrieve.
470    ///
471    /// # Returns
472    ///
473    /// The stored value, or `None` when `key` is absent.
474    #[inline]
475    #[must_use]
476    pub fn get_raw(&self, key: &str) -> Option<&Value> {
477        self.0.get(key)
478    }
479
480    /// Returns the concrete data type of the value stored under `key`.
481    ///
482    /// # Parameters
483    ///
484    /// * `key` - Metadata key to inspect.
485    ///
486    /// # Returns
487    ///
488    /// The stored value's data type, or `None` when `key` is absent.
489    #[inline]
490    #[must_use]
491    pub fn data_type(&self, key: &str) -> Option<DataType> {
492        self.0.get(key).map(Value::data_type)
493    }
494
495    /// Inserts a typed value and returns the previous value.
496    ///
497    /// # Parameters
498    ///
499    /// * `key` - Metadata key to replace.
500    /// * `value` - Typed value to store.
501    ///
502    /// # Returns
503    ///
504    /// The previous value when the key was already present, or `None`.
505    #[inline]
506    pub fn insert<T>(&mut self, key: &str, value: T) -> Option<Value>
507    where
508        T: Into<Value>,
509    {
510        self.0.insert(key.to_string(), value.into())
511    }
512
513    /// Sets a typed value and returns this metadata object for chaining.
514    ///
515    /// # Parameters
516    ///
517    /// * `key` - Metadata key to replace.
518    /// * `value` - Typed value to store.
519    ///
520    /// # Returns
521    ///
522    /// A mutable reference to this metadata object.
523    #[inline]
524    pub fn set<T>(&mut self, key: &str, value: T) -> &mut Self
525    where
526        T: Into<Value>,
527    {
528        let _ = self.insert(key, value);
529        self
530    }
531
532    /// Returns a new metadata object with `key` set to `value`.
533    ///
534    /// # Parameters
535    ///
536    /// * `key` - Metadata key to replace.
537    /// * `value` - Typed value to store.
538    ///
539    /// # Returns
540    ///
541    /// This metadata object after inserting the value.
542    #[inline]
543    #[must_use]
544    pub fn with<T>(mut self, key: &str, value: T) -> Self
545    where
546        T: Into<Value>,
547    {
548        self.set(key, value);
549        self
550    }
551
552    /// Inserts a typed value after validating it against `schema` and returns
553    /// the previous value.
554    ///
555    /// # Parameters
556    ///
557    /// * `schema` - Schema used to validate the entry.
558    /// * `key` - Metadata key to replace.
559    /// * `value` - Typed value to validate and store.
560    ///
561    /// # Returns
562    ///
563    /// The previous value when the key was already present, or `None`.
564    ///
565    /// # Errors
566    ///
567    /// Returns [`MetadataError::UnknownField`] when `key` is rejected by the
568    /// schema, [`MetadataError::MissingRequiredField`] when a required field is
569    /// assigned [`Value::Unset`], or [`MetadataError::TypeMismatch`] when the
570    /// constructed value's concrete type does not match the schema field type.
571    #[cfg(feature = "schema")]
572    #[inline]
573    pub fn insert_checked<T>(&mut self, schema: &MetadataSchema, key: &str, value: T) -> MetadataResult<Option<Value>>
574    where
575        T: Into<Value>,
576    {
577        let value = value.into();
578        schema.validate_entry(key, &value)?;
579        Ok(self.insert(key, value))
580    }
581
582    /// Sets a typed value after schema validation and returns this metadata
583    /// object for chaining.
584    ///
585    /// # Parameters
586    ///
587    /// * `schema` - Schema used to validate the entry.
588    /// * `key` - Metadata key to replace.
589    /// * `value` - Typed value to validate and store.
590    ///
591    /// # Returns
592    ///
593    /// A mutable reference to this metadata object.
594    ///
595    /// # Errors
596    ///
597    /// Returns [`MetadataError::UnknownField`] when `key` is rejected by the
598    /// schema, [`MetadataError::MissingRequiredField`] when a required field is
599    /// assigned [`Value::Unset`], or [`MetadataError::TypeMismatch`] when the
600    /// constructed value's concrete type does not match the schema field type.
601    #[cfg(feature = "schema")]
602    #[inline]
603    pub fn set_checked<T>(&mut self, schema: &MetadataSchema, key: &str, value: T) -> MetadataResult<&mut Self>
604    where
605        T: Into<Value>,
606    {
607        let _ = self.insert_checked(schema, key, value)?;
608        Ok(self)
609    }
610
611    /// Returns a new metadata object with a typed value validated and inserted.
612    ///
613    /// # Parameters
614    ///
615    /// * `schema` - Schema used to validate the entry.
616    /// * `key` - Metadata key to replace.
617    /// * `value` - Typed value to validate and store.
618    ///
619    /// # Returns
620    ///
621    /// This metadata object after inserting the validated value.
622    ///
623    /// # Errors
624    ///
625    /// Returns [`MetadataError::UnknownField`] when `key` is rejected by the
626    /// schema, [`MetadataError::MissingRequiredField`] when a required field is
627    /// assigned [`Value::Unset`], or [`MetadataError::TypeMismatch`] when the
628    /// constructed value's concrete type does not match the schema field type.
629    #[cfg(feature = "schema")]
630    #[inline]
631    pub fn with_checked<T>(mut self, schema: &MetadataSchema, key: &str, value: T) -> MetadataResult<Self>
632    where
633        T: Into<Value>,
634    {
635        self.set_checked(schema, key, value)?;
636        Ok(self)
637    }
638
639    /// Removes the entry for `key` and returns the stored [`Value`] if it
640    /// existed.
641    ///
642    /// # Parameters
643    ///
644    /// * `key` - Metadata key to remove.
645    ///
646    /// # Returns
647    ///
648    /// The removed value, or `None` when `key` was absent.
649    #[inline]
650    pub fn remove(&mut self, key: &str) -> Option<Value> {
651        self.0.remove(key)
652    }
653
654    /// Removes all entries.
655    #[inline]
656    pub fn clear(&mut self) {
657        self.0.clear();
658    }
659
660    /// Returns an iterator over `(&str, &Value)` pairs in key-sorted order.
661    ///
662    /// # Returns
663    ///
664    /// A borrowing iterator over entries in key order.
665    #[inline]
666    #[must_use = "the metadata iterator must be consumed to inspect entries"]
667    pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
668        self.0.iter().map(|(key, value)| (key.as_str(), value))
669    }
670
671    /// Returns an iterator over the keys in sorted order.
672    ///
673    /// # Returns
674    ///
675    /// A borrowing iterator over keys in sorted order.
676    #[inline]
677    #[must_use = "the metadata key iterator must be consumed to inspect keys"]
678    pub fn keys(&self) -> impl Iterator<Item = &str> {
679        self.0.keys().map(String::as_str)
680    }
681
682    /// Returns an iterator over the values in key-sorted order.
683    ///
684    /// # Returns
685    ///
686    /// A borrowing iterator over values in key order.
687    #[inline]
688    #[must_use = "the metadata value iterator must be consumed to inspect values"]
689    pub fn values(&self) -> impl Iterator<Item = &Value> {
690        self.0.values()
691    }
692
693    /// Merges all entries from `other` into `self`, overwriting existing keys.
694    ///
695    /// # Parameters
696    ///
697    /// * `other` - Metadata entries to consume and merge.
698    pub fn merge(&mut self, mut other: Metadata) {
699        self.0.append(&mut other.0);
700    }
701
702    /// Returns a new `Metadata` that contains entries from `self` and `other`.
703    ///
704    /// Entries from `other` take precedence on key conflicts.
705    ///
706    /// # Parameters
707    ///
708    /// * `other` - Metadata entries to merge.
709    ///
710    /// # Returns
711    ///
712    /// A merged copy without modifying either input.
713    #[must_use]
714    pub fn merged(&self, other: &Metadata) -> Metadata {
715        let mut result = self.clone();
716        let mut right = other.0.clone();
717        result.0.append(&mut right);
718        result
719    }
720
721    /// Retains only the entries for which `predicate` returns `true`.
722    ///
723    /// # Parameters
724    ///
725    /// * `predicate` - Callback invoked for each key and value; returning
726    ///   `false` removes that entry.
727    #[inline]
728    pub fn retain<F>(&mut self, mut predicate: F)
729    where
730        F: FnMut(&str, &Value) -> bool,
731    {
732        self.0.retain(|key, value| predicate(key.as_str(), value));
733    }
734
735    /// Converts this metadata object into its underlying map.
736    ///
737    /// # Returns
738    ///
739    /// The owned, key-sorted map of metadata values.
740    #[inline]
741    #[must_use]
742    pub fn into_inner(self) -> BTreeMap<String, Value> {
743        self.0
744    }
745
746    /// Validates that this metadata object fits the strict V1 wire contract.
747    ///
748    /// This preflight checks the same entry-count and key-byte limits enforced
749    /// by [`Serialize::serialize`], allowing callers to reject invalid metadata
750    /// at the write boundary instead of discovering the error during encoding.
751    ///
752    /// # Returns
753    ///
754    /// `Ok(())` when every entry can satisfy the metadata map limits.
755    ///
756    /// # Errors
757    ///
758    /// Returns [`MetadataError::WireLimitExceeded`] when the entry count or a
759    /// key exceeds the strict V1 limit.
760    #[inline]
761    pub fn validate_wire_contract(&self) -> MetadataResult<()> {
762        if self.0.len() > STRICT_STRING_MAP_MAX_ENTRIES {
763            return Err(MetadataError::WireLimitExceeded {
764                kind: crate::MetadataWireLimitKind::Entries,
765                value: self.0.len(),
766                maximum: STRICT_STRING_MAP_MAX_ENTRIES,
767            });
768        }
769        if let Some(key) = self.0.keys().find(|key| key.len() > STRICT_STRING_MAP_MAX_KEY_BYTES) {
770            return Err(MetadataError::WireLimitExceeded {
771                kind: crate::MetadataWireLimitKind::KeyBytes,
772                value: key.len(),
773                maximum: STRICT_STRING_MAP_MAX_KEY_BYTES,
774            });
775        }
776        Ok(())
777    }
778}
779
780impl Redact for Metadata {
781    /// Writes a policy-redacted metadata representation.
782    ///
783    /// Metadata is pure domain structure, so this traversal consumes nodes,
784    /// collection items, and output bytes but no diagnostic input bytes. The
785    /// metadata node and its map field are admitted before the stored map is
786    /// accessed. The map writer then enters exactly one map node and admits
787    /// each exact remaining entry before iterator advancement. Its admitted-
788    /// item path classifies entries without charging duplicate keyed root and
789    /// field nodes; pass-through values still enter their legitimate nested
790    /// value scopes.
791    ///
792    /// # Parameters
793    ///
794    /// * `session` - Shared policy and cumulative diagnostic budgets.
795    /// * `formatter` - Destination formatting context.
796    ///
797    /// # Returns
798    ///
799    /// The formatter result for the admitted safe map representation.
800    ///
801    /// # Errors
802    ///
803    /// Returns [`fmt::Error`] when the destination rejects safe output.
804    fn write_redacted(&self, writer: &mut RedactionWriter<'_>) {
805        writer.record("Metadata", |fields| {
806            fields.nested("values", &MetadataValues(&self.0));
807        });
808    }
809}
810
811impl fmt::Debug for Metadata {
812    /// Writes the strict-policy redacted representation.
813    #[inline]
814    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
815        let output = Redactor::strict().redact_text(self);
816        let text = output.text_or_marker("<redaction incomplete>");
817        formatter.write_str(text.as_ref())
818    }
819}
820
821impl fmt::Display for Metadata {
822    /// Writes a bounded, strict-policy redacted representation as
823    /// single-line diagnostic text.
824    ///
825    /// The strict policy redacts values according to the diagnostic policy, but
826    /// this output is not a confidentiality boundary for arbitrary user-defined
827    /// keys or error text.
828    #[inline]
829    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
830        let output = Redactor::strict().redact_text(self);
831        let text = output.text_or_marker("<redaction incomplete>");
832        formatter.write_str(text.as_ref())
833    }
834}
835
836impl Serialize for Metadata {
837    /// Serializes metadata as the strict v1 envelope.
838    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
839    where
840        S: Serializer,
841    {
842        self.validate_wire_contract().map_err(<S::Error as SerError>::custom)?;
843        MetadataWireV1 {
844            version: METADATA_WIRE_VERSION_V1,
845            values: MetadataWireValuesRef(&self.0),
846        }
847        .serialize(serializer)
848    }
849}
850
851impl<'de> Deserialize<'de> for Metadata {
852    /// Deserializes only the strict v1 envelope.
853    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
854    where
855        D: Deserializer<'de>,
856    {
857        let wire: MetadataWireV1<StrictStringMap<ValueWirePayloadV1>> =
858            MetadataWireV1Seed::new(StrictStringMapValueSeed::new(
859                STRICT_STRING_MAP_MAX_ENTRIES,
860                STRICT_STRING_MAP_MAX_KEY_BYTES,
861                ValueWirePayloadV1Seed::new(),
862            ))
863            .deserialize(deserializer)?;
864        if wire.version != METADATA_WIRE_VERSION_V1 {
865            return Err(de::Error::custom("unsupported Metadata wire format version"));
866        }
867        let values = Self::from_wire(wire).map_err(de::Error::custom)?;
868        Ok(Self(values))
869    }
870}
871
872impl Metadata {
873    /// Converts a decoded wire envelope into scalar metadata values.
874    fn from_wire(
875        wire: MetadataWireV1<StrictStringMap<ValueWirePayloadV1>>,
876    ) -> Result<BTreeMap<String, Value>, &'static str> {
877        wire.values
878            .into_inner()
879            .into_iter()
880            .map(|(key, value)| {
881                value
882                    .into_container()
883                    .into_scalar()
884                    .map(|value| (key, value))
885                    .map_err(|_| "metadata values must use scalar V1 payloads")
886            })
887            .collect()
888    }
889}
890
891impl From<BTreeMap<String, Value>> for Metadata {
892    #[inline]
893    fn from(map: BTreeMap<String, Value>) -> Self {
894        Self(map)
895    }
896}
897
898impl From<Metadata> for BTreeMap<String, Value> {
899    #[inline]
900    fn from(meta: Metadata) -> Self {
901        meta.0
902    }
903}
904
905impl FromIterator<(String, Value)> for Metadata {
906    #[inline]
907    fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Self {
908        Self(iter.into_iter().collect())
909    }
910}
911
912impl IntoIterator for Metadata {
913    type IntoIter = std::collections::btree_map::IntoIter<String, Value>;
914    type Item = (String, Value);
915
916    #[inline]
917    fn into_iter(self) -> Self::IntoIter {
918        self.0.into_iter()
919    }
920}
921
922impl<'a> IntoIterator for &'a Metadata {
923    type IntoIter = std::collections::btree_map::Iter<'a, String, Value>;
924    type Item = (&'a String, &'a Value);
925
926    #[inline]
927    fn into_iter(self) -> Self::IntoIter {
928        self.0.iter()
929    }
930}
931
932impl Extend<(String, Value)> for Metadata {
933    #[inline]
934    fn extend<I: IntoIterator<Item = (String, Value)>>(&mut self, iter: I) {
935        self.0.extend(iter);
936    }
937}