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

Rust Integration Tutorial

This tutorial walks you through adding SCHEMABOUND to a Rust project and spinning up a local gRPC server on port 50051. By the end, you will have a running instance that can introspect SQLite databases and enforce policy on queries.

Installation

Add schemabound to your Cargo.toml:

[dependencies]
schemabound = "0.6"
tokio = { version = "1", features = ["full"] }

Start a gRPC Server

use schemabound::grpc_executor::GrpcExecutor;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let executor = GrpcExecutor::new("path/to/database.db")?;
    let handle = executor.start_server("0.0.0.0:50051").await?;
    handle.await?;
    Ok(())
}

Introspect a SQLite Database

#![allow(unused)]
fn main() {
use schemabound::mirror::introspect_sqlite_path;

let schema = introspect_sqlite_path("path/to/database.db")?;

for table in &schema.tables {
    println!("Table: {}", table.name);
    for col in &table.columns {
        println!("  Column: {} ({}){}", col.name, col.sql_type,
            if col.primary_key { " PK" } else { "" });
    }
    for trigger in &table.triggers {
        println!("  Trigger: {} {} {}", trigger.name, trigger.timing, trigger.event);
    }
}

println!("UDTs: {}", schema.user_defined_types.len());
}

Use The MirrorProvider Trait

MirrorProvider is an async trait for pluggable schema introspection:

#![allow(unused)]
fn main() {
use schemabound::{MirrorProvider, SqliteMirrorProvider};

let provider = SqliteMirrorProvider::new("database.db");
let schema = provider.introspect_schema().await?;
}

Implement the trait to integrate custom databases:

#![allow(unused)]
fn main() {
use schemabound::MirrorProvider;
use schemabound::mirror::SchemaModel;

struct MyCustomProvider;

#[async_trait::async_trait]
impl MirrorProvider for MyCustomProvider {
    async fn introspect_schema(&self) -> Result<SchemaModel, String> {
        // custom introspection logic
        Ok(SchemaModel { tables: vec![], user_defined_types: vec![] })
    }
}
}

Register a CoR Handler

Handlers are invoked in registration order for every dispatched event. Use the built-in collection from schemabound::handlers or implement EventHandler yourself:

#![allow(unused)]
fn main() {
use schemabound::{get_event_bus, AuditLogHandler, DefaultHandlerChain, SharedHandler};
use std::sync::Arc;

let chain = DefaultHandlerChain::new();
let bus = get_event_bus();

bus.register_handler(Box::new(AuditLogHandler))?;
bus.register_handler(Box::new(SharedHandler(Arc::clone(&chain.query_metrics))))?;
bus.register_handler(Box::new(SharedHandler(Arc::clone(&chain.session_activity))))?;

let snap = chain.query_metrics.snapshot();
let sessions = chain.session_activity.session_count();
}

Subscribe to Events (fire-and-forget)

#![allow(unused)]
fn main() {
use schemabound::interceptor::{get_event_bus, Event};

let bus = get_event_bus();

let id = bus.register_subscriber(Box::new(|event: &Event| {
    println!("Event: {}", event.event_type());
}))?;

bus.unregister_subscriber(id)?;
}

Apply a Policy to a Query

#![allow(unused)]
fn main() {
use schemabound::executor::{QueryServiceImpl, ValidateQueryRequest};
use schemabound::policy_engine::{
    AuthorizationContext, AuthorizedSubqueryShape, PolicyContext, SubqueryPolicy, ToolContract, ToolIntent,
};

let mut service = QueryServiceImpl::new();
service.set_db_path("database.db")?;

let request = ValidateQueryRequest {
    db_identifier: "db".into(),
    query: "SELECT id FROM users WHERE org_id IN (SELECT id FROM organizations)".into(),
    parameters: Default::default(),
};

let policy = PolicyContext {
    tool: ToolContract {
        name: "list-users".into(),
        intent: ToolIntent::ReadSelect,
        subquery_policy: SubqueryPolicy::AllowListed(vec![
            AuthorizedSubqueryShape { table: "organizations".into() },
        ]),
    },
    authorization: AuthorizationContext {
        allowed_intents: vec![ToolIntent::ReadSelect],
        grants: vec!["tool:users.read".into()],
    },
};

let response = service.validate_query_with_policy(request, policy).await?;
assert!(response.valid);
}

Next Steps