Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Typed Codegen

QAIL can generate fully typed Rust modules from your schema.qail file, enabling compile-time checked table and column references.

Setup

1. Add build script

Create or update your build.rs:

fn main() {
    let out_dir = std::env::var("OUT_DIR").unwrap();
    qail_core::build::generate_typed_schema(
        "schema.qail",
        &format!("{}/schema.rs", out_dir)
    ).unwrap();
    
    println!("cargo:rerun-if-changed=schema.qail");
}

2. Include generated module

In your lib.rs or main.rs:

#![allow(unused)]
fn main() {
include!(concat!(env!("OUT_DIR"), "/schema.rs"));
}

Usage

Generated Structure

From this schema:

table users {
    id UUID primary_key
    email TEXT not_null
    age INT
}

table posts {
    id UUID primary_key
    user_id UUID ref:users.id
    title TEXT
}

QAIL generates:

#![allow(unused)]
fn main() {
pub mod users {
    pub struct Users;
    impl Table for Users { ... }
    
    pub const table: Users = Users;
    pub const id: TypedColumn<uuid::Uuid> = ...;
    pub const email: TypedColumn<String> = ...;
    pub const age: TypedColumn<i32> = ...;
}

pub mod posts {
    pub struct Posts;
    pub const table: Posts = Posts;
    pub const user_id: TypedColumn<uuid::Uuid> = ...;
}
}

Using Typed References

#![allow(unused)]
fn main() {
use schema::{users, posts};

// Tables and columns are type-safe
Qail::get(users::table)
    .columns([users::id, users::email])
    .join_on(posts::table)
}

Type Mapping

QAIL TypeRust Type
UUIDuuid::Uuid
TEXT, VARCHARString
INT, INTEGERi32
BIGINTi64
FLOAT, REALf32
DOUBLEf64
BOOLbool
TIMESTAMPchrono::DateTime<Utc>
JSON, JSONBserde_json::Value

Logic-Safe Relations (Scenario B)

QAIL codegen now generates compile-time relationship checking using the RelatedTo<T> trait.

How It Works

When schema.qail contains foreign key references:

table posts {
    user_id UUID ref:users.id
}

The codegen produces:

#![allow(unused)]
fn main() {
// Forward: child -> parent
impl RelatedTo<users::Users> for posts::Posts {
    fn join_columns() -> (&'static str, &'static str) { ("user_id", "id") }
}

// Reverse: parent -> children
impl RelatedTo<posts::Posts> for users::Users {
    fn join_columns() -> (&'static str, &'static str) { ("id", "user_id") }
}
}

Compile-Time Safety

This enables “logic-safe” joins that fail at compile time:

#![allow(unused)]
fn main() {
// ✅ Compiles - tables are related
Qail::get(users::table).join_related(posts::table)

// ❌ Compile Error: "Users: RelatedTo<Products> is not satisfied"
Qail::get(users::table).join_related(products::table)
}

Data Access Policies (Phase 4)

QAIL now supports compile-time data governance using the protected keyword.

Schema Definition

Mark sensitive columns with protected:

table users {
    id UUID primary_key
    email TEXT not_null
    password_hash TEXT protected
    two_factor_secret TEXT protected
}

Generated Types

Protected columns get TypedColumn<T, Protected> instead of TypedColumn<T, Public>:

#![allow(unused)]
fn main() {
// Public - accessible by default
pub const email: TypedColumn<String, Public> = ...;

// Protected - requires capability witness
pub const password_hash: TypedColumn<String, Protected> = ...;
}

Policy Hierarchy

PolicyDescriptionUse Case
PublicDefault, no restrictionsNormal data
ProtectedRequires AdminCap witnessPasswords, secrets
RestrictedRequires SystemCap witnessAudit-critical data

Capability Witness API

Access protected columns using the builder pattern:

#![allow(unused)]
fn main() {
use qail_core::typed::{CapabilityProvider, WithCap};    

// In your auth middleware (Root of Trust):
let admin_cap = CapabilityProvider::mint_admin();  // After JWT verification

// Build query with typed table reference (no strings!)
let query = Qail::get(users::table)                 // ✓ Typed, not string
    .with_cap(&admin_cap)                           // Prove authorization
    .column(users::email)                           // Public - always allowed
    .column_protected(users::password_hash)         // Protected - now allowed!
    .build();
}

Root of Trust

Important

AdminCap and SystemCap have sealed constructors (private fields). They can only be minted via CapabilityProvider::mint_*(). Place this in a single, auditable auth layer.

#![allow(unused)]
fn main() {
// In your AuthService (the ONLY place that can mint capabilities):
impl AuthService {
    pub fn verify_admin(&self, token: &str) -> Result<AdminCap, AuthError> {
        let claims = self.verify_jwt(token)?;
        if claims.role == "admin" {
            Ok(CapabilityProvider::mint_admin())
        } else {
            Err(AuthError::Forbidden)
        }
    }
}
}

Compile-Time Enforcement

Attempting to access protected columns without capability fails at compile time:

#![allow(unused)]
fn main() {
// ❌ Compile Error: Protected: PolicyAllowedBy<NoCap> is not satisfied
Qail::get(users::table)
    .with_cap(&NoCap)
    .column_protected(users::password_hash)
}