Skip to content

Commit d38ffcf

Browse files
authored
Add Unified Social Credit Code (USCC) validation to the format validator. (#33)
* Determine whether it is a valid Unified Social Credit Code * cargo fmt
1 parent 3247e49 commit d38ffcf

File tree

1 file changed

+75
-1
lines changed

1 file changed

+75
-1
lines changed

aiscript-directive/src/validator/format.rs

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,62 @@ static WEEK_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\d{4}-W\d{2}
4444
static COLOR_REGEX: LazyLock<Regex> =
4545
LazyLock::new(|| Regex::new(r"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$").unwrap());
4646

47+
mod uscc {
48+
use std::{collections::HashMap, sync::LazyLock};
49+
50+
use regex::Regex;
51+
52+
static USCC_REGEX: LazyLock<Regex> = LazyLock::new(|| {
53+
Regex::new(r"^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$").unwrap()
54+
});
55+
56+
static USCC_BASE_CHARS: LazyLock<HashMap<char, u8>> = LazyLock::new(|| {
57+
let mut base_chars = HashMap::with_capacity(17);
58+
59+
[
60+
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
61+
'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'T', 'U', 'W', 'X', 'Y',
62+
]
63+
.into_iter()
64+
.enumerate()
65+
.for_each(|(index, c)| {
66+
base_chars.insert(c, index as u8);
67+
});
68+
69+
base_chars
70+
});
71+
72+
static USCC_WEIGHT: LazyLock<[u8; 17]> = LazyLock::new(|| {
73+
[
74+
1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28,
75+
]
76+
});
77+
78+
/// Determine whether it is a valid [Unified Social Credit Code](http://c.gb688.cn/bzgk/gb/showGb?type=online&hcno=24691C25985C1073D3A7C85629378AC0).
79+
pub fn is_valid_unified_social_credit_code(uscc: &str) -> bool {
80+
if USCC_REGEX.is_match(uscc) {
81+
let total_weight = uscc
82+
.chars()
83+
.take(17)
84+
.enumerate()
85+
.map(|(index, ref c)| {
86+
// The previously executed regular expression match ensures that the value retrieval operation here is safe.
87+
*USCC_BASE_CHARS.get(c).unwrap() as usize * USCC_WEIGHT[index] as usize
88+
})
89+
.sum::<usize>();
90+
91+
let check_flag = ((31 - (total_weight % 31)) % 31) as u8;
92+
93+
match USCC_BASE_CHARS.iter().find(|(_, v)| **v == check_flag) {
94+
Some((&flag, _)) => uscc.chars().last().unwrap() == flag,
95+
_ => false,
96+
}
97+
} else {
98+
false
99+
}
100+
}
101+
}
102+
47103
pub struct FormatValidator {
48104
pub format_type: String,
49105
}
@@ -172,6 +228,7 @@ impl Validator for FormatValidator {
172228
&& chrono::DateTime::parse_from_rfc3339(value_str).is_ok()
173229
}
174230
"color" => COLOR_REGEX.is_match(value_str),
231+
"uscc" => uscc::is_valid_unified_social_credit_code(value_str),
175232
_ => return Err(format!("Unsupported format type: {}", self.format_type)),
176233
};
177234

@@ -197,7 +254,7 @@ impl FromDirective for FormatValidator {
197254
match params.get("type").and_then(|v| v.as_str()) {
198255
Some(format_type) => match format_type {
199256
"email" | "url" | "uuid" | "ipv4" | "ipv6" | "date" | "datetime"
200-
| "time" | "month" | "week" | "color" => Ok(Self {
257+
| "time" | "month" | "week" | "color" | "uscc" => Ok(Self {
201258
format_type: format_type.to_string(),
202259
}),
203260
_ => Err(format!("Unsupported format type: {}", format_type)),
@@ -452,4 +509,21 @@ mod tests {
452509
assert!(validator.validate(&json!(null)).is_err());
453510
assert!(validator.validate(&json!(["[email protected]"])).is_err());
454511
}
512+
513+
#[test]
514+
fn test_uscc_format() {
515+
let mut params = HashMap::new();
516+
params.insert("type".into(), json!("uscc"));
517+
let directive = create_directive(params);
518+
let validator = FormatValidator::from_directive(directive).unwrap();
519+
520+
assert!(validator.validate(&json!("91440300MA5FXT4K8N")).is_ok());
521+
assert!(validator.validate(&json!("91110108660511594M")).is_ok());
522+
assert!(validator.validate(&json!("91330110MA2AXY0E7F")).is_ok());
523+
assert!(validator.validate(&json!("91330100716105852F")).is_ok());
524+
assert!(validator.validate(&json!("911101085923662400")).is_ok());
525+
assert!(validator.validate(&json!("911101085923662401")).is_err()); // The value of the check digit (the last character) is incorrect.
526+
assert!(validator.validate(&json!("91110108592366240")).is_err()); // invalid length
527+
assert!(validator.validate(&json!("9111010859236624001")).is_err()); // invalid length
528+
}
455529
}

0 commit comments

Comments
 (0)