Compare commits

...

24 Commits

Author SHA1 Message Date
Vlasislav Kashin
519148ffd9 Update readme 2025-07-13 21:32:08 +03:00
Vlasislav Kashin
6be301ebd8 Refactor names and imports 2025-07-13 21:29:41 +03:00
Vlasislav Kashin
76a8d4857f Refactor exports and imports 2025-07-13 21:24:52 +03:00
Vlasislav Kashin
7f7bab7f16 Reorganize imports and exports 2025-07-13 21:16:14 +03:00
Vlasislav Kashin
71d2562d83 Add readme, license 2025-07-13 19:25:17 +03:00
Vlasislav Kashin
9c67b42e9c Revert fixes 2025-07-13 18:57:38 +03:00
Vlasislav Kashin
56734bcb1c Add documentation, minor fixes 2025-07-13 18:55:50 +03:00
Vlasislav Kashin
b64c063563 * 2025-07-13 17:51:02 +03:00
Vlasislav Kashin
da9f5e9198 Add copyright header 2025-07-13 17:50:43 +03:00
Vlasislav Kashin
0ceb7c6c8c Add skip install flag to generate_metadata script 2025-07-13 17:47:05 +03:00
Vlasislav Kashin
b979b290b8 Sanitize project 2025-07-13 17:37:30 +03:00
Vlasislav Kashin
923a941473 Update tests 2025-07-13 17:30:47 +03:00
Vlasislav Kashin
03911c0572 Add more tests, better naming and bug fixes 2025-07-13 17:07:40 +03:00
Vlasislav Kashin
77fa0e2b09 Sanitaze code 2025-07-13 16:24:00 +03:00
Vlasislav Kashin
f646fe4605 Update generate metadata script 2025-07-13 15:03:02 +03:00
Vlasislav Kashin
cb5f0d8fcc Refactor build script 2025-07-13 15:01:40 +03:00
Vlasislav Kashin
3a2e8e6c0f Move helper constants out of folder 2025-07-13 14:59:39 +03:00
Vlasislav Kashin
467416e3ef Update generated location 2025-07-13 14:58:49 +03:00
Vlasislav Kashin
1464119ff8 Better error naming 2025-07-13 14:52:06 +03:00
Vlasislav Kashin
beae04dee8 Add more tests, better error handling 2025-07-13 14:49:56 +03:00
Vlasislav Kashin
ebe7d236e9 feat: update regex, bug fixes, add tests 2025-07-12 23:30:44 +03:00
Vlasislav Kashin
2fea8f1e20 Phonenumberutil: add is_alpha_number 2025-07-12 21:21:32 +03:00
Vlasislav Kashin
392c793d5c Update phonenumberutil get_national_significant_number - &self reciever 2025-07-12 20:59:17 +03:00
Vlasislav Kashin
e7daffa6f7 helper_constants: fix const REGION_CODE_FOR_NON_GEO_ENTITY 2025-07-12 20:35:33 +03:00
40 changed files with 5963 additions and 1435 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
/target
.vscode

489
Cargo.lock generated
View File

