use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum WafClass {
PlainModSec,
CloudflareManagedRules,
AwsCoreRuleSet,
GenericCrs,
AwsBotControl,
CloudflareBotMgmt,
AkamaiBotManager,
Datadome,
Unknown,
}
impl WafClass {
#[must_use]
pub fn is_ml_backed(self) -> bool {
matches!(
self,
Self::AwsBotControl | Self::CloudflareBotMgmt | Self::AkamaiBotManager | Self::Datadome
)
}
#[must_use]
pub fn is_ensemble(self) -> bool {
matches!(
self,
Self::CloudflareManagedRules | Self::AwsCoreRuleSet | Self::GenericCrs
)
}
#[must_use]
pub fn from_waf_name(name: &str) -> Self {
let lower = name.to_ascii_lowercase();
if lower.contains("bot control") || lower.contains("botcontrol") {
return Self::AwsBotControl;
}
if lower.contains("bot management") || lower.contains("botmanagement") {
return Self::CloudflareBotMgmt;
}
if lower.contains("bot manager") || lower.contains("botmanager") {
return Self::AkamaiBotManager;
}
if lower.contains("datadome") {
return Self::Datadome;
}
if lower.contains("cloudflare") {
return Self::CloudflareManagedRules;
}
if lower.contains("aws") || lower.contains("amazon") {
return Self::AwsCoreRuleSet;
}
if lower.contains("modsec") || lower.contains("coraza") {
return Self::PlainModSec;
}
if lower.contains("owasp") || lower.contains("crs") {
return Self::GenericCrs;
}
Self::Unknown
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ml_backed_variants_identified() {
assert!(WafClass::AwsBotControl.is_ml_backed());
assert!(WafClass::CloudflareBotMgmt.is_ml_backed());
assert!(WafClass::AkamaiBotManager.is_ml_backed());
assert!(WafClass::Datadome.is_ml_backed());
}
#[test]
fn rule_based_not_ml() {
assert!(!WafClass::PlainModSec.is_ml_backed());
assert!(!WafClass::CloudflareManagedRules.is_ml_backed());
assert!(!WafClass::AwsCoreRuleSet.is_ml_backed());
assert!(!WafClass::Unknown.is_ml_backed());
}
#[test]
fn ensemble_variants_identified() {
assert!(WafClass::CloudflareManagedRules.is_ensemble());
assert!(WafClass::AwsCoreRuleSet.is_ensemble());
assert!(WafClass::GenericCrs.is_ensemble());
}
#[test]
fn ml_backed_not_ensemble() {
assert!(!WafClass::AwsBotControl.is_ensemble());
assert!(!WafClass::CloudflareBotMgmt.is_ensemble());
}
#[test]
fn from_waf_name_cloudflare() {
assert_eq!(
WafClass::from_waf_name("Cloudflare WAF"),
WafClass::CloudflareManagedRules
);
}
#[test]
fn from_waf_name_aws_bot_control() {
assert_eq!(
WafClass::from_waf_name("AWS Bot Control"),
WafClass::AwsBotControl
);
}
#[test]
fn from_waf_name_akamai_bot_manager() {
assert_eq!(
WafClass::from_waf_name("Akamai Bot Manager"),
WafClass::AkamaiBotManager
);
}
#[test]
fn from_waf_name_unknown() {
assert_eq!(
WafClass::from_waf_name("SomeUnknownThing"),
WafClass::Unknown
);
}
#[test]
fn serde_roundtrip() {
let classes = [
WafClass::AwsBotControl,
WafClass::CloudflareBotMgmt,
WafClass::PlainModSec,
WafClass::Unknown,
];
for cls in classes {
let s = serde_json::to_string(&cls).expect("serialize");
let back: WafClass = serde_json::from_str(&s).expect("deserialize");
assert_eq!(cls, back);
}
}
}