Skip to main content

teaql_tool_core/
audit.rs

1use std::collections::HashMap;
2
3/// Identifies a specific tool module in the TeaQL ecosystem.
4/// Used as the key for per-module audit configuration.
5/// Application layer can only reference these predefined modules.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7#[non_exhaustive]
8pub enum Module {
9    // IO modules (default: Full audit)
10    Http,
11    File,
12    Cmd,
13    Email,
14    Kv,
15
16    // Security modules (default: Summary audit)
17    Crypto,
18    Jwt,
19
20    // Computation modules (default: Silent)
21    Time,
22    Id,
23    Text,
24    Decimal,
25    Money,
26    Json,
27    Regex,
28    Codec,
29    List,
30    Map,
31    Diff,
32    Url,
33    Validate,
34    Color,
35    Unit,
36    DateRange,
37    Desensitize,
38    Filter,
39    Tree,
40    System,
41}
42
43/// Controls how much detail is captured for a module's operations.
44/// Application layer picks from this closed set — no custom implementations allowed.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46#[non_exhaustive]
47pub enum AuditLevel {
48    /// No output at all. Maximum performance.
49    Silent,
50
51    /// Operation type + elapsed time only.
52    /// Example: `[HTTP GET] 127ms OK`
53    Summary,
54
55    /// Operation type + comment + target + elapsed + trace ID.
56    /// Example: `[HTTP GET] Intent: [Sync tasks] URL: https://... 127ms OK Trace: abc-123`
57    Full,
58
59    /// Full detail + captured request/response payload (truncated).
60    /// For debugging and compliance-critical environments.
61    FullWithPayload {
62        max_bytes: usize,
63    },
64}
65
66/// Per-module audit configuration.
67/// Application layer can only select from predefined presets or
68/// set individual module levels. No raw logging API is exposed.
69#[derive(Debug, Clone)]
70pub struct AuditConfig {
71    levels: HashMap<Module, AuditLevel>,
72    default_io_level: AuditLevel,
73    default_compute_level: AuditLevel,
74}
75
76impl AuditConfig {
77    /// Create a new AuditConfig with explicit default levels.
78    pub fn new(io_level: AuditLevel, compute_level: AuditLevel) -> Self {
79        Self {
80            levels: HashMap::new(),
81            default_io_level: io_level,
82            default_compute_level: compute_level,
83        }
84    }
85
86    // ─── Presets ───────────────────────────────────────────────
87
88    /// Everything silent. Use in CI/test environments or when
89    /// you want zero audit overhead.
90    pub fn silent_all() -> Self {
91        Self {
92            levels: HashMap::new(),
93            default_io_level: AuditLevel::Silent,
94            default_compute_level: AuditLevel::Silent,
95        }
96    }
97
98    /// Production defaults: IO = Full, Security = Summary, Compute = Silent.
99    pub fn production() -> Self {
100        let mut cfg = Self {
101            levels: HashMap::new(),
102            default_io_level: AuditLevel::Full,
103            default_compute_level: AuditLevel::Silent,
104        };
105        cfg.levels.insert(Module::Crypto, AuditLevel::Summary);
106        cfg.levels.insert(Module::Jwt, AuditLevel::Summary);
107        cfg
108    }
109
110    /// Focus on a single module with Full audit, silence everything else.
111    /// Ideal for development: only see what you care about.
112    pub fn focus_on(module: Module) -> Self {
113        let mut cfg = Self::silent_all();
114        cfg.levels.insert(module, AuditLevel::Full);
115        cfg
116    }
117
118    /// Focus on multiple modules, silence everything else.
119    pub fn focus_on_many(modules: &[Module]) -> Self {
120        let mut cfg = Self::silent_all();
121        for m in modules {
122            cfg.levels.insert(*m, AuditLevel::Full);
123        }
124        cfg
125    }
126
127    /// All IO modules at Full, all compute modules Silent.
128    pub fn io_only() -> Self {
129        Self {
130            levels: HashMap::new(),
131            default_io_level: AuditLevel::Full,
132            default_compute_level: AuditLevel::Silent,
133        }
134    }
135
136    /// Everything at Full. Use for deep debugging sessions.
137    pub fn verbose_all() -> Self {
138        Self {
139            levels: HashMap::new(),
140            default_io_level: AuditLevel::Full,
141            default_compute_level: AuditLevel::Full,
142        }
143    }
144
145    // ─── Per-module overrides ──────────────────────────────────
146
147    /// Set a specific module's audit level. Returns self for chaining.
148    pub fn enable(mut self, module: Module, level: AuditLevel) -> Self {
149        self.levels.insert(module, level);
150        self
151    }
152
153    /// Silence a specific module. Returns self for chaining.
154    pub fn silence(mut self, module: Module) -> Self {
155        self.levels.insert(module, AuditLevel::Silent);
156        self
157    }
158
159    // ─── Runtime query (used by framework infra layer only) ───
160
161    /// Returns the effective audit level for a given module.
162    /// Framework infrastructure calls this internally to decide
163    /// whether to log. Application code never calls this.
164    pub fn level_for(&self, module: Module) -> AuditLevel {
165        if let Some(&level) = self.levels.get(&module) {
166            return level;
167        }
168        if Self::is_io_module(module) {
169            self.default_io_level
170        } else {
171            self.default_compute_level
172        }
173    }
174
175    /// Returns true if the given module should produce any output.
176    pub fn is_active(&self, module: Module) -> bool {
177        self.level_for(module) != AuditLevel::Silent
178    }
179
180    fn is_io_module(module: Module) -> bool {
181        matches!(
182            module,
183            Module::Http
184                | Module::File
185                | Module::Cmd
186                | Module::Email
187                | Module::Kv
188                | Module::Crypto
189                | Module::Jwt
190        )
191    }
192}
193
194impl Default for AuditConfig {
195    /// Sensible defaults: IO at Full, Compute at Silent.
196    fn default() -> Self {
197        Self::production()
198    }
199}