Skip to main content

EvalTrace

Struct EvalTrace 

Source
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

Source

pub fn new() -> Self

Creates an empty trace with no evaluation results.

Source

pub fn with_root(result: PolicyEvalResult) -> Self

Creates a trace with the given PolicyEvalResult as the root node.

Source

pub fn set_root(&mut self, result: PolicyEvalResult)

Sets (or replaces) the root node of the evaluation tree.

Source

pub fn root(&self) -> Option<&PolicyEvalResult>

Returns a reference to the root PolicyEvalResult, if present.

Source

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?
examples/mfa_freshness_context.rs (line 246)
245fn report(label: &str, eval: &AccessEvaluation) {
246    println!("{label} → {}\n{}", verdict(eval), eval.trace().format());
247}
More examples
Hide additional examples
examples/actix_web.rs (line 401)
400fn forbidden(reason: &str, trace: &EvalTrace) -> HttpResponse {
401    HttpResponse::Forbidden().body(format!("Denied: {}\n{}", reason, trace.format()))
402}
examples/combinator_policy.rs (line 121)
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}

Trait Implementations§

Source§

impl Clone for EvalTrace

Source§

fn clone(&self) -> EvalTrace

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for EvalTrace

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for EvalTrace

Source§

fn default() -> EvalTrace

Returns the “default value” for a type. Read more
Source§

impl Serialize for EvalTrace

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more