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}