@@ -11,6 +11,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstream"
version = "0.6.19"
@@ -73,18 +79,91 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cfg-if"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "ciborium"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
[[package]]
name = "ciborium-ll"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
dependencies = [
"ciborium-io",
"half",
]
[[package]]
name = "clap"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
dependencies = [
"anstyle",
"clap_lex",
]
[[package]]
name = "clap_lex"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "colog"
version = "1.3.0"
@@ -112,12 +191,73 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "criterion"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
dependencies = [
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot",
"is-terminal",
"itertools",
"num-traits",
"once_cell",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crunchy"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "csv"
version = "1.3.1"
@@ -230,6 +370,12 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getrandom"
version = "0.3.3"
@@ -242,6 +388,16 @@ dependencies = [
"wasi",
]
[[package]]
name = "half"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
dependencies = [
"cfg-if",
"crunchy",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
@@ -260,6 +416,12 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "home"
version = "0.5.11"
@@ -279,12 +441,32 @@ dependencies = [
"hashbrown 0.15.4",
]
[[package]]
name = "is-terminal"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.15"
@@ -315,6 +497,16 @@ dependencies = [
"syn",
]
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -327,6 +519,12 @@ version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
@@ -355,12 +553,46 @@ version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "lru-cache"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "memchr"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
@@ -373,6 +605,18 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]]
name = "oncemutex"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d11de466f4a3006fe8a5e7ec84e93b79c70cb992ae0aa0eb631ad2df8abfe2"
[[package]]
name = "oorandom"
version = "11.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
[[package]]
name = "parking_lot_core"
version = "0.9.11"
@@ -386,6 +630,54 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "phonenumber"
version = "0.3.7+8.13.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2247167dc3741816fdd4d3690e97f56a892a264b44f4c702078b72d1f8b6bd40"
dependencies = [
"bincode",
"either",
"fnv",
"nom",
"once_cell",
"quick-xml",
"regex",
"regex-cache",
"serde",
"serde_derive",
"strum 0.26.3",
"thiserror 1.0.69",
]
[[package]]
name = "plotters"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
[[package]]
name = "plotters-svg"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
dependencies = [
"plotters-backend",
]
[[package]]
name = "portable-atomic"
version = "1.11.1"
@@ -461,6 +753,15 @@ dependencies = [
"thiserror 1.0.69",
]
[[package]]
name = "quick-xml"
version = "0.37.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.40"
@@ -476,6 +777,26 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "redox_syscall"
version = "0.5.13"
@@ -494,7 +815,7 @@ dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
"regex-syntax 0.8.5",
]
[[package]]
@@ -505,9 +826,27 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"regex-syntax 0.8.5",
]
[[package]]
name = "regex-cache"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7b62d69743b8b94f353b6b7c3deb4c5582828328bcb8d5fedf214373808793"
dependencies = [
"lru-cache",
"oncemutex",
"regex",
"regex-syntax 0.6.29",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.8.5"
@@ -519,16 +858,18 @@ name = "rlibphonenumber"
version = "0.1.0"
dependencies = [
"colog",
"criterion",
"dashmap",
"dec_from_char",
"env_logger",
"fast-cat",
"itoa",
"log",
"phonenumber",
"protobuf",
"protobuf-codegen",
"regex",
"strum",
"strum 0.27.1",
"thiserror 2.0.12",
]
@@ -570,6 +911,15 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -596,19 +946,53 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "strum"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [
"strum_macros 0.26.4",
]
[[package]]
name = "strum"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
dependencies = [
"strum_macros",
"strum_macros 0.27.1",
]
[[package]]
name = "strum_macros"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
@@ -688,6 +1072,16 @@ dependencies = [
"syn",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
@@ -700,6 +1094,16 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
@@ -709,6 +1113,74 @@ dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "which"
version = "4.4.2"
@@ -721,6 +1193,15 @@ dependencies = [
"rustix 0.38.44",
]
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "windows-sys"
version = "0.59.0"

View File

@@ -10,7 +10,7 @@ build = "build/rust_build.rs"
log = "0.4.27"
# helpful error package
thiserror = "2.0.12"
# google protobuf lib required to use .proto files from assets
# protobuf lib required to use .proto files from assets
protobuf = "3.7.2"
# optimized concurrent map
dashmap = "6.1.0"
@@ -34,4 +34,14 @@ protobuf-codegen = "3.7.2"
[dev-dependencies]
colog = "1.3.0"
env_logger = "0.11.8"
env_logger = "0.11.8"
criterion = "0.5"
phonenumber = "0.3"
[[bench]]
name = "format_bench"
harness = false
[[bench]]
name = "parsing_bench"
harness = false

176
LICENSE Normal file
View File

@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

121
Readme.md Normal file
View File

@@ -0,0 +1,121 @@
# libphonenumber-rust
[![Crates.io](https://img.shields.io/crates/v/rlibphonenumber.svg)](https://crates.io/crates/rlibphonenumber)
[![Docs.rs](https://docs.rs/phonenumber/badge.svg)](https://docs.rs/rlibphonenumber)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
A Rust port of Google's comprehensive library for parsing, formatting, and validating international phone numbers.
## Overview
This library is a new adaptation of Google's `libphonenumber` for Rust. Its primary goal is to provide a powerful and efficient tool for handling phone numbers, with a structure that is intuitively close to the original C++ version.
You might be aware of an existing Rust implementation of `libphonenumber`. However, its maintenance has slowed, and I believe that a fresh start is the best path forward. This project aims to deliver a more direct and familiar port for developers acquainted with the C++ or Java versions of the original library.
This library gives you access to a wide range of functionalities, including:
* Parsing and formatting phone numbers.
* Validating phone numbers for all regions of the world.
* Determining the number type (e.g., Mobile, Fixed-line, Toll-free).
* Providing example numbers for every country.
## Performance
The following benchmarks were run against the `rust-phonenumber` crate. All tests were performed on the same machine and dataset. *Lower is better.*
### Formatting
| Format | rlibphonenumber (this crate) | rust-phonenumber | Performance Gain |
|:---|:---:|:---:|:---:|
| **E164** | **~78 ns** | ~2.59 µs | **~33x faster** |
| **International** | **~1.34 µs** | ~3.21 µs | **~2.4x faster** |
| **National** | **~2.33 µs** | ~4.87 µs | **~2.1x faster** |
| **RFC3966** | **~1.62 µs** | ~3.47 µs | **~2.1x faster** |
### Parsing
| Task | rlibphonenumber (this crate) | rust-phonenumber | Performance Gain |
|:--- |:---:|:---:|:---:|
| **Parse** | **~11.60 µs** | ~13.45 µs | **~16% faster** |
This significant performance advantage is achieved through a focus on minimizing allocations, a more direct implementation path, and the use of modern tooling for metadata generation.
## Current Status
The project is currently in its initial phase of development. The core functionalities are being ported module by module to ensure quality and consistency.
### Implemented:
* **PhoneNumberUtil:** The main utility for all phone number operations, such as parsing, formatting, and validation (Passes original tests).
### Future Plans:
The roadmap includes porting the following key components:
* **AsYouTypeFormatter:** To format phone numbers as they are being typed.
* **PhoneNumberOfflineGeocoder:** To provide geographical information for a phone number.
* **PhoneNumberToCarrierMapper:** To identify the carrier associated with a phone number.
## Installation
Add this to your `Cargo.toml`:
```toml
[dependencies]
rlibphonenumber = "0.1.0" # Replace with the actual version
```
## Getting Started
Here is a basic example of how to parse and format a phone number:
```rust
use rlibphonenumber::{PhoneNumberFormat, PHONE_NUMBER_UTIL};
fn main() {
let number_to_parse = "+14155552671";
let default_region = "US";
match PHONE_NUMBER_UTIL.parse(number_to_parse, default_region) {
Ok(number) => {
println!("Parsed number: {:?}", number);
let formatted_number = PHONE_NUMBER_UTIL.format(&number, PhoneNumberFormat::International).unwrap();
println!("International format: {}", formatted_number);
let is_valid = PHONE_NUMBER_UTIL.is_valid_number(&number).unwrap();
println!("Is the number valid? {}", is_valid);
}
Err(e) => {
println!("Error parsing number: {:?}", e);
}
}
}
```
## For Contributors
Contributions are **highly** welcome! Whether you are fixing a bug, improving documentation, or helping to port a new module, your help is appreciated.
### Code Generation
To maintain consistency with the original library, this project uses pre-compiled metadata. If you need to regenerate the metadata, for instance, after updating the `PhoneNumberMetadata.xml` file, you can use the provided tools.
The `tools` directory contains a rewritten Rust-based code generator for the C++ pre-compiled metadata.
To run the code generation process, simply execute the following script:
```sh
./tools/scripts/generate_metadata.sh
```
This script will:
1. Build the Java-based tool that converts the XML metadata to a Rust-compatible format.
2. Run the generator for the main metadata and the test metadata.
3. Place the generated `.rs` files into the `src/generated/metadata` directory.
You can skip the Java build step by passing the `--skip-install` flag, which is useful if no changes were made to the generator itself.
```sh
./tools/scripts/generate_metadata.sh --skip-install```
## License
This project is licensed under the Apache License, Version 2.0. Please see the `LICENSE` file for details.

76
benches/format_bench.rs Normal file
View File

@@ -0,0 +1,76 @@
use criterion::{Criterion, black_box, criterion_group, criterion_main};
use rlibphonenumber::{PhoneNumberFormat, PHONE_NUMBER_UTIL};
use phonenumber::{
self as rlp,
country::Id::{self, AU}, Mode,
};
type TestEntity = (&'static str, &'static str, Id);
fn setup_numbers() -> Vec<TestEntity> {
vec![("0011 54 9 11 8765 4321 ext. 1234", "AU", AU)]
}
fn convert_to_rlp_numbers(numbers: &[TestEntity]) -> Vec<rlp::PhoneNumber> {
numbers
.iter()
.map(|s| rlp::parse(Some(s.2), s.0).unwrap())
.collect()
}
fn convert_to_rlibphonenumber_numbers(
numbers: &[TestEntity],
) -> Vec<rlibphonenumber::PhoneNumber> {
numbers
.iter()
.map(|s| PHONE_NUMBER_UTIL.parse(s.0, s.1).unwrap())
.collect()
}
fn formatting_benchmark(c: &mut Criterion) {
let numbers = setup_numbers();
let rlp_numbers = convert_to_rlp_numbers(&numbers);
let numbers = convert_to_rlibphonenumber_numbers(&numbers);
let mut group = c.benchmark_group("Formatting Comparison");
let mut test = |format_a: PhoneNumberFormat, format_b: Mode| {
group.bench_function(format!("rlibphonenumber: format({:?})", format_a), |b| {
b.iter(|| {
for number in &numbers {
PHONE_NUMBER_UTIL
.format(black_box(number), black_box(format_a))
.unwrap();
}
})
});
group.bench_function(format!("rust-phonenumber: format({:?})", format_b), |b| {
b.iter(|| {
for number in &rlp_numbers {
rlp::format(black_box(number)).mode(format_b).to_string();
}
})
});
for (number_a, number_b) in rlp_numbers.iter().zip(numbers.iter()) {
assert_eq!(
rlp::format(number_a).mode(format_b).to_string(),
PHONE_NUMBER_UTIL
.format(number_b, format_a)
.unwrap()
);
}
};
test(PhoneNumberFormat::E164, Mode::E164);
test(PhoneNumberFormat::International, Mode::International);
test(PhoneNumberFormat::National, Mode::National);
test(PhoneNumberFormat::RFC3966, Mode::Rfc3966);
group.finish();
}
criterion_group!(benches, formatting_benchmark);
criterion_main!(benches);

73
benches/parsing_bench.rs Normal file
View File

@@ -0,0 +1,73 @@
// benches/parsing_benchmark.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
// --- Импорты из вашей библиотеки ---
use rlibphonenumber::PHONE_NUMBER_UTIL;
// --- Импорты из внешней библиотеки ---
use phonenumber::{self as rlp, country::Id};
// Тип для наших тестовых данных: (строкаомера, регион_для_вас, регион_для_rlp)
type TestEntity = (&'static str, &'static str, Id);
/// Подготавливает разнообразный набор данных для тестирования парсинга.
/// Это дает более объективную оценку, чем один номер.
fn setup_parsing_data() -> Vec<TestEntity> {
use phonenumber::country::Id::*;
vec![
// Оригинальный номер из вашего примера
("0011 54 9 11 8765 4321 ext. 1234", "AU", AU),
// Простой номер США в национальном формате
("(650) 253-0000", "US", US),
// Номер Великобритании в международном формате со знаком +
("+44 20 8765 4321", "GB", GB),
// Номер Великобритании с национальным префиксом (ведущий ноль)
("020 8765 4321", "GB", GB),
// Сложный мобильный номер Аргентины
("011 15-1234-5678", "AR", AR),
// Итальянский номер со значащим ведущим нулем
("02 12345678", "IT", IT),
// "Vanity" номер (с буквами)
("1-800-FLOWERS", "US", US),
// Короткий номер, который может быть валидным в некоторых регионах
("12345", "DE", DE),
]
}
fn parsing_benchmark(c: &mut Criterion) {
// Получаем наш набор тестовых данных
let numbers_to_parse = setup_parsing_data();
let mut group = c.benchmark_group("Parsing Comparison");
// --- Бенчмарк для вашей библиотеки rlibphonenumber ---
group.bench_function("rlibphonenumber: parse()", |b| {
// b.iter() запускает код в цикле много раз для замера
b.iter(|| {
// Итерируемся по всем номерам в нашем наборе
for (number_str, region, _) in &numbers_to_parse {
// Вызываем parse, обернув аргументы в black_box.
// Это гарантирует, что компилятор не оптимизирует вызов.
// Мы не используем результат, так как нас интересует только скорость выполнения.
let _ = PHONE_NUMBER_UTIL.parse(black_box(number_str), black_box(region));
}
})
});
// --- Бенчмарк для библиотеки rust-phonenumber ---
group.bench_function("rust-phonenumber: parse()", |b| {
b.iter(|| {
for (number_str, _, region_id) in &numbers_to_parse {
// Аналогичный вызов для второй библиотеки
let _ = rlp::parse(black_box(Some(*region_id)), black_box(number_str));
}
})
});
group.finish();
}
// Макросы для регистрации и запуска бенчмарка
criterion_group!(benches, parsing_benchmark);
criterion_main!(benches);

View File

@@ -1,76 +1,20 @@
/**
* This file represents content of https://github.com/google/libphonenumber/tree/master/tools/cpp
*/
use std::{collections::BTreeMap, fs::File, io::{BufRead, BufReader}, num::ParseIntError, path::Path};
use thiserror::Error;
#[derive(Debug, Error)]
enum BuildError {
#[error("IO error occurred: {0}")]
IO(#[from] std::io::Error),
#[error("Line {line_num} is too long (max is {max_len} bytes)")]
LineTooLong { line_num: usize, max_len: usize },
#[error("Failed to parse prefix '{prefix}': {source}")]
PrefixParseError {
prefix: String,
#[source]
source: ParseIntError,
},
}
fn parse_prefixes(path: &str, prefixes: &mut BTreeMap<i32, String>) -> Result<(), BuildError> {
prefixes.clear();
let input = File::open(path)?;
const MAX_LINE_LENGTH: usize = 2 * 1024;
let mut reader = BufReader::new(input);
let mut line_buffer = String::with_capacity(MAX_LINE_LENGTH);
let mut line_number = 0;
loop {
line_number += 1;
line_buffer.clear();
let bytes_read = reader.read_line(&mut line_buffer)?;
if bytes_read == 0 {
break;
}
if !line_buffer.ends_with('\n') {
return Err(BuildError::LineTooLong {
line_num: line_number,
max_len: MAX_LINE_LENGTH,
});
}
let line = line_buffer.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some((prefix_str, desc)) = line.split_once('|') {
if prefix_str.is_empty() {
continue;
}
let prefix_code = prefix_str.parse().map_err(|e| BuildError::PrefixParseError {
prefix: prefix_str.to_string(),
source: e,
})?;
prefixes.insert(prefix_code, desc.to_string());
}
}
Ok(())
}
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
fn main() -> Result<(), BuildError> {
fn main() {
protobuf_codegen::Codegen::new()
.pure()
.includes(["resources"])
@@ -78,5 +22,4 @@ fn main() -> Result<(), BuildError> {
.input("resources/phonenumber.proto")
.cargo_out_dir("proto_gen")
.run_from_script();
Ok(())
}

View File

@@ -0,0 +1,26 @@
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
mod metadata;
// use only in test case
#[cfg(test)]
mod test_metadata;
pub use metadata::METADATA;
#[cfg(test)]
pub use test_metadata::TEST_METADATA;

View File

@@ -1,4 +1,5 @@
// Copyright (C) 2025 @Vloldik
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,5 +12,6 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This file is generated automatically, do not edit it manually.
pub mod proto;
pub mod metadata;

View File

@@ -1,3 +0,0 @@
mod region_code;
pub use region_code::RegionCode;

View File

@@ -1,13 +0,0 @@
pub struct RegionCode {
}
impl RegionCode {
/// Returns a region code string representing the "unknown" region.
pub fn get_unknown() -> &'static str {
return Self::zz();
}
pub fn zz() -> &'static str {
return "ZZ";
}
}

View File

@@ -1,9 +1,23 @@
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::generated::proto::phonemetadata::PhoneNumberDesc;
use crate::proto_gen::phonemetadata::PhoneNumberDesc;
/// Internal phonenumber matching API used to isolate the underlying
/// implementation of the matcher and allow different implementations to be
/// swapped in easily.
pub(crate) trait MatcherApi: Send + Sync {
/// Returns whether the given national number (a string containing only decimal
/// digits) matches the national number pattern defined in the given

View File

@@ -1,11 +1,25 @@
mod shortnumberinfo;
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
mod interfaces;
/// This module is automatically generated from /resources/*.proto
mod proto_gen;
mod generated;
mod phonenumberutil;
mod regexp_cache;
mod regex_based_matcher;
pub mod i18n;
pub mod region_code;
pub(crate) mod regex_util;
pub(crate) mod string_util;
@@ -27,9 +41,12 @@ pub use phonenumberutil::{
ExtractNumberResult,
PhoneNumberUtil
},
errors,
enums,
errors::{*},
enums::{*},
};
pub use proto_gen::phonemetadata;
pub use proto_gen::phonenumber;
pub use generated::proto::phonemetadata;
pub use generated::proto::phonenumber::PhoneNumber;
pub use generated::proto::phonenumber::phone_number::CountryCodeSource;
pub use regexp_cache::InvalidRegexError;
mod tests;

View File

@@ -1,3 +1,17 @@
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// std::borrow::Cow
// std::option::Option

View File

@@ -1,3 +1,18 @@
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use strum::EnumIter;
/// INTERNATIONAL and NATIONAL formats are consistent with the definition
@@ -65,7 +80,7 @@ pub enum MatchType {
/// Possible outcomes when testing if a PhoneNumber is possible.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ValidNumberLenType {
pub enum NumberLengthType {
/// The number length matches that of valid numbers for this region.
IsPossible,
/// The number length matches that of local numbers for this region only

View File

@@ -1,16 +1,31 @@
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::num::ParseIntError;
use thiserror::Error;
use crate::regexp_cache::ErrorInvalidRegex;
use crate::regexp_cache::InvalidRegexError;
#[derive(Debug, PartialEq, Error)]
pub enum InternalLogicError {
#[error("{0}")]
InvalidRegexError(#[from] ErrorInvalidRegex),
InvalidRegex(#[from] InvalidRegexError),
#[error("{0}")]
InvalidMetadataForValidRegionError(#[from] InvalidMetadataForValidRegionError)
InvalidMetadataForValidRegion(#[from] InvalidMetadataForValidRegionError)
}
#[derive(Debug, PartialEq, Error)]
@@ -18,9 +33,9 @@ pub enum ParseError {
// Removed as OK variant
// NoParsingError,
#[error("Invalid country code")]
InvalidCountryCodeError, // INVALID_COUNTRY_CODE in the java version.
#[error("Not a number")]
NotANumber,
InvalidCountryCode, // INVALID_COUNTRY_CODE in the java version.
#[error("Not a number: {0}")]
NotANumber(#[from] NotANumberError),
#[error("Too short after idd")]
TooShortAfterIdd,
#[error("Too short Nsn")]
@@ -28,11 +43,19 @@ pub enum ParseError {
#[error("Too long nsn")]
TooLongNsn, // TOO_LONG in the java version.
#[error("{0}")]
InvalidRegexError(#[from] ErrorInvalidRegex),
InvalidRegex(#[from] InvalidRegexError),
}
#[derive(Debug, PartialEq, Error)]
pub enum NotANumberError {
#[error("Number not matched a valid number pattern")]
NotMatchedValidNumberPattern,
#[error("Invalid phone context")]
InvalidPhoneContext,
#[error("{0}")]
ParseNumberAsIntError(#[from] ParseIntError),
FailedToParseNumberAsInt(#[from] ParseIntError),
#[error("{0}")]
ExtractNumberError(#[from] ExtractNumberError),
FailedToExtractNumber(#[from] ExtractNumberError),
}
#[derive(Debug, PartialEq, Error)]
@@ -43,18 +66,24 @@ pub enum ExtractNumberError {
NotANumber,
}
impl From<ExtractNumberError> for ParseError {
fn from(value: ExtractNumberError) -> Self {
NotANumberError::FailedToExtractNumber(value).into()
}
}
#[derive(Debug, PartialEq, Error)]
pub enum GetExampleNumberError {
#[error("Parse error: {0}")]
ParseError(#[from] ParseError),
FailedToParse(#[from] ParseError),
#[error("{0}")]
InternalLogicError(#[from] InternalLogicError),
Internal(#[from] InternalLogicError),
#[error("No example number")]
NoExampleNumberError,
NoExampleNumber,
#[error("Could not get number")]
CouldNotGetNumberError,
CouldNotGetNumber,
#[error("Invalid metadata")]
InvalidMetadataError
InvalidMetadata
}
@@ -68,7 +97,7 @@ pub struct InvalidMetadataForValidRegionError;
/// Possible outcomes when testing if a PhoneNumber is possible.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Error)]
pub enum ValidationResultErr {
pub enum ValidationError {
/// The number has an invalid country calling code.
#[error("The number has an invalid country calling code")]
InvalidCountryCode,

View File

@@ -1,3 +0,0 @@
pub mod metadata;
pub mod test_metadata;

View File

@@ -1,3 +1,19 @@
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The minimum and maximum length of the national significant number.
pub const MIN_LENGTH_FOR_NSN: usize = 2;
// The ITU says the maximum length should be 15, but we have found longer
@@ -28,7 +44,7 @@ pub const VALID_PUNCTUATION: &'static str = "-x\
pub const CAPTURE_UP_TO_SECOND_NUMBER_START: &'static str = r"(.*)[\\/] *x";
pub const REGION_CODE_FOR_NON_GEO_ENTITY: &'static str = "0001";
pub const REGION_CODE_FOR_NON_GEO_ENTITY: &'static str = "001";
pub const PLUS_SIGN: &'static str = "+";
pub const STAR_SIGN: &'static str = "*";
@@ -50,11 +66,11 @@ pub const VALID_ALPHA_INCL_UPPERCASE: &'static str = "A-Za-z";
// prefix. This can be overridden by region-specific preferences.
pub const DEFAULT_EXTN_PREFIX: &'static str = " ext. ";
pub const POSSIBLE_SEPARATORS_BETWEEN_NUMBER_AND_EXT_LABEL: &'static str = "0001";
pub const POSSIBLE_SEPARATORS_BETWEEN_NUMBER_AND_EXT_LABEL: &'static str = "[ \u{00A0}\\t,]*";
// Optional full stop (.) or colon, followed by zero or more
// spaces/tabs/commas.
pub const POSSIBLE_CHARS_AFTER_EXT_LABEL: &'static str = "[ \u{00A0}\\t,]*";
pub const OPTIONAL_EXT_SUFFIX: &'static str = "[:\\.\u{FF0E}]?[ \u{00A0}\\t,-]*";
pub const POSSIBLE_CHARS_AFTER_EXT_LABEL: &'static str = "[:\\.\u{FF0E}]?[ \u{00A0}\\t,-]*";
pub const OPTIONAL_EXT_SUFFIX: &'static str = "#?";
pub const NANPA_COUNTRY_CODE: i32 = 1;

View File

@@ -1,3 +0,0 @@
mod helper_constants;
pub(super) use helper_constants::{*};

View File

@@ -1,18 +1,36 @@
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::collections::{HashMap, HashSet};
use protobuf::Message;
use strum::IntoEnumIterator;
use crate::{
interfaces::MatcherApi, phonenumberutil::generated::metadata::METADATA, proto_gen::{
phonemetadata::{PhoneMetadata, PhoneMetadataCollection, PhoneNumberDesc},
interfaces::MatcherApi, generated::metadata::METADATA,
generated::proto::{
phonemetadata::{
PhoneMetadata, PhoneMetadataCollection, PhoneNumberDesc
},
phonenumber::PhoneNumber,
}
}
};
use super::{
PhoneNumberFormat, PhoneNumberType, ValidNumberLenType,
errors::ValidationResultErr,
enums::{PhoneNumberFormat, PhoneNumberType, NumberLengthType},
errors::ValidationError,
helper_constants::{
OPTIONAL_EXT_SUFFIX, PLUS_SIGN, POSSIBLE_CHARS_AFTER_EXT_LABEL,
POSSIBLE_SEPARATORS_BETWEEN_NUMBER_AND_EXT_LABEL, RFC3966_EXTN_PREFIX, RFC3966_PREFIX,
@@ -342,7 +360,7 @@ pub fn test_number_length(
phone_number: &str,
phone_metadata: &PhoneMetadata,
phone_number_type: PhoneNumberType,
) -> Result<ValidNumberLenType, ValidationResultErr> {
) -> Result<NumberLengthType, ValidationError> {
let desc_for_type = get_number_desc_by_type(phone_metadata, phone_number_type);
// There should always be "possibleLengths" set for every element. This is
// declared in the XML schema which is verified by
@@ -394,31 +412,31 @@ pub fn test_number_length(
// If the type is not suported at all (indicated by the possible lengths
// containing -1 at this point) we return invalid length.
if *possible_lengths.first().unwrap_or(&-1) == -1 {
return Err(ValidationResultErr::InvalidLength);
return Err(ValidationError::InvalidLength);
}
let actual_length = phone_number.len() as i32;
// This is safe because there is never an overlap beween the possible lengths
// and the local-only lengths; this is checked at build time.
if local_lengths.contains(&actual_length) {
return Ok(ValidNumberLenType::IsPossibleLocalOnly);
return Ok(NumberLengthType::IsPossibleLocalOnly);
}
// here we can unwrap safe
let minimum_length = possible_lengths[0];
if minimum_length == actual_length {
return Ok(ValidNumberLenType::IsPossible);
return Ok(NumberLengthType::IsPossible);
} else if minimum_length > actual_length {
return Err(ValidationResultErr::TooShort);
return Err(ValidationError::TooShort);
} else if possible_lengths[possible_lengths.len() - 1] < actual_length {
return Err(ValidationResultErr::TooLong);
return Err(ValidationError::TooLong);
}
// We skip the first element; we've already checked it.
return if possible_lengths[1..].contains(&actual_length) {
Ok(ValidNumberLenType::IsPossible)
Ok(NumberLengthType::IsPossible)
} else {
Err(ValidationResultErr::InvalidLength)
Err(ValidationError::InvalidLength)
};
}
@@ -428,7 +446,7 @@ pub fn test_number_length(
pub fn test_number_length_with_unknown_type(
phone_number: &str,
phone_metadata: &PhoneMetadata,
) -> Result<ValidNumberLenType, ValidationResultErr> {
) -> Result<NumberLengthType, ValidationError> {
return test_number_length(phone_number, phone_metadata, PhoneNumberType::Unknown);
}

View File

@@ -1,6 +1,22 @@
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::borrow::Cow;
use crate::proto_gen::phonenumber::phone_number::CountryCodeSource;
use crate::CountryCodeSource;
#[derive(Debug)]
pub struct PhoneNumberWithCountryCodeSource<'a> {
@@ -13,19 +29,3 @@ impl<'a> PhoneNumberWithCountryCodeSource<'a> {
Self { phone_number, country_code_source }
}
}
#[derive(Debug)]
pub struct PhoneNumberAndCarrierCode<'a> {
pub carrier_code: Option<&'a str>,
pub phone_number: Cow<'a, str>
}
impl<'a> PhoneNumberAndCarrierCode<'a> {
pub fn new<B: Into<Cow<'a, str>>>(carrier_code: Option<&'a str>, phone_number: B) -> Self {
Self { carrier_code, phone_number: phone_number.into() }
}
pub fn new_phone<B: Into<Cow<'a, str>>>(phone_number: B) -> Self {
Self { carrier_code: None, phone_number: phone_number.into() }
}
}

View File

@@ -1,3 +1,18 @@
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
mod helper_constants;
mod helper_functions;
pub mod errors;
@@ -5,14 +20,12 @@ pub mod enums;
pub mod phonenumberutil;
mod phone_number_regexps_and_mappings;
pub(self) mod helper_types;
pub(self) mod comparisons;
pub(crate) mod generated;
use std::sync::LazyLock;
pub use enums::{MatchType, PhoneNumberFormat, PhoneNumberType, ValidNumberLenType};
use crate::phonenumberutil::phonenumberutil::PhoneNumberUtil;
/// Singleton instance of phone number util for general use
pub static PHONE_NUMBER_UTIL: LazyLock<PhoneNumberUtil> = LazyLock::new(|| {
PhoneNumberUtil::new()
});

View File

@@ -1,3 +1,19 @@
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::collections::{HashMap, HashSet};
use regex::Regex;
@@ -8,6 +24,7 @@ use crate::{phonenumberutil::{helper_constants::{
VALID_PUNCTUATION
}, helper_functions::create_extn_pattern}, regexp_cache::RegexCache};
#[allow(unused)]
pub(super) struct PhoneNumberRegExpsAndMappings {
/// Regular expression of viable phone numbers. This is location independent.
/// Checks we have at least three leading digits, and only valid punctuation,
@@ -307,13 +324,12 @@ impl PhoneNumberRegExpsAndMappings {
separator_pattern: Regex::new(&format!("[{}]+", VALID_PUNCTUATION)).unwrap(),
extn_patterns_for_matching: create_extn_pattern(false),
extn_pattern: Regex::new(&format!("(?i)(?:{})$", &extn_patterns_for_parsing)).unwrap(),
valid_phone_number_pattern: Regex::new(&format!("(?i){}(?:{})?",
valid_phone_number_pattern: Regex::new(&format!("(?i)^(?:{})(?:{})?$",
&valid_phone_number,
extn_patterns_for_parsing
)).unwrap(),
valid_alpha_phone_pattern: Regex::new(&format!("(?i)(?:.*?[{}]){{3}}",
VALID_ALPHA
&extn_patterns_for_parsing
)).unwrap(),
// from java
valid_alpha_phone_pattern: Regex::new("(?:.*?[A-Za-z]){3}.*").unwrap(),
// The first_group_capturing_pattern was originally set to $1 but there
// are some countries for which the first group is not used in the
// national pattern (e.g. Argentina) so the $1 group does not match

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,23 @@
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use log::{error};
use super::regex_util::{RegexFullMatch, RegexConsume};
use crate::{interfaces, proto_gen::phonemetadata::PhoneNumberDesc, regexp_cache::{ErrorInvalidRegex, RegexCache}};
use crate::{interfaces, generated::proto::phonemetadata::PhoneNumberDesc, regexp_cache::{InvalidRegexError, RegexCache}};
pub struct RegexBasedMatcher {
cache: RegexCache,
@@ -16,7 +32,7 @@ impl RegexBasedMatcher {
&self, phone_number: &str,
number_pattern: &str,
allow_prefix_match: bool
) -> Result<bool, ErrorInvalidRegex> {
) -> Result<bool, InvalidRegexError> {
let regexp = self.cache.get_regex(number_pattern)?;
// find first occurrence

View File

@@ -1,3 +1,18 @@
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use regex::{Captures, Match, Regex};
pub trait RegexFullMatch {

View File

@@ -1,3 +1,18 @@
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::sync::Arc;
use dashmap::DashMap;
@@ -5,7 +20,7 @@ use thiserror::Error;
#[derive(Debug, PartialEq, Error)]
#[error("An error occurred while trying to create regex: {0}")]
pub struct ErrorInvalidRegex(#[from] regex::Error);
pub struct InvalidRegexError(#[from] regex::Error);
pub struct RegexCache {
cache: DashMap<String, Arc<regex::Regex>>
@@ -19,7 +34,7 @@ impl RegexCache {
}
}
pub fn get_regex(&self, pattern: &str) -> Result<Arc<regex::Regex>, ErrorInvalidRegex> {
pub fn get_regex(&self, pattern: &str) -> Result<Arc<regex::Regex>, InvalidRegexError> {
if let Some(regex) = self.cache.get(pattern) {
Ok(regex.value().clone())
} else {

28
src/region_code.rs Normal file
View File

@@ -0,0 +1,28 @@
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pub struct RegionCode {
}
impl RegionCode {
/// Returns a region code string representing the "unknown" region.
pub fn get_unknown() -> &'static str {
return Self::zz();
}
pub fn zz() -> &'static str {
return "ZZ";
}
}

View File

View File

@@ -1,3 +1,17 @@
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::borrow::Cow;
/// Strips prefix of given string Cow. Returns option with `Some` if

View File

@@ -1 +1,3 @@
mod tests;
#[cfg(test)]
mod phonenumberutil_tests;
pub(self) mod region_code;

File diff suppressed because it is too large Load Diff

161
src/tests/region_code.rs Normal file
View File

@@ -0,0 +1,161 @@
pub struct RegionCode {}
#[allow(unused)]
impl RegionCode {
pub fn ad() -> &'static str {
"AD"
}
pub fn ae() -> &'static str {
"AE"
}
pub fn am() -> &'static str {
"AM"
}
pub fn ao() -> &'static str {
"AO"
}
pub fn aq() -> &'static str {
"AQ"
}
pub fn ar() -> &'static str {
"AR"
}
pub fn au() -> &'static str {
"AU"
}
pub fn bb() -> &'static str {
"BB"
}
pub fn br() -> &'static str {
"BR"
}
pub fn bs() -> &'static str {
"BS"
}
pub fn by() -> &'static str {
"BY"
}
pub fn ca() -> &'static str {
"CA"
}
pub fn ch() -> &'static str {
"CH"
}
pub fn cl() -> &'static str {
"CL"
}
pub fn cn() -> &'static str {
"CN"
}
pub fn co() -> &'static str {
"CO"
}
pub fn cs() -> &'static str {
"CS"
}
pub fn cx() -> &'static str {
"CX"
}
pub fn de() -> &'static str {
"DE"
}
pub fn fr() -> &'static str {
"FR"
}
pub fn gb() -> &'static str {
"GB"
}
pub fn hu() -> &'static str {
"HU"
}
pub fn it() -> &'static str {
"IT"
}
pub fn jp() -> &'static str {
"JP"
}
pub fn kr() -> &'static str {
"KR"
}
pub fn mx() -> &'static str {
"MX"
}
pub fn nz() -> &'static str {
"NZ"
}
pub fn pl() -> &'static str {
"PL"
}
pub fn re() -> &'static str {
"RE"
}
pub fn ru() -> &'static str {
"RU"
}
pub fn se() -> &'static str {
"SE"
}
pub fn sg() -> &'static str {
"SG"
}
pub fn un001() -> &'static str {
"001"
}
pub fn us() -> &'static str {
"US"
}
pub fn uz() -> &'static str {
"UZ"
}
pub fn yt() -> &'static str {
"YT"
}
pub fn zw() -> &'static str {
"ZW"
}
/// s a region code string representing the "unknown" region.
pub fn get_unknown() -> &'static str {
Self::zz()
}
pub fn zz() -> &'static str {
"ZZ"
}
}

