Skip to main content

qubit_redact/policy/field/
redaction_floor_builder.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//! Builder for immutable minimum redaction floors.
9
10use std::sync::Arc;
11
12use super::FieldNameMatching;
13use super::RedactionFloor;
14use super::SensitiveFieldPreset;
15use super::Sensitivity;
16use super::UnknownFieldPolicy;
17use crate::policy::PolicyError;
18use crate::policy::PolicyLocation;
19use crate::policy::RedactionRulesBuilder;
20
21/// Builder for a [`RedactionFloor`].
22///
23/// # Examples
24///
25/// ```
26/// use qubit_redact::RedactionFloor;
27/// use qubit_redact::Sensitivity;
28///
29/// let floor = RedactionFloor::builder().raise("pin", Sensitivity::Secret)?.build()?;
30/// assert!(floor.sensitive_rules().any(|rule| rule.field() == "pin"));
31/// # Ok::<(), qubit_redact::PolicyError>(())
32/// ```
33#[derive(Debug, Clone)]
34pub struct RedactionFloorBuilder {
35    /// Mutable rules validated in the floor policy location.
36    rules: RedactionRulesBuilder,
37}
38
39impl RedactionFloorBuilder {
40    /// Creates an empty builder for the floor construction context.
41    #[must_use]
42    #[inline(always)]
43    pub(super) fn empty() -> Self {
44        Self {
45            rules: RedactionRulesBuilder::empty(PolicyLocation::Floor),
46        }
47    }
48
49    /// Copies every field rule from `floor`.
50    #[must_use]
51    #[inline(always)]
52    pub(super) fn from_floor(floor: &RedactionFloor) -> Self {
53        Self {
54            rules: RedactionRulesBuilder::from_inner(&floor.inner, PolicyLocation::Floor),
55        }
56    }
57
58    /// Sets field-name matching behavior.
59    #[must_use]
60    #[inline(always)]
61    pub fn matching(mut self, matching: FieldNameMatching) -> Self {
62        self.rules.matching(matching);
63        self
64    }
65
66    /// Sets the fallback for fields without an explicit floor rule.
67    #[must_use]
68    #[inline(always)]
69    pub fn unknown_field_policy(mut self, policy: UnknownFieldPolicy) -> Self {
70        self.rules.unknown_field_policy(policy);
71        self
72    }
73
74    /// Adds every sensitive field in one preset.
75    #[must_use]
76    #[inline(always)]
77    pub fn include_preset(mut self, preset: SensitiveFieldPreset) -> Self {
78        self.rules.include_preset(preset);
79        self
80    }
81
82    /// Raises `field` to at least `level`.
83    ///
84    /// # Errors
85    ///
86    /// Returns [`PolicyError::EmptyFieldName`] when `field` has no canonical
87    /// floor-rule name.
88    pub fn raise(mut self, field: &str, level: Sensitivity) -> Result<Self, PolicyError> {
89        self.rules.raise(field, level)?;
90        Ok(self)
91    }
92
93    /// Validates and constructs the immutable floor.
94    ///
95    /// # Errors
96    ///
97    /// Currently infallible because field names are validated when added.
98    /// The result retains the policy construction error type.
99    pub fn build(self) -> Result<RedactionFloor, PolicyError> {
100        let inner = self.rules.build_inner()?;
101        Ok(RedactionFloor { inner: Arc::new(inner) })
102    }
103}