-
Notifications
You must be signed in to change notification settings - Fork 152
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Jeff Lai
committed
Jun 7, 2022
1 parent
985507a
commit aeb9e22
Showing
6 changed files
with
307 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
package phonenumbers | ||
|
||
import ( | ||
proto "github.com/golang/protobuf/proto" | ||
) | ||
|
||
var ( | ||
shortNumberRegionToMetadataMap = make(map[string]*PhoneMetadata) | ||
) | ||
|
||
func readFromShortNumberRegionToMetadataMap(key string) (*PhoneMetadata, bool) { | ||
v, ok := shortNumberRegionToMetadataMap[key] | ||
return v, ok | ||
} | ||
|
||
func writeToShortNumberRegionToMetadataMap(key string, val *PhoneMetadata) { | ||
shortNumberRegionToMetadataMap[key] = val | ||
} | ||
|
||
func init() { | ||
err := loadShortNumberMetadataFromFile() | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
var ( | ||
currShortNumberMetadataColl *PhoneMetadataCollection | ||
shortNumberReloadMetadata = true | ||
) | ||
|
||
func ShortNumberMetadataCollection() (*PhoneMetadataCollection, error) { | ||
if !shortNumberReloadMetadata { | ||
return currShortNumberMetadataColl, nil | ||
} | ||
|
||
rawBytes, err := decodeUnzipString(shortNumberMetadataData) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
metadataCollection := &PhoneMetadataCollection{} | ||
err = proto.Unmarshal(rawBytes, metadataCollection) | ||
shortNumberReloadMetadata = false | ||
return metadataCollection, err | ||
} | ||
|
||
func loadShortNumberMetadataFromFile() error { | ||
metadataCollection, err := ShortNumberMetadataCollection() | ||
if err != nil { | ||
return err | ||
} else if currShortNumberMetadataColl == nil { | ||
currShortNumberMetadataColl = metadataCollection | ||
} | ||
|
||
metadataList := metadataCollection.GetMetadata() | ||
if len(metadataList) == 0 { | ||
return ErrEmptyMetadata | ||
} | ||
|
||
for _, meta := range metadataList { | ||
region := meta.GetId() | ||
if region == "001" { | ||
// it's a non geographical entity, unused | ||
} else { | ||
writeToShortNumberRegionToMetadataMap(region, meta) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// Check whether a short number is a possible number. If a country calling code is shared by | ||
// multiple regions, this returns true if it's possible in any of them. This provides a more | ||
// lenient check than #isValidShortNumber. | ||
// See IsPossibleShortNumberForRegion(PhoneNumber, string) for details. | ||
func IsPossibleShortNumber(number PhoneNumber) bool { | ||
regionsCodes := GetRegionCodesForCountryCode(int(number.GetCountryCode())) | ||
shortNumberLength := len(GetNationalSignificantNumber(&number)) | ||
for _, region := range regionsCodes { | ||
phoneMetadata := getShortNumberMetadataForRegion(region) | ||
if phoneMetadata == nil { | ||
continue | ||
} | ||
if phoneMetadata.GeneralDesc.hasPossibleLength(int32(shortNumberLength)) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// Check whether a short number is a possible number when dialed from the given region. This | ||
// provides a more lenient check than IsValidShortNumberForRegion. | ||
func IsPossibleShortNumberForRegion(number PhoneNumber, regionDialingFrom string) bool { | ||
if !regionDialingFromMatchesNumber(number, regionDialingFrom) { | ||
return false | ||
} | ||
phoneMetadata := getShortNumberMetadataForRegion(regionDialingFrom) | ||
if phoneMetadata == nil { | ||
return false | ||
} | ||
numberLength := len(GetNationalSignificantNumber(&number)) | ||
return phoneMetadata.GeneralDesc.hasPossibleLength(int32(numberLength)) | ||
} | ||
|
||
// Tests whether a short number matches a valid pattern. If a country calling code is shared by | ||
// multiple regions, this returns true if it's valid in any of them. Note that this doesn't verify | ||
// the number is actually in use, which is impossible to tell by just looking at the number | ||
// itself. See IsValidShortNumberForRegion(PhoneNumber, String) for details. | ||
func IsValidShortNumber(number PhoneNumber) bool { | ||
regionCodes := GetRegionCodesForCountryCode(int(number.GetCountryCode())) | ||
regionCode := getRegionCodeForShortNumberFromRegionList(number, regionCodes) | ||
if len(regionCodes) > 1 && regionCode != "" { | ||
// If a matching region had been found for the phone number from among two or more regions, | ||
// then we have already implicitly verified its validity for that region. | ||
return true | ||
} | ||
return IsValidShortNumberForRegion(number, regionCode) | ||
} | ||
|
||
// Tests whether a short number matches a valid pattern in a region. Note that this doesn't verify | ||
// the number is actually in use, which is impossible to tell by just looking at the number itself. | ||
func IsValidShortNumberForRegion(number PhoneNumber, regionDialingFrom string) bool { | ||
if !regionDialingFromMatchesNumber(number, regionDialingFrom) { | ||
return false | ||
} | ||
phoneMetadata := getShortNumberMetadataForRegion(regionDialingFrom) | ||
if phoneMetadata == nil { | ||
return false | ||
} | ||
shortNumber := GetNationalSignificantNumber(&number) | ||
generalDesc := phoneMetadata.GeneralDesc | ||
if !matchesPossibleNumberAndNationalNumber(shortNumber, generalDesc) { | ||
return false | ||
} | ||
shortNumberDesc := phoneMetadata.GetShortCode() | ||
return matchesPossibleNumberAndNationalNumber(shortNumber, shortNumberDesc) | ||
} | ||
|
||
func getShortNumberMetadataForRegion(regionCode string) *PhoneMetadata { | ||
val, _ := readFromShortNumberRegionToMetadataMap(regionCode) | ||
return val | ||
} | ||
|
||
func getRegionCodeForShortNumberFromRegionList(number PhoneNumber, regionCodes []string) string { | ||
if len(regionCodes) == 0 { | ||
return "" | ||
} | ||
if len(regionCodes) == 1 { | ||
return regionCodes[0] | ||
} | ||
nationalNumber := GetNationalSignificantNumber(&number) | ||
for _, regionCode := range regionCodes { | ||
phoneMetadata := getShortNumberMetadataForRegion(regionCode) | ||
if phoneMetadata != nil && matchesPossibleNumberAndNationalNumber(nationalNumber, phoneMetadata.GetShortCode()) { | ||
// The number is valid for this region. | ||
return regionCode | ||
} | ||
} | ||
return "" | ||
} | ||
|
||
// Helper method to check that the country calling code of the number matches the region it's | ||
// being dialed from. | ||
func regionDialingFromMatchesNumber(number PhoneNumber, regionDialingFrom string) bool { | ||
regionCodes := GetRegionCodesForCountryCode(int(number.GetCountryCode())) | ||
for _, region := range regionCodes { | ||
if region == regionDialingFrom { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// TODO: Once we have benchmarked ShortNumberInfo, consider if it is worth keeping | ||
// this performance optimization. | ||
func matchesPossibleNumberAndNationalNumber(number string, numberDesc *PhoneNumberDesc) bool { | ||
if numberDesc == nil { | ||
return false | ||
} | ||
if len(numberDesc.PossibleLength) > 0 && !numberDesc.hasPossibleLength(int32(len(number))) { | ||
return false | ||
} | ||
return MatchNationalNumber(number, *numberDesc, false) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package phonenumbers | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
////////// Copied from java-libphonenumber | ||
/** | ||
* Unit tests for ShortNumberInfo.java | ||
*/ | ||
|
||
func TestIsPossibleShortNumber(t *testing.T) { | ||
countryCode := int32(33) | ||
nationalNumber := uint64(123456) | ||
possibleNumber := &PhoneNumber{ | ||
CountryCode: &countryCode, | ||
NationalNumber: &nationalNumber, | ||
} | ||
assert.True(t, IsPossibleShortNumber(*possibleNumber)) | ||
|
||
possibleNumber, err := Parse("123456", "FR") | ||
if err != nil { | ||
t.Errorf("Error parsing number: %s: %s", "123456", err) | ||
} | ||
assert.True(t, IsPossibleShortNumberForRegion(*possibleNumber, "FR")) | ||
|
||
nationalNumber = 9 | ||
impossibleNumber := &PhoneNumber{ | ||
CountryCode: &countryCode, | ||
NationalNumber: &nationalNumber, | ||
} | ||
assert.False(t, IsPossibleShortNumber(*impossibleNumber)) | ||
|
||
// Note that GB and GG share the country calling code 44, and that this number is possible but | ||
// not valid. | ||
countryCode = 44 | ||
nationalNumber = 11001 | ||
possibleNumber = &PhoneNumber{ | ||
CountryCode: &countryCode, | ||
NationalNumber: &nationalNumber, | ||
} | ||
assert.True(t, IsPossibleShortNumber(*possibleNumber)) | ||
} | ||
|
||
func TestIsValidShortNumber(t *testing.T) { | ||
countryCode := int32(33) | ||
nationalNumber := uint64(1010) | ||
validNumber := &PhoneNumber{ | ||
CountryCode: &countryCode, | ||
NationalNumber: &nationalNumber, | ||
} | ||
assert.True(t, IsValidShortNumber(*validNumber)) | ||
|
||
validNumber, err := Parse("1010", "FR") | ||
if err != nil { | ||
t.Errorf("Error parsing number: %s: %s", "1010", err) | ||
} | ||
assert.True(t, IsValidShortNumberForRegion(*validNumber, "FR")) | ||
|
||
nationalNumber = uint64(123456) | ||
invalidNumber := &PhoneNumber{ | ||
CountryCode: &countryCode, | ||
NationalNumber: &nationalNumber, | ||
} | ||
assert.False(t, IsValidShortNumber(*invalidNumber)) | ||
|
||
invalidNumber, err = Parse("123456", "FR") | ||
if err != nil { | ||
t.Errorf("Error parsing number: %s: %s", "1010", err) | ||
} | ||
assert.False(t, IsValidShortNumberForRegion(*invalidNumber, "FR")) | ||
} |
Oops, something went wrong.