Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #46 (Wrong result from local number) fix #47

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ build/

# Directory created by dartdoc
doc/api/

# macOS files
.DS_Store

5 changes: 5 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,9 @@ void main(List<String> arguments) {
two - 1 == one;
final another = one + 2;
print('$another == $three');

final usLocalNumber = PhoneNumber.parse("(707) 555-1854", callerCountry: IsoCode.US);
cedvdb marked this conversation as resolved.
Show resolved Hide resolved
final usIntrnNumber = PhoneNumber.parse("+1 (707) 555-1854");

print("US local number $usLocalNumber equals US international number $usIntrnNumber - ${usLocalNumber == usIntrnNumber}"); // true
}
15 changes: 11 additions & 4 deletions lib/src/metadata/metadata_matcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import 'package:phone_numbers_parser/src/parsers/_validator.dart';
import 'models/phone_metadata.dart';

abstract class MetadataMatcher {
static PhoneMetadata getMatchUsingPatterns(

static PhoneMetadata? getMatchUsingPatternsStrict(
String nationalNumber,
List<PhoneMetadata> potentialFits,
List<PhoneMetadata> potentialFits
) {
if (potentialFits.length == 1) return potentialFits[0];
potentialFits = reducePotentialMetadatasFits(nationalNumber, potentialFits);
for (var fit in potentialFits) {
final isValidForIso = Validator.validateWithPattern(
Expand All @@ -17,7 +17,14 @@ abstract class MetadataMatcher {
return fit;
}
}
return potentialFits[0];
return null;
}

static PhoneMetadata getMatchUsingPatterns(
Copy link
Owner

@cedvdb cedvdb Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not seem to be used anywhere anymore. Therefor this can be removed and the "Strict" can be removed from the new one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getMatchUsingPatternsStrict was introduced to avoid duplicated code in getMatchUsingPatterns. As I wrote, I tried to localize changes maximally, but I suggest redesigning interfaces to avoid exceptions and non-valid metadata matches.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You misunderstood me. It seems like getMatchUsingPatterns is not used anywhere in the code base except here. It can safely be removed. When getMatchUsingPatterns is removed we will only have getMatchUsingPatternsStrict which can be renamed to getMatchUsingPatterns

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but I suggest redesigning interfaces to avoid exceptions and non-valid metadata matches.

MetadataMatcher already returns a nullable. That discussion could be made for the country code parser though

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

String nationalNumber,
List<PhoneMetadata> potentialFits,
) {
return getMatchUsingPatternsStrict(nationalNumber, potentialFits) ?? potentialFits[0];
}

/// Given a list of metadata fits, return the ones that fit a national number
Expand Down
57 changes: 47 additions & 10 deletions lib/src/parsers/phone_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ abstract class PhoneParser {
final containsExitCode = withoutExitCode.length != phoneNumber.length;
// if no destination metadata was provided we have to find it,
destinationMetadata ??= _findDestinationMetadata(
phoneHadExitCode: containsExitCode,
phoneWithoutExitCode: withoutExitCode,
callerMetadata: callerMetadata,
);
Expand Down Expand Up @@ -91,6 +92,7 @@ abstract class PhoneParser {
// find destination of a normalized phone number, which supposedly
// starts with a country code.
static PhoneMetadata _findDestinationMetadata({
required bool phoneHadExitCode,
required String phoneWithoutExitCode,
required PhoneMetadata? callerMetadata,
}) {
Expand All @@ -102,15 +104,50 @@ abstract class PhoneParser {
phoneWithoutExitCode.startsWith(callerNationalPrefix)) {
return callerMetadata;
}
// if no caller was provided we need to make a best guess given the country code
final countryCode =
CountryCodeParser.extractCountryCode(phoneWithoutExitCode);
final national =
CountryCodeParser.removeCountryCode(phoneWithoutExitCode, countryCode);
// multiple countries use the same country code
final metadatas = MetadataFinder.getMetadatasForCountryCode(countryCode);
// if multiple countries share the same country code, patterns on the national number
// is used to find the correct country.
return MetadataMatcher.getMatchUsingPatterns(national, metadatas);

String? countryCode;
Object? countryCodeException;
List<PhoneMetadata>? metadatas;

try {
countryCode = CountryCodeParser.extractCountryCode(phoneWithoutExitCode);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there is something unsound here:

Currently the code in the parser assumes that if there was no exit code, then the next part is not a country code. (line 60)

    // if there was no exit code then we assume we are dealing with a national number
    if (containsExitCode) {
      national = CountryCodeParser.removeCountryCode(
        withoutExitCode,
        destinationMetadata.countryCode,
      );
    }

But here this assumption is not taken into account.

However, some people want to be able to parse phone number starting with a country code without the exit code see: #39 . But I believe that's outside the scope of this PR

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any tests that cover this case?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any tests that cover this case?

I suggest not handling this case in this PR.

There is a list of test in the issue that I believe would not pass currently without the leading '+'. That is:

var expected = '+64211234567';
expect(sanitizePhoneNumber('640211234567', countryCode: IsoCode.NZ), expected);
expect(sanitizePhoneNumber('64211234567', countryCode: IsoCode.NZ), expected);

This is outside the scope of this Pull request though.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.

Copy link
Owner

@cedvdb cedvdb Nov 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain to me why don't we do (as per my first comment)

    // if there was an exit code then we can safely assume that the next part is a country code. If no country code is found then it is invalid.
    if (phoneHadExitCode) {  
     countryCode = CountryCodeParser.extractCountryCode(phoneWithoutExitCode);
    }

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Earlier, there was no difference in whether the phone had an exit code or not; the parser tried to interpret the first digits as country code. So, my changes were an attempt to preserve the current way to process input.

} catch(e) {
countryCodeException = e;
}

if (countryCode != null) {
metadatas = MetadataFinder.getMetadatasForCountryCode(countryCode);
final national = CountryCodeParser.removeCountryCode(phoneWithoutExitCode, countryCode);
final metadataMatch = MetadataMatcher.getMatchUsingPatternsStrict(national, metadatas);

if (phoneHadExitCode) {
// 1.
// if phone number has the exit code, we should use it
// despite caller's metadata and phone number validity
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is ambiguous. What is "it" in "we should use => it <=". And why "phone number validity", as metadatas you return already check validity ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a part of handling a partial input that starts with an exit code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated the comment

return metadataMatch ?? metadatas.first;
}

// 2.
// if number has no exit code. Try to figure out if it
// starts with a country code
if (metadataMatch != null) {
return metadataMatch;
}
}

// 3.
// If any direct match was't found, use provided callerMetadata
if (callerMetadata != null) {
return callerMetadata;
}

if (metadatas != null) {
// 4.
// The last resort. Use any of found metadatas ignoring validity
return metadatas.first;
}

// rethrow previously got error
throw countryCodeException!;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's not use the !

Copy link
Author

@aazhevsky aazhevsky Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is not possible because it leads to compilation issues. This can be avoided with the transformation CountryCodeParser.extractCountryCode and _findDestinationMetadata to return an optional, which causes to more remarkable code changes.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like you were talking about the country_code_parser interface that throws when the country code is not found. Which could instead return a String?. That's a fair observation and I'm okay with that. At first glance this seems to be a small change but it'd be better if it was in a separate PR imo, just to make it easy to review.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. What do you think is the best to do in this case?

}
}
18 changes: 18 additions & 0 deletions test/phone_number_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,24 @@ void main() {
equals('499999999'));
// expect()
});

test(
'should resolve local numbers w/o national prefix as they belong to callerCountry',
() {
expect(
PhoneNumber.parse('(888) 555-5512', callerCountry: IsoCode.US)
.international,
equals('+18885555512'));
expect(
PhoneNumber.parse('(555) 522-8243', callerCountry: IsoCode.US)
.international,
equals('+15555228243'));
expect(
PhoneNumber.parse('(707) 555-1854', callerCountry: IsoCode.US)
.international,
equals('+17075551854'));
// expect()
cedvdb marked this conversation as resolved.
Show resolved Hide resolved
});
});

group('Validity', () {
Expand Down
Loading