combinator_policy/
combinator_policy.rs1use async_trait::async_trait;
12use gatehouse::*;
13use std::sync::atomic::{AtomicUsize, Ordering};
14use std::sync::Arc;
15use uuid::Uuid;
16
17#[derive(Debug, Clone)]
19struct User {
20 id: Uuid,
21}
22impl User {
23 fn new() -> Self {
24 Self { id: Uuid::new_v4() }
25 }
26}
27
28#[derive(Debug, Clone)]
29struct Document {
30 id: Uuid,
31}
32impl Document {
33 fn new() -> Self {
34 Self { id: Uuid::new_v4() }
35 }
36}
37
38#[derive(Debug, Clone)]
39struct ViewAction;
40
41struct DocumentDomain;
42
43impl PolicyDomain for DocumentDomain {
44 type Subject = User;
45 type Action = ViewAction;
46 type Resource = Document;
47 type Context = ();
48}
49
50struct CountingPolicy {
52 allow: bool,
53 name: String,
54 counter: Arc<AtomicUsize>,
55}
56
57#[async_trait]
58impl Policy<DocumentDomain> for CountingPolicy {
59 async fn evaluate(&self, ctx: &EvalCtx<'_, DocumentDomain>) -> PolicyEvalResult {
60 self.counter.fetch_add(1, Ordering::SeqCst);
62 println!("Evaluating policy: {}", self.name);
63
64 if self.allow {
65 ctx.grant(format!("{} grants access", self.name))
66 } else {
67 ctx.not_applicable(format!("{} denies access", self.name))
68 }
69 }
70
71 fn policy_type(&self) -> std::borrow::Cow<'static, str> {
72 std::borrow::Cow::Owned(self.name.clone())
73 }
74}
75
76#[tokio::main]
77async fn main() {
78 let user = User::new();
79 let document = Document::new();
80 let action = ViewAction;
81 let context = ();
82 println!("=== AND Policy Short-Circuit Example ===");
88 {
89 let counter = Arc::new(AtomicUsize::new(0));
90
91 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 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 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 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 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 assert_eq!(counter.load(Ordering::SeqCst), 2);
214 }
215}