1use async_trait::async_trait;
31use std::marker::PhantomData;
32use std::sync::Arc;
33
34pub type PolicyDecision = Result<bool, String>;
40
41#[derive(Debug, Clone, Default)]
49pub struct PolicyContext {
50 pub user_id: Option<String>,
52 pub roles: Vec<String>,
54 pub permissions: Vec<String>,
56 pub metadata: std::collections::HashMap<String, String>,
58}
59
60impl PolicyContext {
61 pub fn new() -> Self {
62 Self::default()
63 }
64
65 pub fn with_user(mut self, user_id: impl Into<String>) -> Self {
66 self.user_id = Some(user_id.into());
67 self
68 }
69
70 pub fn with_role(mut self, role: impl Into<String>) -> Self {
71 self.roles.push(role.into());
72 self
73 }
74
75 pub fn with_permission(mut self, perm: impl Into<String>) -> Self {
76 self.permissions.push(perm.into());
77 self
78 }
79
80 pub fn has_role(&self, role: &str) -> bool {
81 self.roles.iter().any(|r| r == role)
82 }
83
84 pub fn has_permission(&self, perm: &str) -> bool {
85 self.permissions.iter().any(|p| p == perm)
86 }
87}
88
89#[derive(Debug, Clone, PartialEq)]
94pub enum PolicyOutcome {
95 Permit,
97 Deny(String),
99}
100
101impl PolicyOutcome {
102 pub fn is_permit(&self) -> bool {
103 matches!(self, PolicyOutcome::Permit)
104 }
105
106 pub fn is_deny(&self) -> bool {
107 matches!(self, PolicyOutcome::Deny(_))
108 }
109
110 pub fn reason(&self) -> Option<&str> {
111 match self {
112 PolicyOutcome::Deny(reason) => Some(reason.as_str()),
113 PolicyOutcome::Permit => None,
114 }
115 }
116
117 pub fn into_decision(self) -> PolicyDecision {
118 match self {
119 PolicyOutcome::Permit => Ok(true),
120 PolicyOutcome::Deny(reason) => Err(reason),
121 }
122 }
123}
124
125#[async_trait]
133pub trait DomainPolicy<E: Send + Sync + 'static>: Send + Sync {
134 async fn can_create(&self, _entity: &E) -> PolicyDecision {
136 Ok(true)
137 }
138
139 async fn can_update(&self, _current: &E, _updated: &E) -> PolicyDecision {
141 Ok(true)
142 }
143
144 async fn can_delete(&self, _entity: &E) -> PolicyDecision {
146 Ok(true)
147 }
148
149 async fn can_restore(&self, _entity: &E) -> PolicyDecision {
151 Ok(true)
152 }
153
154 async fn enforce_create(&self, entity: &E) -> Result<(), String> {
156 self.can_create(entity).await?.then_some(()).ok_or_else(|| "create denied".into())
157 }
158
159 async fn enforce_delete(&self, entity: &E) -> Result<(), String> {
161 self.can_delete(entity).await?.then_some(()).ok_or_else(|| "delete denied".into())
162 }
163}
164
165pub struct PermitAllPolicy<E> {
170 _phantom: PhantomData<E>,
171}
172
173impl<E> PermitAllPolicy<E> {
174 pub fn new() -> Self {
175 Self {
176 _phantom: PhantomData,
177 }
178 }
179}
180
181impl<E> Default for PermitAllPolicy<E> {
182 fn default() -> Self {
183 Self::new()
184 }
185}
186
187#[async_trait]
188impl<E: Send + Sync + 'static> DomainPolicy<E> for PermitAllPolicy<E> {
189 }
191
192pub struct DenyAllPolicy<E> {
194 reason: String,
195 _phantom: PhantomData<E>,
196}
197
198impl<E> DenyAllPolicy<E> {
199 pub fn new(reason: impl Into<String>) -> Self {
200 Self {
201 reason: reason.into(),
202 _phantom: PhantomData,
203 }
204 }
205}
206
207#[async_trait]
208impl<E: Send + Sync + 'static> DomainPolicy<E> for DenyAllPolicy<E> {
209 async fn can_create(&self, _entity: &E) -> PolicyDecision {
210 Err(self.reason.clone())
211 }
212 async fn can_update(&self, _current: &E, _updated: &E) -> PolicyDecision {
213 Err(self.reason.clone())
214 }
215 async fn can_delete(&self, _entity: &E) -> PolicyDecision {
216 Err(self.reason.clone())
217 }
218 async fn can_restore(&self, _entity: &E) -> PolicyDecision {
219 Err(self.reason.clone())
220 }
221}
222
223pub struct AllOfPolicy<E> {
226 policies: Vec<Arc<dyn DomainPolicy<E>>>,
227}
228
229impl<E: Send + Sync + 'static> AllOfPolicy<E> {
230 pub fn new(policies: Vec<Arc<dyn DomainPolicy<E>>>) -> Self {
231 Self { policies }
232 }
233}
234
235#[async_trait]
236impl<E: Send + Sync + 'static> DomainPolicy<E> for AllOfPolicy<E> {
237 async fn can_create(&self, entity: &E) -> PolicyDecision {
238 for p in &self.policies {
239 match p.can_create(entity).await {
240 Ok(true) => {}
241 other => return other,
242 }
243 }
244 Ok(true)
245 }
246
247 async fn can_update(&self, current: &E, updated: &E) -> PolicyDecision {
248 for p in &self.policies {
249 match p.can_update(current, updated).await {
250 Ok(true) => {}
251 other => return other,
252 }
253 }
254 Ok(true)
255 }
256
257 async fn can_delete(&self, entity: &E) -> PolicyDecision {
258 for p in &self.policies {
259 match p.can_delete(entity).await {
260 Ok(true) => {}
261 other => return other,
262 }
263 }
264 Ok(true)
265 }
266
267 async fn can_restore(&self, entity: &E) -> PolicyDecision {
268 for p in &self.policies {
269 match p.can_restore(entity).await {
270 Ok(true) => {}
271 other => return other,
272 }
273 }
274 Ok(true)
275 }
276}
277
278pub struct AnyOfPolicy<E> {
281 policies: Vec<Arc<dyn DomainPolicy<E>>>,
282 deny_reason: String,
283}
284
285impl<E: Send + Sync + 'static> AnyOfPolicy<E> {
286 pub fn new(policies: Vec<Arc<dyn DomainPolicy<E>>>, deny_reason: impl Into<String>) -> Self {
287 Self {
288 policies,
289 deny_reason: deny_reason.into(),
290 }
291 }
292}
293
294#[async_trait]
295impl<E: Send + Sync + 'static> DomainPolicy<E> for AnyOfPolicy<E> {
296 async fn can_create(&self, entity: &E) -> PolicyDecision {
297 for p in &self.policies {
298 if matches!(p.can_create(entity).await, Ok(true)) {
299 return Ok(true);
300 }
301 }
302 Err(self.deny_reason.clone())
303 }
304
305 async fn can_update(&self, current: &E, updated: &E) -> PolicyDecision {
306 for p in &self.policies {
307 if matches!(p.can_update(current, updated).await, Ok(true)) {
308 return Ok(true);
309 }
310 }
311 Err(self.deny_reason.clone())
312 }
313
314 async fn can_delete(&self, entity: &E) -> PolicyDecision {
315 for p in &self.policies {
316 if matches!(p.can_delete(entity).await, Ok(true)) {
317 return Ok(true);
318 }
319 }
320 Err(self.deny_reason.clone())
321 }
322
323 async fn can_restore(&self, entity: &E) -> PolicyDecision {
324 for p in &self.policies {
325 if matches!(p.can_restore(entity).await, Ok(true)) {
326 return Ok(true);
327 }
328 }
329 Err(self.deny_reason.clone())
330 }
331}
332
333pub struct NotPolicy<E> {
335 inner: Arc<dyn DomainPolicy<E>>,
336 deny_reason: String,
337}
338
339impl<E: Send + Sync + 'static> NotPolicy<E> {
340 pub fn new(inner: Arc<dyn DomainPolicy<E>>, deny_reason: impl Into<String>) -> Self {
341 Self {
342 inner,
343 deny_reason: deny_reason.into(),
344 }
345 }
346}
347
348#[async_trait]
349impl<E: Send + Sync + 'static> DomainPolicy<E> for NotPolicy<E> {
350 async fn can_create(&self, entity: &E) -> PolicyDecision {
351 match self.inner.can_create(entity).await {
352 Ok(true) => Err(self.deny_reason.clone()),
353 _ => Ok(true),
354 }
355 }
356
357 async fn can_delete(&self, entity: &E) -> PolicyDecision {
358 match self.inner.can_delete(entity).await {
359 Ok(true) => Err(self.deny_reason.clone()),
360 _ => Ok(true),
361 }
362 }
363
364 async fn can_update(&self, current: &E, updated: &E) -> PolicyDecision {
365 match self.inner.can_update(current, updated).await {
366 Ok(true) => Err(self.deny_reason.clone()),
367 _ => Ok(true),
368 }
369 }
370
371 async fn can_restore(&self, entity: &E) -> PolicyDecision {
372 match self.inner.can_restore(entity).await {
373 Ok(true) => Err(self.deny_reason.clone()),
374 _ => Ok(true),
375 }
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382
383 #[derive(Debug)]
384 struct FakeEntity {
385 owner_id: String,
386 is_locked: bool,
387 }
388
389 struct LockedPolicy;
390
391 #[async_trait]
392 impl DomainPolicy<FakeEntity> for LockedPolicy {
393 async fn can_delete(&self, entity: &FakeEntity) -> PolicyDecision {
394 if entity.is_locked {
395 Err("entity is locked".into())
396 } else {
397 Ok(true)
398 }
399 }
400 }
401
402 #[tokio::test]
403 async fn permit_all_always_permits() {
404 let policy = PermitAllPolicy::<FakeEntity>::new();
405 let entity = FakeEntity {
406 owner_id: "u1".into(),
407 is_locked: false,
408 };
409 assert!(matches!(policy.can_create(&entity).await, Ok(true)));
410 assert!(matches!(policy.can_delete(&entity).await, Ok(true)));
411 }
412
413 #[tokio::test]
414 async fn deny_all_always_denies_with_reason() {
415 let policy = DenyAllPolicy::<FakeEntity>::new("deprecated");
416 let entity = FakeEntity {
417 owner_id: "u1".into(),
418 is_locked: false,
419 };
420 assert!(policy.can_create(&entity).await.is_err());
421 assert!(policy.can_delete(&entity).await.is_err());
422 }
423
424 #[tokio::test]
425 async fn locked_policy_denies_delete_for_locked_entity() {
426 let policy = LockedPolicy;
427 let locked = FakeEntity {
428 owner_id: "u1".into(),
429 is_locked: true,
430 };
431 let unlocked = FakeEntity {
432 owner_id: "u1".into(),
433 is_locked: false,
434 };
435 assert!(policy.can_delete(&locked).await.is_err());
436 assert!(matches!(policy.can_delete(&unlocked).await, Ok(true)));
437 }
438
439 #[tokio::test]
440 async fn all_of_denies_when_any_denies() {
441 let entity = FakeEntity {
442 owner_id: "u1".into(),
443 is_locked: true,
444 };
445 let policy: AllOfPolicy<FakeEntity> = AllOfPolicy::new(vec![
446 Arc::new(PermitAllPolicy::new()),
447 Arc::new(LockedPolicy),
448 ]);
449 assert!(policy.can_delete(&entity).await.is_err());
450 }
451
452 #[tokio::test]
453 async fn any_of_permits_when_one_permits() {
454 let entity = FakeEntity {
455 owner_id: "u1".into(),
456 is_locked: false,
457 };
458 let policy: AnyOfPolicy<FakeEntity> = AnyOfPolicy::new(
459 vec![Arc::new(LockedPolicy)],
460 "all policies denied",
461 );
462 assert!(matches!(policy.can_delete(&entity).await, Ok(true)));
463 }
464}