pub struct SecureSelect<E: EntityTrait, S> { /* private fields */ }Expand description
A type-safe wrapper around SeaORM’s Select that enforces scoping.
This wrapper uses the typestate pattern to ensure that queries cannot
be executed without first applying access control via .scope_with().
When scoped (SecureSelect<E, Scoped>), the query carries the AccessScope
internally. This allows related-entity queries (find_also_related,
find_with_related) to automatically apply the same scope to related
entities without requiring the scope to be passed again.
§Type Parameters
E: TheSeaORMentity typeS: The typestate (UnscopedorScoped)
§Example
use modkit_db::secure::{AccessScope, SecureEntityExt};
let scope = AccessScope::tenants_only(vec![tenant_id]);
let users = user::Entity::find()
.secure() // Returns SecureSelect<E, Unscoped>
.scope_with(&scope) // Returns SecureSelect<E, Scoped>
.all(conn) // Now can execute
.await?;
// Related queries auto-apply scope:
let orders_with_items = Order::find()
.secure()
.scope_with(&scope)
.find_with_related(line_item::Entity) // scope auto-applied to LineItem
.all(conn)
.await?;Implementations§
Source§impl<E> SecureSelect<E, Unscoped>
impl<E> SecureSelect<E, Unscoped>
Sourcepub fn scope_with(self, scope: &AccessScope) -> SecureSelect<E, Scoped>
pub fn scope_with(self, scope: &AccessScope) -> SecureSelect<E, Scoped>
Apply access control scope to this query, transitioning to the Scoped state.
The scope is stored internally and will be automatically applied to any
related-entity queries (e.g., find_also_related, find_with_related).
This applies the implicit policy:
- Empty scope → deny all
- Tenants only → filter by tenant
- Resources only → filter by resource IDs
- Both → AND them together
Sourcepub fn scope_with_arc(self, scope: Arc<AccessScope>) -> SecureSelect<E, Scoped>
pub fn scope_with_arc(self, scope: Arc<AccessScope>) -> SecureSelect<E, Scoped>
Apply access control scope using an Arc<AccessScope>.
This is useful when you already have the scope in an Arc and want to
avoid an extra clone.
Source§impl<E> SecureSelect<E, Scoped>where
E: EntityTrait,
impl<E> SecureSelect<E, Scoped>where
E: EntityTrait,
Sourcepub async fn all(
self,
runner: &impl DBRunner,
) -> Result<Vec<E::Model>, ScopeError>
pub async fn all( self, runner: &impl DBRunner, ) -> Result<Vec<E::Model>, ScopeError>
Execute the query and return all matching results.
§Errors
Returns ScopeError::Db if the database query fails.
Sourcepub async fn one(
self,
runner: &impl DBRunner,
) -> Result<Option<E::Model>, ScopeError>
pub async fn one( self, runner: &impl DBRunner, ) -> Result<Option<E::Model>, ScopeError>
Execute the query and return at most one result.
§Errors
Returns ScopeError::Db if the database query fails.
Sourcepub async fn count(self, runner: &impl DBRunner) -> Result<u64, ScopeError>
pub async fn count(self, runner: &impl DBRunner) -> Result<u64, ScopeError>
Execute the query and return the number of matching results.
§Errors
Returns ScopeError::Db if the database query fails.
Sourcepub fn and_id(self, id: Uuid) -> Result<Self, ScopeError>
pub fn and_id(self, id: Uuid) -> Result<Self, ScopeError>
Add an additional filter for a specific resource ID.
This is useful when you want to further narrow a scoped query to a single resource.
§Example
let user = User::find()
.secure()
.scope_with(&scope)?
.and_id(user_id)
.one(conn)
.await?;§Errors
Returns ScopeError::Invalid if the entity doesn’t have a resource column.
Source§impl<E> SecureSelect<E, Scoped>where
E: EntityTrait,
impl<E> SecureSelect<E, Scoped>where
E: EntityTrait,
Sourcepub fn filter(self, filter: Condition) -> Self
pub fn filter(self, filter: Condition) -> Self
Add additional filters to the scoped query. The scope conditions remain in place.
Sourcepub fn order_by<C>(self, col: C, order: Order) -> Selfwhere
C: IntoSimpleExpr,
pub fn order_by<C>(self, col: C, order: Order) -> Selfwhere
C: IntoSimpleExpr,
Add ordering to the scoped query.
Sourcepub fn and_scope_for<J>(self, scope: &AccessScope) -> Self
pub fn and_scope_for<J>(self, scope: &AccessScope) -> Self
Sourcepub fn scope_via_exists<J>(self, scope: &AccessScope) -> Self
pub fn scope_via_exists<J>(self, scope: &AccessScope) -> Self
Apply scoping via EXISTS subquery on a related entity.
This is particularly useful when the base entity doesn’t have a tenant column but is related to one that does.
§Note
This is a simplified version that filters by tenant on the joined entity.
For complex join predicates, use into_inner() and build custom EXISTS clauses.
§Example
// Find settings that exist in a tenant-scoped relationship
GlobalSetting::find()
.secure()
.scope_with(&AccessScope::resources_only(vec![]))?
.scope_via_exists::<TenantSetting>(&scope)
.all(conn)
.await?Sourcepub fn into_inner(self) -> Select<E>
pub fn into_inner(self) -> Select<E>
Unwrap the inner SeaORM Select for advanced use cases.
§Safety
The caller must ensure they don’t remove or bypass the security
conditions that were applied during .scope_with().
Source§impl<E> SecureSelect<E, Scoped>where
E: EntityTrait,
impl<E> SecureSelect<E, Scoped>where
E: EntityTrait,
Sourcepub fn scope(&self) -> &AccessScope
pub fn scope(&self) -> &AccessScope
Get a reference to the stored scope.
This is useful when you need to pass the scope to other secure operations.
Sourcepub fn scope_arc(&self) -> Arc<AccessScope>
pub fn scope_arc(&self) -> Arc<AccessScope>
Get the stored scope as an Arc.
This is useful when you need to share the scope without cloning.
Find related entities using find_also_related with automatic scoping.
This executes a LEFT JOIN to fetch the primary entity along with an
optional related entity. The related entity will be None if no
matching row exists.
§Automatic Scoping
- The primary entity
Eis already scoped by the parentSecureSelect. - The related entity
Rwill automatically have tenant filtering applied if it has a tenant column (i.e.,R::tenant_col()returnsSome). - For global entities (those with
#[secure(no_tenant)]), no additional filtering is applied — the scoping becomes a no-op automatically.
This unified API handles both tenant-scoped and global entities transparently. The caller does not need to know or care whether the related entity is tenant-scoped or global.
§Entity Requirements
All entities used with this method must derive Scopable. For global entities,
use #[secure(no_tenant, no_resource, no_owner, no_type)].
§Example
let scope = AccessScope::tenants_only(vec![tenant_id]);
// Tenant-scoped related entity - scope is auto-applied to Customer
let rows: Vec<(order::Model, Option<customer::Model>)> = Order::find()
.secure()
.scope_with(&scope)
.find_also_related(customer::Entity)
.all(db)
.await?;
// Global related entity (no tenant column) - no filtering applied to GlobalConfig
let rows: Vec<(order::Model, Option<global_config::Model>)> = Order::find()
.secure()
.scope_with(&scope)
.find_also_related(global_config::Entity) // same API, auto no-op!
.all(db)
.await?;Find all related entities using find_with_related with automatic scoping.
This executes a query to fetch the primary entity along with all its related entities (one-to-many relationship).
§Automatic Scoping
- The primary entity
Eis already scoped by the parentSecureSelect. - The related entity
Rwill automatically have tenant filtering applied if it has a tenant column (i.e.,R::tenant_col()returnsSome). - For global entities (those with
#[secure(no_tenant)]), no additional filtering is applied — the scoping becomes a no-op automatically.
This unified API handles both tenant-scoped and global entities transparently. The caller does not need to know or care whether the related entity is tenant-scoped or global.
§Entity Requirements
All entities used with this method must derive Scopable. For global entities,
use #[secure(no_tenant, no_resource, no_owner, no_type)].
§Example
let scope = AccessScope::tenants_only(vec![tenant_id]);
// Tenant-scoped related entity - scope is auto-applied to LineItem
let rows: Vec<(order::Model, Vec<line_item::Model>)> = Order::find()
.secure()
.scope_with(&scope)
.find_with_related(line_item::Entity)
.all(db)
.await?;
// Global related entity (no tenant column) - no filtering applied to SystemTag
let rows: Vec<(order::Model, Vec<system_tag::Model>)> = Order::find()
.secure()
.scope_with(&scope)
.find_with_related(system_tag::Entity) // same API, auto no-op!
.all(db)
.await?;Trait Implementations§
Source§impl<E: Clone + EntityTrait, S: Clone> Clone for SecureSelect<E, S>
impl<E: Clone + EntityTrait, S: Clone> Clone for SecureSelect<E, S>
Source§fn clone(&self) -> SecureSelect<E, S>
fn clone(&self) -> SecureSelect<E, S>
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl<E, S> Freeze for SecureSelect<E, S>where
S: Freeze,
impl<E, S> !RefUnwindSafe for SecureSelect<E, S>
impl<E, S> Send for SecureSelect<E, S>where
S: Send,
impl<E, S> Sync for SecureSelect<E, S>where
S: Sync,
impl<E, S> Unpin for SecureSelect<E, S>
impl<E, S> !UnwindSafe for SecureSelect<E, S>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere
T: ?Sized,
Source§fn fg(&self, value: Color) -> Painted<&T>
fn fg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the foreground set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like red() and
green(), which have the same functionality but are
pithier.
§Example
Set foreground color to white using fg():
use yansi::{Paint, Color};
painted.fg(Color::White);Set foreground color to white using white().
use yansi::Paint;
painted.white();Source§fn bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
Source§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
Source§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
Source§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
Source§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
Source§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
Source§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
Source§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
Source§fn bg(&self, value: Color) -> Painted<&T>
fn bg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the background set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like on_red() and
on_green(), which have the same functionality but
are pithier.
§Example
Set background color to red using fg():
use yansi::{Paint, Color};
painted.bg(Color::Red);Set background color to red using on_red().
use yansi::Paint;
painted.on_red();Source§fn on_primary(&self) -> Painted<&T>
fn on_primary(&self) -> Painted<&T>
Source§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
Source§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
Source§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
Source§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
Source§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
Source§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
Source§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
Source§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
Source§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
Source§fn attr(&self, value: Attribute) -> Painted<&T>
fn attr(&self, value: Attribute) -> Painted<&T>
Enables the styling Attribute value.
This method should be used rarely. Instead, prefer to use
attribute-specific builder methods like bold() and
underline(), which have the same functionality
but are pithier.
§Example
Make text bold using attr():
use yansi::{Paint, Attribute};
painted.attr(Attribute::Bold);Make text bold using using bold().
use yansi::Paint;
painted.bold();Source§fn rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
Source§fn quirk(&self, value: Quirk) -> Painted<&T>
fn quirk(&self, value: Quirk) -> Painted<&T>
Enables the yansi Quirk value.
This method should be used rarely. Instead, prefer to use quirk-specific
builder methods like mask() and
wrap(), which have the same functionality but are
pithier.
§Example
Enable wrapping using .quirk():
use yansi::{Paint, Quirk};
painted.quirk(Quirk::Wrap);Enable wrapping using wrap().
use yansi::Paint;
painted.wrap();Source§fn clear(&self) -> Painted<&T>
👎Deprecated since 1.0.1: renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
fn clear(&self) -> Painted<&T>
resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.Source§fn whenever(&self, value: Condition) -> Painted<&T>
fn whenever(&self, value: Condition) -> Painted<&T>
Conditionally enable styling based on whether the Condition value
applies. Replaces any previous condition.
See the crate level docs for more details.
§Example
Enable styling painted only when both stdout and stderr are TTYs:
use yansi::{Paint, Condition};
painted.red().on_yellow().whenever(Condition::STDOUTERR_ARE_TTY);