pub trait RequestView {
fn query(&self) -> Option<&str>;
fn is_read_only(&self) -> bool {
false
}
fn database(&self) -> Option<&str> {
None
}
fn client_id(&self) -> Option<&str> {
None
}
fn tenant_id(&self) -> Option<&str> {
None
}
}
#[cfg(feature = "wasm-plugins")]
impl RequestView for crate::plugins::QueryContext {
fn query(&self) -> Option<&str> {
Some(self.query.as_str())
}
fn is_read_only(&self) -> bool {
self.is_read_only
}
fn database(&self) -> Option<&str> {
self.hook_context.database.as_deref()
}
fn client_id(&self) -> Option<&str> {
self.hook_context.client_id.as_deref()
}
fn tenant_id(&self) -> Option<&str> {
self.hook_context
.attributes
.get("tenant_id")
.map(String::as_str)
}
}
#[cfg(all(test, feature = "wasm-plugins"))]
mod tests {
use super::*;
use crate::plugins::{HookContext, QueryContext};
use std::collections::HashMap;
fn make_ctx(sql: &str, is_read_only: bool) -> QueryContext {
let mut hc = HookContext::default();
hc.client_id = Some("session-abc".to_string());
hc.database = Some("app".to_string());
hc.attributes
.insert("tenant_id".to_string(), "acme".to_string());
QueryContext {
query: sql.to_string(),
normalized: sql.to_string(),
tables: Vec::new(),
is_read_only,
hook_context: hc,
}
}
fn summarise<V: RequestView>(v: &V) -> String {
format!(
"sql={:?} ro={} db={:?} client={:?} tenant={:?}",
v.query(),
v.is_read_only(),
v.database(),
v.client_id(),
v.tenant_id(),
)
}
#[test]
fn test_request_view_for_query_context() {
let ctx = make_ctx("SELECT 1", true);
assert_eq!(ctx.query(), Some("SELECT 1"));
assert!(ctx.is_read_only());
assert_eq!(ctx.database(), Some("app"));
assert_eq!(ctx.client_id(), Some("session-abc"));
assert_eq!(ctx.tenant_id(), Some("acme"));
}
#[test]
fn test_request_view_for_query_context_write() {
let ctx = make_ctx("INSERT INTO orders VALUES (1)", false);
assert_eq!(ctx.query(), Some("INSERT INTO orders VALUES (1)"));
assert!(!ctx.is_read_only());
}
#[test]
fn test_request_view_tenant_missing() {
let hc = HookContext {
client_id: None,
database: None,
attributes: HashMap::new(),
..HookContext::default()
};
let ctx = QueryContext {
query: "SELECT 1".to_string(),
normalized: "SELECT 1".to_string(),
tables: Vec::new(),
is_read_only: true,
hook_context: hc,
};
assert_eq!(ctx.tenant_id(), None);
}
#[test]
fn test_generic_consumer_over_trait() {
let ctx = make_ctx("SELECT 42", true);
let summary = summarise(&ctx);
assert!(summary.contains("sql=Some(\"SELECT 42\")"));
assert!(summary.contains("ro=true"));
assert!(summary.contains("tenant=Some(\"acme\")"));
}
}