smartystreets_ruby_sdk 8.1.1 → 9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: efc93c57b151ba68bae6b932c750aa6dd99c152d8461777be6de4e99bc3c92ab
4
- data.tar.gz: 2109c0a80c243df5141d2569c4d48f19f854cca030a8a5be771fd2128b9aeff4
3
+ metadata.gz: 1f80a7994c3b9e2b9c0936875503a6fe6751db574e64861014ead029f5e1079e
4
+ data.tar.gz: d488f5c6698c089bc147d562793d67e5478b70c23da9d76e8607fe03111b0a93
5
5
  SHA512:
6
- metadata.gz: 23260f7cf247a188e01fb3dca36a0644c8019fb2a72968d2545825cd27044c4785e98722f259415bdf9ef720b0d2a7a9099482a185585bb2c4bd0c92cf789e62
7
- data.tar.gz: 794bc979c32379a448b2330451438c92239e63706cb3a15ddaab0f2738c85736611ca6cd85f583545922c4268aeb22371e8fa7a6944cb161f98db5c425335dfd
6
+ metadata.gz: 739c0edcc1fc7e0a6d4cff83ec12adb0f2f146518e211fe981988d2d4dc18921c16866e911de63612df5e3638a72835bdf6f74a69a8b218c459211941a257c3b
7
+ data.tar.gz: 1f8de619ff3655c99a97d7acb5a3c95d892451c7aab832dd8e0cc88c32ecb12a8b1d6e8237cb3f9032ca7fee8573c43cbdcf805313f10b3c67de8fab42317484
@@ -0,0 +1,10 @@
1
+ name: Ruby Tests
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - '**'
7
+
8
+ jobs:
9
+ test:
10
+ uses: ./.github/workflows/test.yml
@@ -0,0 +1,29 @@
1
+ name: Ruby Gem Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - '**'
7
+
8
+ jobs:
9
+ test:
10
+ uses: ./.github/workflows/test.yml
11
+
12
+ publish:
13
+ needs: test
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v6
17
+ with:
18
+ fetch-depth: 0
19
+
20
+ - uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: '3.3'
23
+ bundler-cache: false
24
+
25
+ - name: Publish
26
+ run: |
27
+ VERSION=${GITHUB_REF#refs/*/} make publish
28
+ env:
29
+ API_KEY: "${{ secrets.GEM_HOST_API_KEY }}"
@@ -0,0 +1,21 @@
1
+ name: Ruby Tests (reusable)
2
+
3
+ on:
4
+ workflow_call:
5
+
6
+ jobs:
7
+ test:
8
+ runs-on: ubuntu-latest
9
+ strategy:
10
+ matrix:
11
+ ruby-version: ['3.3', '3.4', '4.0']
12
+ steps:
13
+ - uses: actions/checkout@v6
14
+ - uses: ruby/setup-ruby@v1
15
+ with:
16
+ ruby-version: ${{ matrix.ruby-version }}
17
+ bundler-cache: false
18
+ - name: Install dependencies
19
+ run: make dependencies
20
+ - name: Test
21
+ run: make test
data/CLAUDE.md CHANGED
@@ -25,7 +25,7 @@ make examples
25
25
 
26
26
  ## Architecture
27
27
 
28
- This is the official SmartyStreets Ruby SDK for address verification APIs. It uses pure Ruby with Net::HTTP (no external runtime dependencies).
28
+ This is the official SmartyStreets Ruby SDK for address verification APIs. It uses pure Ruby with Net::HTTP (no external runtime dependencies). Compatible with Ruby 3.3 and later.
29
29
 
30
30
  ### Core Design Patterns
31
31
 
data/Makefile CHANGED
@@ -12,7 +12,7 @@ test:
12
12
  dependencies:
13
13
  gem install minitest
14
14
 
15
- package: clean dependencies test
15
+ package: clean dependencies
16
16
  sed -i "s/0\.0\.0/${VERSION}/g" "$(VERSION_FILE)" \
17
17
  && gem build *.gemspec \
18
18
  && git checkout "$(VERSION_FILE)"
@@ -37,7 +37,7 @@ us_autocomplete_pro_api:
37
37
  cd examples && ruby us_autocomplete_pro_example.rb
38
38
 
39
39
  us_enrichment_api:
40
- cd examples && ruby us_enrichment_example.rb && ruby us_enrichment_business_example.rb && ruby us_enrichment_etag_example.rb
40
+ cd examples && ruby us_enrichment_example.rb && ruby us_enrichment_business_example.rb && ruby us_enrichment_etag_example.rb && ruby us_enrichment_business_name_search_example.rb
41
41
 
42
42
  us_extract_api:
43
43
  cd examples && ruby us_extract_example.rb
@@ -46,11 +46,14 @@ us_reverse_geo_api:
46
46
  cd examples && ruby us_reverse_geo_example.rb
47
47
 
48
48
  us_street_api:
49
- cd examples && ruby us_street_single_address_example.rb && ruby us_street_multiple_address_example.rb && ruby us_street_component_analysis_example.rb && ruby us_street_iana_timezone_example.rb
49
+ cd examples && ruby us_street_single_address_example.rb && ruby us_street_multiple_address_example.rb && ruby us_street_component_analysis_example.rb && ruby us_street_iana_timezone_example.rb && ruby us_street_match_strategy_example.rb
50
+
51
+ us_street_match_strategy_api:
52
+ cd examples && ruby us_street_match_strategy_example.rb
50
53
 
51
54
  us_zipcode_api:
52
55
  cd examples && ruby us_zipcode_single_lookup_example.rb && ruby us_zipcode_multiple_lookup_example.rb
53
56
 
54
57
  examples: international_autocomplete_api international_postal_code_api international_street_api us_autocomplete_pro_api us_enrichment_api us_extract_api us_reverse_geo_api us_street_api us_zipcode_api
55
58
 
56
- .PHONY: clean test dependencies package publish international_autocomplete_api international_postal_code_api international_street_api us_autocomplete_pro_api us_enrichment_api us_extract_api us_reverse_geo_api us_street_api us_zipcode_api examples
59
+ .PHONY: clean test dependencies package publish international_autocomplete_api international_postal_code_api international_street_api us_autocomplete_pro_api us_enrichment_api us_extract_api us_reverse_geo_api us_street_api us_street_match_strategy_api us_zipcode_api examples
data/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  The official client libraries for accessing SmartyStreets APIs from Ruby
6
6
 
7
+ Compatible with Ruby 3.3 and later.
8
+
7
9
  You may have noticed this page is curiously sparse. Don't panic, there's [documentation](https://smartystreets.com/docs/sdk/ruby) and [examples](examples).
8
10
 
9
11
  [Apache 2.0 License](LICENSE.txt)
@@ -3,9 +3,11 @@ require '../lib/smartystreets_ruby_sdk/static_credentials'
3
3
  require '../lib/smartystreets_ruby_sdk/basic_auth_credentials'
4
4
  require '../lib/smartystreets_ruby_sdk/client_builder'
5
5
  require '../lib/smartystreets_ruby_sdk/us_autocomplete_pro/lookup'
6
+ require '../lib/smartystreets_ruby_sdk/us_autocomplete_pro/source_type'
6
7
 
7
8
  class USAutocompleteProExample
8
9
  Lookup = SmartyStreets::USAutocompletePro::Lookup
10
+ SourceType = SmartyStreets::USAutocompletePro::SourceType
9
11
 
10
12
  def run
11
13
  # key = 'Your SmartyStreets Auth Key here'
@@ -29,7 +31,7 @@ class USAutocompleteProExample
29
31
  lookup.add_city_filter('Orem,UT')
30
32
  lookup.max_results = 5
31
33
  lookup.prefer_ratio = 3
32
- lookup.source = "all"
34
+ lookup.source = SourceType::ALL
33
35
 
34
36
  # lookup.add_custom_parameter('parameter', 'value')
35
37
 
@@ -0,0 +1,76 @@
1
+ require '../lib/smartystreets_ruby_sdk/static_credentials'
2
+ require '../lib/smartystreets_ruby_sdk/shared_credentials'
3
+ require '../lib/smartystreets_ruby_sdk/basic_auth_credentials'
4
+ require '../lib/smartystreets_ruby_sdk/client_builder'
5
+ require '../lib/smartystreets_ruby_sdk/us_enrichment/lookup'
6
+
7
+ class USEnrichmentBusinessNameSearchExample
8
+ def run
9
+ id = ENV['SMARTY_AUTH_ID']
10
+ token = ENV['SMARTY_AUTH_TOKEN']
11
+ credentials = SmartyStreets::BasicAuthCredentials.new(id, token)
12
+
13
+ client = SmartyStreets::ClientBuilder.new(credentials).build_us_enrichment_api_client
14
+
15
+ business_name = "delta air"
16
+
17
+ lookup = SmartyStreets::USEnrichment::Lookup.new
18
+ lookup.business_name = business_name
19
+ lookup.city = "atlanta"
20
+
21
+ begin
22
+ summary_results = client.send_business_lookup(lookup)
23
+ rescue SmartyStreets::SmartyError => err
24
+ puts err
25
+ return
26
+ end
27
+
28
+ if summary_results.nil? || summary_results.empty?
29
+ puts "No response returned for business-name search"
30
+ return
31
+ end
32
+
33
+ summary = summary_results[0]
34
+ if summary.businesses.nil? || summary.businesses.empty?
35
+ puts "No businesses found for this business name search"
36
+ return
37
+ end
38
+
39
+ puts "Summary results for BusinessName: #{business_name}"
40
+ summary.businesses.each do |biz|
41
+ puts " - #{biz.company_name} (ID: #{biz.business_id})"
42
+ end
43
+
44
+ first = summary.businesses[0]
45
+ puts
46
+ puts "Fetching details for business: #{first.company_name} (ID: #{first.business_id})"
47
+
48
+ begin
49
+ detail_result = client.send_business_detail_lookup(first.business_id)
50
+ rescue SmartyStreets::SmartyError => err
51
+ puts err
52
+ return
53
+ end
54
+
55
+ if detail_result.nil?
56
+ puts "No detail result returned"
57
+ return
58
+ end
59
+
60
+ puts
61
+ puts "Detail result:"
62
+ puts " smarty_key: #{detail_result.smarty_key}"
63
+ puts " data_set_name: #{detail_result.data_set_name}"
64
+ puts " business_id: #{detail_result.business_id}"
65
+ attrs = detail_result.attributes
66
+ return if attrs.nil?
67
+ attrs.instance_variables.each do |var|
68
+ value = attrs.instance_variable_get(var)
69
+ next if value.nil?
70
+ puts " #{var.to_s.delete('@')}: #{value}"
71
+ end
72
+ end
73
+ end
74
+
75
+ example = USEnrichmentBusinessNameSearchExample.new
76
+ example.run
@@ -40,10 +40,12 @@ class USEnrichmentEtagExample
40
40
  second = SmartyStreets::USEnrichment::Lookup.new(smarty_key, 'business')
41
41
  second.request_etag = captured_etag
42
42
  begin
43
- @client.send_business_lookup(second)
44
- puts " Call 2 (matching Etag): 200 - server did NOT honor the conditional. Etag=#{display(second.response_etag)}"
45
- rescue SmartyStreets::NotModifiedInfo => ex
46
- puts " Call 2 (matching Etag): 304 NotModifiedInfo - caller treats this as cache-valid. Refreshed Etag=#{display(ex.response_etag)}"
43
+ results = @client.send_business_lookup(second)
44
+ if results.empty?
45
+ puts " Call 2 (matching Etag): 304 not modified - cache is still valid. Refreshed Etag=#{display(second.response_etag)}"
46
+ else
47
+ puts " Call 2 (matching Etag): 200 - server did NOT honor the conditional. Etag=#{display(second.response_etag)}"
48
+ end
47
49
  rescue SmartyStreets::SmartyError => err
48
50
  puts " Call 2 unexpected failure: #{err.class}: #{err.message}"
49
51
  return nil
@@ -52,10 +54,12 @@ class USEnrichmentEtagExample
52
54
  third = SmartyStreets::USEnrichment::Lookup.new(smarty_key, 'business')
53
55
  third.request_etag = captured_etag + "X"
54
56
  begin
55
- @client.send_business_lookup(third)
56
- puts " Call 3 (mutated Etag): 200 as expected. Etag=#{display(third.response_etag)}"
57
- rescue SmartyStreets::NotModifiedInfo
58
- puts " Call 3 (mutated Etag): 304 - UNEXPECTED. Server treated a different Etag as matching."
57
+ results = @client.send_business_lookup(third)
58
+ if results.empty?
59
+ puts " Call 3 (mutated Etag): 304 - UNEXPECTED. Server treated a different Etag as matching."
60
+ else
61
+ puts " Call 3 (mutated Etag): 200 as expected. Etag=#{display(third.response_etag)}"
62
+ end
59
63
  rescue SmartyStreets::SmartyError => err
60
64
  puts " Call 3 unexpected failure: #{err.class}: #{err.message}"
61
65
  end
@@ -86,9 +90,11 @@ class USEnrichmentEtagExample
86
90
  second.request_etag = captured_etag
87
91
  begin
88
92
  @client.send_business_detail_lookup(second)
89
- puts " Call 2 (matching Etag): 200 - server did NOT honor the conditional. Etag=#{display(second.response_etag)}"
90
- rescue SmartyStreets::NotModifiedInfo => ex
91
- puts " Call 2 (matching Etag): 304 NotModifiedInfo - caller treats this as cache-valid. Refreshed Etag=#{display(ex.response_etag)}"
93
+ if second.result.nil?
94
+ puts " Call 2 (matching Etag): 304 not modified - cache is still valid. Refreshed Etag=#{display(second.response_etag)}"
95
+ else
96
+ puts " Call 2 (matching Etag): 200 - server did NOT honor the conditional. Etag=#{display(second.response_etag)}"
97
+ end
92
98
  rescue SmartyStreets::SmartyError => err
93
99
  puts " Call 2 unexpected failure: #{err.class}: #{err.message}"
94
100
  return
@@ -98,9 +104,11 @@ class USEnrichmentEtagExample
98
104
  third.request_etag = captured_etag + "X"
99
105
  begin
100
106
  @client.send_business_detail_lookup(third)
101
- puts " Call 3 (mutated Etag): 200 as expected. Etag=#{display(third.response_etag)}"
102
- rescue SmartyStreets::NotModifiedInfo
103
- puts " Call 3 (mutated Etag): 304 - UNEXPECTED. Server treated a different Etag as matching."
107
+ if third.result.nil?
108
+ puts " Call 3 (mutated Etag): 304 - UNEXPECTED. Server treated a different Etag as matching."
109
+ else
110
+ puts " Call 3 (mutated Etag): 200 as expected. Etag=#{display(third.response_etag)}"
111
+ end
104
112
  rescue SmartyStreets::SmartyError => err
105
113
  puts " Call 3 unexpected failure: #{err.class}: #{err.message}"
106
114
  end
@@ -154,7 +154,7 @@ class USEnrichmentAddressExample
154
154
  end
155
155
 
156
156
  def one_liner(obj)
157
- obj.instance_variables.filter_map do |var|
157
+ obj.instance_variables.filter do |var|
158
158
  value = obj.instance_variable_get(var)
159
159
  "#{var.to_s.delete('@')}=#{value}" unless value.nil?
160
160
  end.join(' ')
@@ -3,9 +3,11 @@ require '../lib/smartystreets_ruby_sdk/shared_credentials'
3
3
  require '../lib/smartystreets_ruby_sdk/basic_auth_credentials'
4
4
  require '../lib/smartystreets_ruby_sdk/client_builder'
5
5
  require '../lib/smartystreets_ruby_sdk/us_reverse_geo/lookup'
6
+ require '../lib/smartystreets_ruby_sdk/us_reverse_geo/source_type'
6
7
 
7
8
  class USReverseGeoExample
8
9
  Lookup = SmartyStreets::USReverseGeo::Lookup
10
+ SourceType = SmartyStreets::USReverseGeo::SourceType
9
11
 
10
12
  def run
11
13
  # key = 'Your SmartyStreets Auth Key here'
@@ -25,6 +27,7 @@ class USReverseGeoExample
25
27
  # https://smartystreets.com/docs/cloud/us-reverse-geo-api#http-request-input-fields
26
28
 
27
29
  lookup = Lookup.new(40.111111, -111.111111)
30
+ lookup.source = SourceType::ALL
28
31
 
29
32
  # lookup.add_custom_parameter('parameter', 'value')
30
33
 
@@ -0,0 +1,85 @@
1
+ require '../lib/smartystreets_ruby_sdk/static_credentials'
2
+ require '../lib/smartystreets_ruby_sdk/shared_credentials'
3
+ require '../lib/smartystreets_ruby_sdk/basic_auth_credentials'
4
+ require '../lib/smartystreets_ruby_sdk/client_builder'
5
+ require '../lib/smartystreets_ruby_sdk/batch'
6
+ require '../lib/smartystreets_ruby_sdk/us_street/lookup'
7
+ require '../lib/smartystreets_ruby_sdk/us_street/match_type'
8
+
9
+ class USStreetLookupsWithMatchStrategyExample
10
+ Lookup = SmartyStreets::USStreet::Lookup
11
+ MatchType = SmartyStreets::USStreet::MatchType
12
+
13
+ def run
14
+ # We recommend storing your secret keys in environment variables instead---it's safer!
15
+ id = ENV['SMARTY_AUTH_ID']
16
+ token = ENV['SMARTY_AUTH_TOKEN']
17
+ credentials = SmartyStreets::BasicAuthCredentials.new(id, token)
18
+ client = SmartyStreets::ClientBuilder.new(credentials).build_us_street_api_client
19
+
20
+ # Each address is run through all three match strategies so you can compare how
21
+ # 'strict', 'enhanced', and 'invalid' each handle a valid, an invalid, and an
22
+ # ambiguous address.
23
+ # - strict: only returns candidates that are valid, mailable addresses.
24
+ # - enhanced: returns a more comprehensive dataset (requires a US Core or Rooftop license).
25
+ # - invalid: most permissive; always returns at least one candidate (a best-guess standardization).
26
+ # Documentation for input fields: https://smartystreets.com/docs/cloud/us-street-api
27
+ addresses = [
28
+ ['valid (real, deliverable)', '1600 Amphitheatre Pkwy', 'Mountain View', 'CA', '94043'],
29
+ ['invalid (no such address)', '9999 W 1150 S', 'Provo', 'UT', '84601'],
30
+ ['ambiguous (missing ZIP/unit)', '1 Rosedale St', 'Baltimore', 'MD', '']
31
+ ]
32
+ strategies = [MatchType::STRICT, MatchType::ENHANCED, MatchType::INVALID]
33
+
34
+ batch = SmartyStreets::Batch.new
35
+ cases = [] # parallel metadata for each lookup, in the order they are added to the batch
36
+
37
+ addresses.each do |label, street, city, state, zipcode|
38
+ strategies.each do |strategy|
39
+ lookup = Lookup.new
40
+ lookup.street = street
41
+ lookup.city = city
42
+ lookup.state = state
43
+ lookup.zipcode = zipcode
44
+ lookup.match = strategy
45
+ lookup.candidates = 10 # allow ambiguous addresses to return more than one match
46
+ batch.add(lookup)
47
+ cases << [label, "#{street}, #{city}, #{state}", strategy]
48
+ end
49
+ end
50
+
51
+ begin
52
+ client.send_batch(batch)
53
+ rescue SmartyStreets::SmartyError => err
54
+ puts err
55
+ return
56
+ end
57
+
58
+ last_address = nil
59
+ batch.each_with_index do |lookup, i|
60
+ label, address_display, strategy = cases[i]
61
+
62
+ unless address_display == last_address
63
+ puts "\n" + ('=' * 70)
64
+ puts " Address: #{address_display} [#{label}]"
65
+ puts('=' * 70)
66
+ last_address = address_display
67
+ end
68
+
69
+ candidates = lookup.result
70
+ puts "\n--- '#{strategy}' strategy ---"
71
+
72
+ if candidates.empty?
73
+ puts ' 0 candidates - no match returned under this strategy.'
74
+ next
75
+ end
76
+
77
+ puts " #{candidates.length} candidate(s):"
78
+ candidates.each do |candidate|
79
+ puts " [#{candidate.candidate_index}] #{candidate.delivery_line_1} #{candidate.last_line}"
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ USStreetLookupsWithMatchStrategyExample.new.run
@@ -1,28 +1,33 @@
1
1
  module SmartyStreets
2
- NOT_MODIFIED = 'Not Modified: This data has not been modified since the last request. Please remove the etag header to fetch this data'.freeze
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
- for the account associated with the credentials submitted with the request.'.freeze
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 = 'Because the international service is currently in a limited release phase, only approved accounts' \
11
- ' may access the service.'.freeze
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 street field or the request body of a
16
- POST request contained malformed JSON, possibly because a value was submitted as a number rather than as a string.'.freeze
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
- 'A serious, yet rare occurrence indeed.'.freeze
32
+ 'A serious, yet rare occurrence indeed.'.freeze
28
33
  end
@@ -18,6 +18,12 @@ module SmartyStreets
18
18
  class ForbiddenError < SmartyError
19
19
  end
20
20
 
21
+ class RequestTimeoutError < SmartyError
22
+ end
23
+
24
+ class BadGatewayError < SmartyError
25
+ end
26
+
21
27
  class PaymentRequiredError < SmartyError
22
28
  end
23
29
 
@@ -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,:administrative_area_short, :administrative_area_long, :post_box,
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
- @administrative_area_short = obj.fetch('administrative_area_short', nil)
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)
@@ -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, :address9, :address10, :address11, :address12
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
@@ -7,6 +7,7 @@ module SmartyStreets
7
7
  end
8
8
 
9
9
  def deserialize(payload)
10
+ return {} if payload.nil? || payload.empty?
10
11
  JSON.load(payload)
11
12
  end
12
13
  end
@@ -22,38 +22,38 @@ module SmartyStreets
22
22
  private
23
23
 
24
24
  def parse_rate_limit_response(response)
25
- error_message = ""
26
- if !response.payload.nil?
27
- response_json = JSON.parse(response.payload)
28
- response_json["errors"].each do |error|
29
- error_message += (" " + error["message"])
30
- end
31
- error_message.strip!
32
- end
33
- if error_message == ""
34
- error_message = TOO_MANY_REQUESTS
35
- end
36
- TooManyRequestsError.new(error_message)
25
+ TooManyRequestsError.new(from_message(response, TOO_MANY_REQUESTS))
37
26
  end
38
27
 
39
28
  def from_message(response, fallback)
40
- return fallback if response.payload.nil?
41
-
42
- errors = JSON.parse(response.payload)["errors"]
43
- return fallback if errors.nil? || errors.empty?
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?
39
+ end
40
+ end
44
41
 
45
- message = errors.map { |error| error["message"] }.join(" ")
46
- message.empty? ? fallback : message
42
+ "#{fallback} Body: #{body}".strip
47
43
  end
48
44
 
49
45
  def assign_exception(response)
50
- response.error = case response.status_code
51
- when '304'
52
- NotModifiedInfo.new(NOT_MODIFIED, response.find_header('etag'))
46
+ response.error = case response.status_code.to_s
47
+ when '200', '304'
48
+ nil
53
49
  when '401'
54
50
  BadCredentialsError.new(from_message(response, BAD_CREDENTIALS))
55
51
  when '402'
56
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))
57
57
  when '413'
