Skip to main content

qubit_redact_derive/
lib.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//! Derive macros for borrowing, policy-aware `qubit-redact` domain objects.
9
10use proc_macro::TokenStream;
11use syn::Error;
12use syn::parse;
13
14mod attributes;
15mod expand;
16mod model;
17mod runtime_path;
18mod scalar;
19mod serde;
20
21#[cfg(test)]
22mod tests;
23
24/// Derives the borrowing `qubit_redact::Redact` implementation.
25///
26/// Fields without an attribute intentionally use ordinary `Debug` formatting.
27/// Sensitivity is downstream business-domain knowledge that the macro cannot
28/// infer reliably from a field name or Rust type. Ordinary fields are the large
29/// majority, so an explicit "not sensitive" attribute on every field would add
30/// noise without adding knowledge. Downstream types must explicitly annotate
31/// sensitive fields and review that classification when their model changes;
32/// strict policy and inspection deliberately do not override this decision.
33///
34/// Supported field modes are:
35///
36/// - `#[redact(level = "low" | "medium" | "high" | "secret")]` masks every
37///   supported scalar leaf while preserving recursive container shape. The
38///   explicit level is final for text, inspection and Serde: runtime name
39///   rules, sensitivity floors and strict mode cannot override it. Disabled
40///   policy bypasses masking but retains resource limits. `RedactScalar`
41///   newtypes are supported leaves; map keys remain ordinary unless separately
42///   annotated;
43/// - `#[redact(nested)]` delegates to nested `Redact` values;
44/// - `#[redact(map)]` classifies text-keyed map values by key;
45/// - `#[redact(json)]` recursively redacts supported JSON text or parsed Value
46///   fields;
47/// - `#[redact(skip)]` omits the field while redaction is enabled;
48/// - `#[redact(keyed_by = key)]` classifies by a sibling textual key;
49/// - `#[redact(map_key_level = "...", map_value_level = "...")]` assigns fixed
50///   levels to map keys and values (the value level is optional);
51/// - `#[redact(level = "...", display)]` selects lazy Display text for a
52///   third-party scalar, without requiring Debug or ordinary Serialize.
53///
54/// Container options `#[redact(debug)]` and `#[redact(display)]` generate
55/// policy-aware formatting implementations. `#[redact(serde)]` generates a
56/// structured `serde::Serialize` implementation. Generated formatting writes
57/// enabled-policy text directly for every completion state because it remains
58/// confidentiality-safe;
59/// callers that require completeness must use the runtime API and inspect its
60/// summary instead.
61///
62/// Generated `Debug`, `Display`, and `Serialize` implementations intentionally
63/// call `qubit_redact::Redactor::application_default()` at the start of every
64/// formatting or serialization operation. They do not capture a policy when
65/// the value is created. Replacing the process-wide application default affects
66/// subsequent generated calls, and installing a disabled default deliberately
67/// restores source values. Callers own authorization for that global debugging
68/// escape hatch. Explicit runtime redactors, composers, and batches retain the
69/// policy snapshot with which they were created.
70///
71/// # Parameters
72///
73/// * `input` - Compiler-provided derive input for a struct or enum.
74///
75/// # Returns
76///
77/// Generated implementations, or compile-error tokens for invalid input.
78///
79/// # Examples
80///
81/// ```
82/// use qubit_redact::Redactor;
83/// use qubit_redact_derive::Redact;
84///
85/// #[derive(Redact)]
86/// struct Login {
87///     user: String,
88///     #[redact(level = "secret")]
89///     password: String,
90/// }
91///
92/// let login = Login {
93///     user: "ada".to_owned(),
94///     password: "raw-secret".to_owned(),
95/// };
96/// let output = Redactor::standard().redact_text(&login);
97/// assert!(output.text().as_str().contains("ada"));
98/// assert!(!output.text().as_str().contains("raw-secret"));
99/// ```
100#[proc_macro_derive(Redact, attributes(redact, serde))]
101pub fn derive_redact(input: TokenStream) -> TokenStream {
102    parse(input)
103        .and_then(|input| expand::expand(&input))
104        .unwrap_or_else(Error::into_compile_error)
105        .into()
106}
107
108/// Derives a scalar leaf capability for a one-field value object.
109///
110/// The inner field must be a primitive scalar or another `RedactScalar`.
111/// No Debug, Display, or ordinary Serialize implementation is generated.
112/// Select the sensitivity on the field that uses this value object.
113///
114/// # Parameters
115///
116/// * `input` - Compiler-provided single-field value-object declaration.
117///
118/// # Returns
119///
120/// Delegating scalar capability implementations, or compile-error tokens for
121/// invalid shapes, attributes, or runtime paths.
122///
123/// # Examples
124///
125/// ```
126/// use qubit_redact::Redactor;
127/// use qubit_redact_derive::Redact;
128/// use qubit_redact_derive::RedactScalar;
129///
130/// #[derive(RedactScalar)]
131/// struct AccountId(u64);
132///
133/// #[derive(Redact)]
134/// struct Event {
135///     #[redact(level = "secret")]
136///     account: AccountId,
137/// }
138///
139/// let event = Event { account: AccountId(42) };
140/// let output = Redactor::standard().redact_text(&event);
141/// assert_eq!(output.text().as_str(), r#"Event { account: "<redacted>" }"#);
142/// ```
143#[proc_macro_derive(RedactScalar, attributes(redact))]
144pub fn derive_redact_scalar(input: TokenStream) -> TokenStream {
145    parse(input)
146        .and_then(|input| scalar::expand(&input))
147        .unwrap_or_else(Error::into_compile_error)
148        .into()
149}