use serde::{Deserialize, Serialize};
use super::de::null_default;
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize, Serialize)]
#[non_exhaustive]
pub struct UserAgent {
#[serde(default, deserialize_with = "null_default")]
pub header: String,
#[serde(default, deserialize_with = "null_default")]
pub name: String,
#[serde(default, rename = "type", deserialize_with = "null_default")]
pub agent_type: String,
#[serde(default, deserialize_with = "null_default")]
pub version: String,
#[serde(default, deserialize_with = "null_default")]
pub version_major: String,
#[serde(default, deserialize_with = "null_default")]
pub device: UserAgentDevice,
#[serde(default, deserialize_with = "null_default")]
pub engine: UserAgentEngine,
#[serde(default, deserialize_with = "null_default")]
pub os: UserAgentOs,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize, Serialize)]
#[non_exhaustive]
pub struct UserAgentDevice {
#[serde(default, deserialize_with = "null_default")]
pub brand: String,
#[serde(default, deserialize_with = "null_default")]
pub name: String,
#[serde(default, rename = "type", deserialize_with = "null_default")]
pub device_type: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize, Serialize)]
#[non_exhaustive]
pub struct UserAgentEngine {
#[serde(default, deserialize_with = "null_default")]
pub name: String,
#[serde(default, rename = "type", deserialize_with = "null_default")]
pub engine_type: String,
#[serde(default, deserialize_with = "null_default")]
pub version: String,
#[serde(default, deserialize_with = "null_default")]
pub version_major: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize, Serialize)]
#[non_exhaustive]
pub struct UserAgentOs {
#[serde(default, deserialize_with = "null_default")]
pub name: String,
#[serde(default, rename = "type", deserialize_with = "null_default")]
pub os_type: String,
#[serde(default, deserialize_with = "null_default")]
pub version: String,
}
pub fn is_bot(user_agent: &str) -> bool {
let ua = user_agent.to_ascii_lowercase();
ua.contains("bot") || ua.contains("spider") || ua.contains("slurp")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_bots() {
for ua in [
"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
"Mozilla/5.0 (compatible; Baiduspider/2.0)",
"Mozilla/5.0 (compatible; Yahoo! Slurp)",
"SomeBOT/1.0",
] {
assert!(is_bot(ua), "{ua} should be detected as a bot");
}
}
#[test]
fn does_not_flag_regular_browsers() {
for ua in [
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 \
(KHTML, like Gecko) Chrome/120.0 Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) Version/17.0 Mobile Safari",
"",
] {
assert!(!is_bot(ua), "{ua} should not be detected as a bot");
}
}
}