View File

@@ -1,506 +0,0 @@
#[cfg(test)]
use std::{cell::LazyCell, sync::LazyLock};
use std::{collections::{BTreeSet, HashSet}};
use dec_from_char::DecimalExtended;
#[cfg(test)]
use env_logger::Logger;
use log::trace;
use protobuf::Message;
use crate::{errors::ParseError, phonemetadata::PhoneMetadataCollection, phonenumber::PhoneNumber, PhoneNumberUtil};
use crate::phonenumberutil::generated::test_metadata::TEST_METADATA;
// This setup function simulates getting the PhoneNumberUtil instance for each test.
fn get_phone_util() -> PhoneNumberUtil {
let metadata = PhoneMetadataCollection::parse_from_bytes(&TEST_METADATA)
.expect("Metadata should be valid");
// In a real scenario, this would likely return a singleton instance.
return PhoneNumberUtil::new_for_metadata(metadata);
}
// NOTE: To keep the translation focused on the test logic, the mock implementations
// of the methods below are omitted. The translated tests call these methods as if
// they are fully implemented in the Rust `phonenumbers` library.
// =====================================================================
// Конец секции с моками
// =====================================================================
#[test]
fn contains_only_valid_digits() {
// В оригинале это был protected-метод, но мы предполагаем, что он доступен.
fn contains_only_valid_digits(s: &str) -> bool {
// Mock implementation
!s.chars().any(|c| !c.is_decimal_utf8() && c != '')
}
assert!(contains_only_valid_digits(""));
assert!(contains_only_valid_digits("2"));
assert!(contains_only_valid_digits("25"));
assert!(contains_only_valid_digits("")); // ""
assert!(!contains_only_valid_digits("a"));
assert!(!contains_only_valid_digits("2a"));
}
#[test]
fn interchange_invalid_codepoints() {
colog::default_builder()
.filter_level(log::LevelFilter::Trace)
.init();
let phone_util = get_phone_util();
let mut phone_number = PhoneNumber::new();
let valid_inputs = vec![
"+44\u{2013}2087654321", // U+2013, EN DASH
];
for input in valid_inputs {
assert_eq!(input, dec_from_char::normalize_decimals(input));
assert!(phone_util.is_viable_phone_number(input));
phone_util.parse(input, "GB").unwrap();
}
let invalid_inputs = vec![
"+44\u{96}2087654321", // Invalid sequence
"+44\u{0096}2087654321", // U+0096
"+44\u{fffe}2087654321", // U+FFFE
];
for input in invalid_inputs {
assert!(!phone_util.is_viable_phone_number(input));
assert!(
phone_util.parse(input, "GB").is_err_and(| err | matches!(err, ParseError::NotANumber))
);
}
}
#[test]
fn get_supported_regions() {
let phone_util = get_phone_util();
assert!(phone_util
.get_supported_regions()
.count() > 0
)
}
#[test]
fn get_supported_global_network_calling_codes() {
let phone_util = get_phone_util();
let mut calling_codes = BTreeSet::<i32>::new();
// phone_util.get_supported_global_network_calling_codes(&mut calling_codes);
// assert!(!calling_codes.is_empty());
// for &code in &calling_codes {
// assert!(code > 0);
// let mut region_code = String::new();
// phone_util.get_region_code_for_country_code(code, &mut region_code);
// assert_eq!(RegionCode::un001(), region_code);
// }
}
#[test]
fn get_supported_calling_codes() {
let phone_util = get_phone_util();
let mut calling_codes = BTreeSet::<i32>::new();
// phone_util.get_supported_calling_codes(&mut calling_codes);
// assert!(!calling_codes.is_empty());
// for &code in &calling_codes {
// assert!(code > 0);
// let mut region_code = String::new();
// phone_util.get_region_code_for_country_code(code, &mut region_code);
// assert_ne!(RegionCode::zz(), region_code);
// }
// let mut supported_global_network_calling_codes = BTreeSet::<i32>::new();
// phone_util.get_supported_global_network_calling_codes(
// &mut supported_global_network_calling_codes,
// );
// assert!(calling_codes.len() > supported_global_network_calling_codes.len());
// assert!(calling_codes.contains(&979));
}
#[test]
fn get_supported_types_for_region() {
let phone_util = get_phone_util();
let mut types = HashSet::<PhoneNumber>::new();
// phone_util.get_supported_types_for_region(RegionCode::br(), &mut types);
// assert!(types.contains(&PhoneNumberType::FixedLine));
// assert!(!types.contains(&PhoneNumberType::Mobile));
// assert!(!types.contains(&PhoneNumberType::Unknown));
// types.clear();
// phone_util.get_supported_types_for_region(RegionCode::us(), &mut types);
// assert!(types.contains(&PhoneNumberType::FixedLine));
// assert!(types.contains(&PhoneNumberType::Mobile));
// assert!(!types.contains(&PhoneNumberType::FixedLineOrMobile));
// types.clear();
// phone_util.get_supported_types_for_region(RegionCode::zz(), &mut types);
// assert_eq!(0, types.len());
}
#[test]
fn get_supported_types_for_non_geo_entity() {
let phone_util = get_phone_util();
let mut types = HashSet::<PhoneNumber>::new();
// phone_util.get_supported_types_for_non_geo_entity(999, &mut types);
// assert_eq!(0, types.len());
// types.clear();
// phone_util.get_supported_types_for_non_geo_entity(979, &mut types);
// assert!(types.contains(&PhoneNumberType::PremiumRate));
// assert!(!types.contains(&PhoneNumberType::Mobile));
// assert!(!types.contains(&PhoneNumberType::Unknown));
}
#[test]
fn get_region_codes_for_country_calling_code() {
let phone_util = get_phone_util();
let mut regions = Vec::<String>::new();
// phone_util.get_region_codes_for_country_calling_code(1, &mut regions);
// assert!(regions.contains(&RegionCode::us().to_string()));
// assert!(regions.contains(&RegionCode::bs().to_string()));
// regions.clear();
// phone_util.get_region_codes_for_country_calling_code(44, &mut regions);
// assert!(regions.contains(&RegionCode::gb().to_string()));
// regions.clear();
// phone_util.get_region_codes_for_country_calling_code(49, &mut regions);
// assert!(regions.contains(&RegionCode::de().to_string()));
// regions.clear();
// phone_util.get_region_codes_for_country_calling_code(800, &mut regions);
// assert!(regions.contains(&RegionCode::un001().to_string()));
// regions.clear();
// const K_INVALID_COUNTRY_CODE: i32 = 2;
// phone_util.get_region_codes_for_country_calling_code(K_INVALID_COUNTRY_CODE, &mut regions);
// assert!(regions.is_empty());
}
#[test]
fn get_instance_load_us_metadata() {
let phone_util = get_phone_util();
// let metadata = phone_util.get_metadata_for_region(RegionCode::us()).unwrap();
// assert_eq!("US", metadata.id());
// assert_eq!(1, metadata.country_code());
// assert_eq!("011", metadata.international_prefix());
// assert!(metadata.has_national_prefix());
// assert_eq!(2, metadata.number_format().len());
// assert_eq!("(\\d{3})(\\d{3})(\\d{4})", metadata.number_format()[1].pattern());
// assert_eq!("$1 $2 $3", metadata.number_format()[1].format());
// assert_eq!("[13-689]\\d{9}|2[0-35-9]\\d{8}", metadata.general_desc().national_number_pattern());
// assert_eq!("[13-689]\\d{9}|2[0-35-9]\\d{8}", metadata.fixed_line().national_number_pattern());
// assert_eq!(1, metadata.general_desc().possible_length().len());
// assert_eq!(10, metadata.general_desc().possible_length()[0]);
// assert_eq!(0, metadata.toll_free().possible_length().len());
// assert_eq!("900\\d{7}", metadata.premium_rate().national_number_pattern());
// assert!(!metadata.shared_cost().has_national_number_pattern());
}
// ... Другие тесты, связанные с метаданными, могут быть переведены аналогично ...
#[test]
fn get_national_significant_number() {
let phone_util = get_phone_util();
let mut number = PhoneNumber::new();
number.set_country_code(1);
number.set_national_number(6502530000);
let mut national_significant_number = String::new();
// phone_util.get_national_significant_number(&number, &mut national_significant_number);
// assert_eq!("6502530000", national_significant_number);
national_significant_number.clear();
number.set_country_code(39);
number.set_national_number(312345678);
// phone_util.get_national_significant_number(&number, &mut national_significant_number);
// assert_eq!("312345678", national_significant_number);
national_significant_number.clear();
number.set_country_code(39);
number.set_national_number(236618300);
number.set_italian_leading_zero(true);
// phone_util.get_national_significant_number(&number, &mut national_significant_number);
// assert_eq!("0236618300", national_significant_number);
national_significant_number.clear();
number.clear();
number.set_country_code(800);
number.set_national_number(12345678);
// phone_util.get_national_significant_number(&number, &mut national_significant_number);
// assert_eq!("12345678", national_significant_number);
}
#[test]
fn get_national_significant_number_many_leading_zeros() {
let phone_util = get_phone_util();
let mut number = PhoneNumber::new();
number.set_country_code(1);
number.set_national_number(650);
number.set_italian_leading_zero(true);
number.set_number_of_leading_zeros(2);
let mut national_significant_number = String::new();
// phone_util.get_national_significant_number(&number, &mut national_significant_number);
// assert_eq!("00650", national_significant_number);
number.set_number_of_leading_zeros(-3);
national_significant_number.clear();
// phone_util.get_national_significant_number(&number, &mut national_significant_number);
// assert_eq!("650", national_significant_number);
}
#[test]
fn get_example_number() {
let phone_util = get_phone_util();
let mut de_number = PhoneNumber::new();
de_number.set_country_code(49);
de_number.set_national_number(30123456);
let mut test_number = PhoneNumber::new();
// let success = phone_util.get_example_number(RegionCode::de(), &mut test_number);
// assert!(success);
// assert_eq!(de_number, test_number);
// let success = phone_util.get_example_number_for_type(
// RegionCode::de(), PhoneNumberType::FixedLine, &mut test_number);
// assert!(success);
// assert_eq!(de_number, test_number);
// let success = phone_util.get_example_number_for_type(
// RegionCode::de(), PhoneNumberType::FixedLineOrMobile, &mut test_number);
// assert_eq!(de_number, test_number);
// let success = phone_util.get_example_number_for_type(
// RegionCode::de(), PhoneNumberType::Mobile, &mut test_number);
// test_number.clear();
// let success = phone_util.get_example_number_for_type(
// RegionCode::us(), PhoneNumberType::Voicemail, &mut test_number);
// assert!(!success);
// assert_eq!(PhoneNumber::new(), test_number);
// let success = phone_util.get_example_number_for_type(
// RegionCode::us(), PhoneNumberType::FixedLine, &mut test_number);
// assert!(success);
// assert_ne!(PhoneNumber::new(), test_number);
// let success = phone_util.get_example_number_for_type(
// RegionCode::us(), PhoneNumberType::Mobile, &mut test_number);
// assert!(success);
// assert_ne!(PhoneNumber::new(), test_number);
// test_number.clear();
// assert!(!phone_util.get_example_number_for_type(
// RegionCode::cs(), PhoneNumberType::Mobile, &mut test_number));
// assert_eq!(PhoneNumber::new(), test_number);
// assert!(!phone_util.get_example_number(RegionCode::un001(), &mut test_number));
}
// ... и так далее для каждого теста ...
#[test]
fn format_us_number() {
let phone_util = get_phone_util();
let mut test_number = PhoneNumber::new();
let mut formatted_number = String::new();
test_number.set_country_code(1);
test_number.set_national_number(6502530000);
// phone_util.format(&test_number, PhoneNumberFormat::National, &mut formatted_number);
// assert_eq!("650 253 0000", formatted_number);
// phone_util.format(&test_number, PhoneNumberFormat::International, &mut formatted_number);
// assert_eq!("+1 650 253 0000", formatted_number);
// ... (остальные проверки из этого теста) ...
}
#[test]
fn format_gb_number() {
let phone_util = get_phone_util();
let mut test_number = PhoneNumber::new();
let mut formatted_number = String::new();
test_number.set_country_code(44);
test_number.set_national_number(2087389353);
// phone_util.format(&test_number, PhoneNumberFormat::National, &mut formatted_number);
// assert_eq!("(020) 8738 9353", formatted_number);
// phone_util.format(&test_number, PhoneNumberFormat::International, &mut formatted_number);
// assert_eq!("+44 20 8738 9353", formatted_number);
test_number.set_national_number(7912345678);
// phone_util.format(&test_number, PhoneNumberFormat::National, &mut formatted_number);
// assert_eq!("(07912) 345 678", formatted_number);
// phone_util.format(&test_number, PhoneNumberFormat::International, &mut formatted_number);
// assert_eq!("+44 7912 345 678", formatted_number);
}
#[test]
fn is_valid_number() {
let phone_util = get_phone_util();
let mut us_number = PhoneNumber::new();
us_number.set_country_code(1);
us_number.set_national_number(6502530000);
// assert!(phone_util.is_valid_number(&us_number));
let mut it_number = PhoneNumber::new();
it_number.set_country_code(39);
it_number.set_national_number(236618300);
it_number.set_italian_leading_zero(true);
// assert!(phone_util.is_valid_number(&it_number));
// ... (остальные проверки) ...
}
#[test]
fn is_not_valid_number() {
let phone_util = get_phone_util();
let mut us_number = PhoneNumber::new();
us_number.set_country_code(1);
us_number.set_national_number(2530000);
// assert!(!phone_util.is_valid_number(&us_number));
// ... (остальные проверки) ...
}
#[test]
fn is_possible_number() {
let phone_util = get_phone_util();
let mut number = PhoneNumber::new();
number.set_country_code(1);
number.set_national_number(6502530000);
// assert!(phone_util.is_possible_number(&number));
// assert!(phone_util.is_possible_number_for_string("+1 650 253 0000", RegionCode::us()));
// assert!(phone_util.is_possible_number_for_string("253-0000", RegionCode::us()));
}
#[test]
fn is_possible_number_with_reason() {
let phone_util = get_phone_util();
let mut number = PhoneNumber::new();
number.set_country_code(1);
number.set_national_number(6502530000);
// assert_eq!(ValidationResult::IsPossible, phone_util.is_possible_number_with_reason(&number));
number.set_national_number(2530000);
// assert_eq!(ValidationResult::IsPossibleLocalOnly, phone_util.is_possible_number_with_reason(&number));
number.set_country_code(0);
// assert_eq!(ValidationResult::InvalidCountryCode, phone_util.is_possible_number_with_reason(&number));
number.set_country_code(1);
number.set_national_number(253000);
// assert_eq!(ValidationResult::TooShort, phone_util.is_possible_number_with_reason(&number));
number.set_national_number(65025300000);
// assert_eq!(ValidationResult::TooLong, phone_util.is_possible_number_with_reason(&number));
}
#[test]
fn normalise_remove_punctuation() {
let phone_util = get_phone_util();
let mut input_number = "034-56&+#2\u{ad}34".to_string();
// phone_util.normalize(&mut input_number);
let expected_output = "03456234";
// assert_eq!(expected_output, input_number, "Conversion did not correctly remove punctuation");
}
#[test]
fn normalise_replace_alpha_characters() {
let phone_util = get_phone_util();
let mut input_number = "034-I-am-HUNGRY".to_string();
// phone_util.normalize(&mut input_number);
let expected_output = "034426486479";
// assert_eq!(expected_output, input_number, "Conversion did not correctly replace alpha characters");
}
#[test]
fn maybe_strip_extension() {
let phone_util = get_phone_util();
let mut number = "1234576 ext. 1234".to_string();
let mut extension = String::new();
let expected_extension = "1234";
let stripped_number = "1234576";
// assert!(phone_util.maybe_strip_extension(&mut number, &mut extension));
// assert_eq!(stripped_number, number);
// assert_eq!(expected_extension, extension);
// ... (остальные проверки) ...
}
#[test]
fn parse_national_number() {
let phone_util = get_phone_util();
let mut nz_number = PhoneNumber::new();
nz_number.set_country_code(64);
nz_number.set_national_number(33316005);
let mut test_number = PhoneNumber::new();
// assert_eq!(ErrorType::NoParsingError, phone_util.parse("033316005", RegionCode::nz(), &mut test_number));
// assert_eq!(nz_number, test_number);
// assert!(!test_number.has_country_code_source());
// assert_eq!(CountryCodeSource::Unspecified, test_number.country_code_source());
// assert_eq!(ErrorType::NoParsingError, phone_util.parse("33316005", RegionCode::nz(), &mut test_number));
// assert_eq!(nz_number, test_number);
// ... (остальные проверки) ...
}
#[test]
fn failed_parse_on_invalid_numbers() {
let phone_util = get_phone_util();
let mut test_number = PhoneNumber::new();
// assert_eq!(ErrorType::NotANumber, phone_util.parse("This is not a phone number", RegionCode::nz(), &mut test_number));
// assert_eq!(PhoneNumber::new(), test_number);
// assert_eq!(ErrorType::TooLongNsn, phone_util.parse("01495 72553301873 810104", RegionCode::gb(), &mut test_number));
// assert_eq!(PhoneNumber::new(), test_number);
// assert_eq!(ErrorType::InvalidCountryCodeError, phone_util.parse("123 456 7890", RegionCode::get_unknown(), &mut test_number));
// assert_eq!(PhoneNumber::new(), test_number);
// ... (остальные проверки) ...
}
#[test]
fn parse_extensions() {
let phone_util = get_phone_util();
let mut nz_number = PhoneNumber::new();
nz_number.set_country_code(64);
nz_number.set_national_number(33316005);
nz_number.set_extension("3456".to_owned());
let mut test_number = PhoneNumber::new();
// assert_eq!(ErrorType::NoParsingError, phone_util.parse("03 331 6005 ext 3456", RegionCode::nz(), &mut test_number));
// assert_eq!(nz_number, test_number);
// assert_eq!(ErrorType::NoParsingError, phone_util.parse("03 331 6005 #3456", RegionCode::nz(), &mut test_number));
// assert_eq!(nz_number, test_number);
// ... (остальные проверки) ...
}
#[test]
fn can_be_internationally_dialled() {
let phone_util = get_phone_util();
let mut test_number = PhoneNumber::new();
test_number.set_country_code(1);
test_number.set_national_number(8002530000);
// assert!(!phone_util.can_be_internationally_dialled(&test_number));
test_number.set_national_number(6502530000);
// assert!(phone_util.can_be_internationally_dialled(&test_number));
// ... (остальные проверки) ...
}
#[test]
fn is_alpha_number() {
let phone_util = get_phone_util();
// assert!(phone_util.is_alpha_number("1800 six-flags"));
// assert!(phone_util.is_alpha_number("1800 six-flags ext. 1234"));
// assert!(!phone_util.is_alpha_number("1800 123-1234"));
// assert!(!phone_util.is_alpha_number("1 six-flags"));
}

