addressing 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -0
- data/README.md +223 -112
- data/data/UPSTREAM_VERSION +1 -0
- data/data/address_formats.json +2256 -206
- data/data/countries.json +1282 -0
- data/data/locale.json +222 -0
- data/data/subdivision/BR-AC.json +22 -22
- data/data/subdivision/BR-AL.json +102 -102
- data/data/subdivision/BR-AM.json +62 -62
- data/data/subdivision/BR-AP.json +16 -16
- data/data/subdivision/BR-BA.json +417 -417
- data/data/subdivision/BR-CE.json +184 -184
- data/data/subdivision/BR-DF.json +1 -1
- data/data/subdivision/BR-ES.json +79 -79
- data/data/subdivision/BR-GO.json +246 -246
- data/data/subdivision/BR-MA.json +217 -217
- data/data/subdivision/BR-MG.json +853 -853
- data/data/subdivision/BR-MS.json +78 -78
- data/data/subdivision/BR-MT.json +141 -141
- data/data/subdivision/BR-PA.json +144 -144
- data/data/subdivision/BR-PB.json +223 -223
- data/data/subdivision/BR-PE.json +185 -185
- data/data/subdivision/BR-PI.json +223 -223
- data/data/subdivision/BR-PR.json +400 -400
- data/data/subdivision/BR-RJ.json +93 -93
- data/data/subdivision/BR-RN.json +166 -166
- data/data/subdivision/BR-RO.json +52 -52
- data/data/subdivision/BR-RR.json +15 -15
- data/data/subdivision/BR-RS.json +497 -497
- data/data/subdivision/BR-SC.json +295 -295
- data/data/subdivision/BR-SE.json +75 -75
- data/data/subdivision/BR-SP.json +645 -645
- data/data/subdivision/BR-TO.json +139 -139
- data/data/subdivision/CL-AI.json +10 -10
- data/data/subdivision/CL-AN.json +9 -9
- data/data/subdivision/CL-AP.json +4 -4
- data/data/subdivision/CL-AR.json +32 -32
- data/data/subdivision/CL-AT.json +9 -9
- data/data/subdivision/CL-BI.json +34 -34
- data/data/subdivision/CL-CO.json +15 -15
- data/data/subdivision/CL-LI.json +33 -33
- data/data/subdivision/CL-LL.json +30 -30
- data/data/subdivision/CL-LR.json +12 -12
- data/data/subdivision/CL-MA.json +11 -11
- data/data/subdivision/CL-ML.json +30 -30
- data/data/subdivision/CL-NB.json +21 -21
- data/data/subdivision/CL-RM.json +52 -52
- data/data/subdivision/CL-TA.json +7 -7
- data/data/subdivision/CL-VS.json +38 -38
- data/data/subdivision/CV.json +9 -9
- data/data/subdivision/KY.json +3 -3
- data/data/subdivision/TV.json +1 -1
- data/lib/addressing/address.rb +7 -3
- data/lib/addressing/address_format.rb +55 -81
- data/lib/addressing/address_validator.rb +98 -0
- data/lib/addressing/blank.rb +32 -0
- data/lib/addressing/country.rb +28 -314
- data/lib/addressing/data_source.rb +75 -0
- data/lib/addressing/default_formatter.rb +25 -25
- data/lib/addressing/enum.rb +2 -6
- data/lib/addressing/field_violation.rb +17 -0
- data/lib/addressing/lazy_subdivisions.rb +16 -15
- data/lib/addressing/locale.rb +20 -269
- data/lib/addressing/model.rb +20 -78
- data/lib/addressing/postal_label_formatter.rb +16 -15
- data/lib/addressing/subdivision.rb +113 -116
- data/lib/addressing/version.rb +1 -1
- data/lib/addressing.rb +6 -1
- metadata +9 -3
- data/data/address_formats.dump +0 -0
|
@@ -19,6 +19,24 @@ module Addressing
|
|
|
19
19
|
AddressField::DEPENDENT_LOCALITY
|
|
20
20
|
].freeze
|
|
21
21
|
|
|
22
|
+
# The defaults for every address format, including the fallback for
|
|
23
|
+
# countries without a definition.
|
|
24
|
+
GENERIC_DEFINITION = {
|
|
25
|
+
format: "%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality",
|
|
26
|
+
required_fields: [
|
|
27
|
+
"address_line1", "locality"
|
|
28
|
+
].freeze,
|
|
29
|
+
uppercase_fields: [
|
|
30
|
+
"locality"
|
|
31
|
+
].freeze,
|
|
32
|
+
subdivision_fields: [].freeze,
|
|
33
|
+
administrative_area_type: "province",
|
|
34
|
+
locality_type: "city",
|
|
35
|
+
dependent_locality_type: "suburb",
|
|
36
|
+
postal_code_type: "postal"
|
|
37
|
+
}.freeze
|
|
38
|
+
private_constant :GENERIC_DEFINITION
|
|
39
|
+
|
|
22
40
|
class << self
|
|
23
41
|
# Gets the address format for the provided country code.
|
|
24
42
|
#
|
|
@@ -26,53 +44,45 @@ module Addressing
|
|
|
26
44
|
# @return [AddressFormat] Address format instance
|
|
27
45
|
def get(country_code)
|
|
28
46
|
country_code = country_code.upcase
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
unless @address_formats.key?(country_code)
|
|
32
|
-
definition = process_definition(definitions[country_code] || {country_code: country_code})
|
|
33
|
-
@address_formats[country_code] = new(definition)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
@address_formats[country_code]
|
|
47
|
+
# Unknown country codes often come from user input, so they are not cached.
|
|
48
|
+
address_formats.fetch(country_code) { new(process_definition(country_code: country_code)) }
|
|
37
49
|
end
|
|
38
50
|
|
|
39
51
|
def all
|
|
40
|
-
|
|
41
|
-
definition = process_definition(definition)
|
|
42
|
-
[country_code, new(definition)]
|
|
43
|
-
end.to_h
|
|
52
|
+
address_formats.dup
|
|
44
53
|
end
|
|
45
54
|
|
|
46
55
|
private
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
# Gets the address formats by country code.
|
|
58
|
+
#
|
|
59
|
+
# All are built when the data is loaded, so that every call returns
|
|
60
|
+
# the same instance for a country.
|
|
61
|
+
def address_formats
|
|
62
|
+
Addressing.data_source.fetch("address_formats") do |definitions|
|
|
63
|
+
definitions.to_h do |country_code, definition|
|
|
64
|
+
definition = definition.transform_keys(&:to_sym)
|
|
65
|
+
definition[:default_values] = definition[:default_values].transform_keys(&:to_sym) if definition[:default_values]
|
|
66
|
+
|
|
67
|
+
[country_code, new(process_definition(definition))]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
50
70
|
end
|
|
51
71
|
|
|
52
72
|
def process_definition(definition)
|
|
53
73
|
# Merge-in defaults.
|
|
54
|
-
definition =
|
|
74
|
+
definition = GENERIC_DEFINITION.merge(definition)
|
|
55
75
|
|
|
56
76
|
# Always require the given name and family name.
|
|
57
|
-
definition[:required_fields]
|
|
58
|
-
definition[:required_fields] << AddressField::FAMILY_NAME
|
|
59
|
-
definition
|
|
60
|
-
end
|
|
77
|
+
definition[:required_fields] = definition[:required_fields] | [AddressField::GIVEN_NAME, AddressField::FAMILY_NAME]
|
|
61
78
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"locality"
|
|
70
|
-
],
|
|
71
|
-
administrative_area_type: "province",
|
|
72
|
-
locality_type: "city",
|
|
73
|
-
dependent_locality_type: "suburb",
|
|
74
|
-
postal_code_type: "postal"
|
|
75
|
-
}
|
|
79
|
+
# The address formats are shared, so callers must not be able to
|
|
80
|
+
# change their field lists.
|
|
81
|
+
[:required_fields, :uppercase_fields, :subdivision_fields].each do |key|
|
|
82
|
+
definition[key] = definition[key].dup.freeze
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
definition
|
|
76
86
|
end
|
|
77
87
|
end
|
|
78
88
|
|
|
@@ -130,33 +140,20 @@ module Addressing
|
|
|
130
140
|
@default_values = definition[:default_values]
|
|
131
141
|
@subdivision_fields = definition[:subdivision_fields]
|
|
132
142
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
if used_fields.include?(AddressField::DEPENDENT_LOCALITY)
|
|
148
|
-
if definition[:dependent_locality_type]
|
|
149
|
-
DependentLocalityType.assert_exists(definition[:dependent_locality_type])
|
|
150
|
-
@dependent_locality_type = definition[:dependent_locality_type]
|
|
151
|
-
end
|
|
143
|
+
# A type is only set when the format uses its field.
|
|
144
|
+
{
|
|
145
|
+
administrative_area_type: [AddressField::ADMINISTRATIVE_AREA, AdministrativeAreaType],
|
|
146
|
+
locality_type: [AddressField::LOCALITY, LocalityType],
|
|
147
|
+
dependent_locality_type: [AddressField::DEPENDENT_LOCALITY, DependentLocalityType],
|
|
148
|
+
postal_code_type: [AddressField::POSTAL_CODE, PostalCodeType]
|
|
149
|
+
}.each do |key, (field, type)|
|
|
150
|
+
next unless definition[key] && used_fields.include?(field)
|
|
151
|
+
|
|
152
|
+
type.assert_exists(definition[key])
|
|
153
|
+
instance_variable_set(:"@#{key}", definition[key])
|
|
152
154
|
end
|
|
153
155
|
|
|
154
156
|
if used_fields.include?(AddressField::POSTAL_CODE)
|
|
155
|
-
if definition[:postal_code_type]
|
|
156
|
-
PostalCodeType.assert_exists(definition[:postal_code_type])
|
|
157
|
-
@postal_code_type = definition[:postal_code_type]
|
|
158
|
-
end
|
|
159
|
-
|
|
160
157
|
@postal_code_pattern = definition[:postal_code_pattern]
|
|
161
158
|
@postal_code_prefix = definition[:postal_code_prefix]
|
|
162
159
|
end
|
|
@@ -164,9 +161,7 @@ module Addressing
|
|
|
164
161
|
|
|
165
162
|
# Gets the list of used fields.
|
|
166
163
|
def used_fields
|
|
167
|
-
@used_fields ||= AddressField.all.
|
|
168
|
-
value if @format.include?("%" + value)
|
|
169
|
-
end
|
|
164
|
+
@used_fields ||= AddressField.all.values.select { |field| @format.include?("%#{field}") }
|
|
170
165
|
end
|
|
171
166
|
|
|
172
167
|
# Gets the list of used subdivision fields.
|
|
@@ -177,25 +172,4 @@ module Addressing
|
|
|
177
172
|
SUBDIVISION_FIELDS & used_fields
|
|
178
173
|
end
|
|
179
174
|
end
|
|
180
|
-
|
|
181
|
-
class AddressFormatHelper
|
|
182
|
-
class << self
|
|
183
|
-
# Gets the required fields.
|
|
184
|
-
#
|
|
185
|
-
# Applies field overrides to the required fields
|
|
186
|
-
# specified by the address format.
|
|
187
|
-
def required_fields(address_format, field_overrides)
|
|
188
|
-
required_fields = address_format.required_fields
|
|
189
|
-
required_fields -= field_overrides.optional_fields
|
|
190
|
-
required_fields -= field_overrides.hidden_fields
|
|
191
|
-
|
|
192
|
-
if field_overrides.required_fields
|
|
193
|
-
required_fields += field_overrides.required_fields
|
|
194
|
-
required_fields = required_fields.uniq
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
required_fields
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
end
|
|
201
175
|
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Addressing
|
|
4
|
+
# Validates an address against the address format of its country.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# address = Addressing::Address.new(country_code: "US", administrative_area: "XX")
|
|
8
|
+
# Addressing::AddressValidator.validate(address)
|
|
9
|
+
# # => [#<data Addressing::FieldViolation field=:address_line1, kind=:blank>, ...]
|
|
10
|
+
class AddressValidator
|
|
11
|
+
private_class_method :new
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
# Returns the field violations of the address.
|
|
15
|
+
#
|
|
16
|
+
# The rules, in the order in which their violations are reported:
|
|
17
|
+
#
|
|
18
|
+
# 1. Every required field is present. The field overrides make fields
|
|
19
|
+
# optional, hidden or required.
|
|
20
|
+
# 2. Every field that the format does not use, or that is hidden, is blank.
|
|
21
|
+
# 3. The subdivision levels match the predefined subdivisions, from the
|
|
22
|
+
# administrative area down. A hidden level ends the check.
|
|
23
|
+
# 4. The postal code matches the whole pattern, ignoring case. The pattern
|
|
24
|
+
# comes from the last subdivision in the subdivision chain that has
|
|
25
|
+
# one, and otherwise from the address format.
|
|
26
|
+
#
|
|
27
|
+
# An address without a country code gives a single :blank violation on
|
|
28
|
+
# the country code. Without a country there is no address format to
|
|
29
|
+
# check the other fields against.
|
|
30
|
+
#
|
|
31
|
+
# @param address [Address] The address to validate
|
|
32
|
+
# @param field_overrides [FieldOverrides] Overrides of the fields of the address format
|
|
33
|
+
# @param verify_postal_code [Boolean] Whether to check the postal code against its pattern
|
|
34
|
+
# @return [Array<FieldViolation>] The field violations, empty when the address is valid
|
|
35
|
+
def validate(address, field_overrides: FieldOverrides.new({}), verify_postal_code: true)
|
|
36
|
+
return [violation(:country_code, :blank)] if blank?(address.country_code)
|
|
37
|
+
|
|
38
|
+
address_format = AddressFormat.get(address.country_code)
|
|
39
|
+
violations = []
|
|
40
|
+
|
|
41
|
+
required_fields(address_format, field_overrides).each do |field|
|
|
42
|
+
violations << violation(field, :blank) if blank?(address.send(field))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
used_fields = address_format.used_fields - field_overrides.hidden_fields
|
|
46
|
+
|
|
47
|
+
(AddressField.all.values - used_fields).each do |field|
|
|
48
|
+
violations << violation(field, :present) unless blank?(address.send(field))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
subdivision_fields = address_format.subdivision_fields
|
|
52
|
+
# A hidden level is not validated, and neither are the levels below it.
|
|
53
|
+
subdivision_values = subdivision_fields.map { |field| address.send(field) unless field_overrides.hidden_fields.include?(field) }
|
|
54
|
+
chain = Subdivision.chain(address_format.country_code, subdivision_values)
|
|
55
|
+
|
|
56
|
+
violations << violation(subdivision_fields[chain.unmatched_level], :invalid) if chain.unmatched_level
|
|
57
|
+
|
|
58
|
+
if verify_postal_code && used_fields.include?(AddressField::POSTAL_CODE) && !valid_postal_code?(address.postal_code, chain.subdivisions, address_format)
|
|
59
|
+
violations << violation(AddressField::POSTAL_CODE, :invalid)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
violations
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def violation(field, kind)
|
|
68
|
+
FieldViolation.new(field: field.to_sym, kind: kind)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Whether the postal code matches the pattern of the last subdivision in
|
|
72
|
+
# the chain that has one, or else the pattern of the address format.
|
|
73
|
+
def valid_postal_code?(postal_code, subdivisions, address_format)
|
|
74
|
+
# Nothing to validate.
|
|
75
|
+
return true if blank?(postal_code)
|
|
76
|
+
|
|
77
|
+
pattern = subdivisions.inject(address_format.postal_code_pattern) do |pattern, subdivision|
|
|
78
|
+
blank?(subdivision.postal_code_pattern) ? pattern : subdivision.postal_code_pattern
|
|
79
|
+
end
|
|
80
|
+
return true unless pattern
|
|
81
|
+
|
|
82
|
+
# The pattern must match the provided value completely.
|
|
83
|
+
match = postal_code.match(Regexp.new(pattern, "i"))
|
|
84
|
+
!match.nil? && match[0] == postal_code
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Applies the field overrides to the required fields of the address format.
|
|
88
|
+
def required_fields(address_format, field_overrides)
|
|
89
|
+
required_fields = address_format.required_fields - field_overrides.optional_fields - field_overrides.hidden_fields
|
|
90
|
+
(required_fields + field_overrides.required_fields).uniq
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def blank?(value)
|
|
94
|
+
Blank.blank?(value)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Addressing
|
|
4
|
+
# Decides whether a value is blank.
|
|
5
|
+
module Blank
|
|
6
|
+
# Matches a value that holds nothing but whitespace, Unicode included.
|
|
7
|
+
PATTERN = /\A[[:space:]]*\z/
|
|
8
|
+
private_constant :PATTERN
|
|
9
|
+
|
|
10
|
+
# Whether the value is blank. Follows ActiveSupport's blank?, and uses it
|
|
11
|
+
# when it is loaded.
|
|
12
|
+
def self.blank?(value)
|
|
13
|
+
return value.blank? if value.respond_to?(:blank?)
|
|
14
|
+
|
|
15
|
+
case value
|
|
16
|
+
when String then blank_string?(value)
|
|
17
|
+
when nil, false then true
|
|
18
|
+
else value.respond_to?(:empty?) && value.empty?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.blank_string?(value)
|
|
23
|
+
PATTERN.match?(value)
|
|
24
|
+
rescue Encoding::CompatibilityError
|
|
25
|
+
# The value has an encoding that is not compatible with ASCII.
|
|
26
|
+
Regexp.new(PATTERN.source.encode(value.encoding), PATTERN.options | Regexp::FIXEDENCODING).match?(value)
|
|
27
|
+
end
|
|
28
|
+
private_class_method :blank_string?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private_constant :Blank
|
|
32
|
+
end
|