58
58
  RequestEntityTooLargeError.new(from_message(response, REQUEST_ENTITY_TOO_LARGE))
59
59
  when '400'
@@ -61,13 +61,17 @@ module SmartyStreets
61
61
  when '422'
62
62
  UnprocessableEntityError.new(from_message(response, UNPROCESSABLE_ENTITY))
63
63
  when '429'
64
- TooManyRequestsError.new(TOO_MANY_REQUESTS)
64
+ TooManyRequestsError.new(from_message(response, TOO_MANY_REQUESTS))
65
65
  when '500'
66
- 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))
67
69
  when '503'
68
- ServiceUnavailableError.new(SERVICE_UNAVAILABLE)
70
+ ServiceUnavailableError.new(from_message(response, SERVICE_UNAVAILABLE))
71
+ when '504'
72
+ GatewayTimeoutError.new(from_message(response, GATEWAY_TIMEOUT))
69
73
  else
70
- nil
74
+ SmartyError.new(from_message(response, "The server returned an unexpected HTTP status code: #{response.status_code}"))
71
75
  end
72
76
  end
73
77
  end
@@ -0,0 +1,8 @@
1
+ module SmartyStreets
2
+ module USAutocompletePro
3
+ module SourceType
4
+ ALL = 'all'.freeze
5
+ POSTAL = 'postal'.freeze
6
+ end
7
+ end
8
+ end
@@ -1,5 +1,6 @@
1
1
  require_relative './us_autocomplete_pro/lookup'
