qubit-redact 0.8.1

Rule-driven redaction for fields, diagnostics, HTTP data, and Rust domain objects
Documentation
// =============================================================================
//    Copyright (c) 2025 - 2026 Haixing Hu.
//
//    SPDX-License-Identifier: Apache-2.0
//
//    Licensed under the Apache License, Version 2.0.
// =============================================================================
//! Structured serialization for maps classified by their keys.

use std::collections::BTreeMap;
use std::collections::HashMap;

use serde::Serialize;
use serde::Serializer;
use serde::ser::SerializeMap;

use super::budget_serialize::BudgetSerialize;
use super::redact_level_serialize::RedactLevelSerialize;
use super::redact_serialize_scope::admit_collection_items;
use super::redacted_level_serialize_ref::RedactedLevelSerializeRef;
use crate::RedactionPolicy;
use crate::Sensitivity;

/// Internal structured serialization capability for policy-classified maps.
#[doc(hidden)]
pub trait RedactMapSerialize {
    /// Serializes a map after classifying each value by its field key.
    ///
    /// # Errors
    ///
    /// Propagates admission or downstream serialization failures.
    ///
    /// # Type Parameters
    ///
    /// - `S`: Downstream serializer defining the success and error types.
    ///
    /// # Parameters
    ///
    /// - `serializer`: Destination receiving the map or absent optional value.
    /// - `policy`: Immutable policy classifying values by runtime key.
    ///
    /// # Returns
    ///
    /// The destination result after all emitted data passes shared admission.
    fn serialize_redacted_map<S>(&self, serializer: S, policy: &RedactionPolicy) -> Result<S::Ok, S::Error>
    where
        S: Serializer;
}

/// Generates classified serialization for each supported map and its optional
/// form.
macro_rules! map_redact_serialize {
    ($map:ty) => {
        impl<K, V> RedactMapSerialize for $map
        where
            K: AsRef<str> + Serialize,
            V: RedactLevelSerialize + Serialize,
        {
            /// Serializes an available map with key classification and
            /// cumulative child admission.
            ///
            /// # Errors
            ///
            /// Propagates budget or downstream serialization failures.
            ///
            /// # Type Parameters
            ///
            /// - `S`: Downstream serializer defining the success and error types.
            ///
            /// # Parameters
            ///
            /// - `serializer`: Destination receiving the map or absent optional value.
            /// - `policy`: Immutable policy classifying values by runtime key.
            ///
            /// # Returns
            ///
            /// The destination result after all emitted data passes shared
            /// admission.
            fn serialize_redacted_map<S>(&self, serializer: S, policy: &RedactionPolicy) -> Result<S::Ok, S::Error>
            where
                S: Serializer,
            {
                if !admit_collection_items(self.len()) {
                    return super::redact_serialize_scope::serialize_payload(
                        serializer,
                        policy.masking().mask_opaque(Sensitivity::Secret).as_ref(),
                    );
                }
                let mut map = serializer.serialize_map(Some(self.len()))?;
                for (key, value) in self {
                    let key_name = key.as_ref();
                    super::redact_serialize_scope::check_key_bytes::<S::Error>(key_name)?;
                    map.serialize_key(&BudgetSerialize::new(key))?;
                    if !policy.is_disabled() {
                        if let Some(level) = policy.sensitivity_for(key_name) {
                            map.serialize_value(&RedactedLevelSerializeRef::new(value, policy, level))?;
                            continue;
                        }
                    }
                    map.serialize_value(&BudgetSerialize::new(value))?;
                }
                map.end()
            }
        }

        impl<K, V> RedactMapSerialize for Option<$map>
        where
            K: AsRef<str> + Serialize,
            V: RedactLevelSerialize + Serialize,
        {
            /// Serializes an available map with key classification and
            /// cumulative child admission.
            ///
            /// # Errors
            ///
            /// Propagates budget or downstream serialization failures.
            ///
            /// # Type Parameters
            ///
            /// - `S`: Downstream serializer defining the success and error types.
            ///
            /// # Parameters
            ///
            /// - `serializer`: Destination receiving the map or absent optional value.
            /// - `policy`: Immutable policy classifying values by runtime key.
            ///
            /// # Returns
            ///
            /// The destination result after all emitted data passes shared
            /// admission.
            fn serialize_redacted_map<S>(&self, serializer: S, policy: &RedactionPolicy) -> Result<S::Ok, S::Error>
            where
                S: Serializer,
            {
                match self {
                    Some(value) => value.serialize_redacted_map(serializer, policy),
                    None => serializer.serialize_none(),
                }
            }
        }
    };
}

map_redact_serialize!(HashMap<K, V>);
map_redact_serialize!(BTreeMap<K, V>);