use crate::ports::coherence::{
CoherenceContext, CoherencePort, CoherenceVerdict, IsoCountry, MismatchField, MismatchSeverity,
Tz,
};
#[derive(Debug, Default, Clone, Copy)]
pub struct DefaultCoherenceValidator;
impl DefaultCoherenceValidator {
#[must_use]
pub const fn new() -> Self {
Self
}
}
impl CoherencePort for DefaultCoherenceValidator {
fn evaluate(&self, ctx: &CoherenceContext) -> CoherenceVerdict {
if let Some(mismatch) = check_country_agreement(ctx) {
return mismatch;
}
if let Some(mismatch) = check_webrtc_slash_16(ctx) {
return mismatch;
}
if let Some(mismatch) = check_timezone(ctx) {
return mismatch;
}
if let Some(mismatch) = check_locale(ctx) {
return mismatch;
}
if let Some(mismatch) = check_accept_language(ctx) {
return mismatch;
}
CoherenceVerdict::Coherent
}
}
fn check_country_agreement(ctx: &CoherenceContext) -> Option<CoherenceVerdict> {
match (
ctx.proxy_geo_country.as_ref(),
ctx.dns_resolver_country.as_ref(),
) {
(Some(proxy), Some(dns)) if proxy == dns => None,
(Some(_), Some(_)) => Some(CoherenceVerdict::Mismatch {
field: MismatchField::ProxyGeoVsDns,
severity: MismatchSeverity::Hard,
}),
_ => None,
}
}
fn check_webrtc_slash_16(ctx: &CoherenceContext) -> Option<CoherenceVerdict> {
match (ctx.webrtc_public_ip, ctx.proxy_ip) {
(Some(public), Some(proxy)) => match CoherenceContext::same_slash_16(public, proxy) {
Some(true) => None,
Some(false) => Some(CoherenceVerdict::Mismatch {
field: MismatchField::WebRtcPublicIp,
severity: MismatchSeverity::Hard,
}),
None => Some(CoherenceVerdict::unknown("webrtc_proxy_ip_family_mismatch")),
},
(Some(_), None) => Some(CoherenceVerdict::unknown("missing_proxy_ip")),
_ => None,
}
}
fn check_timezone(ctx: &CoherenceContext) -> Option<CoherenceVerdict> {
let Some(country) = effective_country(ctx) else {
return Some(CoherenceVerdict::unknown("missing_geo_country"));
};
if tz_matches_country(&ctx.browser_timezone, country) {
None
} else {
Some(CoherenceVerdict::Mismatch {
field: MismatchField::Timezone,
severity: MismatchSeverity::Advisory,
})
}
}
fn check_locale(ctx: &CoherenceContext) -> Option<CoherenceVerdict> {
let Some(country) = effective_country(ctx) else {
return Some(CoherenceVerdict::unknown("missing_geo_country"));
};
match ctx.browser_locale.region() {
Some(region) if region.eq_ignore_ascii_case(country.as_str()) => None,
Some(_) => Some(CoherenceVerdict::Mismatch {
field: MismatchField::Locale,
severity: MismatchSeverity::Advisory,
}),
None => Some(CoherenceVerdict::unknown("locale_missing_region")),
}
}
fn check_accept_language(ctx: &CoherenceContext) -> Option<CoherenceVerdict> {
let Some(country) = effective_country(ctx) else {
return Some(CoherenceVerdict::unknown("missing_geo_country"));
};
match ctx.accept_language.primary_region() {
Some(region)
if region
.region()
.is_some_and(|r| r.eq_ignore_ascii_case(country.as_str())) =>
{
None
}
Some(_) => Some(CoherenceVerdict::Mismatch {
field: MismatchField::AcceptLanguage,
severity: MismatchSeverity::Advisory,
}),
None => None, }
}
fn effective_country(ctx: &CoherenceContext) -> Option<&IsoCountry> {
ctx.proxy_geo_country
.as_ref()
.or(ctx.dns_resolver_country.as_ref())
}
fn tz_matches_country(tz: &Tz, country: &IsoCountry) -> bool {
let Some((_, allowed_regions)) = COUNTRY_TIMEZONE_REGIONS
.iter()
.find(|(code, _)| code.eq_ignore_ascii_case(country.as_str()))
else {
return false;
};
tz.region().is_some_and(|tz_region| {
allowed_regions
.iter()
.any(|prefix| prefix.eq_ignore_ascii_case(tz_region))
})
}
type TzPrefixList = &'static [&'static str];
const COUNTRY_TIMEZONE_REGIONS: &[(&str, TzPrefixList)] = &[
(
"US",
&[
"America", "Pacific", "US", ],
),
(
"PK",
&[
"Asia", ],
),
(
"GB",
&[
"Europe", ],
),
(
"DE",
&[
"Europe", ],
),
(
"FR",
&[
"Europe", ],
),
(
"NL",
&[
"Europe", ],
),
(
"JP",
&[
"Asia", ],
),
(
"AU",
&[
"Australia", "Antarctica", ],
),
(
"CA",
&[
"America", ],
),
(
"IN",
&[
"Asia", ],
),
(
"BR",
&[
"America", ],
),
(
"MX",
&[
"America", ],
),
(
"SG",
&[
"Asia", ],
),
(
"HK",
&[
"Asia", ],
),
];
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing
)]
mod tests {
use super::*;
use std::net::IpAddr;
use std::str::FromStr;
use std::sync::Arc;
use crate::ports::coherence::{
AcceptLanguage, CoherencePort, Locale, MismatchField, MismatchSeverity,
};
fn validator() -> DefaultCoherenceValidator {
DefaultCoherenceValidator
}
fn us() -> IsoCountry {
IsoCountry::new("US").unwrap()
}
fn pk() -> IsoCountry {
IsoCountry::new("PK").unwrap()
}
fn en_us_locale() -> Locale {
Locale::new("en-US").unwrap()
}
fn fr_fr_locale() -> Locale {
Locale::new("fr-FR").unwrap()
}
fn ny_tz() -> Tz {
Tz::new("America/New_York").unwrap()
}
fn london_tz() -> Tz {
Tz::new("Europe/London").unwrap()
}
fn al_en_us() -> AcceptLanguage {
AcceptLanguage::new("en-US,en;q=0.9").unwrap()
}
fn al_fr_fr() -> AcceptLanguage {
AcceptLanguage::new("fr-FR,fr;q=0.9").unwrap()
}
fn base_us_ctx() -> CoherenceContext {
CoherenceContext {
proxy_geo_country: Some(us()),
dns_resolver_country: Some(us()),
browser_locale: en_us_locale(),
browser_timezone: ny_tz(),
accept_language: al_en_us(),
webrtc_local_ip: None,
webrtc_public_ip: Some(IpAddr::from_str("192.0.2.42").unwrap()),
proxy_ip: Some(IpAddr::from_str("192.0.2.7").unwrap()),
}
}
#[test]
fn us_proxy_us_dns_en_us_america_new_york_is_coherent() {
let ctx = base_us_ctx();
assert_eq!(validator().evaluate(&ctx), CoherenceVerdict::Coherent);
}
#[test]
fn us_proxy_pk_dns_is_hard_dns_mismatch() {
let ctx = CoherenceContext {
dns_resolver_country: Some(pk()),
..base_us_ctx()
};
assert_eq!(
validator().evaluate(&ctx),
CoherenceVerdict::Mismatch {
field: MismatchField::ProxyGeoVsDns,
severity: MismatchSeverity::Hard,
}
);
}
#[test]
fn webrtc_public_ip_same_slash_16_is_coherent() {
let ctx = base_us_ctx();
assert_eq!(validator().evaluate(&ctx), CoherenceVerdict::Coherent);
}
#[test]
fn webrtc_public_ip_different_slash_16_is_hard_mismatch() {
let ctx = CoherenceContext {
webrtc_public_ip: Some(IpAddr::from_str("203.0.113.5").unwrap()),
..base_us_ctx()
};
assert_eq!(
validator().evaluate(&ctx),
CoherenceVerdict::Mismatch {
field: MismatchField::WebRtcPublicIp,
severity: MismatchSeverity::Hard,
}
);
}
#[test]
fn us_proxy_europe_london_tz_is_advisory_timezone_mismatch() {
let ctx = CoherenceContext {
browser_timezone: london_tz(),
..base_us_ctx()
};
assert_eq!(
validator().evaluate(&ctx),
CoherenceVerdict::Mismatch {
field: MismatchField::Timezone,
severity: MismatchSeverity::Advisory,
}
);
}
#[test]
fn validator_is_send_sync_static() {
fn assert_send_sync_static<T: Send + Sync + 'static>() {}
assert_send_sync_static::<DefaultCoherenceValidator>();
assert_send_sync_static::<BoxedValidator>();
}
type BoxedValidator = Arc<dyn CoherencePort>;
#[test]
fn validator_is_stateless_across_repeated_calls() {
let v = validator();
let ctx_clean = base_us_ctx();
let ctx_mismatch = CoherenceContext {
browser_timezone: london_tz(),
..base_us_ctx()
};
for _ in 0..1_000 {
assert_eq!(v.evaluate(&ctx_clean), CoherenceVerdict::Coherent);
assert!(matches!(
v.evaluate(&ctx_mismatch),
CoherenceVerdict::Mismatch {
field: MismatchField::Timezone,
severity: MismatchSeverity::Advisory,
}
));
}
}
#[test]
fn boxed_dispatch_matches_direct_call() {
let v: BoxedValidator = Arc::new(DefaultCoherenceValidator::new());
let ctx = CoherenceContext {
browser_timezone: london_tz(),
..base_us_ctx()
};
assert_eq!(v.evaluate(&ctx), validator().evaluate(&ctx));
}
#[test]
fn locale_mismatch_is_advisory() {
let ctx = CoherenceContext {
browser_locale: fr_fr_locale(),
..base_us_ctx()
};
assert_eq!(
validator().evaluate(&ctx),
CoherenceVerdict::Mismatch {
field: MismatchField::Locale,
severity: MismatchSeverity::Advisory,
}
);
}
#[test]
fn accept_language_mismatch_is_advisory() {
let ctx = CoherenceContext {
accept_language: al_fr_fr(),
..base_us_ctx()
};
assert_eq!(
validator().evaluate(&ctx),
CoherenceVerdict::Mismatch {
field: MismatchField::AcceptLanguage,
severity: MismatchSeverity::Advisory,
}
);
}
#[test]
fn bare_language_accept_language_does_not_mismatch() {
let ctx = CoherenceContext {
accept_language: AcceptLanguage::new("en;q=1.0").unwrap(),
..base_us_ctx()
};
assert_eq!(validator().evaluate(&ctx), CoherenceVerdict::Coherent);
}
#[test]
fn hard_dns_mismatch_short_circuits_advisory() {
let ctx = CoherenceContext {
dns_resolver_country: Some(pk()),
browser_timezone: london_tz(),
browser_locale: fr_fr_locale(),
accept_language: al_fr_fr(),
..base_us_ctx()
};
assert_eq!(
validator().evaluate(&ctx),
CoherenceVerdict::Mismatch {
field: MismatchField::ProxyGeoVsDns,
severity: MismatchSeverity::Hard,
}
);
}
#[test]
fn hard_webrtc_mismatch_short_circuits_advisory() {
let ctx = CoherenceContext {
webrtc_public_ip: Some(IpAddr::from_str("203.0.113.5").unwrap()),
browser_timezone: london_tz(),
browser_locale: fr_fr_locale(),
..base_us_ctx()
};
assert_eq!(
validator().evaluate(&ctx),
CoherenceVerdict::Mismatch {
field: MismatchField::WebRtcPublicIp,
severity: MismatchSeverity::Hard,
}
);
}
#[test]
fn missing_geo_returns_unknown() {
let ctx = CoherenceContext {
proxy_geo_country: None,
dns_resolver_country: None,
..base_us_ctx()
};
assert!(matches!(
validator().evaluate(&ctx),
CoherenceVerdict::Unknown(_)
));
}
#[test]
fn missing_proxy_ip_returns_unknown() {
let ctx = CoherenceContext {
proxy_ip: None,
..base_us_ctx()
};
assert!(matches!(
validator().evaluate(&ctx),
CoherenceVerdict::Unknown(reason) if reason == "missing_proxy_ip"
));
}
#[test]
fn webrtc_disabled_does_not_block_other_checks() {
let ctx = CoherenceContext {
webrtc_public_ip: None,
proxy_ip: None,
browser_timezone: london_tz(),
..base_us_ctx()
};
assert_eq!(
validator().evaluate(&ctx),
CoherenceVerdict::Mismatch {
field: MismatchField::Timezone,
severity: MismatchSeverity::Advisory,
}
);
}
#[test]
fn dns_country_fills_in_when_proxy_country_missing() {
let ctx = CoherenceContext {
proxy_geo_country: None,
dns_resolver_country: Some(us()),
browser_timezone: london_tz(),
webrtc_public_ip: None,
proxy_ip: None,
..base_us_ctx()
};
assert_eq!(
validator().evaluate(&ctx),
CoherenceVerdict::Mismatch {
field: MismatchField::Timezone,
severity: MismatchSeverity::Advisory,
}
);
}
#[test]
fn mismatched_proxy_and_dns_is_hard_mismatch() {
let ctx = CoherenceContext {
proxy_geo_country: Some(us()),
dns_resolver_country: Some(pk()),
webrtc_public_ip: None,
proxy_ip: None,
..base_us_ctx()
};
assert_eq!(
validator().evaluate(&ctx),
CoherenceVerdict::Mismatch {
field: MismatchField::ProxyGeoVsDns,
severity: MismatchSeverity::Hard,
}
);
}
#[test]
fn hot_path_budget_10k_calls() {
let v = validator();
let ctx = base_us_ctx();
let start = std::time::Instant::now();
for _ in 0..10_000 {
let _ = v.evaluate(&ctx);
}
let elapsed = start.elapsed();
assert!(
elapsed < std::time::Duration::from_secs(1),
"10k coherence checks took {elapsed:?}; hot-path budget violated"
);
}
#[test]
fn country_timezone_table_covers_test_cases() {
for code in ["US", "PK"] {
assert!(
COUNTRY_TIMEZONE_REGIONS
.iter()
.any(|(country, _)| *country == code),
"country {code} missing from COUNTRY_TIMEZONE_REGIONS"
);
}
}
#[test]
fn tz_matches_us_timezones() {
for tz_str in [
"America/New_York",
"America/Chicago",
"America/Denver",
"America/Los_Angeles",
"America/Phoenix",
"America/Anchorage",
"Pacific/Honolulu",
] {
let tz = Tz::new(tz_str).unwrap();
assert!(tz_matches_country(&tz, &us()), "should accept {tz_str}");
}
}
#[test]
fn tz_rejects_non_us_timezones() {
for tz_str in [
"Europe/London",
"Asia/Tokyo",
"Asia/Karachi",
"Australia/Sydney",
] {
let tz = Tz::new(tz_str).unwrap();
assert!(
!tz_matches_country(&tz, &us()),
"should reject {tz_str} for US"
);
}
}
#[test]
fn tz_unknown_country_returns_false() {
let country = IsoCountry::new("ZZ").unwrap();
assert!(!tz_matches_country(&ny_tz(), &country));
}
}