Skip to main content

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 pointWhat it is forOverride or implement
Request policyPlatform-wide data and infrastructure boundary before select/insert/update/delete/recover executionRequestPolicy::enforce_select, enforce_insert, enforce_update, enforce_delete, enforce_recover
Repository behaviorEntity-scoped repository lifecycle customizationRepositoryBehavior::before_select, before_insert, before_update, before_delete, before_recover, relation_loads
CheckersValidation and record fixing with translated validation messagesChecker::check_and_fix or TypedChecker::check_and_fix_typed
Event sinkAudit, outbox, or integration events after mutationsEntityEventSink::on_event
ID generationCustom internal ID allocationInternalIdGenerator::generate_id
Schema providerProvider-owned schema bootstrapSchemaProvider::ensure_schema
Query executorDatabase or storage execution implementationProvider executor traits used by the repository layer
Runtime moduleGenerated metadata, repositories, behaviors, and checkers registrationRuntimeModule, generated module(), module_with_behaviors(), module_with_behaviors_and_checkers()
Context resourcesRequest-scoped infrastructure, tenant, operator, trace, and service objectsUserContext::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.