2
2
  require_relative './us_autocomplete_pro/geolocation_type'
3
+ require_relative './us_autocomplete_pro/source_type'
3
4
  require_relative './us_autocomplete_pro/suggestion'
4
5
  require_relative './us_autocomplete_pro/client'
5
6
 
@@ -84,6 +84,7 @@ module SmartyStreets
84
84
 
85
85
  response = @sender.send(smarty_request)
86
86
  capture_response_etag(response, lookup)
87
+ return lookup.result if response.status_code.to_s == '304'
87
88
  results = @serializer.deserialize(response.payload)
88
89
 
89
90
  results = [] if results.nil?
@@ -114,8 +115,8 @@ module SmartyStreets
114
115
  private
115
116
 
116
117
  def __send(lookup)
117
- if lookup.nil? || (blank?(lookup.smarty_key) && blank?(lookup.street) && blank?(lookup.freeform))
118
- raise SmartyError.new("Lookup requires one of 'smartykey', 'street', or 'freeform' to be set")
118
+ if lookup.nil? || (blank?(lookup.smarty_key) && blank?(lookup.street) && blank?(lookup.freeform) && blank?(lookup.business_name))
119
+ raise SmartyError.new("Lookup requires one of 'smarty_key', 'street', 'freeform', or 'business_name' to be set")
119
120
  end
