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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/README.md +223 -112
  4. data/data/UPSTREAM_VERSION +1 -0
  5. data/data/address_formats.json +2256 -206
  6. data/data/countries.json +1282 -0
  7. data/data/locale.json +222 -0
  8. data/data/subdivision/BR-AC.json +22 -22
  9. data/data/subdivision/BR-AL.json +102 -102
  10. data/data/subdivision/BR-AM.json +62 -62
  11. data/data/subdivision/BR-AP.json +16 -16
  12. data/data/subdivision/BR-BA.json +417 -417
  13. data/data/subdivision/BR-CE.json +184 -184
  14. data/data/subdivision/BR-DF.json +1 -1
  15. data/data/subdivision/BR-ES.json +79 -79
  16. data/data/subdivision/BR-GO.json +246 -246
  17. data/data/subdivision/BR-MA.json +217 -217
  18. data/data/subdivision/BR-MG.json +853 -853
  19. data/data/subdivision/BR-MS.json +78 -78
  20. data/data/subdivision/BR-MT.json +141 -141
  21. data/data/subdivision/BR-PA.json +144 -144
  22. data/data/subdivision/BR-PB.json +223 -223
  23. data/data/subdivision/BR-PE.json +185 -185
  24. data/data/subdivision/BR-PI.json +223 -223
  25. data/data/subdivision/BR-PR.json +400 -400
  26. data/data/subdivision/BR-RJ.json +93 -93
  27. data/data/subdivision/BR-RN.json +166 -166
  28. data/data/subdivision/BR-RO.json +52 -52
  29. data/data/subdivision/BR-RR.json +15 -15
  30. data/data/subdivision/BR-RS.json +497 -497
  31. data/data/subdivision/BR-SC.json +295 -295
  32. data/data/subdivision/BR-SE.json +75 -75
  33. data/data/subdivision/BR-SP.json +645 -645
  34. data/data/subdivision/BR-TO.json +139 -139
  35. data/data/subdivision/CL-AI.json +10 -10
  36. data/data/subdivision/CL-AN.json +9 -9
  37. data/data/subdivision/CL-AP.json +4 -4
  38. data/data/subdivision/CL-AR.json +32 -32
  39. data/data/subdivision/CL-AT.json +9 -9
  40. data/data/subdivision/CL-BI.json +34 -34
  41. data/data/subdivision/CL-CO.json +15 -15
  42. data/data/subdivision/CL-LI.json +33 -33
  43. data/data/subdivision/CL-LL.json +30 -30
  44. data/data/subdivision/CL-LR.json +12 -12
  45. data/data/subdivision/CL-MA.json +11 -11
  46. data/data/subdivision/CL-ML.json +30 -30
  47. data/data/subdivision/CL-NB.json +21 -21
  48. data/data/subdivision/CL-RM.json +52 -52
  49. data/data/subdivision/CL-TA.json +7 -7
  50. data/data/subdivision/CL-VS.json +38 -38
  51. data/data/subdivision/CV.json +9 -9
  52. data/data/subdivision/KY.json +3 -3
  53. data/data/subdivision/TV.json +1 -1
  54. data/lib/addressing/address.rb +7 -3
  55. data/lib/addressing/address_format.rb +55 -81
  56. data/lib/addressing/address_validator.rb +98 -0
  57. data/lib/addressing/blank.rb +32 -0
  58. data/lib/addressing/country.rb +28 -314
  59. data/lib/addressing/data_source.rb +75 -0
  60. data/lib/addressing/default_formatter.rb +25 -25
  61. data/lib/addressing/enum.rb +2 -6
  62. data/lib/addressing/field_violation.rb +17 -0
  63. data/lib/addressing/lazy_subdivisions.rb +16 -15
  64. data/lib/addressing/locale.rb +20 -269
  65. data/lib/addressing/model.rb +20 -78
  66. data/lib/addressing/postal_label_formatter.rb +16 -15
  67. data/lib/addressing/subdivision.rb +113 -116
  68. data/lib/addressing/version.rb +1 -1
  69. data/lib/addressing.rb +6 -1
  70. metadata +9 -3
  71. 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
- @address_formats ||= {}
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
- definitions.map do |country_code, definition|
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
- def definitions
49
- @definitions ||= Marshal.load(File.read(File.expand_path("../../../data/address_formats.dump", __FILE__).to_s))
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 = generic_definition.merge(definition)
74
+ definition = GENERIC_DEFINITION.merge(definition)
55
75
 
56
76
  # Always require the given name and family name.
57
- definition[:required_fields] << AddressField::GIVEN_NAME
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
- def generic_definition
63
- {
64
- format: "%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality",
65
- required_fields: [
66
- "address_line1", "locality"
67
- ],
68
- uppercase_fields: [
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
- if used_fields.include?(AddressField::ADMINISTRATIVE_AREA)
134
- if definition[:administrative_area_type]
135
- AdministrativeAreaType.assert_exists(definition[:administrative_area_type])
136
- @administrative_area_type = definition[:administrative_area_type]
137
- end
138
- end
139
-
140
- if used_fields.include?(AddressField::LOCALITY)
141
- if definition[:locality_type]
142
- LocalityType.assert_exists(definition[:locality_type])
143
- @locality_type = definition[:locality_type]
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.filter_map do |key, value|
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