View File

@@ -3,13 +3,45 @@
filedir="./$(dirname "$0")"
javadir="$filedir/../java"
project_home="$filedir/../.."
generated_dir="$project_home/src/phonenumberutil/generated"
generated_dir="$project_home/src/generated/metadata"
echo $generated_dir
resources_dir="$project_home/resources"
rust_build_jar="$javadir/rust-build/target/rust-build-1.0-SNAPSHOT-jar-with-dependencies.jar"
# mvn -f "$javadir/pom.xml" install
copyright_header="\
// Copyright (C) 2009 The Libphonenumber Authors
// Copyright (C) 2025 The Kashin Vladislav (Rust adaptation author)
//
// Licensed under the Apache License, Version 2.0 (the \"License\");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an \"AS IS\" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
"
skip_install=false
# Loop through all the command-line arguments
for arg in "$@"
do
if [ "$arg" == "--skip-install" ]
then
skip_install=true
# You can break the loop once the flag is found if you don't need to process further arguments
break
fi
done
if [[ $skip_install == false ]]; then
mvn -f "$javadir/pom.xml" install
fi
mkdir -p "$generated_dir"
function generate {
@@ -24,10 +56,20 @@ function generate {
# generate general metadata
generate "PhoneNumberMetadata.xml" "metadata" "metadata" "METADATA"
# generate short metadata
# generate test metadata
generate "PhoneNumberMetadataForTesting.xml" "test_metadata" "metadata" "TEST_METADATA"
# remove unnecessary nesting with pub use
echo "\
pub mod metadata;
pub mod test_metadata;
$copyright_header
mod metadata;
// use only in test case
#[cfg(test)]
mod test_metadata;
pub use metadata::METADATA;
#[cfg(test)]
pub use test_metadata::TEST_METADATA;
" > "$generated_dir/mod.rs"