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}