120
121
 
121
122
  smarty_request = Request.new
@@ -135,6 +136,7 @@ module SmartyStreets
135
136
  add_parameter(smarty_request, 'city', lookup.city)
136
137
  add_parameter(smarty_request, 'state', lookup.state)
137
138
  add_parameter(smarty_request, 'zipcode', lookup.zipcode)
139
+ add_parameter(smarty_request, 'business_name', lookup.business_name)
138
140
  else
139
141
  if (lookup.data_sub_set.nil?)
140
142
  smarty_request.url_components = '/' + lookup.smarty_key + '/' + lookup.data_set
@@ -147,6 +149,7 @@ module SmartyStreets
147
149
 
148
150
  response = @sender.send(smarty_request)
149
151
  capture_response_etag(response, lookup)
152
+ return [] if response.status_code.to_s == '304'
150
153
  results = @serializer.deserialize(response.payload)
151
154
 
152
155
  results = [] if results.nil?
@@ -28,9 +28,9 @@ module SmartyStreets
28
28
  end
29
29
 
30
30
  class Lookup < LookupBase
31
- attr_accessor :smarty_key, :data_set, :data_sub_set, :freeform, :street, :city, :state, :zipcode
31
+ attr_accessor :smarty_key, :data_set, :data_sub_set, :freeform, :street, :city, :state, :zipcode, :business_name
32
32
 
