Files
quicproquo/crates/quicproquo-gen/src/generators/hook.rs
Chris Nennemann dc4e4e49a0 feat: Phase 9 — developer experience, extensibility, and community growth
New crates:
- quicproquo-bot: Bot SDK with polling API + JSON pipe mode
- quicproquo-kt: Key Transparency Merkle log (RFC 9162 subset)
- quicproquo-plugin-api: no_std C-compatible plugin vtable API
- quicproquo-gen: scaffolding tool (qpq-gen plugin/bot/rpc/hook)

Server features:
- ServerHooks trait wired into all RPC handlers (enqueue, fetch, auth,
  channel, registration) with plugin rejection support
- Dynamic plugin loader (libloading) with --plugin-dir config
- Delivery proof canary tokens (Ed25519 server signatures on enqueue)
- Key Transparency Merkle log with inclusion proofs on resolveUser

Core library:
- Safety numbers (60-digit HMAC-SHA256 key verification codes)
- Verifiable transcript archive (CBOR + ChaCha20-Poly1305 + hash chain)
- Delivery proof verification utility
- Criterion benchmarks (hybrid KEM, MLS, identity, sealed sender, padding)

Client:
- /verify REPL command for out-of-band key verification
- Full-screen TUI via Ratatui (feature-gated --features tui)
- qpq export / qpq export-verify CLI subcommands
- KT inclusion proof verification on user resolution

Also: ROADMAP Phase 9 added, bot SDK docs, server hooks docs,
crate-responsibilities updated, example plugins (rate_limit, logging).
2026-03-03 22:47:38 +01:00

135 lines
3.9 KiB
Rust

pub fn generate(name: &str) -> Result<(), String> {
let snake = name.to_lowercase().replace(['-', ' '], "_");
let pascal = to_pascal_case(&snake);
println!("=== Adding hook event: on_{snake} ===");
println!();
println!("Follow these steps to add a new `on_{snake}` hook event.");
println!();
// Step 1: Event struct
println!("--- Step 1: Event struct ---");
println!("File: crates/quicproquo-server/src/hooks.rs");
println!();
println!(
r#"/// Event data for {snake} operations.
#[derive(Clone, Debug)]
pub struct {pascal}Event {{
// TODO: add your event fields here
// Example:
// pub channel_id: Vec<u8>,
// pub user_key: Vec<u8>,
}}
"#,
);
// Step 2: Trait method
println!("--- Step 2: Trait method ---");
println!("File: crates/quicproquo-server/src/hooks.rs");
println!();
println!("Add to the `ServerHooks` trait:");
println!();
println!(
r#" /// Called when {snake} occurs.
fn on_{snake}(&self, _event: &{pascal}Event) {{
// Default: no-op
}}
"#,
);
// Step 3: TracingHooks implementation
println!("--- Step 3: TracingHooks implementation ---");
println!("File: crates/quicproquo-server/src/hooks.rs");
println!();
println!("Add to `impl ServerHooks for TracingHooks`:");
println!();
println!(
r#" fn on_{snake}(&self, _event: &{pascal}Event) {{
tracing::info!("hook: {snake}");
}}
"#,
);
// Step 4: Plugin API (C-compatible struct)
println!("--- Step 4: Plugin API ---");
println!("File: crates/quicproquo-plugin-api/src/lib.rs");
println!();
println!("Add a C-compatible event struct:");
println!();
println!(
r#"#[repr(C)]
pub struct C{pascal}Event {{
// TODO: mirror the fields from {pascal}Event using C-compatible types
// Use *const u8 + len for byte slices, *const c_char for strings
}}
"#,
);
println!("Add to `HookVTable`:");
println!();
println!(
r#" pub on_{snake}: Option<extern "C" fn(*mut c_void, *const C{pascal}Event)>,
"#,
);
// Step 5: Wire into PluginHooks
println!("--- Step 5: PluginHooks dispatch ---");
println!("File: crates/quicproquo-server/src/plugin_loader.rs");
println!();
println!("Add to `impl ServerHooks for PluginHooks`:");
println!();
println!(
r#" fn on_{snake}(&self, event: &{pascal}Event) {{
if let Some(hook_fn) = self.vtable.on_{snake} {{
let c_event = C{pascal}Event {{
// TODO: convert fields
}};
hook_fn(self.vtable.user_data, &c_event);
}}
}}
"#,
);
// Step 6: Call the hook
println!("--- Step 6: Call the hook in the RPC handler ---");
println!("In the relevant handler file under crates/quicproquo-server/src/node_service/:");
println!();
println!(
r#" use crate::hooks::{pascal}Event;
// At the appropriate point in the handler:
self.hooks.on_{snake}(&{pascal}Event {{
// fill in fields
}});
"#,
);
// Step 7: Verify
println!("--- Step 7: Verify ---");
println!(" cargo build -p quicproquo-plugin-api");
println!(" cargo build -p quicproquo-server");
println!(" cargo test -p quicproquo-server");
println!();
// Summary
println!("=== Files to modify ===");
println!(" [modify] crates/quicproquo-server/src/hooks.rs");
println!(" [modify] crates/quicproquo-plugin-api/src/lib.rs");
println!(" [modify] crates/quicproquo-server/src/plugin_loader.rs");
println!(" [modify] crates/quicproquo-server/src/node_service/<handler>.rs");
Ok(())
}
fn to_pascal_case(snake: &str) -> String {
snake
.split('_')
.map(|word| {
let mut chars = word.chars();
match chars.next() {
None => String::new(),
Some(c) => c.to_uppercase().to_string() + chars.as_str(),
}
})
.collect()
}