-
Notifications
You must be signed in to change notification settings - Fork 16
/
zone.go
126 lines (100 loc) · 3.49 KB
/
zone.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package address
import (
"regexp"
"strconv"
"strings"
)
// Zone is a list of territories. Zones are useful for determining whether an address is in a country, administrative area
// or a range of post codes to determine shipping or tax rates.
type Zone []Territory
// Contains checks to see an address is in a zone.
func (z Zone) Contains(address Address) bool {
for _, zone := range z {
if zone.contains(address) {
return true
}
}
return false
}
// Territory is a rule within a zone.
// It is able to match the following fields:
// - Country: An ISO 3166-1 country code
// - AdministrativeArea: The administrative area name. If the country has a list of pre-defined administrative areas,
// use the key of the administrative area.
// - Locality: The locality name. If the country has a list of pre-defined localities, use the key of the locality.
// - DependentLocality: The dependent locality name. If the country has a list of pre-defined dependent localities,
// use the key of the locality.
// - IncludedPostCodes: A PostCodeMatcher that includes the address within the territory if the post code matches.
// - ExcludedPostCodes: A PostCodeMatcher that excludes the address from the territory if the post code matches.
type Territory struct {
Country string
AdministrativeArea string
Locality string
DependentLocality string
IncludedPostCodes PostCodeMatcher
ExcludedPostCodes PostCodeMatcher
}
// PostCodeMatcher returns a boolean signaling whether the post code matched or not.
type PostCodeMatcher interface {
Match(postcode string) bool
}
func (t Territory) contains(address Address) bool {
if t.Country != "" && address.Country != t.Country {
return false
}
if t.AdministrativeArea != "" && strings.ToLower(address.AdministrativeArea) != strings.ToLower(t.AdministrativeArea) {
return false
}
if t.Locality != "" && strings.ToLower(address.Locality) != strings.ToLower(t.Locality) {
return false
}
if t.DependentLocality != "" && strings.ToLower(address.DependentLocality) != strings.ToLower(t.DependentLocality) {
return false
}
matchIncluded := true
if t.IncludedPostCodes != nil {
matchIncluded = t.IncludedPostCodes.Match(address.PostCode)
}
matchExcluded := false
if t.ExcludedPostCodes != nil {
matchExcluded = t.ExcludedPostCodes.Match(address.PostCode)
}
return matchIncluded && !matchExcluded
}
// ExactMatcher matches post codes exactly in the list defined in the Matches field. If the post code is numeric,
// it's also possible to define a slice of ranges using the Ranges fiel.d
type ExactMatcher struct {
Matches []string
Ranges []PostCodeRange
}
// Match checks to see if the post code matches a post code defined in Matches or if it is within a range defined in Ranges.
func (m ExactMatcher) Match(postCode string) bool {
for _, match := range m.Matches {
if postCode == match {
return true
}
}
i, err := strconv.Atoi(postCode)
if err != nil {
return false
}
for _, match := range m.Ranges {
if i >= match.Start && i <= match.End {
return true
}
}
return false
}
// PostCodeRange defines a range of numeric post codes, inclusive of the Start and End.
type PostCodeRange struct {
Start int
End int
}
// RegexMatcher defines a post code matcher that uses a regular expression.
type RegexMatcher struct {
Regex *regexp.Regexp
}
// Match returns whether the post code is matched by the regular expression defined in the matcher.
func (m RegexMatcher) Match(postCode string) bool {
return m.Regex.MatchString(postCode)
}