33
- def initialize(smarty_key=nil, data_set=nil, data_sub_set=nil, freeform=nil, street=nil, city=nil, state=nil, zipcode=nil, request_etag=nil, features=nil)
33
+ def initialize(smarty_key=nil, data_set=nil, data_sub_set=nil, freeform=nil, street=nil, city=nil, state=nil, zipcode=nil, request_etag=nil, features=nil, business_name=nil)
34
34
  super()
35
35
  @smarty_key = smarty_key
36
36
  @data_set = data_set
@@ -42,6 +42,7 @@ module SmartyStreets
42
42
  @zipcode = zipcode
43
43
  @request_etag = request_etag
44
44
  @features = features
45
+ @business_name = business_name
45
46
  end
46
47
  end
47
48
 
@@ -0,0 +1,8 @@
1
+ module SmartyStreets
2
+ module USReverseGeo
3
+ module SourceType
4
+ ALL = 'all'.freeze
5
+ POSTAL = 'postal'.freeze
6
+ end
7
+ end
8
+ end
@@ -8,7 +8,7 @@ module SmartyStreets
8
8
  def initialize(obj)
9
9
  @results = []
10
10
 
11
- obj['results'].each do |result|
11
+ obj.fetch('results', []).each do |result|
12
12
  @results.push(Result.new(result))
