From c4679373b9d19f9ca5d474482c4a6c4e92aa4b6c Mon Sep 17 00:00:00 2001 From: Geoffrey Redon Date: Wed, 28 Oct 2026 17:24:31 +0100 Subject: [PATCH 1/2] SDS-3137: Add Japan My Number checksum validator --- sds/src/scanner/regex_rule/config.rs | 1 + .../japan_my_number_checksum.rs | 67 +++++++++++++++++++ sds/src/secondary_validation/mod.rs | 3 + 3 files changed, 71 insertions(+) create mode 100644 sds/src/secondary_validation/japan_my_number_checksum.rs diff --git a/sds/src/scanner/regex_rule/config.rs b/sds/src/scanner/regex_rule/config.rs index d4600ceb..e1ac69fa 100644 --- a/sds/src/scanner/regex_rule/config.rs +++ b/sds/src/scanner/regex_rule/config.rs @@ -248,6 +248,7 @@ pub enum SecondaryValidator { IbanChecker, IrishPpsChecksum, ItalianNationalIdChecksum, + JapanMyNumberChecksum, JwtClaimsValidator { config: JwtClaimsValidatorConfig, }, diff --git a/sds/src/secondary_validation/japan_my_number_checksum.rs b/sds/src/secondary_validation/japan_my_number_checksum.rs new file mode 100644 index 00000000..9049f4de --- /dev/null +++ b/sds/src/secondary_validation/japan_my_number_checksum.rs @@ -0,0 +1,67 @@ +use crate::secondary_validation::{Validator, validate_mod11_weighted_checksum}; + +/// Check digit defined by Article 5 of Japanese ordinance No. 85: +/// https://laws.e-gov.go.jp/law/426M60000008085#Mp-At_5 +pub struct JapanMyNumberChecksum; + +const WEIGHTS: &[u32; 11] = &[6, 5, 4, 3, 2, 7, 6, 5, 4, 3, 2]; + +impl Validator for JapanMyNumberChecksum { + fn is_valid_match(&self, regex_match: &str) -> bool { + validate_mod11_weighted_checksum(regex_match, WEIGHTS, |remainder| { + Some(if remainder <= 1 { 0 } else { 11 - remainder }) + }) + } +} + +#[cfg(test)] +mod test { + use crate::secondary_validation::{JapanMyNumberChecksum, Validator}; + + #[test] + fn test_published_japan_my_numbers() { + // TKC examples: + // https://www.prft.tkc.co.jp/SystemqaCPNYFree/Main/QaItem.aspx?category_id=2044&item_id=83412&system_id=260 + assert!(JapanMyNumberChecksum.is_valid_match("123456789018")); + assert!(!JapanMyNumberChecksum.is_valid_match("923456789018")); + } + + #[test] + fn test_generated_japan_my_numbers() { + // Generated fixtures from https://box4.dev/en/japan/my-number-generator + for number in [ + "6637 6256 7333", + "8834 6653 3261", + "7680 7456 4211", + "3385 0214 0532", + "5800 0094 7110", + ] { + assert!(JapanMyNumberChecksum.is_valid_match(number), "{number}"); + assert!(JapanMyNumberChecksum.is_valid_match(&number.replace(' ', ""))); + } + } + + #[test] + fn test_incorrect_check_digit() { + assert!(!JapanMyNumberChecksum.is_valid_match("123456789012")); + assert!(!JapanMyNumberChecksum.is_valid_match("1234 5678 9012")); + } + + #[test] + fn test_incorrect_digit_count() { + for number in ["", "12345678901", "1234567890180"] { + assert!(!JapanMyNumberChecksum.is_valid_match(number), "{number}"); + } + } + + #[test] + fn test_zero_check_digit_for_small_remainders() { + // Synthetic arithmetic cases: sums 11 and 12 give remainders 0 and 1. + for number in ["000000000140", "000000000060"] { + assert!(JapanMyNumberChecksum.is_valid_match(number), "{number}"); + } + for number in ["000000000141", "000000000061"] { + assert!(!JapanMyNumberChecksum.is_valid_match(number), "{number}"); + } + } +} diff --git a/sds/src/secondary_validation/mod.rs b/sds/src/secondary_validation/mod.rs index 23995509..5376b436 100644 --- a/sds/src/secondary_validation/mod.rs +++ b/sds/src/secondary_validation/mod.rs @@ -29,6 +29,7 @@ mod iban_checker; mod irish_pps_checksum; mod iso_7064_checksum; mod italian_national_id_checksum; +mod japan_my_number_checksum; pub mod jwt_claims_validator; mod jwt_expiration_checker; mod latvia_national_id_checksum; @@ -100,6 +101,7 @@ pub use crate::secondary_validation::iso_7064_checksum::{ Mod97_10checksum, Mod661_26checksum, Mod1271_36Checksum, }; pub use crate::secondary_validation::italian_national_id_checksum::ItalianNationalIdChecksum; +pub use crate::secondary_validation::japan_my_number_checksum::JapanMyNumberChecksum; pub use crate::secondary_validation::jwt_claims_validator::JwtClaimsValidator; pub use crate::secondary_validation::jwt_expiration_checker::JwtExpirationChecker; pub use crate::secondary_validation::latvia_national_id_checksum::LatviaNationalIdChecksum; @@ -247,6 +249,7 @@ impl SecondaryValidator { SecondaryValidator::IbanChecker => Arc::new(IbanChecker), SecondaryValidator::IrishPpsChecksum => Arc::new(IrishPpsChecksum), SecondaryValidator::ItalianNationalIdChecksum => Arc::new(ItalianNationalIdChecksum), + SecondaryValidator::JapanMyNumberChecksum => Arc::new(JapanMyNumberChecksum), SecondaryValidator::JwtClaimsValidator { config } => { Arc::new(JwtClaimsValidator::new(config.clone())) } From 0a9410bbe6db46f55ae8102ac2f5d5d89bf21801 Mon Sep 17 00:00:00 2001 From: Geoffrey Redon Date: Thu, 29 Oct 2026 10:47:02 +0100 Subject: [PATCH 2/2] Lint mod.rs --- sds/src/scanner/regex_rule/config.rs | 2 +- sds/src/secondary_validation/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sds/src/scanner/regex_rule/config.rs b/sds/src/scanner/regex_rule/config.rs index 08ce24ba..4e7e762e 100644 --- a/sds/src/scanner/regex_rule/config.rs +++ b/sds/src/scanner/regex_rule/config.rs @@ -248,9 +248,9 @@ pub enum SecondaryValidator { IbanChecker, IrishPpsChecksum, ItalianNationalIdChecksum, - JapanMyNumberChecksum, JapanCorporateNumberChecksum, JapanDrivingLicenseChecksum, + JapanMyNumberChecksum, JwtClaimsValidator { config: JwtClaimsValidatorConfig, }, diff --git a/sds/src/secondary_validation/mod.rs b/sds/src/secondary_validation/mod.rs index 0c5ed486..22422ac7 100644 --- a/sds/src/secondary_validation/mod.rs +++ b/sds/src/secondary_validation/mod.rs @@ -29,9 +29,9 @@ mod iban_checker; mod irish_pps_checksum; mod iso_7064_checksum; mod italian_national_id_checksum; -mod japan_my_number_checksum; mod japan_corporate_number_checksum; mod japan_driving_license_checksum; +mod japan_my_number_checksum; pub mod jwt_claims_validator; mod jwt_expiration_checker; mod latvia_national_id_checksum; @@ -103,9 +103,9 @@ pub use crate::secondary_validation::iso_7064_checksum::{ Mod97_10checksum, Mod661_26checksum, Mod1271_36Checksum, }; pub use crate::secondary_validation::italian_national_id_checksum::ItalianNationalIdChecksum; -pub use crate::secondary_validation::japan_my_number_checksum::JapanMyNumberChecksum; pub use crate::secondary_validation::japan_corporate_number_checksum::JapanCorporateNumberChecksum; pub use crate::secondary_validation::japan_driving_license_checksum::JapanDrivingLicenseChecksum; +pub use crate::secondary_validation::japan_my_number_checksum::JapanMyNumberChecksum; pub use crate::secondary_validation::jwt_claims_validator::JwtClaimsValidator; pub use crate::secondary_validation::jwt_expiration_checker::JwtExpirationChecker; pub use crate::secondary_validation::latvia_national_id_checksum::LatviaNationalIdChecksum;