use std::sync::Arc;
use waf_core::{Config, Decision, Phase, RequestContext, WafModule};
use crate::{CaptureConfig, CaptureRing, CapturedRequest, RedactionConfig};
pub struct CaptureModule {
enabled: bool,
max_body_bytes: usize,
redaction: RedactionConfig,
ring: Arc<CaptureRing>,
}
impl CaptureModule {
pub fn new(cfg: &CaptureConfig, ring: Arc<CaptureRing>) -> Self {
Self {
enabled: cfg.enabled,
max_body_bytes: cfg.max_body_bytes,
redaction: cfg.redaction.clone(),
ring,
}
}
}
impl WafModule for CaptureModule {
fn id(&self) -> &str {
"request-capture"
}
fn phase(&self) -> Phase {
Phase::RequestLine
}
fn structural(&self) -> bool {
true
}
fn init(&mut self, _cfg: &Config) {}
fn inspect(&self, ctx: &RequestContext) -> Decision {
if self.enabled {
let capture = CapturedRequest::from_ctx(ctx, self.max_body_bytes, &self.redaction);
self.ring.insert(capture);
}
Decision::Allow
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
use waf_core::testkit::Request;
fn ring() -> Arc<CaptureRing> {
Arc::new(CaptureRing::new(16, 1 << 20, Duration::from_secs(5)))
}
#[test]
fn enabled_module_captures_into_ring_and_allows() {
let ring = ring();
let cfg = CaptureConfig { enabled: true, ..Default::default() };
let m = CaptureModule::new(&cfg, Arc::clone(&ring));
let ctx = Request::new().request_id("req-1").method("POST").path("/x").json_body("{}").build();
assert!(matches!(m.inspect(&ctx), Decision::Allow), "observer never denies");
let cap = ring.take("req-1").expect("captured");
assert_eq!(cap.method, "POST");
assert_eq!(cap.path, "/x");
}
#[test]
fn disabled_module_captures_nothing() {
let ring = ring();
let cfg = CaptureConfig { enabled: false, ..Default::default() };
let m = CaptureModule::new(&cfg, Arc::clone(&ring));
let ctx = Request::new().request_id("req-2").build();
assert!(matches!(m.inspect(&ctx), Decision::Allow));
assert!(ring.is_empty(), "disabled → nothing captured");
}
#[test]
fn module_metadata_is_structural_requestline() {
let m = CaptureModule::new(&CaptureConfig::default(), ring());
assert_eq!(m.id(), "request-capture");
assert_eq!(m.phase(), Phase::RequestLine);
assert!(m.structural(), "must run on the content fast-path skip");
}
}