smartystreets_ruby_sdk 6.4.1 → 10.0.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/.claude/settings.json +18 -0
- data/.github/workflows/ci.yml +10 -0
- data/.github/workflows/publish.yml +29 -0
- data/.github/workflows/test.yml +21 -0
- data/CLAUDE.md +73 -0
- data/Makefile +11 -5
- data/README.md +2 -0
- data/examples/international_autocomplete_example.rb +10 -7
- data/examples/international_example.rb +7 -6
- data/examples/international_postal_code_example.rb +5 -4
- data/examples/us_autocomplete_example.rb +69 -0
- data/examples/us_autocomplete_pro_example.rb +10 -12
- data/examples/us_enrichment_business_example.rb +72 -0
- data/examples/us_enrichment_business_name_search_example.rb +76 -0
- data/examples/us_enrichment_etag_example.rb +130 -0
- data/examples/us_enrichment_example.rb +130 -44
- data/examples/us_extract_example.rb +7 -6
- data/examples/us_reverse_geo_example.rb +10 -6
- data/examples/us_street_component_analysis_example.rb +2 -1
- data/examples/us_street_iana_timezone_example.rb +54 -0
- data/examples/us_street_match_strategy_example.rb +85 -0
- data/examples/us_street_multiple_address_example.rb +7 -6
- data/examples/us_street_single_address_example.rb +7 -6
- data/examples/us_zipcode_multiple_lookup_example.rb +7 -6
- data/examples/us_zipcode_single_lookup_example.rb +7 -6
- data/lib/smartystreets_ruby_sdk/basic_auth_credentials.rb +14 -0
- data/lib/smartystreets_ruby_sdk/client_builder.rb +36 -5
- data/lib/smartystreets_ruby_sdk/custom_header_sender.rb +16 -2
- data/lib/smartystreets_ruby_sdk/errors.rb +16 -11
- data/lib/smartystreets_ruby_sdk/exceptions.rb +12 -0
- data/lib/smartystreets_ruby_sdk/international_autocomplete/client.rb +2 -0
- data/lib/smartystreets_ruby_sdk/international_autocomplete/lookup.rb +4 -2
- data/lib/smartystreets_ruby_sdk/international_street/client.rb +10 -1
- data/lib/smartystreets_ruby_sdk/international_street/components.rb +8 -3
- data/lib/smartystreets_ruby_sdk/international_street/language_mode.rb +20 -3
- data/lib/smartystreets_ruby_sdk/international_street/lookup.rb +5 -12
- data/lib/smartystreets_ruby_sdk/international_street/rootlevel.rb +1 -5
- data/lib/smartystreets_ruby_sdk/native_sender.rb +3 -4
- data/lib/smartystreets_ruby_sdk/native_serializer.rb +1 -0
- data/lib/smartystreets_ruby_sdk/request.rb +2 -1
- data/lib/smartystreets_ruby_sdk/response.rb +13 -0
- data/lib/smartystreets_ruby_sdk/retry_sender.rb +32 -13
- data/lib/smartystreets_ruby_sdk/status_code_sender.rb +39 -22
- data/lib/smartystreets_ruby_sdk/us_autocomplete/client.rb +82 -0
- data/lib/smartystreets_ruby_sdk/us_autocomplete/geolocation_type.rb +8 -0
- data/lib/smartystreets_ruby_sdk/us_autocomplete/lookup.rb +73 -0
- data/lib/smartystreets_ruby_sdk/us_autocomplete/source_type.rb +8 -0
- data/lib/smartystreets_ruby_sdk/us_autocomplete/suggestion.rb +22 -0
- data/lib/smartystreets_ruby_sdk/us_autocomplete.rb +10 -0
- data/lib/smartystreets_ruby_sdk/us_autocomplete_pro/source_type.rb +8 -0
- data/lib/smartystreets_ruby_sdk/us_autocomplete_pro.rb +1 -0
- data/lib/smartystreets_ruby_sdk/us_enrichment/business/detail/attributes.rb +241 -0
- data/lib/smartystreets_ruby_sdk/us_enrichment/business/detail/response.rb +20 -0
- data/lib/smartystreets_ruby_sdk/us_enrichment/business/summary/business_entry.rb +16 -0
- data/lib/smartystreets_ruby_sdk/us_enrichment/business/summary/response.rb +24 -0
- data/lib/smartystreets_ruby_sdk/us_enrichment/client.rb +99 -30
- data/lib/smartystreets_ruby_sdk/us_enrichment/geo_reference/response.rb +2 -3
- data/lib/smartystreets_ruby_sdk/us_enrichment/lookup.rb +42 -9
- data/lib/smartystreets_ruby_sdk/us_enrichment/property/principal/response.rb +3 -4
- data/lib/smartystreets_ruby_sdk/us_enrichment/secondary/count/response.rb +2 -3
- data/lib/smartystreets_ruby_sdk/us_enrichment/secondary/response.rb +2 -3
- data/lib/smartystreets_ruby_sdk/us_enrichment.rb +6 -5
- data/lib/smartystreets_ruby_sdk/us_reverse_geo/source_type.rb +8 -0
- data/lib/smartystreets_ruby_sdk/us_reverse_geo/us_reverse_geo_response.rb +1 -1
- data/lib/smartystreets_ruby_sdk/us_reverse_geo.rb +1 -0
- data/lib/smartystreets_ruby_sdk/us_street/candidate.rb +2 -1
- data/lib/smartystreets_ruby_sdk/us_street/client.rb +10 -3
- data/lib/smartystreets_ruby_sdk/us_street/metadata.rb +5 -1
- data/lib/smartystreets_ruby_sdk/version.rb +1 -1
- data/lib/smartystreets_ruby_sdk.rb +2 -0
- metadata +27 -6
- data/.github/workflows/gem-publish.yml +0 -27
- data/lib/smartystreets_ruby_sdk/us_enrichment/risk/attributes.rb +0 -524
- data/lib/smartystreets_ruby_sdk/us_enrichment/risk/response.rb +0 -19
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require '../lib/smartystreets_ruby_sdk/static_credentials'
|
|
2
2
|
require '../lib/smartystreets_ruby_sdk/shared_credentials'
|
|
3
|
+
require '../lib/smartystreets_ruby_sdk/basic_auth_credentials'
|
|
3
4
|
require '../lib/smartystreets_ruby_sdk/client_builder'
|
|
4
5
|
require '../lib/smartystreets_ruby_sdk/us_zipcode/lookup'
|
|
5
6
|
|
|
@@ -8,13 +9,13 @@ class UsZipcodeSingleLookupExample
|
|
|
8
9
|
# key = 'Your SmartyStreets Auth Key here'
|
|
9
10
|
# referer = 'Your host name here'
|
|
10
11
|
# We recommend storing your secret keys in environment variables instead---it's safer!
|
|
11
|
-
key = ENV['SMARTY_AUTH_WEB']
|
|
12
|
-
referer = ENV['SMARTY_AUTH_REFERER']
|
|
13
|
-
credentials = SmartyStreets::SharedCredentials.new(key, referer)
|
|
12
|
+
# key = ENV['SMARTY_AUTH_WEB']
|
|
13
|
+
# referer = ENV['SMARTY_AUTH_REFERER']
|
|
14
|
+
# credentials = SmartyStreets::SharedCredentials.new(key, referer)
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
id = ENV['SMARTY_AUTH_ID']
|
|
17
|
+
token = ENV['SMARTY_AUTH_TOKEN']
|
|
18
|
+
credentials = SmartyStreets::BasicAuthCredentials.new(id, token)
|
|
18
19
|
|
|
19
20
|
client = SmartyStreets::ClientBuilder.new(credentials).build_us_zipcode_api_client
|
|
20
21
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module SmartyStreets
|
|
2
|
+
class BasicAuthCredentials
|
|
3
|
+
def initialize(auth_id, auth_token)
|
|
4
|
+
raise ArgumentError, 'credentials (auth id, auth token) required' if auth_id.nil? || auth_id.empty? || auth_token.nil? || auth_token.empty?
|
|
5
|
+
|
|
6
|
+
@auth_id = auth_id
|
|
7
|
+
@auth_token = auth_token
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def sign(request)
|
|
11
|
+
request.basic_auth = [@auth_id, @auth_token]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -17,6 +17,7 @@ require_relative 'international_street/client'
|
|
|
17
17
|
require_relative 'international_autocomplete/client'
|
|
18
18
|
require_relative 'us_reverse_geo/client'
|
|
19
19
|
require_relative 'us_autocomplete_pro/client'
|
|
20
|
+
require_relative 'us_autocomplete/client'
|
|
20
21
|
require_relative 'us_enrichment/client'
|
|
21
22
|
require_relative 'international_postal_code/client'
|
|
22
23
|
|
|
@@ -28,6 +29,7 @@ module SmartyStreets
|
|
|
28
29
|
INTERNATIONAL_STREET_API_URL = 'https://international-street.api.smarty.com/verify'.freeze
|
|
29
30
|
INTERNATIONAL_AUTOCOMPLETE_API_URL = "https://international-autocomplete.api.smarty.com/v2/lookup".freeze
|
|
30
31
|
US_AUTOCOMPLETE_PRO_API_URL = 'https://us-autocomplete-pro.api.smarty.com/lookup'.freeze
|
|
32
|
+
US_AUTOCOMPLETE_API_URL = 'https://us-autocomplete.api.smarty.com/v2/lookup'.freeze
|
|
31
33
|
US_EXTRACT_API_URL = 'https://us-extract.api.smarty.com/'.freeze
|
|
32
34
|
US_STREET_API_URL = 'https://us-street.api.smarty.com/street-address'.freeze
|
|
33
35
|
US_ZIP_CODE_API_URL = 'https://us-zipcode.api.smarty.com/lookup'.freeze
|
|
@@ -44,6 +46,7 @@ module SmartyStreets
|
|
|
44
46
|
@url_prefix = nil
|
|
45
47
|
@proxy = nil
|
|
46
48
|
@header = nil
|
|
49
|
+
@append_headers = {}
|
|
47
50
|
@licenses = %w()
|
|
48
51
|
@debug = nil
|
|
49
52
|
@queries = {}
|
|
@@ -65,7 +68,7 @@ module SmartyStreets
|
|
|
65
68
|
self
|
|
66
69
|
end
|
|
67
70
|
|
|
68
|
-
#
|
|
71
|
+
# Sets the innermost HTTP transport sender while keeping the full middleware chain intact.
|
|
69
72
|
#
|
|
70
73
|
# Returns self to accommodate method chaining.
|
|
71
74
|
def with_sender(sender)
|
|
@@ -105,6 +108,18 @@ module SmartyStreets
|
|
|
105
108
|
# Returns self to accommodate method chaining.
|
|
106
109
|
def with_custom_headers(header)
|
|
107
110
|
@header = header
|
|
111
|
+
@append_headers = {}
|
|
112
|
+
self
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Appends the provided value to the existing header value using the specified separator,
|
|
116
|
+
# rather than adding a separate header value. This is useful for single-value headers like User-Agent.
|
|
117
|
+
#
|
|
118
|
+
# Returns self to accommodate method chaining.
|
|
119
|
+
def with_appended_header(key, value, separator)
|
|
120
|
+
@header = {} if @header.nil?
|
|
121
|
+
@append_headers[key] = separator
|
|
122
|
+
@header[key] = (@header[key] || []).concat([value])
|
|
108
123
|
self
|
|
109
124
|
end
|
|
110
125
|
|
|
@@ -141,6 +156,12 @@ module SmartyStreets
|
|
|
141
156
|
self
|
|
142
157
|
end
|
|
143
158
|
|
|
159
|
+
# with_feature_iana_time_zone turns on the IANA timezone feature for the request.
|
|
160
|
+
def with_feature_iana_time_zone()
|
|
161
|
+
self.with_custom_comma_separated_query("features", "iana-timezone")
|
|
162
|
+
self
|
|
163
|
+
end
|
|
164
|
+
|
|
144
165
|
# Enables debug mode, which will print information about the HTTP request and response to $stdout.
|
|
145
166
|
#
|
|
146
167
|
# Returns self to accommodate method chaining.
|
|
@@ -166,6 +187,11 @@ module SmartyStreets
|
|
|
166
187
|
USAutocompletePro::Client.new(build_sender, @serializer)
|
|
167
188
|
end
|
|
168
189
|
|
|
190
|
+
def build_us_autocomplete_api_client
|
|
191
|
+
ensure_url_prefix_not_null(US_AUTOCOMPLETE_API_URL)
|
|
192
|
+
USAutocomplete::Client.new(build_sender, @serializer)
|
|
193
|
+
end
|
|
194
|
+
|
|
169
195
|
def build_us_extract_api_client
|
|
170
196
|
ensure_url_prefix_not_null(US_EXTRACT_API_URL)
|
|
171
197
|
USExtract::Client.new(build_sender, @serializer)
|
|
@@ -199,13 +225,18 @@ module SmartyStreets
|
|
|
199
225
|
# </editor-fold>
|
|
200
226
|
|
|
201
227
|
def build_sender
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
228
|
+
if @http_sender
|
|
229
|
+
conflicts = []
|
|
230
|
+
conflicts << 'with_max_timeout' if @max_timeout != 10
|
|
231
|
+
conflicts << 'with_proxy' if @proxy
|
|
232
|
+
conflicts << 'with_debug' if @debug
|
|
233
|
+
raise ArgumentError, "with_sender cannot be combined with: #{conflicts.join(', ')}. These options only apply to the built-in HTTP transport." unless conflicts.empty?
|
|
234
|
+
end
|
|
235
|
+
sender = @http_sender || NativeSender.new(@max_timeout, @proxy, @debug)
|
|
205
236
|
|
|
206
237
|
sender = StatusCodeSender.new(sender)
|
|
207
238
|
|
|
208
|
-
sender = CustomHeaderSender.new(sender, @header) unless @header.nil?
|
|
239
|
+
sender = CustomHeaderSender.new(sender, @header, @append_headers) unless @header.nil?
|
|
209
240
|
|
|
210
241
|
sender = SigningSender.new(@signer, sender) unless @signer.nil?
|
|
211
242
|
|
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
module SmartyStreets
|
|
2
2
|
class CustomHeaderSender
|
|
3
|
-
def initialize(inner, header)
|
|
3
|
+
def initialize(inner, header, append_headers = {})
|
|
4
4
|
@inner = inner
|
|
5
5
|
@header = header
|
|
6
|
+
@append_headers = append_headers || {}
|
|
6
7
|
end
|
|
7
8
|
|
|
8
9
|
def send(request)
|
|
9
|
-
request.header =
|
|
10
|
+
apply_headers.each { |key, value| request.header[key] = value }
|
|
10
11
|
@inner.send(request)
|
|
11
12
|
end
|
|
13
|
+
|
|
14
|
+
def apply_headers
|
|
15
|
+
result = {}
|
|
16
|
+
@header.each do |key, values|
|
|
17
|
+
if @append_headers.key?(key)
|
|
18
|
+
separator = @append_headers[key]
|
|
19
|
+
result[key] = values.respond_to?(:join) ? values.join(separator) : values.to_s
|
|
20
|
+
else
|
|
21
|
+
result[key] = values.dup
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
result
|
|
25
|
+
end
|
|
12
26
|
end
|
|
13
27
|
end
|
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
module SmartyStreets
|
|
2
|
-
NOT_MODIFIED = 'Not Modified:
|
|
2
|
+
NOT_MODIFIED = 'Not Modified: The requested record has not been modified since the previous request' \
|
|
3
|
+
' with the Etag value.'.freeze
|
|
3
4
|
|
|
4
|
-
BAD_CREDENTIALS = 'Unauthorized: The credentials were provided incorrectly or did not match any existing,
|
|
5
|
-
active credentials.'.freeze
|
|
5
|
+
BAD_CREDENTIALS = 'Unauthorized: The credentials were provided incorrectly or did not match any existing,' \
|
|
6
|
+
' active credentials.'.freeze
|
|
6
7
|
|
|
7
|
-
PAYMENT_REQUIRED = 'Payment Required: There is no active subscription
|
|
8
|
-
|
|
8
|
+
PAYMENT_REQUIRED = 'Payment Required: There is no active subscription for the account associated with the' \
|
|
9
|
+
' credentials submitted with the request.'.freeze
|
|
9
10
|
|
|
10
|
-
FORBIDDEN = '
|
|
11
|
-
|
|
11
|
+
FORBIDDEN = 'Forbidden: The request contained valid data and was understood by the server, but the server' \
|
|
12
|
+
' is refusing action.'.freeze
|
|
13
|
+
|
|
14
|
+
REQUEST_TIMEOUT = 'Request timeout error.'.freeze
|
|
12
15
|
|
|
13
16
|
REQUEST_ENTITY_TOO_LARGE = 'Request Entity Too Large: The request body has exceeded the maximum size.'.freeze
|
|
14
17
|
|
|
15
|
-
BAD_REQUEST = 'Bad Request (Malformed Payload): A GET request lacked a
|
|
16
|
-
POST request contained malformed JSON
|
|
18
|
+
BAD_REQUEST = 'Bad Request (Malformed Payload): A GET request lacked a required field or the request body' \
|
|
19
|
+
' of a POST request contained malformed JSON.'.freeze
|
|
17
20
|
|
|
18
21
|
UNPROCESSABLE_ENTITY = 'GET request lacked required fields.'.freeze
|
|
19
22
|
|
|
20
|
-
TOO_MANY_REQUESTS = 'The rate limit has been exceeded.'.freeze
|
|
23
|
+
TOO_MANY_REQUESTS = 'Too Many Requests: The rate limit for your account has been exceeded.'.freeze
|
|
21
24
|
|
|
22
25
|
INTERNAL_SERVER_ERROR = 'Internal Server Error.'.freeze
|
|
23
26
|
|
|
27
|
+
BAD_GATEWAY = 'Bad Gateway error.'.freeze
|
|
28
|
+
|
|
24
29
|
SERVICE_UNAVAILABLE = 'Service Unavailable. Try again later.'.freeze
|
|
25
30
|
|
|
26
31
|
GATEWAY_TIMEOUT = 'The upstream data provider did not respond in a timely fashion and the request failed. ' \
|
|
27
|
-
|
|
32
|
+
'A serious, yet rare occurrence indeed.'.freeze
|
|
28
33
|
end
|
|
@@ -4,6 +4,12 @@ module SmartyStreets
|
|
|
4
4
|
end
|
|
5
5
|
|
|
6
6
|
class NotModifiedInfo < SmartyError
|
|
7
|
+
attr_reader :response_etag
|
|
8
|
+
|
|
9
|
+
def initialize(message = nil, response_etag = nil)
|
|
10
|
+
super(message)
|
|
11
|
+
@response_etag = response_etag
|
|
12
|
+
end
|
|
7
13
|
end
|
|
8
14
|
|
|
9
15
|
class BadCredentialsError < SmartyError
|
|
@@ -12,6 +18,12 @@ module SmartyStreets
|
|
|
12
18
|
class ForbiddenError < SmartyError
|
|
13
19
|
end
|
|
14
20
|
|
|
21
|
+
class RequestTimeoutError < SmartyError
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class BadGatewayError < SmartyError
|
|
25
|
+
end
|
|
26
|
+
|
|
15
27
|
class PaymentRequiredError < SmartyError
|
|
16
28
|
end
|
|
17
29
|
|
|
@@ -39,6 +39,8 @@ module SmartyStreets
|
|
|
39
39
|
add_parameter(request, 'search', lookup.search)
|
|
40
40
|
add_parameter(request, 'country', lookup.country)
|
|
41
41
|
add_parameter(request, 'max_results', lookup.max_results.to_s)
|
|
42
|
+
add_parameter(request, 'max_group_results', lookup.max_group_results.to_s)
|
|
43
|
+
add_parameter(request, 'geolocation', 'on') if lookup.geolocation
|
|
42
44
|
add_parameter(request, 'include_only_locality', lookup.locality)
|
|
43
45
|
add_parameter(request, 'include_only_postal_code', lookup.postal_code)
|
|
44
46
|
|
|
@@ -6,14 +6,16 @@ module SmartyStreets
|
|
|
6
6
|
# of the lookup after it comes back from the API.
|
|
7
7
|
class Lookup < JSONAble
|
|
8
8
|
|
|
9
|
-
attr_accessor :result, :search, :address_id, :country, :max_results, :locality, :postal_code, :custom_param_hash
|
|
9
|
+
attr_accessor :result, :search, :address_id, :country, :max_results, :max_group_results, :geolocation, :locality, :postal_code, :custom_param_hash
|
|
10
10
|
|
|
11
|
-
def initialize(search = nil, address_id = nil, country = nil, max_results = nil, locality = nil, postal_code = nil, custom_param_hash = nil)
|
|
11
|
+
def initialize(search = nil, address_id = nil, country = nil, max_results = nil, max_group_results = 100, geolocation = false, locality = nil, postal_code = nil, custom_param_hash = nil)
|
|
12
12
|
@result = []
|
|
13
13
|
@search = search
|
|
14
14
|
@address_id = address_id
|
|
15
15
|
@country = country
|
|
16
16
|
@max_results = max_results
|
|
17
|
+
@max_group_results = max_group_results
|
|
18
|
+
@geolocation = geolocation
|
|
17
19
|
@locality = locality
|
|
18
20
|
@postal_code = postal_code
|
|
19
21
|
@custom_param_hash = {}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require_relative '../request'
|
|
2
2
|
require_relative 'candidate'
|
|
3
|
+
require_relative 'language_mode'
|
|
3
4
|
|
|
4
5
|
module SmartyStreets
|
|
5
6
|
module InternationalStreet
|
|
@@ -13,6 +14,7 @@ module SmartyStreets
|
|
|
13
14
|
# Sends a Lookup object to the International Street API and stores the result in the Lookup's result field.
|
|
14
15
|
# Deprecated, please use send_lookup instead.
|
|
15
16
|
def send(lookup)
|
|
17
|
+
raise ArgumentError, 'lookup cannot be nil' if lookup.nil?
|
|
16
18
|
lookup.ensure_enough_info
|
|
17
19
|
request = build_request(lookup)
|
|
18
20
|
|
|
@@ -26,6 +28,7 @@ module SmartyStreets
|
|
|
26
28
|
|
|
27
29
|
# Sends a Lookup object to the International Street API and stores the result in the Lookup's result field.
|
|
28
30
|
def send_lookup(lookup)
|
|
31
|
+
raise ArgumentError, 'lookup cannot be nil' if lookup.nil?
|
|
29
32
|
lookup.ensure_enough_info
|
|
30
33
|
request = build_request(lookup)
|
|
31
34
|
|
|
@@ -43,7 +46,7 @@ module SmartyStreets
|
|
|
43
46
|
add_parameter(request, 'input_id', lookup.input_id)
|
|
44
47
|
add_parameter(request, 'country', lookup.country)
|
|
45
48
|
add_parameter(request, 'geocode', lookup.geocode.to_s)
|
|
46
|
-
add_parameter(request, 'language', lookup.language)
|
|
49
|
+
add_parameter(request, 'language', normalized_language(lookup.language))
|
|
47
50
|
add_parameter(request, 'freeform', lookup.freeform)
|
|
48
51
|
add_parameter(request, 'address1', lookup.address1)
|
|
49
52
|
add_parameter(request, 'address2', lookup.address2)
|
|
@@ -66,6 +69,12 @@ module SmartyStreets
|
|
|
66
69
|
request.parameters[key] = value unless value.nil? or value.empty?
|
|
67
70
|
end
|
|
68
71
|
|
|
72
|
+
def normalized_language(language)
|
|
73
|
+
return nil if language.nil?
|
|
74
|
+
|
|
75
|
+
LanguageMode.from_value(language).value
|
|
76
|
+
end
|
|
77
|
+
|
|
69
78
|
def convert_candidates(raw_candidates)
|
|
70
79
|
candidates = []
|
|
71
80
|
|
|
@@ -5,11 +5,12 @@ module SmartyStreets
|
|
|
5
5
|
attr_reader :premise, :thoroughfare_trailing_type, :sub_building, :locality, :post_box_number,
|
|
6
6
|
:thoroughfare_name, :thoroughfare_postdirection, :dependent_thoroughfare, :premise_prefix_number,
|
|
7
7
|
:thoroughfare, :dependent_thoroughfare_name, :postal_code_short, :dependent_thoroughfare_trailing_type,
|
|
8
|
-
:administrative_area, :administrative_area_iso2
|
|
8
|
+
:administrative_area, :administrative_area_iso2, :attention, :post_box,
|
|
9
9
|
:building_leading_type, :dependent_locality_name, :thoroughfare_type,
|
|
10
10
|
:dependent_thoroughfare_postdirection, :double_dependent_locality, :premise_number,
|
|
11
11
|
:dependent_thoroughfare_type, :post_box_type, :building, :sub_administrative_area, :postal_code_extra,
|
|
12
12
|
:sub_building_name, :postal_code, :dependent_locality, :premise_type, :sub_building_number,
|
|
13
|
+
:short_address_code, :sub_building_leading_type, :sub_building_block, :sub_building_door, :sub_building_staircase,
|
|
13
14
|
:super_administrative_area, :premise_extra, :dependent_thoroughfare_predirection,
|
|
14
15
|
:building_trailing_type, :thoroughfare_predirection, :building_name, :level_type, :level_number,
|
|
15
16
|
:country_iso_3, :sub_building_type, :additional_content, :delivery_installation, :delivery_installation_type,
|
|
@@ -20,8 +21,7 @@ module SmartyStreets
|
|
|
20
21
|
@super_administrative_area = obj.fetch('super_administrative_area', nil)
|
|
21
22
|
@administrative_area = obj.fetch('administrative_area', nil)
|
|
22
23
|
@administrative_area_iso2 = obj.fetch('administrative_area_iso2', nil)
|
|
23
|
-
@
|
|
24
|
-
@administrative_area_long = obj.fetch('administrative_area_long', nil)
|
|
24
|
+
@attention = obj.fetch('attention', nil)
|
|
25
25
|
@sub_administrative_area = obj.fetch('sub_administrative_area', nil)
|
|
26
26
|
@dependent_locality= obj.fetch('dependent_locality', nil)
|
|
27
27
|
@dependent_locality_name = obj.fetch('dependent_locality_name', nil)
|
|
@@ -35,6 +35,11 @@ module SmartyStreets
|
|
|
35
35
|
@premise_number = obj.fetch('premise_number', nil)
|
|
36
36
|
@premise_prefix_number = obj.fetch('premise_prefix_number', nil)
|
|
37
37
|
@premise_type = obj.fetch('premise_type', nil)
|
|
38
|
+
@short_address_code = obj.fetch('short_address_code', nil)
|
|
39
|
+
@sub_building_leading_type = obj.fetch('sub_building_leading_type', nil)
|
|
40
|
+
@sub_building_block = obj.fetch('sub_building_block', nil)
|
|
41
|
+
@sub_building_door = obj.fetch('sub_building_door', nil)
|
|
42
|
+
@sub_building_staircase = obj.fetch('sub_building_staircase', nil)
|
|
38
43
|
@thoroughfare = obj.fetch('thoroughfare', nil)
|
|
39
44
|
@thoroughfare_predirection = obj.fetch('thoroughfare_predirection', nil)
|
|
40
45
|
@thoroughfare_postdirection = obj.fetch('thoroughfare_postdirection', nil)
|
|
@@ -1,9 +1,26 @@
|
|
|
1
|
+
require_relative '../exceptions'
|
|
2
|
+
|
|
1
3
|
module SmartyStreets
|
|
2
4
|
module InternationalStreet
|
|
3
|
-
|
|
4
|
-
|
|
5
|
+
# A closed set of valid Language values, the closest Ruby equivalent to an enum: a fixed set of frozen
|
|
6
|
+
# instances rather than an arbitrary string.
|
|
7
|
+
class LanguageMode < Data.define(:value)
|
|
8
|
+
NATIVE = new('native')
|
|
9
|
+
LATIN = new('latin')
|
|
10
|
+
|
|
11
|
+
ALL = [NATIVE, LATIN].freeze
|
|
12
|
+
|
|
13
|
+
# Resolves a LanguageMode instance or a raw value (eg. from user input or config) into a LanguageMode,
|
|
14
|
+
# matching 'native'/'latin' regardless of case.
|
|
15
|
+
def self.from_value(value)
|
|
16
|
+
return value if value.is_a?(LanguageMode)
|
|
17
|
+
|
|
18
|
+
match = ALL.find { |mode| mode.value.casecmp?(value.to_s) }
|
|
19
|
+
raise SmartyStreets::UnprocessableEntityError,
|
|
20
|
+
"invalid Language value; must be unset, 'native', or 'latin' (case-insensitive)" if match.nil?
|
|
5
21
|
|
|
6
|
-
|
|
22
|
+
match
|
|
23
|
+
end
|
|
7
24
|
end
|
|
8
25
|
end
|
|
9
26
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require_relative 'language_mode'
|
|
2
|
+
|
|
1
3
|
module SmartyStreets
|
|
2
4
|
module InternationalStreet
|
|
3
5
|
# In addition to holding all of the input data for this lookup, this class also will contain the
|
|
@@ -71,18 +73,9 @@ module SmartyStreets
|
|
|
71
73
|
end
|
|
72
74
|
|
|
73
75
|
def ensure_enough_info
|
|
74
|
-
raise UnprocessableEntityError, 'Country field is required.' if
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
raise UnprocessableEntityError, 'Either freeform or address1 is required.' if missing_address1
|
|
79
|
-
|
|
80
|
-
return true if has_postal_code
|
|
81
|
-
|
|
82
|
-
if missing_locality_or_administrative_area
|
|
83
|
-
raise UnprocessableEntityError, 'Insufficient information:'\
|
|
84
|
-
'One or more required fields were not set on the lookup.'
|
|
85
|
-
end
|
|
76
|
+
raise UnprocessableEntityError, 'Country field is required.' if field_is_missing(@country)
|
|
77
|
+
raise UnprocessableEntityError, 'Either freeform or address1 is required.' if field_is_missing(@freeform) && field_is_missing(@address1)
|
|
78
|
+
LanguageMode.from_value(@language) unless @language.nil?
|
|
86
79
|
end
|
|
87
80
|
end
|
|
88
81
|
end
|
|
@@ -2,7 +2,7 @@ module SmartyStreets
|
|
|
2
2
|
module InternationalStreet
|
|
3
3
|
class RootLevel
|
|
4
4
|
attr_reader :input_id, :organization, :address1, :address2, :address3, :address4, :address5, :address6, :address7,
|
|
5
|
-
:address8
|
|
5
|
+
:address8
|
|
6
6
|
|
|
7
7
|
def initialize(obj)
|
|
8
8
|
@input_id = obj.fetch('input_id', nil)
|
|
@@ -15,10 +15,6 @@ module SmartyStreets
|
|
|
15
15
|
@address6 = obj.fetch('address6', nil)
|
|
16
16
|
@address7 = obj.fetch('address7', nil)
|
|
17
17
|
@address8 = obj.fetch('address8', nil)
|
|
18
|
-
@address9 = obj.fetch('address9', nil)
|
|
19
|
-
@address10 = obj.fetch('address10', nil)
|
|
20
|
-
@address11 = obj.fetch('address11', nil)
|
|
21
|
-
@address12 = obj.fetch('address12', nil)
|
|
22
18
|
end
|
|
23
19
|
end
|
|
24
20
|
end
|
|
@@ -47,6 +47,7 @@ module SmartyStreets
|
|
|
47
47
|
request.body = smarty_request.payload
|
|
48
48
|
request['User-Agent'] = "smartystreets (sdk:ruby@#{SmartyStreets::VERSION})"
|
|
49
49
|
request['Referer'] = smarty_request.referer unless smarty_request.referer.nil?
|
|
50
|
+
request.basic_auth(smarty_request.basic_auth[0], smarty_request.basic_auth[1]) unless smarty_request.basic_auth.nil?
|
|
50
51
|
set_custom_headers(smarty_request.header, request)
|
|
51
52
|
request
|
|
52
53
|
end
|
|
@@ -80,10 +81,8 @@ module SmartyStreets
|
|
|
80
81
|
|
|
81
82
|
def self.set_custom_headers(smarty_header, request)
|
|
82
83
|
smarty_header.each do |key, values|
|
|
83
|
-
if values.respond_to?
|
|
84
|
-
values.each
|
|
85
|
-
request.add_field(key, value)
|
|
86
|
-
end
|
|
84
|
+
if values.respond_to?(:each)
|
|
85
|
+
values.each { |value| request.add_field(key, value) }
|
|
87
86
|
else
|
|
88
87
|
request.add_field(key, values)
|
|
89
88
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module SmartyStreets
|
|
2
2
|
class Request
|
|
3
|
-
attr_accessor :parameters, :payload, :url_components, :url_prefix, :referer, :header, :content_type
|
|
3
|
+
attr_accessor :parameters, :payload, :url_components, :url_prefix, :referer, :header, :content_type, :basic_auth
|
|
4
4
|
|
|
5
5
|
def initialize
|
|
6
6
|
@parameters = {}
|
|
@@ -10,6 +10,7 @@ module SmartyStreets
|
|
|
10
10
|
@referer = nil
|
|
11
11
|
@header = {}
|
|
12
12
|
@content_type = 'application/json'
|
|
13
|
+
@basic_auth = nil
|
|
13
14
|
end
|
|
14
15
|
end
|
|
15
16
|
end
|
|
@@ -8,5 +8,18 @@ module SmartyStreets
|
|
|
8
8
|
@header = header
|
|
9
9
|
@error = error
|
|
10
10
|
end
|
|
11
|
+
|
|
12
|
+
# Case-insensitive lookup that works on both plain Hash (used by mocks)
|
|
13
|
+
# and Net::HTTPResponse (returned by the live HTTP transport, which
|
|
14
|
+
# exposes each_header but not each_pair).
|
|
15
|
+
def find_header(name)
|
|
16
|
+
return nil if @header.nil?
|
|
17
|
+
target = name.to_s.downcase
|
|
18
|
+
iterator = @header.respond_to?(:each_header) ? :each_header : :each_pair
|
|
19
|
+
@header.send(iterator) do |k, v|
|
|
20
|
+
return v if k.to_s.downcase == target
|
|
21
|
+
end
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
11
24
|
end
|
|
12
25
|
end
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
require 'timeout'
|
|
3
|
+
|
|
1
4
|
module SmartyStreets
|
|
2
5
|
class RetrySender
|
|
3
6
|
MAX_BACKOFF_DURATION = 10
|
|
4
7
|
STATUS_TOO_MANY_REQUESTS = 429
|
|
5
8
|
STATUS_TO_RETRY = [408, 429, 500, 502, 503, 504]
|
|
9
|
+
TIMEOUT_ERRORS = [Net::OpenTimeout, Net::ReadTimeout, Timeout::Error, Errno::ETIMEDOUT]
|
|
6
10
|
|
|
7
11
|
def initialize(max_retries, inner, sleeper, logger)
|
|
8
12
|
@max_retries = max_retries
|
|
@@ -16,29 +20,44 @@ module SmartyStreets
|
|
|
16
20
|
|
|
17
21
|
(1..@max_retries).each do |i|
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
if timeout_error?(response.error)
|
|
24
|
+
timeout_backoff(i, response.error)
|
|
25
|
+
response = @inner.send(request)
|
|
26
|
+
elsif !STATUS_TO_RETRY.include?(response.status_code.to_i)
|
|
27
|
+
break
|
|
28
|
+
else
|
|
29
|
+
if response.status_code.to_i == STATUS_TOO_MANY_REQUESTS
|
|
30
|
+
seconds_to_backoff = 10
|
|
31
|
+
unless response.header.nil?
|
|
32
|
+
if Integer(response.header["Retry-After"], exception: false)
|
|
33
|
+
seconds_to_backoff = response.header["Retry-After"].to_i
|
|
34
|
+
end
|
|
26
35
|
end
|
|
36
|
+
rate_limit_backoff(seconds_to_backoff)
|
|
37
|
+
else
|
|
38
|
+
backoff(i)
|
|
27
39
|
end
|
|
28
|
-
|
|
29
|
-
else
|
|
30
|
-
backoff(i)
|
|
40
|
+
response = @inner.send(request)
|
|
31
41
|
end
|
|
32
|
-
response = @inner.send(request)
|
|
33
42
|
end
|
|
34
43
|
|
|
35
44
|
response
|
|
36
45
|
end
|
|
37
46
|
|
|
38
|
-
def
|
|
47
|
+
def timeout_error?(error)
|
|
48
|
+
return false if error.nil?
|
|
49
|
+
|
|
50
|
+
TIMEOUT_ERRORS.any? { |klass| error.is_a?(klass) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def timeout_backoff(attempt, error)
|
|
54
|
+
backoff(attempt, "Timeout error (#{error.class}).")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def backoff(attempt, message = 'There was an error processing the request.')
|
|
39
58
|
backoff_duration = [attempt, MAX_BACKOFF_DURATION].min
|
|
40
59
|
|
|
41
|
-
@logger.log("
|
|
60
|
+
@logger.log("#{message} Retrying in #{backoff_duration} seconds...")
|
|
42
61
|
@sleeper.sleep(backoff_duration)
|
|
43
62
|
end
|
|
44
63
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'json'
|
|
1
2
|
require_relative 'exceptions'
|
|
2
3
|
require_relative 'errors'
|
|
3
4
|
|
|
@@ -18,43 +19,59 @@ module SmartyStreets
|
|
|
18
19
|
response
|
|
19
20
|
end
|
|
20
21
|
|
|
22
|
+
private
|
|
23
|
+
|
|
21
24
|
def parse_rate_limit_response(response)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
TooManyRequestsError.new(from_message(response, TOO_MANY_REQUESTS))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def from_message(response, fallback)
|
|
29
|
+
body = response.payload.nil? ? '' : response.payload.to_s.strip
|
|
30
|
+
unless body.empty?
|
|
31
|
+
begin
|
|
32
|
+
errors = JSON.parse(response.payload)["errors"]
|
|
33
|
+
rescue JSON::ParserError, TypeError
|
|
34
|
+
errors = nil
|
|
35
|
+
end
|
|
36
|
+
unless errors.nil? || errors.empty?
|
|
37
|
+
message = errors.map { |error| error["message"] }.join(" ")
|
|
38
|
+
return message unless message.empty?
|
|
27
39
|
end
|
|
28
|
-
error_message.strip!
|
|
29
|
-
end
|
|
30
|
-
if error_message == ""
|
|
31
|
-
error_message = TOO_MANY_REQUESTS
|
|
32
40
|
end
|
|
33
|
-
|
|
41
|
+
|
|
42
|
+
"#{fallback} Body: #{body}".strip
|
|
34
43
|
end
|
|
35
44
|
|
|
36
45
|
def assign_exception(response)
|
|
37
|
-
response.error = case response.status_code
|
|
38
|
-
when '304'
|
|
39
|
-
|
|
46
|
+
response.error = case response.status_code.to_s
|
|
47
|
+
when '200', '304'
|
|
48
|
+
nil
|
|
40
49
|
when '401'
|
|
41
|
-
BadCredentialsError.new(BAD_CREDENTIALS)
|
|
50
|
+
BadCredentialsError.new(from_message(response, BAD_CREDENTIALS))
|
|
42
51
|
when '402'
|
|
43
|
-
PaymentRequiredError.new(PAYMENT_REQUIRED)
|
|
52
|
+
PaymentRequiredError.new(from_message(response, PAYMENT_REQUIRED))
|
|
53
|
+
when '403'
|
|
54
|
+
ForbiddenError.new(from_message(response, FORBIDDEN))
|
|
55
|
+
when '408'
|
|
56
|
+
RequestTimeoutError.new(from_message(response, REQUEST_TIMEOUT))
|
|
44
57
|
when '413'
|
|
45
|
-
RequestEntityTooLargeError.new(REQUEST_ENTITY_TOO_LARGE)
|
|
58
|
+
RequestEntityTooLargeError.new(from_message(response, REQUEST_ENTITY_TOO_LARGE))
|
|
46
59
|
when '400'
|
|
47
|
-
BadRequestError.new(BAD_REQUEST)
|
|
60
|
+
BadRequestError.new(from_message(response, BAD_REQUEST))
|
|
48
61
|
when '422'
|
|
49
|
-
UnprocessableEntityError.new(UNPROCESSABLE_ENTITY)
|
|
62
|
+
UnprocessableEntityError.new(from_message(response, UNPROCESSABLE_ENTITY))
|
|
50
63
|
when '429'
|
|
51
|
-
TooManyRequestsError.new(TOO_MANY_REQUESTS)
|
|
64
|
+
TooManyRequestsError.new(from_message(response, TOO_MANY_REQUESTS))
|
|
52
65
|
when '500'
|
|
53
|
-
InternalServerError.new(INTERNAL_SERVER_ERROR)
|
|
66
|
+
InternalServerError.new(from_message(response, INTERNAL_SERVER_ERROR))
|
|
67
|
+
when '502'
|
|
68
|
+
BadGatewayError.new(from_message(response, BAD_GATEWAY))
|
|
54
69
|
when '503'
|
|
55
|
-
ServiceUnavailableError.new(SERVICE_UNAVAILABLE)
|
|
70
|
+
ServiceUnavailableError.new(from_message(response, SERVICE_UNAVAILABLE))
|
|
71
|
+
when '504'
|
|
72
|
+
GatewayTimeoutError.new(from_message(response, GATEWAY_TIMEOUT))
|
|
56
73
|
else
|
|
57
|
-
|
|
74
|
+
SmartyError.new(from_message(response, "The server returned an unexpected HTTP status code: #{response.status_code}"))
|
|
58
75
|
end
|
|
59
76
|
end
|
|
60
77
|
end
|