13
13
  end
14
14
  end
@@ -1,4 +1,5 @@
1
1
  require_relative './us_reverse_geo/address'
2
+ require_relative './us_reverse_geo/source_type'
2
3
  require_relative './us_reverse_geo/client'
3
4
  require_relative './us_reverse_geo/coordinate'
4
5
  require_relative './us_reverse_geo/lookup'
@@ -1,3 +1,3 @@
1
1
  module SmartyStreets
2
- VERSION = '8.1.1' # DO NOT EDIT (this is updated by a build job when a new release is published)
2
+ VERSION = '9.0.0' # DO NOT EDIT (this is updated by a build job when a new release is published)
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smartystreets_ruby_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.1.1
4
+ version: 9.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SmartyStreets SDK Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-06-08 00:00:00.000000000 Z
11
+ date: 2026-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,7 +80,9 @@ extensions: []
80
80
  extra_rdoc_files: []
81
81
  files:
82
82
  - ".claude/settings.json"
83
- - ".github/workflows/gem-publish.yml"
83
+ - ".github/workflows/ci.yml"
84
+ - ".github/workflows/publish.yml"
85
+ - ".github/workflows/test.yml"
84
86
  - ".gitignore"
85
87
  - CHANGELOG.md
86
88
  - CLAUDE.md
