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

Conversation

aazhevsky
Copy link

@aazhevsky aazhevsky commented Nov 24, 2023

Hello,

These changes will fix the Wrong result obtained from local number #46 issue.

After applying the changes, the following local US phone numbers are parsed successfully:

(888) 555-5512
(408) 555-5270
(707) 555-1854

All the interfaces are kept, as well as all tests passed. The major problem is that the code of the fix is a bit clumsy. At best, CountryCodeParser.extractCountryCode should return an optional, as PhoneParser._findDestinationMetadata itself.

I consciously stopped on as it is, not to spread the changes over the whole project.

@cedvdb
Copy link
Owner

cedvdb commented Nov 24, 2023

Hey, I will take a look this weekend. I agree it is a bit clumsy.

test/phone_number_test.dart Outdated Show resolved Hide resolved
}

// 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?

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.

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.

@aazhevsky
Copy link
Author

Thanks a lot!

Thank you for your efforts!

Could you also add an entry to the changelog?

Please clarify what I have to do.

 - MetadataMatcher.getMatchUsingPatternsStrict renamed to MetadataMatcher.getMatchUsingPatterns
Copy link
Owner

@cedvdb cedvdb left a comment

Choose a reason for hiding this comment

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

I'm still trying to wrap my head around some of the changes. My main question point is line 113, the last comment I added.

What would really help is if you could give a few example phone numbers in the comments related to some of the big logic blocks you added in find metadata.

Phone parsing can be difficult (but fun).

if (countryCode != null) {
metadatas = MetadataFinder.getMetadatasForCountryCode(countryCode);
final national = CountryCodeParser.removeCountryCode(phoneWithoutExitCode, countryCode);
final metadataMatch = MetadataMatcher.getMatchUsingPatterns(national, metadatas);
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.

getMatchUsingPatternsStrict checks that the metadata matches the phone number and and the phone number is valid. Why do we need to check that it is valid if there is a match ? It could be an incomplete phone number for example. Wouldn't that negatively affect that scenario ?

Copy link
Author

Choose a reason for hiding this comment

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

For example, while trying to process local US numbers like (707) 555-1854 without an exit code, the country code was resolved as 70, but the number is not valid for IsoCode.RU. I added the strict match option to determine if the number truly belongs to the found region.

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

List<PhoneMetadata>? metadatas;

try {
countryCode = CountryCodeParser.extractCountryCode(phoneWithoutExitCode);
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);
    }

@cedvdb
Copy link
Owner

cedvdb commented Nov 29, 2023

Hello again, sorry for the back and forth, I somewhat misunderstood your issue at first. I believe the part of the code that should be fixed for your actual issue is not exactly the part that you fixed. Correct me if I'm wrong:

This was your issue:

        expect(
            PhoneNumber.parse('(707) 555-1854', callerCountry: IsoCode.US)
              .international,
            equals('+17075551854'));

The actual culprit is this line https://github.com/cedvdb/phone_numbers_parser/blob/dev/lib/src/parsers/phone_parser.dart#L102

    if (callerMetadata != null &&
        callerNationalPrefix != null &&
        phoneWithoutExitCode.startsWith(callerNationalPrefix)) {   // will be false because it is **local** (same area in us)
      return callerMetadata;
    }

where you'd want to enter this condition. You do not enter this because the phone number is in local form. Therefor the condition: phoneWithoutExitCode.startsWith(callerNationalPrefix)) is wrong or incomplete. It should probably be phoneWithoutExitCode.startsWith(callerNationalPrefix)) || checkThatPhoneNumberIsValidForCallerMetadata())

Am I wrong that this would be the only change required to fix your issue ?

@aazhevsky
Copy link
Author

aazhevsky commented Nov 30, 2023

Hello again, sorry for the back and forth, I somewhat misunderstood your issue at first. I believe the part of the code that should be fixed for your actual issue is not exactly the part that you fixed. Correct me if I'm wrong:

This was your issue:

        expect(
            PhoneNumber.parse('(707) 555-1854', callerCountry: IsoCode.US)
              .international,
            equals('+17075551854'));

The actual culprit is this line https://github.com/cedvdb/phone_numbers_parser/blob/dev/lib/src/parsers/phone_parser.dart#L102

    if (callerMetadata != null &&
        callerNationalPrefix != null &&
        phoneWithoutExitCode.startsWith(callerNationalPrefix)) {   // will be false because it is **local** (same area in us)
      return callerMetadata;
    }

where you'd want to enter this condition. You do not enter this because the phone number is in local form. Therefor the condition: phoneWithoutExitCode.startsWith(callerNationalPrefix)) is wrong or incomplete. It should probably be phoneWithoutExitCode.startsWith(callerNationalPrefix)) || checkThatPhoneNumberIsValidForCallerMetadata())

Am I wrong that this would be the only change required to fix your issue ?

Potentially, yes, but when I replace the block

    if (callerMetadata != null &&
        callerNationalPrefix != null &&
        phoneWithoutExitCode.startsWith(callerNationalPrefix)) {   // will be false because it is **local** (same area in us)
      return callerMetadata;
    }

with

    if (callerMetadata != null &&        
        ((callerNationalPrefix != null && phoneWithoutExitCode.startsWith(callerNationalPrefix)) || 
        Validator.validateWithPattern(PhoneNumber(nsn: phoneWithoutExitCode, isoCode: callerMetadata.isoCode)))) {
      return callerMetadata;
    }

in the initial version of the library, the tests fail on

expect(
            PhoneNumber.parse('+33 655 5705 76', callerCountry: IsoCode.DE)
                .international,
            equals('+33655570576'));

However, the tests I added perform well. I think an exit code check should be mandatory in this method.

@cedvdb
Copy link
Owner

cedvdb commented Dec 7, 2023

I've been away the past few days. I'll have a look into this tomorrow to see what I can do.

@cedvdb
Copy link
Owner

cedvdb commented Dec 7, 2023

I refactored the code with some of your input in PR #50

I fixed the issue in PR #51

Thanks a lot for the help in investigating this

@cedvdb cedvdb closed this Dec 7, 2023
@aazhevsky
Copy link
Author

I see it was a complex solution. Many thanks for your efforts!

@cedvdb
Copy link
Owner

cedvdb commented Dec 7, 2023

@aazhevsky the solution itself was 3 lines (there is a function rename that should not be part of the PR), the refactor took a bit longer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants