git.iko.soy/typeable/libphonenumber
on main help sign in
13 files changed+610−120
since splitsnapshots
unifiedsplit
.github/workflows/ci.yaml+102 LICENSE+21 cxxbits/c_phonenumber.cpp+6−4 cxxbits/c_phonenumberutil.cpp+45−26 include/c_phonenumber.h+4−8 include/c_phonenumberutil.h+20−20 include/common.h+3−2 libphonenumber.cabal+81−36 src/Data/PhoneNumber/Internal/Number.chs+1−1 src/Data/PhoneNumber/Number.hs+69−10 src/Data/PhoneNumber/Util.hs+7−7 test/Leak.hs+6−6 test/Spec.hs+245
.github/workflows/ci.yamlnew file+102
@@ -0,0 +1,102 @@
1+name: CI
2+
3+on:
4+ push:
5+ branches:
6+ - "*"
7+ pull_request:
8+ branches:
9+ - "*"
10+ release:
11+ types:
12+ - prereleased
13+
14+jobs:
15+ build:
16+ strategy:
17+ matrix:
18+ platform:
19+ - ubuntu-latest
20+ ghc_version:
21+ - "8.6.5"
22+ - "8.8.4"
23+ - "8.10.7"
24+ - "9.0.2"
25+ - "9.2.8"
26+ - "9.4.8"
27+ - "9.6.7"
28+ - "9.8.4"
29+ - "9.10.3"
30+ - "9.12.2"
31+ - "9.14.1"
32+ fail-fast: false
33+ name: Build on Linux, GHC ${{ matrix.ghc_version }}
34+ runs-on: ${{ matrix.platform }}
35+ steps:
36+ - uses: actions/checkout@v4
37+ - uses: haskell-actions/setup@v2
38+ with:
39+ ghc-version: ${{ matrix.ghc_version }}
40+ cabal-version: "3.10.1.0"
41+ - name: Install C++ library
42+ run: |
43+ sudo apt-get update
44+ sudo apt-get install -y libphonenumber-dev
45+
46+ - name: cabal sdist
47+ run: cabal sdist --output-dir "$GITHUB_WORKSPACE/sdist"
48+ - name: unpack
49+ run: |
50+ mkdir -p "$GITHUB_WORKSPACE/unpacked"
51+ find "$GITHUB_WORKSPACE/sdist" -maxdepth 1 -type f -name '*.tar.gz' -exec tar -C "$GITHUB_WORKSPACE/unpacked" -xzvf {} \;
52+ echo "PKGDIR_libphonenumber=$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/libphonenumber-[0-9.]*')" >> "$GITHUB_ENV"
53+ - name: cabal configure
54+ run: cd "$PKGDIR_libphonenumber" && cabal configure --with-ghc=ghc-${{ matrix.ghc_version }}
55+ - name: Install dependencies
56+ run: cd "$PKGDIR_libphonenumber" && cabal build --dependencies-only --enable-tests --haddock-all
57+ - name: cabal build
58+ run: cd "$PKGDIR_libphonenumber" && cabal build --ghc-options=-Werror
59+ - name: cabal test spec_test
60+ run: cd "$PKGDIR_libphonenumber" && cabal test spec_test
61+ - name: cabal check
62+ run: cd "$PKGDIR_libphonenumber" && cabal check
63+ - name: cabal haddock
64+ run: cd "$PKGDIR_libphonenumber" && cabal haddock
65+
66+ dist:
67+ needs: build
68+ runs-on: ubuntu-latest
69+ name: Build source tarball
70+ if: github.event_name == 'release'
71+ steps:
72+ - uses: actions/checkout@v3
73+ - uses: haskell-actions/setup@v2
74+ with:
75+ cabal-version: "3.10.1.0"
76+ - name: Install C++ library
77+ run: |
78+ sudo apt-get update
79+ sudo apt-get install -y libphonenumber-dev
80+ - name: cabal sdist
81+ run: |
82+ cabal sdist --output-dir "$GITHUB_WORKSPACE/sdist"
83+ echo "dist=$(find "$GITHUB_WORKSPACE/sdist" -maxdepth 1 -type f -name '*.tar.gz')" >> "$GITHUB_ENV"
84+ - name: cabal haddock
85+ run: |
86+ cabal haddock --builddir "$GITHUB_WORKSPACE/doc" --haddock-for-hackage --enable-doc
87+ echo "docs=$(find "$GITHUB_WORKSPACE/doc" -maxdepth 1 -type f -name '*-docs.tar.gz')" >> "$GITHUB_ENV"
88+ - uses: actions/upload-artifact@v6
89+ with:
90+ name: Source tarball
91+ path: ${{ env.dist }}
92+ - uses: svenstaro/upload-release-action@2.11.3
93+ with:
94+ repo_token: ${{ secrets.GITHUB_TOKEN }}
95+ file: ${{ env.dist }}
96+ tag: ${{ github.ref }}
97+ - name: Upload tarball to hackage
98+ run: |
99+ curl -X POST 'https://hackage.haskell.org/packages/candidates/' -H "Authorization: X-ApiKey ${{ secrets.HACKAGE_KEY }}" -F "package=@$dist" -w '%{url_effective}\n'
100+ - name: Upload haddocks to hackage
101+ run: |
102+ curl -X PUT "https://hackage.haskell.org/package/$(basename ${docs%-docs.tar.gz})/candidate/docs" -H "Authorization: X-ApiKey ${{ secrets.HACKAGE_KEY }}" -H 'Content-type: application/x-tar' -H 'Content-encoding: gzip' --data-binary "@$docs"
LICENSEnew file+21
@@ -0,0 +1,21 @@
1+MIT License
2+
3+Copyright (c) 2023 Typeable
4+
5+Permission is hereby granted, free of charge, to any person obtaining a copy
6+of this software and associated documentation files (the "Software"), to deal
7+in the Software without restriction, including without limitation the rights
8+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+copies of the Software, and to permit persons to whom the Software is
10+furnished to do so, subject to the following conditions:
11+
12+The above copyright notice and this permission notice shall be included in all
13+copies or substantial portions of the Software.
14+
15+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+SOFTWARE.
cxxbits/c_phonenumber.cpp+6−4
@@ -55,7 +55,7 @@ static country_code_source unmarshal_country_code_source(PhoneNumber::CountryCod
5555 return static_cast<country_code_source>(value);
5656}
5757
58−extern "C" PhoneNumber *c_phone_number_marshal(phone_number const *c_number)
58+extern "C" CxxPhoneNumber *c_phone_number_marshal(phone_number const *c_number)
5959{
6060 PhoneNumber *number = new PhoneNumber();
6161 if (c_number->extension != nullptr)
@@ -72,11 +72,12 @@ extern "C" PhoneNumber *c_phone_number_marshal(phone_number const *c_number)
7272 number->set_country_code_source(marshal_country_code_source(c_number->country_code_source));
7373 if (c_number->has_number_of_leading_zeros)
7474 number->set_number_of_leading_zeros(c_number->number_of_leading_zeros);
75− return number;
75+ return reinterpret_cast<CxxPhoneNumber *>(number);
7676}
7777
78−extern "C" void c_phone_number_unmarshal(PhoneNumber const *number, phone_number *c_number)
78+extern "C" void c_phone_number_unmarshal(CxxPhoneNumber const *ptr, phone_number *c_number)
7979{
80+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
8081 if (number->has_extension())
8182 unmarshal_string_size(number->extension(), c_number->extension, c_number->extension_size);
8283 else
@@ -99,7 +100,8 @@ extern "C" void c_phone_number_unmarshal(PhoneNumber const *number, phone_number
99100 c_number->number_of_leading_zeros = number->number_of_leading_zeros();
100101}
101102
102−extern "C" void c_phone_number_free(PhoneNumber *number)
103+extern "C" void c_phone_number_free(CxxPhoneNumber *ptr)
103104{
105+ auto number = reinterpret_cast<PhoneNumber *>(ptr);
104106 delete number;
105107}
cxxbits/c_phonenumberutil.cpp+45−26
@@ -310,10 +310,11 @@ extern "C" void c_phone_number_util_normalize_dialable_chars_only(
310310}
311311
312312extern "C" void c_phone_number_util_get_national_significant_number(
313− PhoneNumber const *number,
313+ CxxPhoneNumber const *ptr,
314314 c_string *c_nsn
315315)
316316{
317+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
317318 std::string nsn;
318319 PhoneNumberUtil::GetInstance()->GetNationalSignificantNumber(*number, &nsn);
319320 unmarshal_c_string(nsn, *c_nsn);
@@ -330,23 +331,25 @@ extern "C" void c_phone_number_util_get_country_mobile_token(
330331}
331332
332333extern "C" void c_phone_number_util_format(
333− PhoneNumber const *number,
334+ CxxPhoneNumber const *ptr,
334335 phone_number_format format,
335336 c_string *c_string
336337)
337338{
339+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
338340 std::string string;
339341 PhoneNumberUtil::GetInstance()->Format(*number, marshal_phone_number_format(format), &string);
340342 unmarshal_c_string(string, *c_string);
341343}
342344
343345extern "C" void c_phone_number_util_format_national_number_with_carrier_code(
344− PhoneNumber const *number,
346+ CxxPhoneNumber const *ptr,
345347 char const *c_carrier_code,
346348 size_t c_carrier_code_size,
347349 c_string *c_string
348350)
349351{
352+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
350353 std::string const carrier_code(c_carrier_code, c_carrier_code_size);
351354 std::string string;
352355 PhoneNumberUtil::GetInstance()->FormatNationalNumberWithCarrierCode(*number, carrier_code, &string);
@@ -354,12 +357,13 @@ extern "C" void c_phone_number_util_format_national_number_with_carrier_code(
354357}
355358
356359extern "C" void c_phone_number_util_format_national_number_with_preferred_carrier_code(
357− PhoneNumber const *number,
360+ CxxPhoneNumber const *ptr,
358361 char const *c_carrier_code,
359362 size_t c_carrier_code_size,
360363 c_string *c_string
361364)
362365{
366+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
363367 std::string const carrier_code(c_carrier_code, c_carrier_code_size);
364368 std::string string;
365369 PhoneNumberUtil::GetInstance()->FormatNationalNumberWithPreferredCarrierCode(*number, carrier_code, &string);
@@ -367,13 +371,14 @@ extern "C" void c_phone_number_util_format_national_number_with_preferred_carrie
367371}
368372
369373extern "C" void c_phone_number_util_format_number_for_mobile_dialing(
370− PhoneNumber const *number,
374+ CxxPhoneNumber const *ptr,
371375 char const *c_region,
372376 size_t c_region_size,
373377 int with_formatting,
374378 c_string *c_string
375379)
376380{
381+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
377382 std::string const region(c_region, c_region_size);
378383 std::string string;
379384 PhoneNumberUtil::GetInstance()->FormatNumberForMobileDialing(*number, region, with_formatting, &string);
@@ -381,12 +386,13 @@ extern "C" void c_phone_number_util_format_number_for_mobile_dialing(
381386}
382387
383388extern "C" void c_phone_number_util_format_out_of_country_calling_number(
384− PhoneNumber const *number,
389+ CxxPhoneNumber const *ptr,
385390 char const *c_region,
386391 size_t c_region_size,
387392 c_string *c_string
388393)
389394{
395+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
390396 std::string const region(c_region, c_region_size);
391397 std::string string;
392398 PhoneNumberUtil::GetInstance()->FormatOutOfCountryCallingNumber(*number, region, &string);
@@ -394,12 +400,13 @@ extern "C" void c_phone_number_util_format_out_of_country_calling_number(
394400}
395401
396402extern "C" void c_phone_number_util_format_in_original_format(
397− PhoneNumber const *number,
403+ CxxPhoneNumber const *ptr,
398404 char const *c_region,
399405 size_t c_region_size,
400406 c_string *c_string
401407)
402408{
409+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
403410 std::string const region(c_region, c_region_size);
404411 std::string string;
405412 PhoneNumberUtil::GetInstance()->FormatInOriginalFormat(*number, region, &string);
@@ -407,24 +414,26 @@ extern "C" void c_phone_number_util_format_in_original_format(
407414}
408415
409416extern "C" void c_phone_number_util_format_out_of_country_keeping_alpha_chars(
410− PhoneNumber const *number,
417+ CxxPhoneNumber const *ptr,
411418 char const *c_region,
412419 size_t c_region_size,
413420 c_string *c_string
414421)
415422{
423+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
416424 std::string const region(c_region, c_region_size);
417425 std::string string;
418426 PhoneNumberUtil::GetInstance()->FormatOutOfCountryKeepingAlphaChars(*number, region, &string);
419427 unmarshal_c_string(string, *c_string);
420428}
421429
422−extern "C" PhoneNumber *c_phone_number_util_truncate_too_long_number(PhoneNumber const *input)
430+extern "C" CxxPhoneNumber *c_phone_number_util_truncate_too_long_number(CxxPhoneNumber const *ptr)
423431{
432+ auto input = reinterpret_cast<PhoneNumber const *>(ptr);
424433 auto *output = new PhoneNumber(*input);
425434 if (PhoneNumberUtil::GetInstance()->TruncateTooLongNumber(output))
426435 {
427− return output;
436+ return reinterpret_cast<CxxPhoneNumber *>(output);
428437 }
429438 else
430439 {
@@ -433,33 +442,37 @@ extern "C" PhoneNumber *c_phone_number_util_truncate_too_long_number(PhoneNumber
433442 }
434443}
435444
436−extern "C" phone_number_type c_phone_number_util_get_number_type(PhoneNumber const *number)
445+extern "C" phone_number_type c_phone_number_util_get_number_type(CxxPhoneNumber const *ptr)
437446{
447+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
438448 return unmarshal_phone_number_type(
439449 PhoneNumberUtil::GetInstance()->GetNumberType(*number)
440450 );
441451}
442452
443−extern "C" int c_phone_number_util_is_valid_number(PhoneNumber const *number)
453+extern "C" int c_phone_number_util_is_valid_number(CxxPhoneNumber const *ptr)
444454{
455+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
445456 return PhoneNumberUtil::GetInstance()->IsValidNumber(*number);
446457}
447458
448459extern "C" int c_phone_number_util_is_valid_number_for_region(
449− PhoneNumber const *number,
460+ CxxPhoneNumber const *ptr,
450461 char const *c_region,
451462 size_t c_region_size
452463)
453464{
465+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
454466 std::string const region(c_region, c_region_size);
455467 return PhoneNumberUtil::GetInstance()->IsValidNumberForRegion(*number, region);
456468}
457469
458470extern "C" void c_phone_number_util_get_region_code_for_number(
459− PhoneNumber const *number,
471+ CxxPhoneNumber const *ptr,
460472 c_string *c_region
461473)
462474{
475+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
463476 std::string region;
464477 PhoneNumberUtil::GetInstance()->GetRegionCodeForNumber(*number, &region);
465478 unmarshal_c_string(region, *c_region);
@@ -518,10 +531,11 @@ extern "C" void c_phone_number_util_get_ndd_prefix_for_region(
518531}
519532
520533extern "C" validation_result c_phone_number_util_is_possible_number_for_type_with_reason(
521− PhoneNumber const *number,
534+ CxxPhoneNumber const *ptr,
522535 phone_number_type ntype
523536)
524537{
538+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
525539 return unmarshal_validation_result(
526540 PhoneNumberUtil::GetInstance()->IsPossibleNumberForTypeWithReason(
527541 *number,
@@ -530,13 +544,15 @@ extern "C" validation_result c_phone_number_util_is_possible_number_for_type_wit
530544 );
531545}
532546
533−extern "C" int c_phone_number_util_can_be_internationally_dialed(PhoneNumber const *number)
547+extern "C" int c_phone_number_util_can_be_internationally_dialed(CxxPhoneNumber const *ptr)
534548{
549+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
535550 return PhoneNumberUtil::GetInstance()->CanBeInternationallyDialled(*number);
536551}
537552
538−extern "C" int c_phone_number_util_is_number_geographical_1(PhoneNumber const *number)
553+extern "C" int c_phone_number_util_is_number_geographical_1(CxxPhoneNumber const *ptr)
539554{
555+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
540556 return PhoneNumberUtil::GetInstance()->IsNumberGeographical(*number);
541557}
542558
@@ -556,14 +572,14 @@ extern "C" error_type c_phone_number_util_parse(
556572 size_t c_string_size,
557573 char const *c_region,
558574 size_t c_region_size,
559− PhoneNumber **number
575+ CxxPhoneNumber **number
560576)
561577{
562578 std::string const string(c_string, c_string_size);
563579 std::string const region(c_region, c_region_size);
564− *number = new PhoneNumber();
580+ *number = reinterpret_cast<CxxPhoneNumber *>(new PhoneNumber());
565581 return unmarshal_error_type(
566− PhoneNumberUtil::GetInstance()->Parse(string, region, *number)
582+ PhoneNumberUtil::GetInstance()->Parse(string, region, reinterpret_cast<PhoneNumber *>(*number))
567583 );
568584}
569585
@@ -572,22 +588,24 @@ extern "C" error_type c_phone_number_util_parse_and_keep_raw_input(
572588 size_t c_string_size,
573589 char const *c_region,
574590 size_t c_region_size,
575− PhoneNumber **number
591+ CxxPhoneNumber **number
576592)
577593{
578594 std::string const string(c_string, c_string_size);
579595 std::string const region(c_region, c_region_size);
580− *number = new PhoneNumber();
596+ *number = reinterpret_cast<CxxPhoneNumber *>(new PhoneNumber());
581597 return unmarshal_error_type(
582− PhoneNumberUtil::GetInstance()->ParseAndKeepRawInput(string, region, *number)
598+ PhoneNumberUtil::GetInstance()->ParseAndKeepRawInput(string, region, reinterpret_cast<PhoneNumber *>(*number))
583599 );
584600}
585601
586602extern "C" match_type c_phone_number_util_is_number_match(
587− PhoneNumber const *number1,
588− PhoneNumber const *number2
603+ CxxPhoneNumber const *ptr1,
604+ CxxPhoneNumber const *ptr2
589605)
590606{
607+ auto number1 = reinterpret_cast<PhoneNumber const *>(ptr1);
608+ auto number2 = reinterpret_cast<PhoneNumber const *>(ptr2);
591609 return unmarshal_match_type(
592610 PhoneNumberUtil::GetInstance()->IsNumberMatch(*number1, *number2)
593611 );
@@ -608,11 +626,12 @@ extern "C" match_type c_phone_number_util_is_number_match_with_two_strings(
608626}
609627
610628extern "C" match_type c_phone_number_util_is_number_match_with_one_string(
611− PhoneNumber const *number1,
629+ CxxPhoneNumber const *ptr1,
612630 char const *c_number2,
613631 size_t c_number2_size
614632)
615633{
634+ auto number1 = reinterpret_cast<PhoneNumber const *>(ptr1);
616635 std::string const number2(c_number2, c_number2_size);
617636 return unmarshal_match_type(
618637 PhoneNumberUtil::GetInstance()->IsNumberMatchWithOneString(*number1, number2)
include/c_phonenumber.h+4−8
@@ -43,20 +43,16 @@ typedef struct
4343 char has_number_of_leading_zeros;
4444} phone_number;
4545
46−#ifdef __cplusplus
47−typedef i18n::phonenumbers::PhoneNumber PhoneNumber;
48−#else
49−typedef struct PhoneNumber PhoneNumber;
50−#endif
46+struct CxxPhoneNumber;
5147
5248// The returned object is allocated by `new` and managed by C++ RAII, and it is
5349// haskell's responsibility to call its finalizer (c_phone_number_free).
54−PhoneNumber *c_phone_number_marshal(phone_number const *);
50+struct CxxPhoneNumber *c_phone_number_marshal(phone_number const *);
5551
5652// phone_number * points to an area which will be filled with the data of the
5753// given PhoneNumber.
58−void c_phone_number_unmarshal(PhoneNumber const *, phone_number *);
54+void c_phone_number_unmarshal(struct CxxPhoneNumber const *, phone_number *);
5955
60−void c_phone_number_free(PhoneNumber *);
56+void c_phone_number_free(struct CxxPhoneNumber *);
6157
6258#endif
include/c_phonenumberutil.h+20−20
@@ -97,34 +97,34 @@ int c_phone_number_util_is_alpha_number(char const *, size_t);
9797void c_phone_number_util_convert_alpha_characters_in_number(char const *, size_t, c_string *);
9898void c_phone_number_util_normalize_digits_only(char const *, size_t, c_string *);
9999void c_phone_number_util_normalize_dialable_chars_only(char const *, size_t, c_string *);
100−void c_phone_number_util_get_national_significant_number(PhoneNumber const *, c_string *);
100+void c_phone_number_util_get_national_significant_number(struct CxxPhoneNumber const *, c_string *);
101101void c_phone_number_util_get_country_mobile_token(int, c_string *);
102−void c_phone_number_util_format(PhoneNumber const *, enum phone_number_format, c_string *);
103−void c_phone_number_util_format_national_number_with_carrier_code(PhoneNumber const *, char const *, size_t, c_string *);
104−void c_phone_number_util_format_national_number_with_preferred_carrier_code(PhoneNumber const *, char const *, size_t, c_string *);
105−void c_phone_number_util_format_number_for_mobile_dialing(PhoneNumber const *, char const *, size_t, int, c_string *);
106−void c_phone_number_util_format_out_of_country_calling_number(PhoneNumber const *, char const *, size_t, c_string *);
107−void c_phone_number_util_format_in_original_format(PhoneNumber const *, char const *, size_t, c_string *);
108−void c_phone_number_util_format_out_of_country_keeping_alpha_chars(PhoneNumber const *, char const *, size_t, c_string *);
102+void c_phone_number_util_format(struct CxxPhoneNumber const *, enum phone_number_format, c_string *);
103+void c_phone_number_util_format_national_number_with_carrier_code(struct CxxPhoneNumber const *, char const *, size_t, c_string *);
104+void c_phone_number_util_format_national_number_with_preferred_carrier_code(struct CxxPhoneNumber const *, char const *, size_t, c_string *);
105+void c_phone_number_util_format_number_for_mobile_dialing(struct CxxPhoneNumber const *, char const *, size_t, int, c_string *);
106+void c_phone_number_util_format_out_of_country_calling_number(struct CxxPhoneNumber const *, char const *, size_t, c_string *);
107+void c_phone_number_util_format_in_original_format(struct CxxPhoneNumber const *, char const *, size_t, c_string *);
108+void c_phone_number_util_format_out_of_country_keeping_alpha_chars(struct CxxPhoneNumber const *, char const *, size_t, c_string *);
109109// /!\ May return NULL
110−PhoneNumber *c_phone_number_util_truncate_too_long_number(PhoneNumber const *);
111−enum phone_number_type c_phone_number_util_get_number_type(PhoneNumber const *);
112−int c_phone_number_util_is_valid_number(PhoneNumber const *);
113−int c_phone_number_util_is_valid_number_for_region(PhoneNumber const *, char const *, size_t);
114−void c_phone_number_util_get_region_code_for_number(PhoneNumber const *, c_string *);
110+struct CxxPhoneNumber *c_phone_number_util_truncate_too_long_number(struct CxxPhoneNumber const *);
111+enum phone_number_type c_phone_number_util_get_number_type(struct CxxPhoneNumber const *);
112+int c_phone_number_util_is_valid_number(struct CxxPhoneNumber const *);
113+int c_phone_number_util_is_valid_number_for_region(struct CxxPhoneNumber const *, char const *, size_t);
114+void c_phone_number_util_get_region_code_for_number(struct CxxPhoneNumber const *, c_string *);
115115int c_phone_number_util_get_country_code_for_region(char const *, size_t);
116116void c_phone_number_util_get_region_code_for_country_code(int, c_string *);
117117void c_phone_number_util_get_region_codes_for_country_calling_code(int, c_string **, size_t *);
118118int c_phone_number_util_is_nanpa_country(char const *, size_t);
119119void c_phone_number_util_get_ndd_prefix_for_region(char const *, size_t, int, c_string *);
120−enum validation_result c_phone_number_util_is_possible_number_for_type_with_reason(PhoneNumber const *, enum phone_number_type);
121−int c_phone_number_util_can_be_internationally_dialed(PhoneNumber const *);
122−int c_phone_number_util_is_number_geographical_1(PhoneNumber const *);
120+enum validation_result c_phone_number_util_is_possible_number_for_type_with_reason(struct CxxPhoneNumber const *, enum phone_number_type);
121+int c_phone_number_util_can_be_internationally_dialed(struct CxxPhoneNumber const *);
122+int c_phone_number_util_is_number_geographical_1(struct CxxPhoneNumber const *);
123123int c_phone_number_util_is_number_geographical_2(enum phone_number_type, int);
124−enum error_type c_phone_number_util_parse(char const *, size_t, char const *, size_t, PhoneNumber **);
125−enum error_type c_phone_number_util_parse_and_keep_raw_input(char const *, size_t, char const *, size_t, PhoneNumber **);
126−enum match_type c_phone_number_util_is_number_match(PhoneNumber const *, PhoneNumber const *);
124+enum error_type c_phone_number_util_parse(char const *, size_t, char const *, size_t, struct CxxPhoneNumber **);
125+enum error_type c_phone_number_util_parse_and_keep_raw_input(char const *, size_t, char const *, size_t, struct CxxPhoneNumber **);
126+enum match_type c_phone_number_util_is_number_match(struct CxxPhoneNumber const *, struct CxxPhoneNumber const *);
127127enum match_type c_phone_number_util_is_number_match_with_two_strings(char const *, size_t, char const *, size_t);
128−enum match_type c_phone_number_util_is_number_match_with_one_string(PhoneNumber const *, char const *, size_t);
128+enum match_type c_phone_number_util_is_number_match_with_one_string(struct CxxPhoneNumber const *, char const *, size_t);
129129
130130#endif
include/common.h+3−2
@@ -4,6 +4,7 @@
44#include <cstddef>
55#include <cstring>
66#include <type_traits>
7+#include <utility>
78
89// Given `enum T { A, B, C }` and `enum S { X, Y, Z }`, we can use
910// `is_same_enum<enum_values<T, A, B, C>, enum_values<S, X, Y, Z>>::value`
@@ -31,7 +32,7 @@ struct is_same_enum: std::false_type
3132
3233template<typename T, T... TVals, typename S, S... SVals>
3334struct is_same_enum<enum_values<T, TVals...>, enum_values<S, SVals...>>:
34− std::integral_constant<bool, std::is_same_v<
35+ std::integral_constant<bool, std::is_same<
3536 std::integer_sequence<
3637 typename std::common_type<
3738 typename std::underlying_type<T>::type,
@@ -46,7 +47,7 @@ struct is_same_enum<enum_values<T, TVals...>, enum_values<S, SVals...>>:
4647 >::type,
4748 SVals...
4849 >
49− >>
50+ >::value>
5051{};
5152
5253inline void unmarshal_string_size(std::string const &string, char *&c_string, size_t &c_string_size)
libphonenumber.cabal+81−36
@@ -1,6 +1,6 @@
11cabal-version: 3.0
22name: libphonenumber
3−version: 0.0.0.0
3+version: 0.1.4.0
44category: Data
55synopsis: Parsing, formatting, and validating international phone
66 numbers
@@ -8,7 +8,8 @@ description:
88 This package provides bindings for the C++ version of the
99 <https://github.com/google/libphonenumber libphonenumber library>.
1010 The bindings currently do not include @AsYouTypeFormatter@
11− and @ShortNumberInfo@.
11+ and @ShortNumberInfo@. The C++ library must be of version
12+ >= 8.7.0.
1213
1314 The interface largely resembles that of the original unified
1415 C++, Java, and JavaScript version of the library, with some
@@ -19,37 +20,66 @@ description:
1920 else has installed a global logger which could observe
2021 side-effects from library calls.
2122
22−build-type: Simple
23+ The underlying library internally uses UTF-8 encoded byte
24+ strings. To avoid decoding overhead where it is unnecessary,
25+ and to avoid a dependency on @text@, we use 'ByteString'
26+ throughout the library instead of 'Data.Text.Text'.
27+
28+copyright: 2023-2025 Typeable
29+author: Typeable
30+maintainer: mniip@typeable.io
31+homepage: https://github.com/typeable/libphonenumber/
32+license: MIT
33+license-file: LICENSE
34+
35+tested-with:
36+ , GHC == 8.6.5
37+ , GHC == 8.8.4
38+ , GHC == 8.10.7
39+ , GHC == 9.0.2
40+ , GHC == 9.2.8
41+ , GHC == 9.4.8
42+ , GHC == 9.6.7
43+ , GHC == 9.8.4
44+ , GHC == 9.10.3
45+ , GHC == 9.12.2
46+ , GHC == 9.14.1
47+
48+build-type: Simple
2349
2450extra-source-files:
25− include/*.h
26− test/libphonenumber.supp
51+ , include/*.h
52+ , test/libphonenumber.supp
53+
54+source-repository head
55+ type: git
56+ location: https://github.com/typeable/libphonenumber/
2757
2858library
2959 exposed-modules:
30− Data.PhoneNumber.Number
31− Data.PhoneNumber.Util
60+ , Data.PhoneNumber.Number
61+ , Data.PhoneNumber.Util
3262 other-modules:
33− Data.PhoneNumber.Internal.Common
34− Data.PhoneNumber.Internal.Number
35− Data.PhoneNumber.Internal.Util
63+ , Data.PhoneNumber.Internal.Common
64+ , Data.PhoneNumber.Internal.Number
65+ , Data.PhoneNumber.Internal.Util
3666 build-depends:
37− base >= 4.12 && < 4.18
38− , bytestring
39− , containers
40− , deepseq
41− , transformers
67+ , base >= 4.12 && < 4.23
68+ , bytestring >= 0.10.8 && < 0.13
69+ , containers >= 0.6.0 && < 0.9
70+ , deepseq >= 1.4.4 && < 1.6
71+ , transformers >= 0.5.5 && < 0.7
4272 build-tool-depends: c2hs:c2hs
73+ pkgconfig-depends: protobuf
4374 include-dirs: include
4475 includes:
45− c_phonenumber.h
46− c_phonenumberutil.h
76+ , c_phonenumber.h
77+ , c_phonenumberutil.h
4778 cxx-sources:
48− cxxbits/c_phonenumber.cpp
49− cxxbits/c_phonenumberutil.cpp
79+ , cxxbits/c_phonenumber.cpp
80+ , cxxbits/c_phonenumberutil.cpp
5081 extra-libraries:
51− phonenumber
52− protobuf
82+ , phonenumber
5383 if impl(ghc >= 9.4)
5484 build-depends: system-cxx-std-lib
5585 else
@@ -57,28 +87,43 @@ library
5787 hs-source-dirs: src
5888 default-language: Haskell2010
5989 default-extensions:
60− DeriveAnyClass
61− DeriveDataTypeable
62− DeriveGeneric
63− DerivingStrategies
64− GeneralizedNewtypeDeriving
65− LambdaCase
66− NamedFieldPuns
67− OverloadedStrings
68− PatternSynonyms
69− StandaloneDeriving
70− RecordWildCards
71− ViewPatterns
90+ , DeriveAnyClass
91+ , DeriveDataTypeable
92+ , DeriveGeneric
93+ , DerivingStrategies
94+ , GeneralizedNewtypeDeriving
95+ , LambdaCase
96+ , NamedFieldPuns
97+ , OverloadedStrings
98+ , PatternSynonyms
99+ , StandaloneDeriving
100+ , RecordWildCards
101+ , ViewPatterns
72102 if impl(ghc >= 9.2)
73103 default-extensions: NoFieldSelectors
74− ghc-options: -Wall -Wextra -Werror
104+ ghc-options: -Wall -Wextra
105+
106+test-suite spec_test
107+ type: exitcode-stdio-1.0
108+ build-depends:
109+ , base
110+ , bytestring
111+ , containers
112+ , hspec >= 2.10.8 && < 2.12
113+ , libphonenumber
114+ , QuickCheck >= 2.13.1 && < 2.16
115+ main-is: Spec.hs
116+ hs-source-dirs: test
117+ default-language: Haskell2010
118+ ghc-options: -Wall -Wextra
75119
76120test-suite leak_test
77121 type: exitcode-stdio-1.0
78122 build-depends:
79− base
123+ , base
80124 , libphonenumber
81− , process
125+ , process >= 1.6.5 && < 1.7
82126 main-is: Leak.hs
83127 hs-source-dirs: test
84128 default-language: Haskell2010
129+ ghc-options: -Wall -Wextra
src/Data/PhoneNumber/Internal/Number.chs+1−1
@@ -126,7 +126,7 @@ acquireCPhoneNumber p = do
126126-- , 'Data.PhoneNumber.Number.numberOfLeadingZeros' :: !('Maybe' 'Int')
127127-- }
128128-- @
129−{#pointer *PhoneNumber foreign finalizer c_phone_number_free newtype#}
129+{#pointer *CxxPhoneNumber as PhoneNumber foreign finalizer c_phone_number_free newtype#}
130130
131131{#fun unsafe c_phone_number_marshal
132132 { withCPhoneNumber* `CPhoneNumber'
src/Data/PhoneNumber/Number.hs+69−10
@@ -21,16 +21,10 @@ module Data.PhoneNumber.Number
2121 ) where
2222
2323import Data.ByteString (ByteString)
24−import Data.PhoneNumber.Internal.Number hiding
25− ( pattern PhoneNumber
26− , extension
27− , rawInput
28− , preferredDomesticCarrierCode
29− , nationalNumber
30− , countryCode
31− , italianLeadingZero
32− , countryCodeSource
33− , numberOfLeadingZeros )
24+import Data.PhoneNumber.Internal.Number
25+ ( CPhoneNumber(CPhoneNumber), PhoneNumber, CountryCodeSource, toCPhoneNumber
26+ , fromCPhoneNumber
27+ )
3428import qualified Data.PhoneNumber.Internal.Number as I
3529
3630-- | A country calling code (International Subscriber Dialing code, ISD code),
@@ -79,3 +73,68 @@ pattern PhoneNumber
7973 = fromCPhoneNumber $ CPhoneNumber{..}
8074
8175{-# COMPLETE PhoneNumber #-}
76+
77+-- | E.g.:
78+--
79+-- @
80+-- 'Data.PhoneNumber.Util.parseNumber' 'Data.PhoneNumber.Util.Canonicalize' 'Nothing' "+1 800-234-5678 ext. 1234"
81+-- = 'Right' v'PhoneNumber' { 'extension' = 'Just' "1234", .. }
82+-- @
83+extension :: PhoneNumber -> Maybe ByteString
84+
85+-- | E.g.:
86+--
87+-- @
88+-- 'Data.PhoneNumber.Util.parseNumber' 'Data.PhoneNumber.Util.KeepRawInput' 'Nothing' " + 1(2-3~4*5.6 "
89+-- = 'Right' v'PhoneNumber' { 'rawInput' = 'Just' " + 1(2-3~4*5.6 ", .. }
90+-- @
91+rawInput :: PhoneNumber -> Maybe ByteString
92+
93+-- | E.g.:
94+--
95+-- @
96+-- 'Data.PhoneNumber.Util.parseNumber' 'Data.PhoneNumber.Util.KeepRawInput' ('Just' \"BR\") "0 41 (21) 2345-6789"
97+-- = 'Right' v'PhoneNumber' { 'preferredDomesticCarrierCode' = 'Just' "41", .. }
98+-- @
99+preferredDomesticCarrierCode :: PhoneNumber -> Maybe ByteString
100+
101+-- | You probably want to use 'Data.PhoneNumber.Util.nationalSignificantNumber'
102+-- instead. E.g.:
103+--
104+-- @
105+-- 'Data.PhoneNumber.Util.parseNumber' 'Data.PhoneNumber.Util.Canonicalize' 'Nothing' "+800 0001 2345"
106+-- = 'Right' v'PhoneNumber' { 'nationalNumber' = 12345, 'numberOfLeadingZeros' = Just 3, .. }
107+-- @
108+nationalNumber :: PhoneNumber -> Word
109+
110+-- | E.g.:
111+--
112+-- @
113+-- 'Data.PhoneNumber.Util.parseNumber' 'Data.PhoneNumber.Util.Canonicalize' ('Just' \"US\") "800-234-5678"
114+-- = 'Right' v'PhoneNumber' { 'countryCode' = 1, .. }
115+-- @
116+countryCode :: PhoneNumber -> CountryCode
117+
118+-- | E.g.:
119+--
120+-- @
121+-- 'Data.PhoneNumber.Util.parseNumber' 'Data.PhoneNumber.Util.Canonicalize' 'Nothing' "+39 06 1234 5678"
122+-- = 'Right' v'PhoneNumber' { 'italianLeadingZero' = 'Just' 'True', .. }
123+-- @
124+italianLeadingZero :: PhoneNumber -> Maybe Bool
125+
126+-- | E.g.:
127+--
128+-- @
129+-- 'Data.PhoneNumber.Util.parseNumber' 'Data.PhoneNumber.Util.KeepRawInput' ('Just' \"US\") "011 800 1234 5678"
130+-- = 'Right' v'PhoneNumber' { 'countryCodeSource' = 'Just' 'FromNumberWithIdd', .. }
131+-- @
132+countryCodeSource :: PhoneNumber -> Maybe CountryCodeSource
133+
134+-- | E.g.:
135+--
136+-- @
137+-- 'Data.PhoneNumber.Util.parseNumber' 'Data.PhoneNumber.Util.Canonicalize' 'Nothing' "+800 0001 2345"
138+-- = 'Right' v'PhoneNumber' { 'numberOfLeadingZeros' = Just 3, 'nationalNumber' = 12345, .. }
139+-- @
140+numberOfLeadingZeros :: PhoneNumber -> Maybe Int
src/Data/PhoneNumber/Util.hs+7−7
@@ -371,12 +371,12 @@ nddPrefixForRegion strip (Region region) = unsafeDupablePerformIO $ mask_ $
371371-- 3. For some numbers (particularly fixed-line), many regions have the concept
372372-- of area code, which together with subscriber number constitute the National
373373-- Significant Number. It is sometimes okay to dial only the subscriber number
374−-- when dialing in the same area. This function will return IsPossibleLocalOnly
375−-- if the subscriber-number-only version is passed in. On the other hand,
376−-- because 'isValidNumber' validates using information on both starting digits
377−-- (for fixed line numbers, that would most likely be area codes) and length
378−-- (obviously includes the length of area codes for fixed line numbers), it will
379−-- return 'False' for the subscriber-number-only version.
374+-- when dialing in the same area. This function will return
375+-- 'IsPossibleLocalOnly' if the subscriber-number-only version is passed in. On
376+-- the other hand, because 'isValidNumber' validates using information on both
377+-- starting digits (for fixed line numbers, that would most likely be area
378+-- codes) and length (obviously includes the length of area codes for fixed line
379+-- numbers), it will return 'False' for the subscriber-number-only version.
380380possibleNumber :: PhoneNumberType -> PhoneNumber -> ValidationResult
381381possibleNumber ntype pn = unsafeDupablePerformIO $
382382 c_phone_number_util_is_possible_number_for_type_with_reason pn ntype
@@ -445,7 +445,7 @@ data ParseMode
445445-- 'isValidNumber'.
446446--
447447-- Returns an error if the string is not considered to be a viable phone number
448−-- (e.g. too few or too many digits) or if 'noRegion' was supplied and the
448+-- (e.g. too few or too many digits) or if no 'Region' was supplied and the
449449-- number is not in international format (does not start with @\'+\'@).
450450parseNumber
451451 :: ParseMode
test/Leak.hs+6−6
@@ -74,7 +74,7 @@ main = do
7474 carrierCode = "10"
7575 nonGeoCode = 800
7676 phoneString = "+41 44 668 1800"
77− Right phoneNumber = parseNumber KeepRawInput noRegion phoneString
77+ Right phoneNumber = parseNumber KeepRawInput Nothing phoneString
7878 hammer iter (\(PhoneNumber a b c d e f g h) -> PhoneNumber a b c d e f g h) phoneNumber
7979 hammer iter supportedTypesForRegion region
8080 hammer iter supportedTypesForNonGeoEntity nonGeoCode
@@ -96,7 +96,7 @@ main = do
9696 hammer iter (formatNumber (OutOfCountry region False)) phoneNumber
9797 hammer iter truncateTooLongNumber phoneNumber
9898 hammer iter numberType phoneNumber
99− hammer iter (isValidNumber (Just region)) phoneNumber
99+ hammer iter (isValidNumber (Just (Right region))) phoneNumber
100100 hammer iter regionForNumber phoneNumber
101101 hammer iter countryCodeForRegion region
102102 hammer iter regionForCountryCode countryCallingCode
@@ -107,10 +107,10 @@ main = do
107107 hammer iter canBeInternationallyDialed phoneNumber
108108 hammer iter isGeographicalNumber phoneNumber
109109 hammer iter (isGeographicalNumberType FixedLine) countryCallingCode
110− hammer iter (parseNumber Canonicalize region) phoneString
111− hammer iter (parseNumber Canonicalize region) ""
112− hammer iter (parseNumber KeepRawInput region) phoneString
113− hammer iter (parseNumber KeepRawInput region) ""
110+ hammer iter (parseNumber Canonicalize (Just region)) phoneString
111+ hammer iter (parseNumber Canonicalize (Just region)) ""
112+ hammer iter (parseNumber KeepRawInput (Just region)) phoneString
113+ hammer iter (parseNumber KeepRawInput (Just region)) ""
114114 hammer iter (matchNumbers (Left phoneString)) (Left phoneString)
115115 hammer iter (matchNumbers (Left phoneString)) (Right phoneNumber)
116116 hammer iter (matchNumbers (Right phoneNumber)) (Left phoneString)
test/Spec.hsnew file+245
@@ -0,0 +1,245 @@
1+{-# LANGUAGE DerivingStrategies #-}
2+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+import Control.Applicative
5+import Control.Monad
6+import qualified Data.ByteString as BS
7+import qualified Data.ByteString.Char8 as BSC
8+import Data.Either
9+import Data.PhoneNumber.Number
10+import Data.PhoneNumber.Util
11+import qualified Data.Set as S
12+import Test.Hspec
13+import Test.Hspec.QuickCheck
14+import Test.QuickCheck
15+
16+newtype CC = CC CountryCode
17+ deriving newtype (Show)
18+
19+instance Arbitrary CC where
20+ arbitrary = CC . CountryCode <$> choose (-10, 1000)
21+ shrink (CC (CountryCode cc)) = CC . CountryCode <$> shrink cc
22+
23+newtype Reg = Reg Region
24+ deriving newtype (Show)
25+
26+instance Arbitrary Reg where
27+ arbitrary = do
28+ c1 <- elements ['A'..'Z']
29+ c2 <- elements ['A'..'Z']
30+ pure $ Reg $ Region $ BSC.pack [c1, c2]
31+
32+newtype MReg = MReg (Maybe Region)
33+ deriving newtype (Show)
34+
35+instance Arbitrary MReg where
36+ arbitrary = MReg . fmap (\(Reg r) -> r) <$> arbitrary
37+ shrink (MReg (Just _)) = [MReg Nothing]
38+ shrink (MReg Nothing) = []
39+
40+newtype ParserInput = ParserInput BS.ByteString
41+ deriving newtype (Show)
42+
43+instance Arbitrary ParserInput where
44+ arbitrary = do
45+ len <- choose (0, 20)
46+ xs <- replicateM len $ frequency
47+ [ (10, elements "#()+-0123456789")
48+ , (1, elements "'*,./:;") ]
49+ pure $ ParserInput $ BSC.pack xs
50+ shrink (ParserInput bs) = ParserInput . BS.pack <$> shrink (BS.unpack bs)
51+
52+data ParsedNumber = ParsedNumber (ParseMode, Maybe Region, ParserInput) PhoneNumber
53+ deriving stock (Show)
54+
55+instance Arbitrary ParsedNumber where
56+ arbitrary = do
57+ mode <- elements [Canonicalize, KeepRawInput]
58+ liftA2 ((,,) mode) arbitrary arbitrary
59+ `suchThatMap` \(_, MReg mRegion, ParserInput input) ->
60+ case parseNumber mode mRegion input of
61+ Right pn -> Just $ ParsedNumber (mode, mRegion, ParserInput input) pn
62+ _ -> Nothing
63+ shrink (ParsedNumber (mode, region, input) _) = do
64+ ParserInput input' <- shrink input
65+ case parseNumber mode region input' of
66+ Right pn -> pure $ ParsedNumber (mode, region, ParserInput input') pn
67+ _ -> empty
68+
69+regZZ, reg001 :: Region
70+regZZ = "ZZ"
71+reg001 = "001"
72+
73+cc0 :: CountryCode
74+cc0 = 0
75+
76+main :: IO ()
77+main = hspec $ modifyMaxSuccess (* 10) $ do
78+ describe "supportedRegions" $ do
79+ prop "doesn't contain ZZ" $ regZZ `S.notMember` supportedRegions
80+ prop "doesn't contain 001" $ reg001 `S.notMember` supportedRegions
81+
82+ describe "supportedGlobalNetworkCallingCodes" $ do
83+ prop "doesn't contain 0" $ cc0 `S.notMember` supportedGlobalNetworkCallingCodes
84+ prop "is a subset of supportedCallingCodes" $
85+ supportedGlobalNetworkCallingCodes `S.isSubsetOf` supportedCallingCodes
86+
87+ describe "supportedCallingCodes" $ do
88+ prop "doesn't contain 0" $ cc0 `S.notMember` supportedCallingCodes
89+
90+ describe "supportedTypesForRegion" $ do
91+ prop "doesn't contain FixedLineOrMobile" $ \(Reg region) ->
92+ FixedLineOrMobile `S.notMember` supportedTypesForRegion region
93+
94+ describe "supportedTypesForNonGeoEntity" $ do
95+ prop "doesn't contain FixedLineOrMobile" $ \(CC cc) ->
96+ FixedLineOrMobile `S.notMember` supportedTypesForNonGeoEntity cc
97+
98+ describe "convertAlphaNumber" $ do
99+ prop "doesn't change the parsing of a number" $ \(MReg mRegion) (ParserInput str) ->
100+ parseNumber Canonicalize mRegion (convertAlphaNumber str)
101+ === parseNumber Canonicalize mRegion str
102+ prop "returns a number that is not alpha" $ \(ParserInput str) ->
103+ not $ isAlphaNumber $ convertAlphaNumber str
104+
105+ describe "normalizeNumber" $ do
106+ prop "returns ASCII digits unmodified" $
107+ forAll (elements [Digits, Dialable]) $ \mode ->
108+ forAll (listOf $ elements ['0'..'9']) $ \str ->
109+ BSC.pack str == normalizeNumber mode (BSC.pack str)
110+ describe "Digits" $ do
111+ prop "returns only ASCII digits" $ \str ->
112+ BSC.all (`elem` ['0'..'9']) $ normalizeNumber Digits $ BSC.pack str
113+ prop "doesn't randomly fail" $ \str ->
114+ not (BS.null $ normalizeNumber Digits $ "0" <> BS.pack str)
115+ .&&. not (BS.null $ normalizeNumber Digits $ BS.pack str <> "0")
116+ describe "Dialable" $ do
117+ -- PhoneNumberUtil::NormalizeDiallableCharsOnly will error on any invalid UTF8
118+ prop "doesn't randomly fail" $ \str ->
119+ not (BS.null $ normalizeNumber Digits $ "0" <> BS.pack str)
120+ .&&. not (BS.null $ normalizeNumber Digits $ BS.pack str <> "0")
121+
122+ describe "nationalSignificantNumber" $ do
123+ prop "is non-empty" $ \(ParsedNumber _ pn) ->
124+ not $ BS.null $ nationalSignificantNumber pn
125+ prop "returns only ASCII digits" $ \(ParsedNumber _ pn) ->
126+ BSC.all (`elem` ['0'..'9']) $ nationalSignificantNumber pn
127+ modifyMaxDiscardRatio (* 10) $ do
128+ prop "concatenated with country code parses to the original (if valid)" $
129+ \(ParsedNumber _ pn) ->
130+ isValidNumber Nothing pn ==>
131+ let
132+ cc = countryCode pn
133+ nsn = nationalSignificantNumber pn
134+ str = "+" <> BSC.pack (show cc) <> nsn
135+ in case parseNumber Canonicalize Nothing str of
136+ Left err -> error $ show err
137+ Right pn' -> cc === countryCode pn'
138+ .&&. nsn === nationalSignificantNumber pn'
139+
140+ describe "countryMobileToken" $ do
141+ prop "returns only ASCII digits" $ \(CC cc) ->
142+ BSC.all (`elem` ['0'..'9']) $ countryMobileToken cc
143+
144+ describe "formatNumber" $ do
145+ prop "with International produces a parseable result (if no ext)" $
146+ \(ParsedNumber _ pn) -> possibleNumber Unknown pn == IsPossible ==>
147+ -- Apparently in the TW region an extension number is formatted
148+ -- with a "#", and the formatter will format
149+ -- "+886 2 2123 4567 ext. 1234567890" as "+886 2 2123 4567#1234567890"
150+ -- even though the former parses and the latter doesn't
151+ extension pn == Nothing ==>
152+ isRight $ parseNumber Canonicalize Nothing
153+ $ formatNumber International pn
154+ modifyMaxDiscardRatio (* 10) $ do
155+ prop "with E164 produces a parseable result (if Canonicalize)" $
156+ \(ParsedNumber (mode, _, _) pn) ->
157+ -- KeepRawInput with pn == "0000" breaks E164 formatting
158+ possibleNumber Unknown pn == IsPossible && mode == Canonicalize ==>
159+ isRight $ parseNumber Canonicalize Nothing
160+ $ formatNumber E164 pn
161+ prop "with RFC3966 produces a parseable result (if Canonicalize)" $
162+ \(ParsedNumber (mode, _, _) pn) ->
163+ -- KeepRawInput with pn == "0000" breaks RFC3966 formatting
164+ possibleNumber Unknown pn == IsPossible && mode == Canonicalize ==>
165+ isRight $ parseNumber Canonicalize Nothing
166+ $ formatNumber RFC3966 pn
167+
168+ describe "truncateTooLongNumber" $ do
169+ pure ()
170+
171+ describe "numberType" $ do
172+ pure ()
173+
174+ describe "isValidNumber" $ do
175+ modifyMaxDiscardRatio (* 10) $ do
176+ prop "is preserved under parse . format" $ \(ParsedNumber _ pn) ->
177+ isValidNumber Nothing pn ==>
178+ case parseNumber Canonicalize Nothing $ formatNumber E164 pn of
179+ Left err -> error $ show err
180+ Right pn' -> isValidNumber Nothing pn'
181+
182+ describe "regionForNumber" $ do
183+ prop "doesn't return ZZ" $ \(ParsedNumber _ pn) ->
184+ regionForNumber pn =/= Just (Right regZZ)
185+ prop "doesn't return 001" $ \(ParsedNumber _ pn) ->
186+ regionForNumber pn =/= Just (Right reg001)
187+ prop "returns a supported region" $ \(ParsedNumber _ pn) ->
188+ case regionForNumber pn of
189+ Just (Right region) -> region `S.member` supportedRegions
190+ _ -> discard
191+ prop "returns Region001 exactly for supported non-geo entities" $
192+ \(ParsedNumber _ pn) ->
193+ (countryCode pn `S.member` supportedGlobalNetworkCallingCodes)
194+ === (regionForNumber pn == Just (Left Region001))
195+
196+ describe "countryCodeForRegion" $ do
197+ prop "doesn't return 0" $ \(Reg region) ->
198+ countryCodeForRegion region =/= Just cc0
199+ prop "returns codes in supportedCallingCodes" $ \(Reg region) ->
200+ case countryCodeForRegion region of
201+ Nothing -> discard
202+ Just cc -> cc `S.member` supportedCallingCodes
203+
204+ describe "regionForCountryCode" $ do
205+ prop "doesn't return ZZ" $ \(CC cc) ->
206+ regionForCountryCode cc =/= Just (Right regZZ)
207+ prop "doesn't return 001" $ \(CC cc) ->
208+ regionForCountryCode cc =/= Just (Right reg001)
209+ prop "returns Region001 exactly for supported non-geo entities" $ \(CC cc) ->
210+ (cc `S.member` supportedGlobalNetworkCallingCodes)
211+ === (regionForCountryCode cc == Just (Left Region001))
212+
213+ describe "regionsForCountryCode" $ do
214+ prop "doesn't return ZZ" $ \(CC cc) ->
215+ Right regZZ `notElem` regionsForCountryCode cc
216+ prop "doesn't return 001" $ \(CC cc) ->
217+ Right reg001 `notElem` regionsForCountryCode cc
218+
219+ describe "isNANPACountry" $ do
220+ pure ()
221+
222+ describe "nddPrefixForRegion" $ do
223+ prop "with True returns only ASCII digits" $ \(Reg region) ->
224+ BSC.all (`elem` ['0'..'9']) $ nddPrefixForRegion True region
225+
226+ describe "possibleNumber" $ do
227+ modifyMaxDiscardRatio (* 10) $ do
228+ prop "with Unknown is more lenient than isValidNumber" $
229+ \(ParsedNumber _ pn) -> isValidNumber Nothing pn ==>
230+ possibleNumber Unknown pn `elem` [IsPossible, IsPossibleLocalOnly]
231+
232+ describe "canBeInternationallyDialed" $ do
233+ pure ()
234+
235+ describe "isGeographicalNumber" $ do
236+ prop "matches isGeographicalNumberType" $ \(ParsedNumber _ pn) ->
237+ isGeographicalNumber pn
238+ === isGeographicalNumberType (numberType pn) (countryCode pn)
239+
240+ describe "isGeographicalNumberType" $ do
241+ pure ()
242+
243+ describe "parseNumber" $ do
244+ prop "doesn't return a number with CountryCode 0" $ \(ParsedNumber _ pn) ->
245+ countryCode pn /= cc0