Skip to main content

sova_scope/
owner.rs

1//! Resource ownership resolvers.
2
3use async_trait::async_trait;
4use sova_db::DbHandle;
5use sova_core::Result;
6use std::collections::HashMap;
7use std::future::Future;
8use std::sync::Arc;
9
10#[async_trait]
11pub trait OwnerResolver: Send + Sync {
12    async fn owner_user_id(&self, db: &DbHandle, resource_id: i64) -> Result<Option<i64>>;
13}
14
15#[derive(Default)]
16pub struct OwnerRegistry {
17    resolvers: HashMap<&'static str, Arc<dyn OwnerResolver>>,
18}
19
20impl OwnerRegistry {
21    pub fn register(&mut self, kind: &'static str, resolver: Arc<dyn OwnerResolver>) {
22        self.resolvers.insert(kind, resolver);
23    }
24
25    pub async fn is_owner(
26        &self,
27        db: &DbHandle,
28        resource: Option<crate::types::ResourceRef>,
29        user_id: i64,
30    ) -> Result<bool> {
31        let Some(resource) = resource else {
32            return Ok(false);
33        };
34        let Some(resolver) = self.resolvers.get(resource.kind) else {
35            return Ok(false);
36        };
37        Ok(resolver
38            .owner_user_id(db, resource.id)
39            .await?
40            .is_some_and(|id| id == user_id))
41    }
42}
43
44struct CallbackOwner<F>(F);
45
46#[async_trait]
47impl<F, Fut> OwnerResolver for CallbackOwner<F>
48where
49    F: Fn(DbHandle, i64) -> Fut + Send + Sync,
50    Fut: Future<Output = Result<Option<i64>>> + Send,
51{
52    async fn owner_user_id(&self, db: &DbHandle, resource_id: i64) -> Result<Option<i64>> {
53        (self.0)(db.clone(), resource_id).await
54    }
55}
56
57pub struct FnOwnerResolver<F>(F);
58
59impl<F> FnOwnerResolver<F> {
60    pub fn new(f: F) -> Self {
61        Self(f)
62    }
63}
64
65impl<F, Fut> OwnerResolver for FnOwnerResolver<F>
66where
67    F: Fn(DbHandle, i64) -> Fut + Send + Sync,
68    Fut: Future<Output = Result<Option<i64>>> + Send,
69{
70    fn owner_user_id<'life0, 'life1, 'async_trait>(
71        &'life0 self,
72        db: &'life1 DbHandle,
73        resource_id: i64,
74    ) -> core::pin::Pin<
75        Box<dyn core::future::Future<Output = Result<Option<i64>>> + Send + 'async_trait>,
76    >
77    where
78        'life0: 'async_trait,
79        'life1: 'async_trait,
80    {
81        Box::pin(async move { (self.0)(db.clone(), resource_id).await })
82    }
83}
84
85pub fn callback_owner<F, Fut>(f: F) -> Arc<dyn OwnerResolver>
86where
87    F: Fn(DbHandle, i64) -> Fut + Send + Sync + 'static,
88    Fut: Future<Output = Result<Option<i64>>> + Send + 'static,
89{
90    Arc::new(CallbackOwner(f))
91}