@@ -44,6 +44,62 @@ static WEEK_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\d{4}-W\d{2}
4444static 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+
47103pub 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