use super::registry::{NamedPredicate, PredicateRegistry};
use serde_json::Value;
use std::sync::Arc;
pub fn register_reference_predicates(registry: &mut PredicateRegistry) {
registry.register(Arc::new(IsCountryCodePredicate));
registry.register(Arc::new(IsCountryNamePredicate));
registry.register(Arc::new(IsCurrencyCodePredicate));
registry.register(Arc::new(IsStateNamePredicate));
registry.register(Arc::new(IsUsStatePredicate));
registry.register(Arc::new(IsUsZipPredicate));
registry.register(Arc::new(IsW2Box12CodePredicate));
registry.register(Arc::new(Is1099BCodePredicate));
registry.register(Arc::new(IsFilingStatusPredicate));
}
struct IsCountryCodePredicate;
impl NamedPredicate for IsCountryCodePredicate {
fn name(&self) -> &str {
"is_country_code"
}
fn evaluate(&self, value: &Value, args: &Value) -> bool {
let s = match value.as_str() {
Some(s) => s.trim().to_uppercase(),
None => return false,
};
if s.len() != 2 {
return false;
}
let exclude_us = args
.get("exclude_us")
.and_then(|v| v.as_bool())
.unwrap_or(false);
if exclude_us && s == "US" {
return false;
}
ISO_COUNTRY_CODES.contains(&s.as_str())
}
}
struct IsCountryNamePredicate;
impl NamedPredicate for IsCountryNamePredicate {
fn name(&self) -> &str {
"is_country_name"
}
fn evaluate(&self, value: &Value, _args: &Value) -> bool {
let s = match value.as_str() {
Some(s) => s.trim(),
None => return false,
};
if s.is_empty() {
return false;
}
COUNTRY_NAMES
.iter()
.any(|name| name.eq_ignore_ascii_case(s))
}
}
struct IsStateNamePredicate;
impl NamedPredicate for IsStateNamePredicate {
fn name(&self) -> &str {
"is_state_name"
}
fn evaluate(&self, value: &Value, args: &Value) -> bool {
let s = match value.as_str() {
Some(s) => s.trim(),
None => return false,
};
if s.is_empty() {
return false;
}
let include_territories = args
.get("include_territories")
.and_then(|v| v.as_bool())
.unwrap_or(true);
if US_STATE_NAMES
.iter()
.any(|name| name.eq_ignore_ascii_case(s))
{
return true;
}
if include_territories
&& US_TERRITORY_NAMES
.iter()
.any(|name| name.eq_ignore_ascii_case(s))
{
return true;
}
false
}
}
struct IsUsStatePredicate;
impl NamedPredicate for IsUsStatePredicate {
fn name(&self) -> &str {
"is_us_state"
}
fn evaluate(&self, value: &Value, _args: &Value) -> bool {
let s = match value.as_str() {
Some(s) => s.trim().to_uppercase(),
None => return false,
};
US_STATE_CODES.contains(&s.as_str())
}
}
struct IsUsZipPredicate;
impl NamedPredicate for IsUsZipPredicate {
fn name(&self) -> &str {
"is_us_zip"
}
fn evaluate(&self, value: &Value, _args: &Value) -> bool {
let s = match value.as_str() {
Some(s) => s,
None => return false,
};
let digits: String = s.chars().filter(|c| c.is_ascii_digit()).collect();
if digits.len() != 5 && digits.len() != 9 {
return false;
}
if digits.chars().all(|c| c == '0') {
return false;
}
true
}
}
struct IsCurrencyCodePredicate;
impl NamedPredicate for IsCurrencyCodePredicate {
fn name(&self) -> &str {
"is_currency_code"
}
fn evaluate(&self, value: &Value, _args: &Value) -> bool {
let s = match value.as_str() {
Some(s) => s.trim().to_uppercase(),
None => return false,
};
if s.len() != 3 {
return false;
}
ISO_CURRENCY_CODES.contains(&s.as_str())
}
}
struct IsW2Box12CodePredicate;
impl NamedPredicate for IsW2Box12CodePredicate {
fn name(&self) -> &str {
"is_w2_box12_code"
}
fn evaluate(&self, value: &Value, _args: &Value) -> bool {
let s = match value.as_str() {
Some(s) => s.trim().to_uppercase(),
None => return false,
};
W2_BOX12_CODES.contains(&s.as_str())
}
}
struct Is1099BCodePredicate;
impl NamedPredicate for Is1099BCodePredicate {
fn name(&self) -> &str {
"is_1099b_code"
}
fn evaluate(&self, value: &Value, args: &Value) -> bool {
let s = match value.as_str() {
Some(s) => s.trim().to_uppercase(),
None => return false,
};
let code_type = args.get("type").and_then(|v| v.as_str());
match code_type {
Some("term") => TERM_CODES.contains(&s.as_str()),
Some("basis") => BASIS_REPORTED_CODES.contains(&s.as_str()),
Some("type") => TRANSACTION_TYPE_CODES.contains(&s.as_str()),
None | Some(_) => {
TERM_CODES.contains(&s.as_str())
|| BASIS_REPORTED_CODES.contains(&s.as_str())
|| TRANSACTION_TYPE_CODES.contains(&s.as_str())
}
}
}
}
struct IsFilingStatusPredicate;
impl NamedPredicate for IsFilingStatusPredicate {
fn name(&self) -> &str {
"is_filing_status"
}
fn evaluate(&self, value: &Value, _args: &Value) -> bool {
let s = match value.as_str() {
Some(s) => s.trim().to_uppercase(),
None => return false,
};
FILING_STATUSES.contains(&s.as_str())
}
}
const COUNTRY_NAMES: &[&str] = &[
"United States",
"Canada",
"Mexico",
"United Kingdom",
"Germany",
"France",
"Italy",
"Spain",
"Japan",
"China",
"India",
"Australia",
"Brazil",
"South Korea",
"Russia",
"Netherlands",
"Switzerland",
"Ireland",
"Singapore",
"Hong Kong",
];
const US_STATE_NAMES: &[&str] = &[
"Alabama",
"Alaska",
"Arizona",
"Arkansas",
"California",
"Colorado",
"Connecticut",
"Delaware",
"District of Columbia",
"Florida",
"Georgia",
"Hawaii",
"Idaho",
"Illinois",
"Indiana",
"Iowa",
"Kansas",
"Kentucky",
"Louisiana",
"Maine",
"Maryland",
"Massachusetts",
"Michigan",
"Minnesota",
"Mississippi",
"Missouri",
"Montana",
"Nebraska",
"Nevada",
"New Hampshire",
"New Jersey",
"New Mexico",
"New York",
"North Carolina",
"North Dakota",
"Ohio",
"Oklahoma",
"Oregon",
"Pennsylvania",
"Rhode Island",
"South Carolina",
"South Dakota",
"Tennessee",
"Texas",
"Utah",
"Vermont",
"Virginia",
"Washington",
"West Virginia",
"Wisconsin",
"Wyoming",
];
const US_TERRITORY_NAMES: &[&str] = &[
"American Samoa",
"Guam",
"Northern Mariana Islands",
"Puerto Rico",
"U.S. Virgin Islands",
"United States Minor Outlying Islands",
];
const US_STATE_CODES: &[&str] = &[
"AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "FL", "GA", "HI", "ID", "IL", "IN", "IA", "KS",
"KY", "LA", "ME", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM", "NY",
"NC", "ND", "OH", "OK", "OR", "PA", "RI", "SC", "SD", "TN", "TX", "UT", "VT", "VA", "WA", "WV",
"WI", "WY", "DC", "AS", "GU", "MP", "PR", "VI", "UM", "AA", "AE", "AP",
];
const ISO_COUNTRY_CODES: &[&str] = &[
"US", "CA", "MX", "GB", "DE", "FR", "IT", "ES", "JP", "CN", "IN", "AU", "BR", "KR", "RU",
"AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "GR", "HU", "IE", "LV", "LT", "LU", "MT",
"NL", "PL", "PT", "RO", "SK", "SI", "SE", "CH", "NO", "IL", "SG", "HK", "TW", "NZ", "ZA", "AE", "SA", "AR", "CL", "CO", "PE", "VE", "PH",
"TH", "VN", "MY", "ID", "PK", "BD", "EG", "NG", "KE", "TR", "UA",
"BM", "KY", "VG", "BS", "PA", "LI", "MC", "AD", "GI", "JE", "GG", "IM", "LU", "MT", "CY",
"JM", "TT", "BB", "PR", "VI", "CU", "DO", "HT", "GT", "HN", "SV", "NI", "CR", "BZ", "AF", "AL", "DZ", "AO", "AM", "AZ", "BH", "BY", "BJ", "BT", "BO", "BA", "BW", "BN", "BF", "BI",
"KH", "CM", "CV", "CF", "TD", "CI", "CD", "CG", "DJ", "EC", "GQ", "ER", "ET", "FJ", "GA", "GM",
"GE", "GH", "GN", "GW", "GY", "IQ", "IR", "IS", "JO", "KZ", "KW", "KG", "LA", "LB", "LS", "LR",
"LY", "MK", "MG", "MW", "MV", "ML", "MR", "MU", "MD", "MN", "ME", "MA", "MZ", "MM", "NA", "NP",
"NE", "KP", "OM", "QA", "RW", "RS", "SL", "SO", "SS", "SD", "SR", "SZ", "SY", "TJ", "TZ", "TG",
"TO", "TN", "TM", "UG", "UY", "UZ", "VU", "YE", "ZM", "ZW",
];
const ISO_CURRENCY_CODES: &[&str] = &[
"USD", "EUR", "GBP", "JPY", "CNY", "CAD", "AUD", "CHF", "HKD", "SGD", "SEK", "NOK", "DKK",
"NZD", "MXN", "BRL", "INR", "RUB", "ZAR", "KRW", "TWD", "THB", "MYR", "IDR", "PHP", "VND",
"AED", "SAR", "ILS", "TRY", "PLN", "CZK", "HUF", "RON", "BGN", "HRK", "ISK", "CLP", "COP",
"PEN", "ARS", "EGP", "NGN", "KES", "PKR", "BDT",
];
const W2_BOX12_CODES: &[&str] = &[
"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "V", "W", "Y", "Z", "AA", "BB", "DD", "EE", "FF", "GG", "HH", ];
const TERM_CODES: &[&str] = &[
"A", "B", "C", "D", "E", "F", "X", ];
const BASIS_REPORTED_CODES: &[&str] = &[
"1", "2", "3", ];
const TRANSACTION_TYPE_CODES: &[&str] = &[
"P", "S", "C", "O", "W", ];
const FILING_STATUSES: &[&str] = &[
"S", "MFJ", "MFS", "HOH", "QW", "1", "2", "3", "4", "5", ];
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_is_country_code() {
let pred = IsCountryCodePredicate;
assert!(pred.evaluate(&json!("US"), &json!(null)));
assert!(pred.evaluate(&json!("us"), &json!(null))); assert!(pred.evaluate(&json!("GB"), &json!(null)));
assert!(pred.evaluate(&json!("JP"), &json!(null)));
assert!(!pred.evaluate(&json!("XX"), &json!(null)));
assert!(!pred.evaluate(&json!("USA"), &json!(null))); assert!(!pred.evaluate(&json!("U"), &json!(null)));
assert!(!pred.evaluate(&json!("US"), &json!({"exclude_us": true})));
assert!(pred.evaluate(&json!("GB"), &json!({"exclude_us": true})));
}
#[test]
fn test_is_currency_code() {
let pred = IsCurrencyCodePredicate;
assert!(pred.evaluate(&json!("USD"), &json!(null)));
assert!(pred.evaluate(&json!("usd"), &json!(null)));
assert!(pred.evaluate(&json!("EUR"), &json!(null)));
assert!(pred.evaluate(&json!("GBP"), &json!(null)));
assert!(!pred.evaluate(&json!("XXX"), &json!(null)));
assert!(!pred.evaluate(&json!("US"), &json!(null))); }
#[test]
fn test_is_us_state() {
let pred = IsUsStatePredicate;
assert!(pred.evaluate(&json!("CA"), &json!(null)));
assert!(pred.evaluate(&json!("ca"), &json!(null)));
assert!(pred.evaluate(&json!("PR"), &json!(null)));
assert!(pred.evaluate(&json!("AE"), &json!(null)));
assert!(!pred.evaluate(&json!("California"), &json!(null)));
assert!(!pred.evaluate(&json!("XX"), &json!(null)));
}
#[test]
fn test_is_us_zip() {
let pred = IsUsZipPredicate;
assert!(pred.evaluate(&json!("94105"), &json!(null)));
assert!(pred.evaluate(&json!("94105-1234"), &json!(null)));
assert!(!pred.evaluate(&json!("00000"), &json!(null)));
assert!(!pred.evaluate(&json!("9410"), &json!(null)));
}
#[test]
fn test_reference_registry_includes_us_state_and_zip() {
let mut registry = PredicateRegistry::new();
register_reference_predicates(&mut registry);
assert!(registry.get("is_us_state").is_some());
assert!(registry.get("is_us_zip").is_some());
}
#[test]
fn test_is_w2_box12_code() {
let pred = IsW2Box12CodePredicate;
assert!(pred.evaluate(&json!("D"), &json!(null))); assert!(pred.evaluate(&json!("W"), &json!(null)));
assert!(pred.evaluate(&json!("DD"), &json!(null))); assert!(pred.evaluate(&json!("AA"), &json!(null)));
assert!(pred.evaluate(&json!("dd"), &json!(null)));
assert!(!pred.evaluate(&json!("X"), &json!(null))); assert!(!pred.evaluate(&json!("DDD"), &json!(null)));
}
#[test]
fn test_is_1099b_code() {
let pred = Is1099BCodePredicate;
assert!(pred.evaluate(&json!("A"), &json!(null))); assert!(pred.evaluate(&json!("D"), &json!(null)));
assert!(pred.evaluate(&json!("1"), &json!(null)));
assert!(pred.evaluate(&json!("P"), &json!(null))); assert!(pred.evaluate(&json!("W"), &json!(null)));
assert!(pred.evaluate(&json!("A"), &json!({"type": "term"})));
assert!(!pred.evaluate(&json!("1"), &json!({"type": "term"})));
assert!(pred.evaluate(&json!("1"), &json!({"type": "basis"})));
}
#[test]
fn test_is_filing_status() {
let pred = IsFilingStatusPredicate;
assert!(pred.evaluate(&json!("S"), &json!(null)));
assert!(pred.evaluate(&json!("MFJ"), &json!(null)));
assert!(pred.evaluate(&json!("HOH"), &json!(null)));
assert!(pred.evaluate(&json!("1"), &json!(null)));
assert!(!pred.evaluate(&json!("SINGLE"), &json!(null)));
}
}