use std::collections::HashMap;
#[derive(Debug, thiserror::Error)]
pub enum HoneypotError {
#[error("Bot detected: {0}")]
BotDetected(String),
}
#[derive(Debug, Clone)]
pub struct HoneypotField {
name: String,
label: Option<String>,
}
impl HoneypotField {
pub fn new(name: String) -> Self {
Self { name, label: None }
}
pub fn with_label(mut self, label: String) -> Self {
self.label = Some(label);
self
}
pub fn name(&self) -> &str {
&self.name
}
pub fn label(&self) -> Option<&str> {
self.label.as_deref()
}
pub fn validate(&self, value: Option<&str>) -> Result<(), HoneypotError> {
match value {
None | Some("") => Ok(()),
Some(_) => Err(HoneypotError::BotDetected(format!(
"Honeypot field '{}' was filled",
self.name
))),
}
}
pub fn validate_form_data(
&self,
data: &HashMap<String, serde_json::Value>,
) -> Result<(), HoneypotError> {
if let Some(value) = data.get(&self.name) {
let is_empty =
value.is_null() || (value.is_string() && value.as_str().unwrap_or("").is_empty());
if !is_empty {
return Err(HoneypotError::BotDetected(format!(
"Honeypot field '{}' was filled",
self.name
)));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_honeypot_field_creation() {
let honeypot = HoneypotField::new("trap".to_string());
assert_eq!(honeypot.name(), "trap");
assert_eq!(honeypot.label(), None);
}
#[test]
fn test_honeypot_field_with_label() {
let honeypot =
HoneypotField::new("trap".to_string()).with_label("Leave this empty".to_string());
assert_eq!(honeypot.label(), Some("Leave this empty"));
}
#[test]
fn test_honeypot_field_validate_empty() {
let honeypot = HoneypotField::new("trap".to_string());
assert!(honeypot.validate(None).is_ok());
assert!(honeypot.validate(Some("")).is_ok());
}
#[test]
fn test_honeypot_field_validate_filled() {
let honeypot = HoneypotField::new("trap".to_string());
let result = honeypot.validate(Some("bot-value"));
assert!(result.is_err());
}
#[test]
fn test_honeypot_form_data_validation() {
let honeypot = HoneypotField::new("email_confirm".to_string());
let mut data = HashMap::new();
data.insert("email_confirm".to_string(), serde_json::json!(""));
assert!(honeypot.validate_form_data(&data).is_ok());
data.insert("email_confirm".to_string(), serde_json::json!("bot-value"));
assert!(honeypot.validate_form_data(&data).is_err());
}
#[test]
fn test_honeypot_form_data_missing_field() {
let honeypot = HoneypotField::new("nonexistent".to_string());
let data = HashMap::new();
assert!(honeypot.validate_form_data(&data).is_ok());
}
#[test]
fn test_honeypot_form_data_null_value() {
let honeypot = HoneypotField::new("trap".to_string());
let mut data = HashMap::new();
data.insert("trap".to_string(), serde_json::Value::Null);
assert!(honeypot.validate_form_data(&data).is_ok());
}
}