git.iko.soy/typeable/libphonenumber
on main help sign in
.github/workflows/ci.yamlnew file+102
since splitsnapshots
unifiedsplit
@@ -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
55 return static_cast<country_code_source>(value);
55 return static_cast<country_code_source>(value);
56}
56}
57
57
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)
59{
59{
60 PhoneNumber *number = new PhoneNumber();
60 PhoneNumber *number = new PhoneNumber();
61 if (c_number->extension != nullptr)
61 if (c_number->extension != nullptr)
@@ -72,11 +72,12 @@ extern "C" PhoneNumber *c_phone_number_marshal(phone_number const *c_number)
72 number->set_country_code_source(marshal_country_code_source(c_number->country_code_source));
72 number->set_country_code_source(marshal_country_code_source(c_number->country_code_source));
73 if (c_number->has_number_of_leading_zeros)
73 if (c_number->has_number_of_leading_zeros)
74 number->set_number_of_leading_zeros(c_number->number_of_leading_zeros);
74 number->set_number_of_leading_zeros(c_number->number_of_leading_zeros);
75− return number;
75+ return reinterpret_cast<CxxPhoneNumber *>(number);
76}
76}
77
77
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)
79{
79{
80+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
80 if (number->has_extension())
81 if (number->has_extension())
81 unmarshal_string_size(number->extension(), c_number->extension, c_number->extension_size);
82 unmarshal_string_size(number->extension(), c_number->extension, c_number->extension_size);
82 else
83 else
@@ -99,7 +100,8 @@ extern "C" void c_phone_number_unmarshal(PhoneNumber const *number, phone_number
99 c_number->number_of_leading_zeros = number->number_of_leading_zeros();
100 c_number->number_of_leading_zeros = number->number_of_leading_zeros();
100}
101}
101
102
102−extern "C" void c_phone_number_free(PhoneNumber *number)
103+extern "C" void c_phone_number_free(CxxPhoneNumber *ptr)
103{
104{
105+ auto number = reinterpret_cast<PhoneNumber *>(ptr);
104 delete number;
106 delete number;
105}
107}
cxxbits/c_phonenumberutil.cpp+45−26
@@ -310,10 +310,11 @@ extern "C" void c_phone_number_util_normalize_dialable_chars_only(
310}
310}
311
311
312extern "C" void c_phone_number_util_get_national_significant_number(
312extern "C" void c_phone_number_util_get_national_significant_number(
313− PhoneNumber const *number,
313+ CxxPhoneNumber const *ptr,
314 c_string *c_nsn
314 c_string *c_nsn
315)
315)
316{
316{
317+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
317 std::string nsn;
318 std::string nsn;
318 PhoneNumberUtil::GetInstance()->GetNationalSignificantNumber(*number, &nsn);
319 PhoneNumberUtil::GetInstance()->GetNationalSignificantNumber(*number, &nsn);
319 unmarshal_c_string(nsn, *c_nsn);
320 unmarshal_c_string(nsn, *c_nsn);
@@ -330,23 +331,25 @@ extern "C" void c_phone_number_util_get_country_mobile_token(
330}
331}
331
332
332extern "C" void c_phone_number_util_format(
333extern "C" void c_phone_number_util_format(
333− PhoneNumber const *number,
334+ CxxPhoneNumber const *ptr,
334 phone_number_format format,
335 phone_number_format format,
335 c_string *c_string
336 c_string *c_string
336)
337)
337{
338{
339+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
338 std::string string;
340 std::string string;
339 PhoneNumberUtil::GetInstance()->Format(*number, marshal_phone_number_format(format), &string);
341 PhoneNumberUtil::GetInstance()->Format(*number, marshal_phone_number_format(format), &string);
340 unmarshal_c_string(string, *c_string);
342 unmarshal_c_string(string, *c_string);
341}
343}
342
344
343extern "C" void c_phone_number_util_format_national_number_with_carrier_code(
345extern "C" void c_phone_number_util_format_national_number_with_carrier_code(
344− PhoneNumber const *number,
346+ CxxPhoneNumber const *ptr,
345 char const *c_carrier_code,
347 char const *c_carrier_code,
346 size_t c_carrier_code_size,
348 size_t c_carrier_code_size,
347 c_string *c_string
349 c_string *c_string
348)
350)
349{
351{
352+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
350 std::string const carrier_code(c_carrier_code, c_carrier_code_size);
353 std::string const carrier_code(c_carrier_code, c_carrier_code_size);
351 std::string string;
354 std::string string;
352 PhoneNumberUtil::GetInstance()->FormatNationalNumberWithCarrierCode(*number, carrier_code, &string);
355 PhoneNumberUtil::GetInstance()->FormatNationalNumberWithCarrierCode(*number, carrier_code, &string);
@@ -354,12 +357,13 @@ extern "C" void c_phone_number_util_format_national_number_with_carrier_code(
354}
357}
355
358
356extern "C" void c_phone_number_util_format_national_number_with_preferred_carrier_code(
359extern "C" void c_phone_number_util_format_national_number_with_preferred_carrier_code(
357− PhoneNumber const *number,
360+ CxxPhoneNumber const *ptr,
358 char const *c_carrier_code,
361 char const *c_carrier_code,
359 size_t c_carrier_code_size,
362 size_t c_carrier_code_size,
360 c_string *c_string
363 c_string *c_string
361)
364)
362{
365{
366+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
363 std::string const carrier_code(c_carrier_code, c_carrier_code_size);
367 std::string const carrier_code(c_carrier_code, c_carrier_code_size);
364 std::string string;
368 std::string string;
365 PhoneNumberUtil::GetInstance()->FormatNationalNumberWithPreferredCarrierCode(*number, carrier_code, &string);
369 PhoneNumberUtil::GetInstance()->FormatNationalNumberWithPreferredCarrierCode(*number, carrier_code, &string);
@@ -367,13 +371,14 @@ extern "C" void c_phone_number_util_format_national_number_with_preferred_carrie
367}
371}
368
372
369extern "C" void c_phone_number_util_format_number_for_mobile_dialing(
373extern "C" void c_phone_number_util_format_number_for_mobile_dialing(
370− PhoneNumber const *number,
374+ CxxPhoneNumber const *ptr,
371 char const *c_region,
375 char const *c_region,
372 size_t c_region_size,
376 size_t c_region_size,
373 int with_formatting,
377 int with_formatting,
374 c_string *c_string
378 c_string *c_string
375)
379)
376{
380{
381+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
377 std::string const region(c_region, c_region_size);
382 std::string const region(c_region, c_region_size);
378 std::string string;
383 std::string string;
379 PhoneNumberUtil::GetInstance()->FormatNumberForMobileDialing(*number, region, with_formatting, &string);
384 PhoneNumberUtil::GetInstance()->FormatNumberForMobileDialing(*number, region, with_formatting, &string);
@@ -381,12 +386,13 @@ extern "C" void c_phone_number_util_format_number_for_mobile_dialing(
381}
386}
382
387
383extern "C" void c_phone_number_util_format_out_of_country_calling_number(
388extern "C" void c_phone_number_util_format_out_of_country_calling_number(
384− PhoneNumber const *number,
389+ CxxPhoneNumber const *ptr,
385 char const *c_region,
390 char const *c_region,
386 size_t c_region_size,
391 size_t c_region_size,
387 c_string *c_string
392 c_string *c_string
388)
393)
389{
394{
395+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
390 std::string const region(c_region, c_region_size);
396 std::string const region(c_region, c_region_size);
391 std::string string;
397 std::string string;
392 PhoneNumberUtil::GetInstance()->FormatOutOfCountryCallingNumber(*number, region, &string);
398 PhoneNumberUtil::GetInstance()->FormatOutOfCountryCallingNumber(*number, region, &string);
@@ -394,12 +400,13 @@ extern "C" void c_phone_number_util_format_out_of_country_calling_number(
394}
400}
395
401
396extern "C" void c_phone_number_util_format_in_original_format(
402extern "C" void c_phone_number_util_format_in_original_format(
397− PhoneNumber const *number,
403+ CxxPhoneNumber const *ptr,
398 char const *c_region,
404 char const *c_region,
399 size_t c_region_size,
405 size_t c_region_size,
400 c_string *c_string
406 c_string *c_string
401)
407)
402{
408{
409+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
403 std::string const region(c_region, c_region_size);
410 std::string const region(c_region, c_region_size);
404 std::string string;
411 std::string string;
405 PhoneNumberUtil::GetInstance()->FormatInOriginalFormat(*number, region, &string);
412 PhoneNumberUtil::GetInstance()->FormatInOriginalFormat(*number, region, &string);
@@ -407,24 +414,26 @@ extern "C" void c_phone_number_util_format_in_original_format(
407}
414}
408
415
409extern "C" void c_phone_number_util_format_out_of_country_keeping_alpha_chars(
416extern "C" void c_phone_number_util_format_out_of_country_keeping_alpha_chars(
410− PhoneNumber const *number,
417+ CxxPhoneNumber const *ptr,
411 char const *c_region,
418 char const *c_region,
412 size_t c_region_size,
419 size_t c_region_size,
413 c_string *c_string
420 c_string *c_string
414)
421)
415{
422{
423+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
416 std::string const region(c_region, c_region_size);
424 std::string const region(c_region, c_region_size);
417 std::string string;
425 std::string string;
418 PhoneNumberUtil::GetInstance()->FormatOutOfCountryKeepingAlphaChars(*number, region, &string);
426 PhoneNumberUtil::GetInstance()->FormatOutOfCountryKeepingAlphaChars(*number, region, &string);
419 unmarshal_c_string(string, *c_string);
427 unmarshal_c_string(string, *c_string);
420}
428}
421
429
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)
423{
431{
432+ auto input = reinterpret_cast<PhoneNumber const *>(ptr);
424 auto *output = new PhoneNumber(*input);
433 auto *output = new PhoneNumber(*input);
425 if (PhoneNumberUtil::GetInstance()->TruncateTooLongNumber(output))
434 if (PhoneNumberUtil::GetInstance()->TruncateTooLongNumber(output))
426 {
435 {
427− return output;
436+ return reinterpret_cast<CxxPhoneNumber *>(output);
428 }
437 }
429 else
438 else
430 {
439 {
@@ -433,33 +442,37 @@ extern "C" PhoneNumber *c_phone_number_util_truncate_too_long_number(PhoneNumber
433 }
442 }
434}
443}
435
444
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)
437{
446{
447+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
438 return unmarshal_phone_number_type(
448 return unmarshal_phone_number_type(
439 PhoneNumberUtil::GetInstance()->GetNumberType(*number)
449 PhoneNumberUtil::GetInstance()->GetNumberType(*number)
440 );
450 );
441}
451}
442
452
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)
444{
454{
455+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
445 return PhoneNumberUtil::GetInstance()->IsValidNumber(*number);
456 return PhoneNumberUtil::GetInstance()->IsValidNumber(*number);
446}
457}
447
458
448extern "C" int c_phone_number_util_is_valid_number_for_region(
459extern "C" int c_phone_number_util_is_valid_number_for_region(
449− PhoneNumber const *number,
460+ CxxPhoneNumber const *ptr,
450 char const *c_region,
461 char const *c_region,
451 size_t c_region_size
462 size_t c_region_size
452)
463)
453{
464{
465+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
454 std::string const region(c_region, c_region_size);
466 std::string const region(c_region, c_region_size);
455 return PhoneNumberUtil::GetInstance()->IsValidNumberForRegion(*number, region);
467 return PhoneNumberUtil::GetInstance()->IsValidNumberForRegion(*number, region);
456}
468}
457
469
458extern "C" void c_phone_number_util_get_region_code_for_number(
470extern "C" void c_phone_number_util_get_region_code_for_number(
459− PhoneNumber const *number,
471+ CxxPhoneNumber const *ptr,
460 c_string *c_region
472 c_string *c_region
461)
473)
462{
474{
475+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
463 std::string region;
476 std::string region;
464 PhoneNumberUtil::GetInstance()->GetRegionCodeForNumber(*number, &region);
477 PhoneNumberUtil::GetInstance()->GetRegionCodeForNumber(*number, &region);
465 unmarshal_c_string(region, *c_region);
478 unmarshal_c_string(region, *c_region);
@@ -518,10 +531,11 @@ extern "C" void c_phone_number_util_get_ndd_prefix_for_region(
518}
531}
519
532
520extern "C" validation_result c_phone_number_util_is_possible_number_for_type_with_reason(
533extern "C" validation_result c_phone_number_util_is_possible_number_for_type_with_reason(
521− PhoneNumber const *number,
534+ CxxPhoneNumber const *ptr,
522 phone_number_type ntype
535 phone_number_type ntype
523)
536)
524{
537{
538+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
525 return unmarshal_validation_result(
539 return unmarshal_validation_result(
526 PhoneNumberUtil::GetInstance()->IsPossibleNumberForTypeWithReason(
540 PhoneNumberUtil::GetInstance()->IsPossibleNumberForTypeWithReason(
527 *number,
541 *number,
@@ -530,13 +544,15 @@ extern "C" validation_result c_phone_number_util_is_possible_number_for_type_wit
530 );
544 );
531}
545}
532
546
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)
534{
548{
549+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
535 return PhoneNumberUtil::GetInstance()->CanBeInternationallyDialled(*number);
550 return PhoneNumberUtil::GetInstance()->CanBeInternationallyDialled(*number);
536}
551}
537
552
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)
539{
554{
555+ auto number = reinterpret_cast<PhoneNumber const *>(ptr);
540 return PhoneNumberUtil::GetInstance()->IsNumberGeographical(*number);
556 return PhoneNumberUtil::GetInstance()->IsNumberGeographical(*number);
541}
557}
542
558
@@ -556,14 +572,14 @@ extern "C" error_type c_phone_number_util_parse(
556 size_t c_string_size,
572 size_t c_string_size,
557 char const *c_region,
573 char const *c_region,
558 size_t c_region_size,
574 size_t c_region_size,
559− PhoneNumber **number
575+ CxxPhoneNumber **number
560)
576)
561{
577{
562 std::string const string(c_string, c_string_size);
578 std::string const string(c_string, c_string_size);
563 std::string const region(c_region, c_region_size);
579 std::string const region(c_region, c_region_size);
564− *number = new PhoneNumber();
580+ *number = reinterpret_cast<CxxPhoneNumber *>(new PhoneNumber());
565 return unmarshal_error_type(
581 return unmarshal_error_type(
566− PhoneNumberUtil::GetInstance()->Parse(string, region, *number)
582+ PhoneNumberUtil::GetInstance()->Parse(string, region, reinterpret_cast<PhoneNumber *>(*number))
567 );
583 );
568}
584}
569
585
@@ -572,22 +588,24 @@ extern "C" error_type c_phone_number_util_parse_and_keep_raw_input(
572 size_t c_string_size,
588 size_t c_string_size,
573 char const *c_region,
589 char const *c_region,
574 size_t c_region_size,
590 size_t c_region_size,
575− PhoneNumber **number
591+ CxxPhoneNumber **number
576)
592)
577{
593{
578 std::string const string(c_string, c_string_size);
594 std::string const string(c_string, c_string_size);
579 std::string const region(c_region, c_region_size);
595 std::string const region(c_region, c_region_size);
580− *number = new PhoneNumber();
596+ *number = reinterpret_cast<CxxPhoneNumber *>(new PhoneNumber());
581 return unmarshal_error_type(
597 return unmarshal_error_type(
582− PhoneNumberUtil::GetInstance()->ParseAndKeepRawInput(string, region, *number)
598+ PhoneNumberUtil::GetInstance()->ParseAndKeepRawInput(string, region, reinterpret_cast<PhoneNumber *>(*number))
583 );
599 );
584}
600}
585
601
586extern "C" match_type c_phone_number_util_is_number_match(
602extern "C" match_type c_phone_number_util_is_number_match(
587− PhoneNumber const *number1,
603+ CxxPhoneNumber const *ptr1,
588− PhoneNumber const *number2
604+ CxxPhoneNumber const *ptr2
589)
605)
590{
606{
607+ auto number1 = reinterpret_cast<PhoneNumber const *>(ptr1);
608+ auto number2 = reinterpret_cast<PhoneNumber const *>(ptr2);
591 return unmarshal_match_type(
609 return unmarshal_match_type(
592 PhoneNumberUtil::GetInstance()->IsNumberMatch(*number1, *number2)
610 PhoneNumberUtil::GetInstance()->IsNumberMatch(*number1, *number2)
593 );
611 );
@@ -608,11 +626,12 @@ extern "C" match_type c_phone_number_util_is_number_match_with_two_strings(
608}
626}
609
627
610extern "C" match_type c_phone_number_util_is_number_match_with_one_string(
628extern "C" match_type c_phone_number_util_is_number_match_with_one_string(
611− PhoneNumber const *number1,
629+ CxxPhoneNumber const *ptr1,
612 char const *c_number2,
630 char const *c_number2,
613 size_t c_number2_size
631 size_t c_number2_size
614)
632)
615{
633{
634+ auto number1 = reinterpret_cast<PhoneNumber const *>(ptr1);
616 std::string const number2(c_number2, c_number2_size);
635 std::string const number2(c_number2, c_number2_size);
617 return unmarshal_match_type(
636 return unmarshal_match_type(
618 PhoneNumberUtil::GetInstance()->IsNumberMatchWithOneString(*number1, number2)
637 PhoneNumberUtil::GetInstance()->IsNumberMatchWithOneString(*number1, number2)
include/c_phonenumber.h+4−8
@@ -43,20 +43,16 @@ typedef struct
43 char has_number_of_leading_zeros;
43 char has_number_of_leading_zeros;
44} phone_number;
44} phone_number;
45
45
46−#ifdef __cplusplus
46+struct CxxPhoneNumber;
47−typedef i18n::phonenumbers::PhoneNumber PhoneNumber;
48−#else
49−typedef struct PhoneNumber PhoneNumber;
50−#endif
51
47
52// The returned object is allocated by `new` and managed by C++ RAII, and it is
48// The returned object is allocated by `new` and managed by C++ RAII, and it is
53// haskell's responsibility to call its finalizer (c_phone_number_free).
49// 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 *);
55
51
56// phone_number * points to an area which will be filled with the data of the
52// phone_number * points to an area which will be filled with the data of the
57// given PhoneNumber.
53// given PhoneNumber.
58−void c_phone_number_unmarshal(PhoneNumber const *, phone_number *);
54+void c_phone_number_unmarshal(struct CxxPhoneNumber const *, phone_number *);
59
55
60−void c_phone_number_free(PhoneNumber *);
56+void c_phone_number_free(struct CxxPhoneNumber *);
61
57
62#endif
58#endif
include/c_phonenumberutil.h+20−20
@@ -97,34 +97,34 @@ int c_phone_number_util_is_alpha_number(char const *, size_t);
97void c_phone_number_util_convert_alpha_characters_in_number(char const *, size_t, c_string *);
97void c_phone_number_util_convert_alpha_characters_in_number(char const *, size_t, c_string *);
98void c_phone_number_util_normalize_digits_only(char const *, size_t, c_string *);
98void c_phone_number_util_normalize_digits_only(char const *, size_t, c_string *);
99void c_phone_number_util_normalize_dialable_chars_only(char const *, size_t, c_string *);
99void 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 *);
101void c_phone_number_util_get_country_mobile_token(int, c_string *);
101void 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 *);
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(PhoneNumber const *, char const *, size_t, 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(PhoneNumber 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(PhoneNumber const *, char const *, size_t, int, 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(PhoneNumber const *, char const *, size_t, 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(PhoneNumber 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(PhoneNumber 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 *);
109// /!\ May return NULL
109// /!\ May return NULL
110−PhoneNumber *c_phone_number_util_truncate_too_long_number(PhoneNumber const *);
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(PhoneNumber 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(PhoneNumber const *);
112+int c_phone_number_util_is_valid_number(struct CxxPhoneNumber const *);
113−int c_phone_number_util_is_valid_number_for_region(PhoneNumber const *, char const *, size_t);
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(PhoneNumber const *, c_string *);
114+void c_phone_number_util_get_region_code_for_number(struct CxxPhoneNumber const *, c_string *);
115int c_phone_number_util_get_country_code_for_region(char const *, size_t);
115int c_phone_number_util_get_country_code_for_region(char const *, size_t);
116void c_phone_number_util_get_region_code_for_country_code(int, c_string *);
116void c_phone_number_util_get_region_code_for_country_code(int, c_string *);
117void c_phone_number_util_get_region_codes_for_country_calling_code(int, c_string **, size_t *);
117void c_phone_number_util_get_region_codes_for_country_calling_code(int, c_string **, size_t *);
118int c_phone_number_util_is_nanpa_country(char const *, size_t);
118int c_phone_number_util_is_nanpa_country(char const *, size_t);
119void c_phone_number_util_get_ndd_prefix_for_region(char const *, size_t, int, c_string *);
119void 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);
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(PhoneNumber const *);
121+int c_phone_number_util_can_be_internationally_dialed(struct CxxPhoneNumber const *);
122−int c_phone_number_util_is_number_geographical_1(PhoneNumber const *);
122+int c_phone_number_util_is_number_geographical_1(struct CxxPhoneNumber const *);
123int c_phone_number_util_is_number_geographical_2(enum phone_number_type, int);
123int 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 **);
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, PhoneNumber **);
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(PhoneNumber const *, PhoneNumber const *);
126+enum match_type c_phone_number_util_is_number_match(struct CxxPhoneNumber const *, struct CxxPhoneNumber const *);
127enum match_type c_phone_number_util_is_number_match_with_two_strings(char const *, size_t, char const *, size_t);
127enum 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);
129
129
130#endif
130#endif
include/common.h+3−2
@@ -4,6 +4,7 @@
4#include <cstddef>
4#include <cstddef>
5#include <cstring>
5#include <cstring>
6#include <type_traits>
6#include <type_traits>
7+#include <utility>
7
8
8// Given `enum T { A, B, C }` and `enum S { X, Y, Z }`, we can use
9// Given `enum T { A, B, C }` and `enum S { X, Y, Z }`, we can use
9// `is_same_enum<enum_values<T, A, B, C>, enum_values<S, X, Y, Z>>::value`
10// `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
31
32
32template<typename T, T... TVals, typename S, S... SVals>
33template<typename T, T... TVals, typename S, S... SVals>
33struct is_same_enum<enum_values<T, TVals...>, enum_values<S, SVals...>>:
34struct 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<
35 std::integer_sequence<
36 std::integer_sequence<
36 typename std::common_type<
37 typename std::common_type<
37 typename std::underlying_type<T>::type,
38 typename std::underlying_type<T>::type,
@@ -46,7 +47,7 @@ struct is_same_enum<enum_values<T, TVals...>, enum_values<S, SVals...>>:
46 >::type,
47 >::type,
47 SVals...
48 SVals...
48 >
49 >
49− >>
50+ >::value>
50{};
51{};
51
52
52inline void unmarshal_string_size(std::string const &string, char *&c_string, size_t &c_string_size)
53inline void unmarshal_string_size(std::string const &string, char *&c_string, size_t &c_string_size)
libphonenumber.cabal+81−36
@@ -1,6 +1,6 @@
1cabal-version: 3.0
1cabal-version: 3.0
2name: libphonenumber
2name: libphonenumber
3−version: 0.0.0.0
3+version: 0.1.4.0
4category: Data
4category: Data
5synopsis: Parsing, formatting, and validating international phone
5synopsis: Parsing, formatting, and validating international phone
6 numbers
6 numbers
@@ -8,7 +8,8 @@ description:
8 This package provides bindings for the C++ version of the
8 This package provides bindings for the C++ version of the
9 <https://github.com/google/libphonenumber libphonenumber library>.
9 <https://github.com/google/libphonenumber libphonenumber library>.
10 The bindings currently do not include @AsYouTypeFormatter@
10 The bindings currently do not include @AsYouTypeFormatter@
11− and @ShortNumberInfo@.
11+ and @ShortNumberInfo@. The C++ library must be of version
12+ >= 8.7.0.
12
13
13 The interface largely resembles that of the original unified
14 The interface largely resembles that of the original unified
14 C++, Java, and JavaScript version of the library, with some
15 C++, Java, and JavaScript version of the library, with some
@@ -19,37 +20,66 @@ description:
19 else has installed a global logger which could observe
20 else has installed a global logger which could observe
20 side-effects from library calls.
21 side-effects from library calls.
21
22
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+
22−build-type: Simple
48+build-type: Simple
23
49
24extra-source-files:
50extra-source-files:
25− include/*.h
51+ , include/*.h
26− test/libphonenumber.supp
52+ , test/libphonenumber.supp
53+
54+source-repository head
55+ type: git
56+ location: https://github.com/typeable/libphonenumber/
27
57
28library
58library
29 exposed-modules:
59 exposed-modules:
30− Data.PhoneNumber.Number
60+ , Data.PhoneNumber.Number
31− Data.PhoneNumber.Util
61+ , Data.PhoneNumber.Util
32 other-modules:
62 other-modules:
33− Data.PhoneNumber.Internal.Common
63+ , Data.PhoneNumber.Internal.Common
34− Data.PhoneNumber.Internal.Number
64+ , Data.PhoneNumber.Internal.Number
35− Data.PhoneNumber.Internal.Util
65+ , Data.PhoneNumber.Internal.Util
36 build-depends:
66 build-depends:
37− base >= 4.12 && < 4.18
67+ , base >= 4.12 && < 4.23
38− , bytestring
68+ , bytestring >= 0.10.8 && < 0.13
39− , containers
69+ , containers >= 0.6.0 && < 0.9
40− , deepseq
70+ , deepseq >= 1.4.4 && < 1.6
41− , transformers
71+ , transformers >= 0.5.5 && < 0.7
42 build-tool-depends: c2hs:c2hs
72 build-tool-depends: c2hs:c2hs
73+ pkgconfig-depends: protobuf
43 include-dirs: include
74 include-dirs: include
44 includes:
75 includes:
45− c_phonenumber.h
76+ , c_phonenumber.h
46− c_phonenumberutil.h
77+ , c_phonenumberutil.h
47 cxx-sources:
78 cxx-sources:
48− cxxbits/c_phonenumber.cpp
79+ , cxxbits/c_phonenumber.cpp
49− cxxbits/c_phonenumberutil.cpp
80+ , cxxbits/c_phonenumberutil.cpp
50 extra-libraries:
81 extra-libraries:
51− phonenumber
82+ , phonenumber
52− protobuf
53 if impl(ghc >= 9.4)
83 if impl(ghc >= 9.4)
54 build-depends: system-cxx-std-lib
84 build-depends: system-cxx-std-lib
55 else
85 else
@@ -57,28 +87,43 @@ library
57 hs-source-dirs: src
87 hs-source-dirs: src
58 default-language: Haskell2010
88 default-language: Haskell2010
59 default-extensions:
89 default-extensions:
60− DeriveAnyClass
90+ , DeriveAnyClass
61− DeriveDataTypeable
91+ , DeriveDataTypeable
62− DeriveGeneric
92+ , DeriveGeneric
63− DerivingStrategies
93+ , DerivingStrategies
64− GeneralizedNewtypeDeriving
94+ , GeneralizedNewtypeDeriving
65− LambdaCase
95+ , LambdaCase
66− NamedFieldPuns
96+ , NamedFieldPuns
67− OverloadedStrings
97+ , OverloadedStrings
68− PatternSynonyms
98+ , PatternSynonyms
69− StandaloneDeriving
99+ , StandaloneDeriving
70− RecordWildCards
100+ , RecordWildCards
71− ViewPatterns
101+ , ViewPatterns
72 if impl(ghc >= 9.2)
102 if impl(ghc >= 9.2)
73 default-extensions: NoFieldSelectors
103 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
75
119
76test-suite leak_test
120test-suite leak_test
77 type: exitcode-stdio-1.0
121 type: exitcode-stdio-1.0
78 build-depends:
122 build-depends:
79− base
123+ , base
80 , libphonenumber
124 , libphonenumber
81− , process
125+ , process >= 1.6.5 && < 1.7
82 main-is: Leak.hs
126 main-is: Leak.hs
83 hs-source-dirs: test
127 hs-source-dirs: test
84 default-language: Haskell2010
128 default-language: Haskell2010
129+ ghc-options: -Wall -Wextra
src/Data/PhoneNumber/Internal/Number.chs+1−1
@@ -126,7 +126,7 @@ acquireCPhoneNumber p = do
126-- , 'Data.PhoneNumber.Number.numberOfLeadingZeros' :: !('Maybe' 'Int')
126-- , 'Data.PhoneNumber.Number.numberOfLeadingZeros' :: !('Maybe' 'Int')
127-- }
127-- }
128-- @
128-- @
129−{#pointer *PhoneNumber foreign finalizer c_phone_number_free newtype#}
129+{#pointer *CxxPhoneNumber as PhoneNumber foreign finalizer c_phone_number_free newtype#}
130
130
131{#fun unsafe c_phone_number_marshal
131{#fun unsafe c_phone_number_marshal
132 { withCPhoneNumber* `CPhoneNumber'
132 { withCPhoneNumber* `CPhoneNumber'
src/Data/PhoneNumber/Number.hs+69−10
@@ -21,16 +21,10 @@ module Data.PhoneNumber.Number
21 ) where
21 ) where
22
22
23import Data.ByteString (ByteString)
23import Data.ByteString (ByteString)
24−import Data.PhoneNumber.Internal.Number hiding
24+import Data.PhoneNumber.Internal.Number
25− ( pattern PhoneNumber
25+ ( CPhoneNumber(CPhoneNumber), PhoneNumber, CountryCodeSource, toCPhoneNumber
26− , extension
26+ , fromCPhoneNumber
27− , rawInput
27+ )
28− , preferredDomesticCarrierCode
29− , nationalNumber
30− , countryCode
31− , italianLeadingZero
32− , countryCodeSource
33− , numberOfLeadingZeros )
34import qualified Data.PhoneNumber.Internal.Number as I
28import qualified Data.PhoneNumber.Internal.Number as I
35
29
36-- | A country calling code (International Subscriber Dialing code, ISD code),
30-- | A country calling code (International Subscriber Dialing code, ISD code),
@@ -79,3 +73,68 @@ pattern PhoneNumber
79 = fromCPhoneNumber $ CPhoneNumber{..}
73 = fromCPhoneNumber $ CPhoneNumber{..}
80
74
81{-# COMPLETE PhoneNumber #-}
75{-# 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+72−51
@@ -29,7 +29,7 @@ module Data.PhoneNumber.Util
29 , supportedTypesForNonGeoEntity
29 , supportedTypesForNonGeoEntity
30 -- * 'Region'/'CountryCode' Queries
30 -- * 'Region'/'CountryCode' Queries
31 , Region(..)
31 , Region(..)
32− , noRegion
32+ , NonGeoRegion(..)
33 , CountryCode(..)
33 , CountryCode(..)
34 , countryCodeForRegion
34 , countryCodeForRegion
35 , regionForCountryCode
35 , regionForCountryCode
@@ -59,19 +59,31 @@ import GHC.Generics
59import GHC.Exts
59import GHC.Exts
60import GHC.IO
60import GHC.IO
61
61
62−-- | Most of the time an ISO 3166-1 alpha-2 country code in upper case, except
62+-- | An ISO 3166-1 alpha-2 country code in upper case.
63−-- in some cases the UN M.49 code @"001"@ (meaning the world) used to represent
64−-- non-geographical entities.
65−--
66−-- In some situations, the 'noRegion' value corresponds to a default or
67−-- unspecified region.
68newtype Region = Region ByteString
63newtype Region = Region ByteString
69 deriving newtype (Eq, Ord, Show, Read, IsString, NFData)
64 deriving newtype (Eq, Ord, Show, Read, IsString, NFData)
70 deriving stock (Data, Generic)
65 deriving stock (Data, Generic)
71
66
72−-- | A default or unspecified region, the user-assigned alpha-2 code @\"ZZ\"@
67+-- | A "region" corresponding to non-geographical entities. The library
73−noRegion :: Region
68+-- internally uses the UN M.49 code @"001"@ (meaning the world) for this.
74−noRegion = "ZZ"
69+data NonGeoRegion = Region001
70+ deriving stock (Eq, Ord, Show, Read, Data, Generic)
71+ deriving anyclass (NFData)
72+
73+filter001 :: ByteString -> Either NonGeoRegion Region
74+filter001 region
75+ | region == "001" = Left Region001
76+ | otherwise = Right $ Region region
77+
78+filter001ZZ :: ByteString -> Maybe (Either NonGeoRegion Region)
79+filter001ZZ region
80+ | region == "ZZ" = Nothing
81+ | region == "001" = Just $ Left Region001
82+ | otherwise = Just $ Right $ Region region
83+
84+filter0 :: Int -> Maybe CountryCode
85+filter0 0 = Nothing
86+filter0 cc = Just $ CountryCode cc
75
87
76-- | All geographical regions the library has metadata for
88-- | All geographical regions the library has metadata for
77supportedRegions :: S.Set Region
89supportedRegions :: S.Set Region
@@ -81,8 +93,8 @@ supportedRegions = S.fromList $ unsafeDupablePerformIO $ mask_ $
81-- | All global network calling codes (country calling codes for
93-- | All global network calling codes (country calling codes for
82-- non-geographical entities) the library has metadata for
94-- non-geographical entities) the library has metadata for
83supportedGlobalNetworkCallingCodes :: S.Set CountryCode
95supportedGlobalNetworkCallingCodes :: S.Set CountryCode
84−supportedGlobalNetworkCallingCodes = S.fromList $ unsafeDupablePerformIO $ mask_ $
96+supportedGlobalNetworkCallingCodes = S.fromList $ map (CountryCode . fromIntegral) $
85− map (CountryCode . fromIntegral) <$>
97+ unsafeDupablePerformIO $ mask_ $
86 c_phone_number_util_get_supported_global_network_calling_codes
98 c_phone_number_util_get_supported_global_network_calling_codes
87
99
88-- | All country calling codes the library has metadata for, covering both
100-- | All country calling codes the library has metadata for, covering both
@@ -90,8 +102,8 @@ supportedGlobalNetworkCallingCodes = S.fromList $ unsafeDupablePerformIO $ mask_
90-- geographical entities. This could be used to populate a drop-down box of
102-- geographical entities. This could be used to populate a drop-down box of
91-- country calling codes for a phone-number widget, for instance.
103-- country calling codes for a phone-number widget, for instance.
92supportedCallingCodes :: S.Set CountryCode
104supportedCallingCodes :: S.Set CountryCode
93−supportedCallingCodes = S.fromList $ unsafeDupablePerformIO $ mask_ $
94− map (CountryCode . fromIntegral) <$>
105+supportedCallingCodes = S.fromList $ map (CountryCode . fromIntegral) $
106+ unsafeDupablePerformIO $ mask_ $
95 c_phone_number_util_get_supported_calling_codes
107 c_phone_number_util_get_supported_calling_codes
96
108
97-- | Returns the types for a given region which the library has metadata for.
109-- | Returns the types for a given region which the library has metadata for.
@@ -101,8 +113,9 @@ supportedCallingCodes = S.fromList $ unsafeDupablePerformIO $ mask_ $
101--
113--
102-- No types will be returned for invalid or unknown region codes.
114-- No types will be returned for invalid or unknown region codes.
103supportedTypesForRegion :: Region -> S.Set PhoneNumberType
115supportedTypesForRegion :: Region -> S.Set PhoneNumberType
104−supportedTypesForRegion (Region region) = S.fromList $ unsafeDupablePerformIO $ mask_ $
116+supportedTypesForRegion (Region region) = S.fromList $
117+ unsafeDupablePerformIO $ mask_ $
105− c_phone_number_util_get_supported_types_for_region region
118+ c_phone_number_util_get_supported_types_for_region region
106
119
107-- | Returns the types for a country-code belonging to a non-geographical entity
120-- | Returns the types for a country-code belonging to a non-geographical entity
108-- which the library has metadata for. Will not include 'FixedLineOrMobile' (if
121-- which the library has metadata for. Will not include 'FixedLineOrMobile' (if
@@ -113,8 +126,9 @@ supportedTypesForRegion (Region region) = S.fromList $ unsafeDupablePerformIO $
113-- No types will be returned for country calling codes that do not map to a
126-- No types will be returned for country calling codes that do not map to a
114-- known non-geographical entity.
127-- known non-geographical entity.
115supportedTypesForNonGeoEntity :: CountryCode -> S.Set PhoneNumberType
128supportedTypesForNonGeoEntity :: CountryCode -> S.Set PhoneNumberType
116−supportedTypesForNonGeoEntity (CountryCode cc) = S.fromList $ unsafeDupablePerformIO $ mask_ $
129+supportedTypesForNonGeoEntity (CountryCode cc) = S.fromList $
130+ unsafeDupablePerformIO $ mask_ $
117− c_phone_number_util_get_supported_types_for_non_geo_entity $ fromIntegral cc
131+ c_phone_number_util_get_supported_types_for_non_geo_entity $ fromIntegral cc
118
132
119-- | Returns true if the number is a valid vanity (alpha) number such as
133-- | Returns true if the number is a valid vanity (alpha) number such as
120-- @"800 MICROSOFT"@. A valid vanity number will start with at least 3 digits
134-- @"800 MICROSOFT"@. A valid vanity number will start with at least 3 digits
@@ -275,24 +289,27 @@ numberType pn = unsafeDupablePerformIO $
275-- @+41 (0) 78 927 2696@ can be parsed into a number with country code @"41"@
289-- @+41 (0) 78 927 2696@ can be parsed into a number with country code @"41"@
276-- and National Significant Number @"789272696"@. This is valid, while the
290-- and National Significant Number @"789272696"@. This is valid, while the
277-- original string is not dialable.
291-- original string is not dialable.
278−isValidNumber :: Maybe Region -> PhoneNumber -> Bool
292+isValidNumber :: Maybe (Either NonGeoRegion Region) -> PhoneNumber -> Bool
279isValidNumber Nothing pn = unsafeDupablePerformIO $
293isValidNumber Nothing pn = unsafeDupablePerformIO $
280 c_phone_number_util_is_valid_number pn
294 c_phone_number_util_is_valid_number pn
295+isValidNumber (Just (Left Region001)) pn = unsafeDupablePerformIO $
296+ c_phone_number_util_is_valid_number_for_region pn "001"
281−isValidNumber (Just (Region region)) pn = unsafeDupablePerformIO $
297+isValidNumber (Just (Right (Region region))) pn = unsafeDupablePerformIO $
282 c_phone_number_util_is_valid_number_for_region pn region
298 c_phone_number_util_is_valid_number_for_region pn region
283
299
284-- | Returns the region where a phone number is from. This could be used for
300-- | Returns the region where a phone number is from. This could be used for
285-- geocoding at the region level. Only guarantees correct results for valid,
301-- geocoding at the region level. Only guarantees correct results for valid,
286-- full numbers (not short-codes, or invalid numbers).
302-- full numbers (not short-codes, or invalid numbers).
287−regionForNumber :: PhoneNumber -> Region
303+regionForNumber :: PhoneNumber -> Maybe (Either NonGeoRegion Region)
304+regionForNumber pn = filter001ZZ $
288−regionForNumber pn = unsafeDupablePerformIO $ mask_ $
305+ unsafeDupablePerformIO $ mask_ $
289− coerce $ c_phone_number_util_get_region_code_for_number pn
306+ c_phone_number_util_get_region_code_for_number pn
290
307
291-- | Returns the country calling code for a specific region. For example, this
308-- | Returns the country calling code for a specific region. For example, this
292-- would be @1@ for the United States, and @64@ for New Zealand.
309-- would be @1@ for the United States, and @64@ for New Zealand.
293−countryCodeForRegion :: Region -> CountryCode
310+countryCodeForRegion :: Region -> Maybe CountryCode
294−countryCodeForRegion (Region region) = unsafeDupablePerformIO $
311+countryCodeForRegion (Region region) = filter0 $ fromIntegral $
295− CountryCode . fromIntegral <$>
312+ unsafeDupablePerformIO $
296 c_phone_number_util_get_country_code_for_region region
313 c_phone_number_util_get_country_code_for_region region
297
314
298-- | Returns the region code that matches the specific country code. Note that
315-- | Returns the region code that matches the specific country code. Note that
@@ -300,18 +317,19 @@ countryCodeForRegion (Region region) = unsafeDupablePerformIO $
300-- (e.g. US and Canada), and in that case, only one of the regions (normally the
317-- (e.g. US and Canada), and in that case, only one of the regions (normally the
301-- one with the largest population) is returned. If the country calling code
318-- one with the largest population) is returned. If the country calling code
302-- entered is valid but doesn't match a specific region (such as in the case of
319-- entered is valid but doesn't match a specific region (such as in the case of
303−-- non-geographical calling codes like @800@) the @'Region' "001"@ will be
320+-- non-geographical calling codes like @800@) @'Region001'@ will be returned.
304−-- returned.
305−regionForCountryCode :: CountryCode -> Region
321+regionForCountryCode :: CountryCode -> Maybe (Either NonGeoRegion Region)
306−regionForCountryCode (CountryCode cc) = unsafeDupablePerformIO $ mask_ $
322+regionForCountryCode (CountryCode cc) = filter001ZZ $
323+ unsafeDupablePerformIO $ mask_ $
307− coerce $ c_phone_number_util_get_region_code_for_country_code $ fromIntegral cc
324+ c_phone_number_util_get_region_code_for_country_code $ fromIntegral cc
308
325
309-- | Returns a list of the region codes that match the specific country calling
326-- | Returns a list of the region codes that match the specific country calling
310−-- code. For non-geographical country calling codes, the region code @"001"@ is
327+-- code. For non-geographical country calling codes, 'Region001' is returned.
311−-- returned. Also, in the case of no region code being found, the list is empty.
328+-- Also, in the case of no region code being found, the list is empty.
312−regionsForCountryCode :: CountryCode -> [Region]
329+regionsForCountryCode :: CountryCode -> [Either NonGeoRegion Region]
313−regionsForCountryCode (CountryCode cc) = unsafeDupablePerformIO $ mask_ $
330+regionsForCountryCode (CountryCode cc) = map filter001 $
331+ unsafeDupablePerformIO $ mask_ $
314− coerce $ c_phone_number_util_get_region_codes_for_country_calling_code $ fromIntegral cc
332+ c_phone_number_util_get_region_codes_for_country_calling_code $ fromIntegral cc
315
333
316-- | Checks if this is a region under the North American Numbering Plan
334-- | Checks if this is a region under the North American Numbering Plan
317-- Administration (NANPA).
335-- Administration (NANPA).
@@ -353,12 +371,12 @@ nddPrefixForRegion strip (Region region) = unsafeDupablePerformIO $ mask_ $
353-- 3. For some numbers (particularly fixed-line), many regions have the concept
371-- 3. For some numbers (particularly fixed-line), many regions have the concept
354-- of area code, which together with subscriber number constitute the National
372-- of area code, which together with subscriber number constitute the National
355-- Significant Number. It is sometimes okay to dial only the subscriber number
373-- Significant Number. It is sometimes okay to dial only the subscriber number
356−-- when dialing in the same area. This function will return IsPossibleLocalOnly
374+-- when dialing in the same area. This function will return
357−-- if the subscriber-number-only version is passed in. On the other hand,
375+-- 'IsPossibleLocalOnly' if the subscriber-number-only version is passed in. On
358−-- because 'isValidNumber' validates using information on both starting digits
376+-- the other hand, because 'isValidNumber' validates using information on both
359−-- (for fixed line numbers, that would most likely be area codes) and length
377+-- starting digits (for fixed line numbers, that would most likely be area
360−-- (obviously includes the length of area codes for fixed line numbers), it will
378+-- codes) and length (obviously includes the length of area codes for fixed line
361−-- return 'False' for the subscriber-number-only version.
379+-- numbers), it will return 'False' for the subscriber-number-only version.
362possibleNumber :: PhoneNumberType -> PhoneNumber -> ValidationResult
380possibleNumber :: PhoneNumberType -> PhoneNumber -> ValidationResult
363possibleNumber ntype pn = unsafeDupablePerformIO $
381possibleNumber ntype pn = unsafeDupablePerformIO $
364 c_phone_number_util_is_possible_number_for_type_with_reason pn ntype
382 c_phone_number_util_is_possible_number_for_type_with_reason pn ntype
@@ -427,20 +445,20 @@ data ParseMode
427-- 'isValidNumber'.
445-- 'isValidNumber'.
428--
446--
429-- Returns an error if the string is not considered to be a viable phone number
447-- Returns an error if the string is not considered to be a viable phone number
430−-- (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
431-- number is not in international format (does not start with @\'+\'@).
449-- number is not in international format (does not start with @\'+\'@).
432parseNumber
450parseNumber
433 :: ParseMode
451 :: ParseMode
434− -> Region -- ^ Default region, the country that we are expecting the number to
452+ -> Maybe Region -- ^ Default region, the country that we are expecting the
435− -- be dialed from, which affects national and international dialing
453+ -- number to be dialed from, which affects national and international
436− -- prefixes. This is only used if the number being parsed is not written in
454+ -- dialing prefixes. This is only used if the number being parsed is not
437− -- international format. In such cases the 'countryCode' of the number would
455+ -- written in international format. In such cases the 'countryCode' of the
438− -- be that of the default region supplied. If the number is guaranteed to
456+ -- number would be that of the default region supplied. If the number is
439− -- start with a @\'+\'@ followed by the country calling code, then
457+ -- guaranteed to start with a @\'+\'@ followed by the country calling code,
440− -- 'noRegion' can be supplied.
458+ -- then this can be omitted.
441 -> ByteString -- ^ Input.
459 -> ByteString -- ^ Input.
442 -> Either ErrorType PhoneNumber
460 -> Either ErrorType PhoneNumber
443−parseNumber mode (Region region) number = case err of
461+parseNumber mode mRegion number = case err of
444 I.NoParsingError -> Right pn
462 I.NoParsingError -> Right pn
445 I.InvalidCountryCodeError -> Left InvalidCountryCodeError
463 I.InvalidCountryCodeError -> Left InvalidCountryCodeError
446 I.NotANumber -> Left NotANumber
464 I.NotANumber -> Left NotANumber
@@ -448,6 +466,9 @@ parseNumber mode (Region region) number = case err of
448 I.TooShortNsn -> Left TooShortNsn
466 I.TooShortNsn -> Left TooShortNsn
449 I.TooLongNsn -> Left TooLongNsn
467 I.TooLongNsn -> Left TooLongNsn
450 where
468 where
469+ region = case mRegion of
470+ Nothing -> "ZZ"
471+ Just (Region reg) -> reg
451 (err, pn) = case mode of
472 (err, pn) = case mode of
452 Canonicalize -> unsafeDupablePerformIO $ mask_ $
473 Canonicalize -> unsafeDupablePerformIO $ mask_ $
453 c_phone_number_util_parse number region
474 c_phone_number_util_parse number region
test/Leak.hs+6−6
@@ -74,7 +74,7 @@ main = do
74 carrierCode = "10"
74 carrierCode = "10"
75 nonGeoCode = 800
75 nonGeoCode = 800
76 phoneString = "+41 44 668 1800"
76 phoneString = "+41 44 668 1800"
77− Right phoneNumber = parseNumber KeepRawInput noRegion phoneString
77+ Right phoneNumber = parseNumber KeepRawInput Nothing phoneString
78 hammer iter (\(PhoneNumber a b c d e f g h) -> PhoneNumber a b c d e f g h) phoneNumber
78 hammer iter (\(PhoneNumber a b c d e f g h) -> PhoneNumber a b c d e f g h) phoneNumber
79 hammer iter supportedTypesForRegion region
79 hammer iter supportedTypesForRegion region
80 hammer iter supportedTypesForNonGeoEntity nonGeoCode
80 hammer iter supportedTypesForNonGeoEntity nonGeoCode
@@ -96,7 +96,7 @@ main = do
96 hammer iter (formatNumber (OutOfCountry region False)) phoneNumber
96 hammer iter (formatNumber (OutOfCountry region False)) phoneNumber
97 hammer iter truncateTooLongNumber phoneNumber
97 hammer iter truncateTooLongNumber phoneNumber
98 hammer iter numberType phoneNumber
98 hammer iter numberType phoneNumber
99− hammer iter (isValidNumber (Just region)) phoneNumber
99+ hammer iter (isValidNumber (Just (Right region))) phoneNumber
100 hammer iter regionForNumber phoneNumber
100 hammer iter regionForNumber phoneNumber
101 hammer iter countryCodeForRegion region
101 hammer iter countryCodeForRegion region
102 hammer iter regionForCountryCode countryCallingCode
102 hammer iter regionForCountryCode countryCallingCode
@@ -107,10 +107,10 @@ main = do
107 hammer iter canBeInternationallyDialed phoneNumber
107 hammer iter canBeInternationallyDialed phoneNumber
108 hammer iter isGeographicalNumber phoneNumber
108 hammer iter isGeographicalNumber phoneNumber
109 hammer iter (isGeographicalNumberType FixedLine) countryCallingCode
109 hammer iter (isGeographicalNumberType FixedLine) countryCallingCode
110− hammer iter (parseNumber Canonicalize region) phoneString
110+ hammer iter (parseNumber Canonicalize (Just region)) phoneString
111− hammer iter (parseNumber Canonicalize region) ""
111+ hammer iter (parseNumber Canonicalize (Just region)) ""
112− hammer iter (parseNumber KeepRawInput region) phoneString
112+ hammer iter (parseNumber KeepRawInput (Just region)) phoneString
113− hammer iter (parseNumber KeepRawInput region) ""
113+ hammer iter (parseNumber KeepRawInput (Just region)) ""
114 hammer iter (matchNumbers (Left phoneString)) (Left phoneString)
114 hammer iter (matchNumbers (Left phoneString)) (Left phoneString)
115 hammer iter (matchNumbers (Left phoneString)) (Right phoneNumber)
115 hammer iter (matchNumbers (Left phoneString)) (Right phoneNumber)
116 hammer iter (matchNumbers (Right phoneNumber)) (Left phoneString)
116 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