coil-runtime 0.1.1

HTTP runtime and request handling for the Coil framework.
Documentation
use super::*;
use std::fmt;
use std::sync::OnceLock;

pub(crate) struct DeferredPostgresRouteCapabilityAuthorizer {
    data: DataRuntimeServices,
    tenant_id: i64,
    database_url: Option<String>,
    auth_package: coil_auth::AuthModelPackageSelection,
    authorizer: OnceLock<Result<PostgresRouteCapabilityAuthorizer, String>>,
}

impl DeferredPostgresRouteCapabilityAuthorizer {
    pub(crate) fn new(
        data: DataRuntimeServices,
        tenant_id: i64,
        database_url: Option<String>,
        auth_package: coil_auth::AuthModelPackageSelection,
    ) -> Self {
        Self {
            data,
            tenant_id,
            database_url,
            auth_package,
            authorizer: OnceLock::new(),
        }
    }

    fn authorizer(&self) -> Result<&PostgresRouteCapabilityAuthorizer, RuntimeServerError> {
        match self.authorizer.get_or_init(|| self.build_authorizer()) {
            Ok(authorizer) => Ok(authorizer),
            Err(reason) => Err(RuntimeServerError::Authorization {
                reason: reason.clone(),
            }),
        }
    }

    fn build_authorizer(&self) -> Result<PostgresRouteCapabilityAuthorizer, String> {
        let runtime = self
            .database_url
            .as_ref()
            .map(|url| self.data.with_resolved_connection_url(url.clone()))
            .unwrap_or_else(|| self.data.clone());
        let client = runtime
            .connect_lazy_postgres()
            .map_err(|error| error.to_string())?;
        let engine = zanzibar::postgres::PostgresRebacEngine::new(client.pool.clone());

        Ok(PostgresRouteCapabilityAuthorizer {
            auth: coil_auth::CoilAuth::new(engine, self.tenant_id),
            package: self.auth_package.clone(),
        })
    }
}

impl fmt::Debug for DeferredPostgresRouteCapabilityAuthorizer {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("DeferredPostgresRouteCapabilityAuthorizer")
            .field("auth_package", &self.auth_package.manifest().name)
            .finish_non_exhaustive()
    }
}

impl LiveRouteCapabilityAuthorizer for DeferredPostgresRouteCapabilityAuthorizer {
    fn check_capability<'a>(
        &'a self,
        subject: &'a coil_auth::DefaultSubject,
        capability: coil_auth::Capability,
        object: &'a coil_auth::Entity,
    ) -> RouteAuthorizationFuture<'a> {
        Box::pin(async move {
            self.authorizer()?
                .check_capability(subject, capability, object)
                .await
        })
    }
}

pub(crate) struct PostgresRouteCapabilityAuthorizer {
    auth: coil_auth::CoilAuth<zanzibar::postgres::PostgresRebacEngine>,
    package: coil_auth::AuthModelPackageSelection,
}

impl PostgresRouteCapabilityAuthorizer {
    async fn check_capability(
        &self,
        subject: &coil_auth::DefaultSubject,
        capability: coil_auth::Capability,
        object: &coil_auth::Entity,
    ) -> Result<bool, RuntimeServerError> {
        self.auth
            .check_capability(self.package.package(), subject, capability, object)
            .await
            .map_err(|error| RuntimeServerError::Authorization {
                reason: error.to_string(),
            })
    }
}

impl fmt::Debug for PostgresRouteCapabilityAuthorizer {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("PostgresRouteCapabilityAuthorizer")
            .field("tenant_id", &self.auth.tenant_id())
            .field("auth_package", &self.package.manifest().name)
            .finish()
    }
}