@@ -98,12 +100,14 @@ files:
98
100
  - examples/international_postal_code_example.rb
99
101
  - examples/us_autocomplete_pro_example.rb
100
102
  - examples/us_enrichment_business_example.rb
103
+ - examples/us_enrichment_business_name_search_example.rb
101
104
  - examples/us_enrichment_etag_example.rb
102
105
  - examples/us_enrichment_example.rb
103
106
  - examples/us_extract_example.rb
104
107
  - examples/us_reverse_geo_example.rb
105
108
  - examples/us_street_component_analysis_example.rb
106
109
  - examples/us_street_iana_timezone_example.rb
110
+ - examples/us_street_match_strategy_example.rb
107
111
  - examples/us_street_multiple_address_example.rb
108
112
  - examples/us_street_single_address_example.rb
109
113
  - examples/us_zipcode_multiple_lookup_example.rb
@@ -153,6 +157,7 @@ files:
153
157
  - lib/smartystreets_ruby_sdk/us_autocomplete_pro/client.rb
154
158
  - lib/smartystreets_ruby_sdk/us_autocomplete_pro/geolocation_type.rb
155
159
  - lib/smartystreets_ruby_sdk/us_autocomplete_pro/lookup.rb
160
+ - lib/smartystreets_ruby_sdk/us_autocomplete_pro/source_type.rb
156
161
  - lib/smartystreets_ruby_sdk/us_autocomplete_pro/suggestion.rb
