wafrift-detect 0.2.13

WAF detection from response headers and body, response fingerprint drift analysis.
Documentation
//! Build script for wafrift-detect.
//!
//! Concatenates all `rules/detect/*.toml` files into a single
//! `EMBEDDED_DETECT_RULES` string that is `include_str!`'d at compile
//! time.  This removes the runtime filesystem dependency and allows
//! `cargo install wafrift` to produce a self-contained binary.

use std::fs;
use std::path::Path;

fn main() {
    // Prefer the in-crate vendored copy (the only one that survives a
    // `cargo publish`); fall back to the workspace tree for monorepo
    // builds where the vendored copy may be a symlink/missing.
    let in_crate = Path::new("rules/detect");
    let workspace = Path::new("../../rules/detect");
    let rules_dir = if in_crate.is_dir() {
        in_crate
    } else {
        workspace
    };

    if !rules_dir.is_dir() {
        // Not in the workspace tree — skip embedding.  The engine
        // will fall back to runtime loading or empty rules.
        println!(
            "cargo::warning=rules/detect not found at {}, embedded rules will be empty",
            rules_dir.display()
        );
        fs::write(
            Path::new(&std::env::var("OUT_DIR").unwrap()).join("embedded_detect_rules.toml"),
            "# No rules found at build time.\n",
        )
        .expect("failed to write empty embedded rules");
        return;
    }

    let mut entries: Vec<_> = fs::read_dir(rules_dir)
        .expect("failed to read rules/detect")
        .filter_map(std::result::Result::ok)
        .filter(|e| {
            e.path()
                .extension()
                .is_some_and(|ext| ext.eq_ignore_ascii_case("toml"))
        })
        .map(|e| e.path())
        .collect();
    entries.sort();

    let mut merged = String::with_capacity(1024 * 1024);
    merged.push_str("# Auto-generated by build.rs — do not edit.\n");
    merged.push_str(&format!("# {} rule files merged.\n\n", entries.len()));

    for entry in &entries {
        let content = fs::read_to_string(entry)
            .unwrap_or_else(|e| panic!("failed to read {}: {e}", entry.display()));
        merged.push_str(&format!(
            "# ── {} ──\n",
            entry.file_name().unwrap().to_string_lossy()
        ));
        merged.push_str(&content);
        merged.push('\n');
    }

    let out_dir = std::env::var("OUT_DIR").unwrap();
    let out_path = Path::new(&out_dir).join("embedded_detect_rules.toml");
    fs::write(&out_path, &merged)
        .unwrap_or_else(|e| panic!("failed to write {}: {e}", out_path.display()));

    // Re-run if any rule file changes.
    println!("cargo::rerun-if-changed={}", rules_dir.display());
    for entry in &entries {
        println!("cargo::rerun-if-changed={}", entry.display());
    }
}