pub struct EvalTrace { /* private fields */ }Expand description
A tree of PolicyEvalResult nodes capturing every policy decision made
during an access evaluation.
Returned as part of AccessEvaluation. Use EvalTrace::format to render
a human-readable tree, useful for debugging and audit logging.
The tree records policy decisions. The inputs that informed a decision —
the facts a fact-backed policy consulted — are attached to the individual
PolicyEvalResult nodes as FactProvenance and rendered inline by
EvalTrace::format. Operational fact-load telemetry (latency, batch
fan-out, cache hits) is a separate concern surfaced through tracing spans
(gatehouse.fact_load), not through this tree.
§Example
// An empty trace produces a fallback message:
let empty = EvalTrace::new();
assert_eq!(empty.format(), "No evaluation trace available");
// A trace built from a policy result renders a decision tree:
let trace = EvalTrace::with_root(PolicyEvalResult::granted(
"AdminPolicy",
Some("User is admin".into()),
));
assert!(trace.format().contains("AdminPolicy GRANTED"));Implementations§
Source§impl EvalTrace
impl EvalTrace
Sourcepub fn with_root(result: PolicyEvalResult) -> Self
pub fn with_root(result: PolicyEvalResult) -> Self
Creates a trace with the given PolicyEvalResult as the root node.
Sourcepub fn set_root(&mut self, result: PolicyEvalResult)
pub fn set_root(&mut self, result: PolicyEvalResult)
Sets (or replaces) the root node of the evaluation tree.
Sourcepub fn root(&self) -> Option<&PolicyEvalResult>
pub fn root(&self) -> Option<&PolicyEvalResult>
Returns a reference to the root PolicyEvalResult, if present.
Sourcepub fn format(&self) -> String
pub fn format(&self) -> String
Returns a formatted, indented representation of the evaluation tree.
Each node shows a ✔ or ✘ prefix, the policy name, and the reason.
Combined nodes indent their children for readability.
Examples found in repository?
More examples
77async fn main() {
78 let user = User::new();
79 let document = Document::new();
80 let action = ViewAction;
81 let context = ();
82 // These policies have no fact sources, so each evaluation binds
83 // `EvaluationSession::empty()`. The checker contributes its own `OR` root
84 // to the trace; the combinator's short-circuit behaviour (what this example
85 // measures) happens inside it regardless.
86
87 println!("=== AND Policy Short-Circuit Example ===");
88 {
89 let counter = Arc::new(AtomicUsize::new(0));
90
91 // Create an AND policy with a non-grant policy first
92 let and_policy = CountingPolicy {
93 allow: false,
94 name: "DenyFirst".to_string(),
95 counter: counter.clone(),
96 }
97 .and(CountingPolicy {
98 allow: true,
99 name: "AllowSecond".to_string(),
100 counter: counter.clone(),
101 });
102
103 let mut checker = PermissionChecker::<DocumentDomain>::new();
104 checker.add_policy(and_policy);
105
106 println!("Evaluating AND(DenyFirst, AllowSecond):");
107 let session = EvaluationSession::empty();
108 let result = checker
109 .bind(&session, &user, &action, &context)
110 .check(&document)
111 .await;
112 println!(
113 "Result: {}",
114 if result.is_granted() {
115 "Access granted"
116 } else {
117 "Access denied"
118 }
119 );
120 println!("Policies evaluated: {}", counter.load(Ordering::SeqCst));
121 println!("Trace:\n{}", result.trace().format());
122
123 // The second policy should not be evaluated due to short-circuiting
124 assert_eq!(counter.load(Ordering::SeqCst), 1);
125 }
126
127 println!("\n=== OR Policy Short-Circuit Example ===");
128 {
129 let counter = Arc::new(AtomicUsize::new(0));
130
131 // Create an OR policy with an allow policy first
132 let or_policy = CountingPolicy {
133 allow: true,
134 name: "AllowFirst".to_string(),
135 counter: counter.clone(),
136 }
137 .or(CountingPolicy {
138 allow: false,
139 name: "DenySecond".to_string(),
140 counter: counter.clone(),
141 });
142
143 let mut checker = PermissionChecker::<DocumentDomain>::new();
144 checker.add_policy(or_policy);
145
146 println!("Evaluating OR(AllowFirst, DenySecond):");
147 let session = EvaluationSession::empty();
148 let result = checker
149 .bind(&session, &user, &action, &context)
150 .check(&document)
151 .await;
152 println!(
153 "Result: {}",
154 if result.is_granted() {
155 "Access granted"
156 } else {
157 "Access denied"
158 }
159 );
160 println!("Policies evaluated: {}", counter.load(Ordering::SeqCst));
161 println!("Trace:\n{}", result.trace().format());
162
163 // The second policy should not be evaluated due to short-circuiting
164 assert_eq!(counter.load(Ordering::SeqCst), 1);
165 }
166
167 println!("\n=== Complex Nested Policy Example ===");
168 {
169 let counter = Arc::new(AtomicUsize::new(0));
170
171 // Create a complex nested policy: OR(AND(Deny, Allow), Allow)
172 let inner_and = CountingPolicy {
173 allow: false,
174 name: "DenyInner".to_string(),
175 counter: counter.clone(),
176 }
177 .and(CountingPolicy {
178 allow: true,
179 name: "AllowInner".to_string(),
180 counter: counter.clone(),
181 });
182
183 let complex_policy = inner_and.or(CountingPolicy {
184 allow: true,
185 name: "AllowOuter".to_string(),
186 counter: counter.clone(),
187 });
188
189 let mut checker = PermissionChecker::<DocumentDomain>::new();
190 checker.add_policy(complex_policy);
191
192 println!("Evaluating OR(AND(DenyInner, AllowInner), AllowOuter):");
193 let session = EvaluationSession::empty();
194 let result = checker
195 .bind(&session, &user, &action, &context)
196 .check(&document)
197 .await;
198 println!(
199 "Result: {} for document with ID {} for user with ID {}",
200 if result.is_granted() {
201 "Access granted"
202 } else {
203 "Access denied"
204 },
205 document.id,
206 user.id
207 );
208 println!("Policies evaluated: {}", counter.load(Ordering::SeqCst));
209 println!("Trace:\n{}", result.trace().format());
210
211 // The inner AND should evaluate only DenyInner (shorts-circuit),
212 // then the OR continues to AllowOuter which grants access
213 assert_eq!(counter.load(Ordering::SeqCst), 2);
214 }
215}