157
162
  - lib/smartystreets_ruby_sdk/us_enrichment.rb
158
163
  - lib/smartystreets_ruby_sdk/us_enrichment/business/detail/attributes.rb
@@ -188,6 +193,7 @@ files:
188
193
  - lib/smartystreets_ruby_sdk/us_reverse_geo/coordinate.rb
189
194
  - lib/smartystreets_ruby_sdk/us_reverse_geo/lookup.rb
190
195
  - lib/smartystreets_ruby_sdk/us_reverse_geo/result.rb
196
+ - lib/smartystreets_ruby_sdk/us_reverse_geo/source_type.rb
191
197
  - lib/smartystreets_ruby_sdk/us_reverse_geo/us_reverse_geo_response.rb
192
198
  - lib/smartystreets_ruby_sdk/us_street.rb
193
199
  - lib/smartystreets_ruby_sdk/us_street/analysis.rb
@@ -227,7 +233,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
227
233
  - !ruby/object:Gem::Version
228
234
  version: '0'
229
235
  requirements: []
230
- rubygems_version: 3.4.19
236
+ rubygems_version: 3.5.22
231
237
  signing_key:
232
238
  specification_version: 4
233
239
  summary: An official library for the SmartyStreets APIs
@@ -1,27 +0,0 @@
1
- name: Ruby Gem Publish
2
-
3
- on:
4
- push:
5
- tags:
6
- - '*'
7
- jobs:
8
- publish:
9
- runs-on: ubuntu-latest
10
- env:
11
- GEM_HOST_API_KEY: ${{ secrets.GEM_HOST_API_KEY }}
12
-
13
- steps:
14
- - uses: actions/checkout@v3
15
- with:
16
- fetch-depth: 0
17
-
18
- - uses: ruby/setup-ruby@v1
19
- with:
20
- ruby-version: '3.2'
21
- bundler-cache: false
22
-
23
- - name: Publish
24
- run: |
25
- VERSION=${GITHUB_REF#refs/*/} API_KEY=${{ secrets.GEM_HOST_API_KEY }} make publish
26
- env:
27
- API_KEY: "${{ secrets.GEM_HOST_API_KEY }}"