nest-rs-authz 0.2.0

CASL-style authorization for nestrs: one ability definition driving an access gate, a SeaORM query pre-filter, and response field-masking. Transport bindings (`http`, `graphql`, `mcp`) live behind Cargo features; the database-coupled extractors (`Bind`, `bind`, `LoaderScope`, `WsDataContext`) live in `nest-rs-seaorm` so the engine stays free of a data-layer dependency.
Documentation
//! [`Scope<E, A>`] — the caller's row-level filter as a handler argument, for a
//! handler that builds its own query. Prefer letting `Repo` scope itself; reach
//! for `Scope` only when a custom query is unavoidable.

use std::marker::PhantomData;
use std::ops::Deref;
use std::sync::Arc;

use poem::http::StatusCode;
use poem::{Error, FromRequest, Request, RequestBody, Result};
use sea_orm::EntityTrait;
use sea_orm::sea_query::Condition;

use crate::{Ability, ActionMarker};

/// The row-level [`Condition`] the caller may apply for action `A` on `E`.
pub struct Scope<E, A>(Condition, PhantomData<fn() -> (E, A)>);

impl<E, A> Scope<E, A> {
    pub fn into_inner(self) -> Condition {
        self.0
    }
}

impl<E, A> Deref for Scope<E, A> {
    type Target = Condition;
    fn deref(&self) -> &Condition {
        &self.0
    }
}

impl<'a, E, A> FromRequest<'a> for Scope<E, A>
where
    E: EntityTrait,
    A: ActionMarker,
{
    async fn from_request(req: &'a Request, _body: &mut RequestBody) -> Result<Self> {
        let ability = req.extensions().get::<Arc<Ability>>().ok_or_else(|| {
            Error::from_string(
                "missing request `Ability` — is the ability guard applied to this route?",
                StatusCode::INTERNAL_SERVER_ERROR,
            )
        })?;
        Ok(Scope(ability.condition_for::<E>(A::ACTION), PhantomData))
    }
}