Rust Runtime Extension Points
TeaQL Rust keeps the generated Q API fluent, but moves platform-level control
into runtime assembly. Application code can describe the business query, while
UserContext owns the infrastructure boundary that decides whether the request
is safe to execute.
The most important platform hook is RequestPolicy.
Request Policy
RequestPolicy is a global runtime policy installed on UserContext. It runs
after entity-scoped RepositoryBehavior, so it is the final place to add tenant
filters, reject unsafe request shapes, cap list sizes, or enforce platform-wide
rules before a query or mutation reaches the provider.
use teaql_core::{Expr, SelectQuery};
use teaql_runtime::{RequestPolicy, RuntimeError, UserContext};
pub struct MultiTalentPolicy;
impl RequestPolicy for MultiTalentPolicy {
fn enforce_select(
&self,
ctx: &UserContext,
query: &mut SelectQuery,
) -> Result<(), RuntimeError> {
if matches!(query.entity.as_str(), "Candidate" | "TalentProfile") {
let customer_id = ctx
.get_named_resource::<u64>("customer_account_id")
.copied()
.ok_or_else(|| RuntimeError::Policy("missing customer account".to_owned()))?;
let tenant_filter = Expr::eq("customer_account_id", customer_id);
query.filter = Some(match query.filter.take() {
Some(existing) => existing.and_expr(tenant_filter),
None => tenant_filter,
});
}
if query.raw_sql.is_some() {
return Err(RuntimeError::Policy(
"raw SQL is not allowed for normal users".to_owned(),
));
}
if let Some(slice) = &mut query.slice {
if slice.limit.is_none_or(|limit| limit > 200) {
slice.limit = Some(200);
}
}
Ok(())
}
}
Register it when assembling the runtime:
let mut ctx = teaql_runtime::UserContext::new()
.with_module(multi_talent::module_with_behaviors_and_checkers())
.with_request_policy(MultiTalentPolicy);
ctx.insert_named_resource("customer_account_id", 10001_u64);
The same trait can also enforce mutation policy:
impl RequestPolicy for MultiTalentPolicy {
fn enforce_insert(
&self,
ctx: &UserContext,
command: &mut teaql_core::InsertCommand,
) -> Result<(), RuntimeError> {
if command.entity == "Candidate" {
let customer_id = ctx
.get_named_resource::<u64>("customer_account_id")
.copied()
.ok_or_else(|| RuntimeError::Policy("missing customer account".to_owned()))?;
command
.values
.insert("customer_account_id".to_owned(), customer_id.into());
}
Ok(())
}
}
Use RequestPolicy for platform-level rules such as tenant isolation,
data-residency boundaries, raw SQL restrictions, page-size caps, audit markers,
and infrastructure safety limits.
Behavior vs Policy
RepositoryBehavior is entity-scoped. RequestPolicy is platform-scoped.
Use RepositoryBehavior when the rule belongs to one entity's repository
lifecycle. Use RequestPolicy when the rule protects the whole platform or the
customer's data boundary.
Generated Q request
-> RepositoryBehavior for the entity
-> RequestPolicy on UserContext
-> Repository
-> database provider
That order lets entity code express domain behavior first. The runtime policy still gets the final decision before execution.
Other Extension Points
| Extension point | What it is for | Override or implement |
|---|---|---|
| Request policy | Platform-wide data and infrastructure boundary before select/insert/update/delete/recover execution | RequestPolicy::enforce_select, enforce_insert, enforce_update, enforce_delete, enforce_recover |
| Repository behavior | Entity-scoped repository lifecycle customization | RepositoryBehavior::before_select, before_insert, before_update, before_delete, before_recover, relation_loads |
| Checkers | Validation and record fixing with translated validation messages | Checker::check_and_fix or TypedChecker::check_and_fix_typed |
| Event sink | Audit, outbox, or integration events after mutations | EntityEventSink::on_event |
| ID generation | Custom internal ID allocation | InternalIdGenerator::generate_id |
| Schema provider | Provider-owned schema bootstrap | SchemaProvider::ensure_schema |
| Query executor | Database or storage execution implementation | Provider executor traits used by the repository layer |
| Runtime module | Generated metadata, repositories, behaviors, and checkers registration | RuntimeModule, generated module(), module_with_behaviors(), module_with_behaviors_and_checkers() |
| Context resources | Request-scoped infrastructure, tenant, operator, trace, and service objects | UserContext::insert_resource, insert_named_resource, put_local |
Design Rule
Keep generated query code readable:
let candidates = Q::candidates()
.select_name()
.select_email()
.with_skills_contain("Rust")
.page(0, 20)
.execute_for_list(&ctx)
.await?;
Then use runtime policy to make the request safe to execute. That keeps business intent visible while letting platform engineers enforce security, observability, and operational limits in one place.