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).
135 lines
3.9 KiB
Rust
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()
|
|
}
|