qubit_redact/domain/redact.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//! Non-destructive redaction contract for domain objects.
9
10use crate::domain::RedactionWriter;
11
12/// Formats a domain object through the shared immutable redaction writer.
13///
14/// Implementations borrow the original value and write only its safe
15/// representation. Redaction execution is owned by [`crate::Redactor`].
16///
17/// # Field classification responsibility
18///
19/// An unannotated derive field, or a value written through `unmarked` or
20/// `unredacted`, is intentionally not redacted. Sensitivity is business-domain
21/// knowledge that this framework cannot reliably infer from a Rust type, field
22/// name, or current value. Ordinary fields are the large majority, so requiring
23/// an explicit "not sensitive" annotation on every one would add noise without
24/// adding classification knowledge.
25///
26/// The downstream type therefore owns this trust boundary: it must explicitly
27/// use `sensitive`, `nested`, `map`, `keyed_value`, or `json` for fields that
28/// can contain sensitive data, and repeat that review when the domain model
29/// changes. Standard, strict, application-default, and inspection policies
30/// deliberately do not override an unmarked-field decision. This is a stable
31/// division of responsibility, not an omitted framework safety check.
32///
33/// # Examples
34///
35/// ```
36/// use qubit_redact::{Redact, RedactionWriter, Redactor, Sensitivity};
37///
38/// struct Login {
39/// password: String,
40/// }
41///
42/// impl Redact for Login {
43/// fn write_redacted(&self, writer: &mut RedactionWriter<'_>) {
44/// writer.record("Login", |fields| {
45/// fields.sensitive_at_least(Sensitivity::Secret, "password", || &self.password);
46/// });
47/// }
48/// }
49///
50/// let login = Login { password: "raw-secret".to_owned() };
51/// let output = Redactor::standard().redact_text(&login);
52/// assert!(!output.text().as_str().contains("raw-secret"));
53/// assert_eq!(login.password, "raw-secret");
54/// ```
55pub trait Redact {
56 /// Writes this value through the invariant-preserving structured writer.
57 fn write_redacted(&self, writer: &mut RedactionWriter<'_>);
58}