truaddress 1.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 +7 -0
- data/README.md +311 -0
- data/lib/truaddress/client.rb +371 -0
- data/lib/truaddress/error.rb +26 -0
- data/lib/truaddress/version.rb +5 -0
- data/lib/truaddress.rb +29 -0
- metadata +130 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f9fae5b2d1a641613378d6a24fa8477f01428983bc37234de5eb35f12ec1f3a4
|
|
4
|
+
data.tar.gz: 2687eb72b7182e779bf9be66b3846e0449710d4c4edb9b6feba2db737abeffae
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f1a85b789f2a1537f53f6e78f7ed0e7b6d2cf9d9fb1a74cd88e736dd8cd14ad93f03d86e9d15ba62f93e0f3ec16df4fe6b52b077616680ea1a363fe4c17a61e7
|
|
7
|
+
data.tar.gz: bc548b44f3219cf093296bafef7a0532e7f396ef2d086321181c1516f43026e64eccfeeb2966d3c654a9f51706862db9875166cdc3a58056d7db4fd98a575065
|
data/README.md
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# TruAddress Ruby SDK
|
|
2
|
+
|
|
3
|
+
Official Ruby SDK for [TruAddress](https://truaddress.net) - Address validation, autocomplete, and geocoding API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add to your Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'truaddress'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then run:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install directly:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
gem install truaddress
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
require 'truaddress'
|
|
29
|
+
|
|
30
|
+
client = Truaddress::Client.new(api_key: "YOUR_API_KEY")
|
|
31
|
+
|
|
32
|
+
# Validate a US address
|
|
33
|
+
results = client.us_street(
|
|
34
|
+
street: "1600 Pennsylvania Ave NW",
|
|
35
|
+
city: "Washington",
|
|
36
|
+
state: "DC",
|
|
37
|
+
zipcode: "20500"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if results.any? && Truaddress::Client.deliverable?(results[0])
|
|
41
|
+
puts "Valid address: #{results[0]['delivery_line_1']}"
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
### Per-client Configuration
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
client = Truaddress::Client.new(
|
|
51
|
+
api_key: "YOUR_API_KEY",
|
|
52
|
+
base_url: "https://truaddress.net" # Optional
|
|
53
|
+
)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Global Configuration
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
Truaddress.configure do |config|
|
|
60
|
+
config.api_key = "YOUR_API_KEY"
|
|
61
|
+
config.base_url = "https://truaddress.net"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Then use the global client
|
|
65
|
+
results = Truaddress.client.us_street(street: "350 5th Ave", city: "New York", state: "NY")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Available Methods
|
|
69
|
+
|
|
70
|
+
### US Endpoints
|
|
71
|
+
|
|
72
|
+
#### `us_street` - US Street Address Validation
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
results = client.us_street(
|
|
76
|
+
street: "350 5th Avenue",
|
|
77
|
+
city: "New York",
|
|
78
|
+
state: "NY",
|
|
79
|
+
zipcode: "10118"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
puts results[0]["analysis"]["dpv_match_code"] # => "Y" (Deliverable)
|
|
83
|
+
puts results[0]["metadata"]["latitude"] # => 40.748535
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**DPV Match Codes:**
|
|
87
|
+
- `Y` - Confirmed deliverable
|
|
88
|
+
- `S` - Secondary (apt/suite) missing
|
|
89
|
+
- `D` - Secondary invalid
|
|
90
|
+
- `N` - Not deliverable
|
|
91
|
+
|
|
92
|
+
#### `us_zipcode` - ZIP Code Lookup
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
# By ZIP code
|
|
96
|
+
result = client.us_zipcode(zipcode: "90210")
|
|
97
|
+
puts result[0]["city_states"][0]["city"] # => "Beverly Hills"
|
|
98
|
+
|
|
99
|
+
# By city/state
|
|
100
|
+
result = client.us_zipcode(city: "Austin", state: "TX")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### `us_autocomplete` - Address Autocomplete
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
result = client.us_autocomplete(
|
|
107
|
+
search: "350 5th Ave",
|
|
108
|
+
max_results: 5,
|
|
109
|
+
state_filter: ["NY"]
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
result["suggestions"].each do |s|
|
|
113
|
+
puts "#{s['street_line']}, #{s['city']}, #{s['state']} #{s['zipcode']}"
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### `us_extract` - Extract Addresses from Text
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
result = client.us_extract("Ship to: 350 5th Avenue, New York, NY 10118. Thanks!")
|
|
121
|
+
|
|
122
|
+
puts result["meta"]["address_count"] # => 1
|
|
123
|
+
puts result["addresses"][0]["verified"] # => true
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### `us_reverse_geo` - Reverse Geocoding
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
result = client.us_reverse_geo(
|
|
130
|
+
latitude: 40.748535,
|
|
131
|
+
longitude: -73.9856571
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
result["results"].each do |r|
|
|
135
|
+
puts "#{r['address']['street']} (#{r['distance'].round}m away)"
|
|
136
|
+
end
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### International Endpoints
|
|
140
|
+
|
|
141
|
+
#### `intl_street` - International Address Validation
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
results = client.intl_street(
|
|
145
|
+
country: "GBR",
|
|
146
|
+
freeform: "10 Downing Street, London"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
puts results[0]["components"]["postal_code"] # => "SW1A 2AB"
|
|
150
|
+
puts Truaddress::Client.verified?(results[0]) # => true
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### `intl_autocomplete` - International Autocomplete
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
result = client.intl_autocomplete(
|
|
157
|
+
search: "Champs Elysees",
|
|
158
|
+
country: "FRA",
|
|
159
|
+
max_results: 5
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Core Endpoints
|
|
164
|
+
|
|
165
|
+
#### `validate` - Global Address Validation
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
results = client.validate(
|
|
169
|
+
country: "US",
|
|
170
|
+
address1: "350 5th Avenue",
|
|
171
|
+
locality: "New York",
|
|
172
|
+
administrative_area: "NY",
|
|
173
|
+
postal_code: "10118"
|
|
174
|
+
)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### `correct` - Address Correction
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
results = client.correct(freeform: "1600 pennsylvania ave washington dc 20500")
|
|
181
|
+
puts results[0]["address1"] # Corrected address
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### `autocomplete` - Global Autocomplete
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
result = client.autocomplete(q: "Buckingham Palace London", limit: 5)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Helper Methods
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
# Check if US address is deliverable
|
|
194
|
+
Truaddress::Client.deliverable?(result) # DPV code = 'Y'
|
|
195
|
+
|
|
196
|
+
# Check if US address is a mail drop (CMRA)
|
|
197
|
+
Truaddress::Client.cmra?(result)
|
|
198
|
+
|
|
199
|
+
# Check if US address is vacant
|
|
200
|
+
Truaddress::Client.vacant?(result)
|
|
201
|
+
|
|
202
|
+
# Check if international address is verified
|
|
203
|
+
Truaddress::Client.verified?(result)
|
|
204
|
+
|
|
205
|
+
# Format addresses as strings
|
|
206
|
+
Truaddress::Client.format_us_address(result) # "350 5th Ave, New York NY 10118"
|
|
207
|
+
Truaddress::Client.format_intl_address(result) # "10 Downing Street, London, SW1A 2AB"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Error Handling
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
begin
|
|
214
|
+
results = client.us_street(street: "123 Main St")
|
|
215
|
+
rescue Truaddress::AuthenticationError => e
|
|
216
|
+
puts "Auth failed: #{e.message}"
|
|
217
|
+
rescue Truaddress::ValidationError => e
|
|
218
|
+
puts "Invalid request: #{e.message}"
|
|
219
|
+
rescue Truaddress::RateLimitError => e
|
|
220
|
+
puts "Rate limited: #{e.message}"
|
|
221
|
+
rescue Truaddress::Error => e
|
|
222
|
+
puts "Error: #{e.message} (#{e.status_code})"
|
|
223
|
+
end
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Rails Integration
|
|
227
|
+
|
|
228
|
+
### Configuration (config/initializers/truaddress.rb)
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
Truaddress.configure do |config|
|
|
232
|
+
config.api_key = Rails.application.credentials.truaddress_api_key
|
|
233
|
+
end
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Controller Example
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
class AddressesController < ApplicationController
|
|
240
|
+
def validate
|
|
241
|
+
results = Truaddress.client.us_street(
|
|
242
|
+
street: params[:street],
|
|
243
|
+
city: params[:city],
|
|
244
|
+
state: params[:state],
|
|
245
|
+
zipcode: params[:zipcode]
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
if results.any? && Truaddress::Client.deliverable?(results.first)
|
|
249
|
+
render json: { valid: true, address: results.first }
|
|
250
|
+
else
|
|
251
|
+
render json: { valid: false, message: "Address not deliverable" }
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Model Validation
|
|
258
|
+
|
|
259
|
+
```ruby
|
|
260
|
+
class Order < ApplicationRecord
|
|
261
|
+
validate :validate_shipping_address
|
|
262
|
+
|
|
263
|
+
private
|
|
264
|
+
|
|
265
|
+
def validate_shipping_address
|
|
266
|
+
return if shipping_street.blank?
|
|
267
|
+
|
|
268
|
+
results = Truaddress.client.us_street(
|
|
269
|
+
street: shipping_street,
|
|
270
|
+
city: shipping_city,
|
|
271
|
+
state: shipping_state,
|
|
272
|
+
zipcode: shipping_zipcode
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
unless results.any? && Truaddress::Client.deliverable?(results.first)
|
|
276
|
+
errors.add(:shipping_address, "is not a valid deliverable address")
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### ActiveJob for Batch Processing
|
|
283
|
+
|
|
284
|
+
```ruby
|
|
285
|
+
class ValidateAddressesJob < ApplicationJob
|
|
286
|
+
def perform(customer_ids)
|
|
287
|
+
Customer.where(id: customer_ids).find_each do |customer|
|
|
288
|
+
results = Truaddress.client.us_street(
|
|
289
|
+
street: customer.street,
|
|
290
|
+
city: customer.city,
|
|
291
|
+
state: customer.state,
|
|
292
|
+
zipcode: customer.zipcode
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
customer.update(
|
|
296
|
+
address_valid: results.any? && Truaddress::Client.deliverable?(results.first),
|
|
297
|
+
address_validated_at: Time.current
|
|
298
|
+
)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Requirements
|
|
305
|
+
|
|
306
|
+
- Ruby >= 2.7.0
|
|
307
|
+
- faraday >= 1.0
|
|
308
|
+
|
|
309
|
+
## License
|
|
310
|
+
|
|
311
|
+
MIT
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Truaddress
|
|
7
|
+
# TruAddress API Client
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# client = Truaddress::Client.new(api_key: "YOUR_API_KEY")
|
|
11
|
+
# results = client.us_street(street: "1600 Pennsylvania Ave NW", city: "Washington", state: "DC")
|
|
12
|
+
#
|
|
13
|
+
class Client
|
|
14
|
+
attr_reader :api_key, :base_url
|
|
15
|
+
|
|
16
|
+
# Initialize a new client
|
|
17
|
+
#
|
|
18
|
+
# @param api_key [String] Your TruAddress API key
|
|
19
|
+
# @param base_url [String] Base URL for the API (default: https://truaddress.net)
|
|
20
|
+
def initialize(api_key:, base_url: nil)
|
|
21
|
+
raise AuthenticationError, "API key is required" if api_key.nil? || api_key.empty?
|
|
22
|
+
|
|
23
|
+
@api_key = api_key
|
|
24
|
+
@base_url = base_url || "https://truaddress.net"
|
|
25
|
+
@conn = build_connection
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# ==========================================================================
|
|
29
|
+
# US Endpoints
|
|
30
|
+
# ==========================================================================
|
|
31
|
+
|
|
32
|
+
# Validate and standardize US street addresses
|
|
33
|
+
#
|
|
34
|
+
# @param street [String] Primary street address (required)
|
|
35
|
+
# @param street2 [String] Secondary address line
|
|
36
|
+
# @param secondary [String] Secondary designator (apt, suite, etc.)
|
|
37
|
+
# @param city [String] City name
|
|
38
|
+
# @param state [String] State abbreviation
|
|
39
|
+
# @param zipcode [String] ZIP code
|
|
40
|
+
# @param candidates [Integer] Maximum candidates to return (1-10)
|
|
41
|
+
# @param match [String] Match strategy ('strict', 'invalid', 'enhanced')
|
|
42
|
+
# @return [Array<Hash>] Validated address results
|
|
43
|
+
#
|
|
44
|
+
# @example
|
|
45
|
+
# results = client.us_street(street: "350 5th Avenue", city: "New York", state: "NY")
|
|
46
|
+
# puts results[0]["analysis"]["dpv_match_code"] # => "Y"
|
|
47
|
+
#
|
|
48
|
+
def us_street(street:, street2: nil, secondary: nil, city: nil, state: nil, zipcode: nil, candidates: nil, match: nil)
|
|
49
|
+
get("/api/us-street", {
|
|
50
|
+
street: street,
|
|
51
|
+
street2: street2,
|
|
52
|
+
secondary: secondary,
|
|
53
|
+
city: city,
|
|
54
|
+
state: state,
|
|
55
|
+
zipcode: zipcode,
|
|
56
|
+
candidates: candidates,
|
|
57
|
+
match: match
|
|
58
|
+
})
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Look up city/state by ZIP code or find ZIP codes by city/state
|
|
62
|
+
#
|
|
63
|
+
# @param zipcode [String] 5-digit ZIP code
|
|
64
|
+
# @param city [String] City name
|
|
65
|
+
# @param state [String] State abbreviation
|
|
66
|
+
# @return [Array<Hash>] Results with city_states and zipcodes
|
|
67
|
+
#
|
|
68
|
+
# @example
|
|
69
|
+
# result = client.us_zipcode(zipcode: "90210")
|
|
70
|
+
# puts result[0]["city_states"][0]["city"] # => "Beverly Hills"
|
|
71
|
+
#
|
|
72
|
+
def us_zipcode(zipcode: nil, city: nil, state: nil)
|
|
73
|
+
get("/api/us-zipcode", { zipcode: zipcode, city: city, state: state })
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Get address suggestions as user types
|
|
77
|
+
#
|
|
78
|
+
# @param search [String] Partial address to search
|
|
79
|
+
# @param selected [String] Previously selected suggestion
|
|
80
|
+
# @param max_results [Integer] Maximum suggestions to return
|
|
81
|
+
# @param city_filter [Array<String>] Cities to limit results to
|
|
82
|
+
# @param state_filter [Array<String>] States to limit results to
|
|
83
|
+
# @param zip_filter [Array<String>] ZIP codes to limit results to
|
|
84
|
+
# @return [Hash] Hash with 'suggestions' array
|
|
85
|
+
#
|
|
86
|
+
# @example
|
|
87
|
+
# result = client.us_autocomplete(search: "350 5th Ave", state_filter: ["NY"])
|
|
88
|
+
#
|
|
89
|
+
def us_autocomplete(search:, selected: nil, max_results: nil, city_filter: nil, state_filter: nil, zip_filter: nil,
|
|
90
|
+
prefer_cities: nil, prefer_states: nil, prefer_zips: nil, prefer_ratio: nil,
|
|
91
|
+
prefer_geolocation: nil, source: nil)
|
|
92
|
+
get("/api/us-autocomplete", {
|
|
93
|
+
search: search,
|
|
94
|
+
selected: selected,
|
|
95
|
+
max_results: max_results,
|
|
96
|
+
city_filter: array_param(city_filter),
|
|
97
|
+
state_filter: array_param(state_filter),
|
|
98
|
+
zip_filter: array_param(zip_filter),
|
|
99
|
+
prefer_cities: array_param(prefer_cities),
|
|
100
|
+
prefer_states: array_param(prefer_states),
|
|
101
|
+
prefer_zips: array_param(prefer_zips),
|
|
102
|
+
prefer_ratio: prefer_ratio,
|
|
103
|
+
prefer_geolocation: prefer_geolocation,
|
|
104
|
+
source: source
|
|
105
|
+
})
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Extract addresses from unstructured text
|
|
109
|
+
#
|
|
110
|
+
# @param text [String] Text containing addresses
|
|
111
|
+
# @param aggressive [Boolean] Enable aggressive extraction mode
|
|
112
|
+
# @param addr_line_breaks [Boolean] Addresses span multiple lines
|
|
113
|
+
# @param addr_per_line [Integer] Max addresses per line
|
|
114
|
+
# @return [Hash] Hash with 'meta' and 'addresses' arrays
|
|
115
|
+
#
|
|
116
|
+
# @example
|
|
117
|
+
# result = client.us_extract("Ship to 350 5th Ave, New York, NY 10118")
|
|
118
|
+
#
|
|
119
|
+
def us_extract(text, aggressive: nil, addr_line_breaks: nil, addr_per_line: nil)
|
|
120
|
+
post_text("/api/us-extract", text, {
|
|
121
|
+
aggressive: aggressive,
|
|
122
|
+
addr_line_breaks: addr_line_breaks,
|
|
123
|
+
addr_per_line: addr_per_line
|
|
124
|
+
})
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Get addresses near GPS coordinates
|
|
128
|
+
#
|
|
129
|
+
# @param latitude [Float] Latitude (-90 to 90)
|
|
130
|
+
# @param longitude [Float] Longitude (-180 to 180)
|
|
131
|
+
# @param source [String] Filter by type ('residential', 'commercial', 'all')
|
|
132
|
+
# @return [Hash] Hash with 'results' array of nearby addresses
|
|
133
|
+
#
|
|
134
|
+
# @example
|
|
135
|
+
# result = client.us_reverse_geo(latitude: 40.748535, longitude: -73.9856571)
|
|
136
|
+
#
|
|
137
|
+
def us_reverse_geo(latitude:, longitude:, source: nil)
|
|
138
|
+
get("/api/us-reverse-geo", { latitude: latitude, longitude: longitude, source: source })
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# ==========================================================================
|
|
142
|
+
# International Endpoints
|
|
143
|
+
# ==========================================================================
|
|
144
|
+
|
|
145
|
+
# Validate international addresses
|
|
146
|
+
#
|
|
147
|
+
# @param country [String] ISO-3 country code (e.g., 'GBR', 'FRA')
|
|
148
|
+
# @param freeform [String] Full address as single string
|
|
149
|
+
# @param address1 [String] Address line 1
|
|
150
|
+
# @param address2 [String] Address line 2
|
|
151
|
+
# @param address3 [String] Address line 3
|
|
152
|
+
# @param address4 [String] Address line 4
|
|
153
|
+
# @param locality [String] City/town
|
|
154
|
+
# @param administrative_area [String] State/province/region
|
|
155
|
+
# @param postal_code [String] Postal/ZIP code
|
|
156
|
+
# @param geocode [Boolean] Include coordinates
|
|
157
|
+
# @return [Array<Hash>] Validated address results
|
|
158
|
+
#
|
|
159
|
+
# @example
|
|
160
|
+
# results = client.intl_street(country: "GBR", freeform: "10 Downing Street, London")
|
|
161
|
+
#
|
|
162
|
+
def intl_street(country:, freeform: nil, address1: nil, address2: nil, address3: nil, address4: nil,
|
|
163
|
+
locality: nil, administrative_area: nil, postal_code: nil, geocode: nil)
|
|
164
|
+
get("/api/intl-street", {
|
|
165
|
+
country: country,
|
|
166
|
+
freeform: freeform,
|
|
167
|
+
address1: address1,
|
|
168
|
+
address2: address2,
|
|
169
|
+
address3: address3,
|
|
170
|
+
address4: address4,
|
|
171
|
+
locality: locality,
|
|
172
|
+
administrative_area: administrative_area,
|
|
173
|
+
postal_code: postal_code,
|
|
174
|
+
geocode: geocode
|
|
175
|
+
})
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Get international address suggestions
|
|
179
|
+
#
|
|
180
|
+
# @param search [String] Partial address to search
|
|
181
|
+
# @param country [String] ISO-3 country code
|
|
182
|
+
# @param max_results [Integer] Maximum suggestions to return
|
|
183
|
+
# @param distance [Integer] Search radius in meters
|
|
184
|
+
# @param latitude [Float] Latitude for location bias
|
|
185
|
+
# @param longitude [Float] Longitude for location bias
|
|
186
|
+
# @return [Hash] Hash with 'candidates' array
|
|
187
|
+
#
|
|
188
|
+
def intl_autocomplete(search:, country:, max_results: nil, distance: nil, latitude: nil, longitude: nil)
|
|
189
|
+
get("/api/intl-autocomplete", {
|
|
190
|
+
search: search,
|
|
191
|
+
country: country,
|
|
192
|
+
max_results: max_results,
|
|
193
|
+
distance: distance,
|
|
194
|
+
latitude: latitude,
|
|
195
|
+
longitude: longitude
|
|
196
|
+
})
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# ==========================================================================
|
|
200
|
+
# Core Endpoints
|
|
201
|
+
# ==========================================================================
|
|
202
|
+
|
|
203
|
+
# Validate addresses worldwide
|
|
204
|
+
#
|
|
205
|
+
# @param address1 [String] Primary address line (required)
|
|
206
|
+
# @param country [String] Country code (ISO-2, ISO-3, or name)
|
|
207
|
+
# @param address2 [String] Secondary address line
|
|
208
|
+
# @param locality [String] City/town
|
|
209
|
+
# @param administrative_area [String] State/province
|
|
210
|
+
# @param postal_code [String] Postal/ZIP code
|
|
211
|
+
# @return [Array<Hash>] Validated address results
|
|
212
|
+
#
|
|
213
|
+
# @example
|
|
214
|
+
# results = client.validate(
|
|
215
|
+
# country: "US",
|
|
216
|
+
# address1: "350 5th Avenue",
|
|
217
|
+
# locality: "New York",
|
|
218
|
+
# administrative_area: "NY"
|
|
219
|
+
# )
|
|
220
|
+
#
|
|
221
|
+
def validate(address1:, country: nil, address2: nil, locality: nil, administrative_area: nil, postal_code: nil)
|
|
222
|
+
post("/api/validate", {
|
|
223
|
+
country: country,
|
|
224
|
+
address1: address1,
|
|
225
|
+
address2: address2,
|
|
226
|
+
locality: locality,
|
|
227
|
+
administrative_area: administrative_area,
|
|
228
|
+
postal_code: postal_code
|
|
229
|
+
})
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Correct and standardize addresses
|
|
233
|
+
#
|
|
234
|
+
# @param freeform [String] Full address as single string
|
|
235
|
+
# @param country [String] Country code
|
|
236
|
+
# @param address1 [String] Address line
|
|
237
|
+
# @param locality [String] City
|
|
238
|
+
# @param administrative_area [String] State/province
|
|
239
|
+
# @param postal_code [String] Postal/ZIP code
|
|
240
|
+
# @return [Array<Hash>] Corrected address results
|
|
241
|
+
#
|
|
242
|
+
# @example
|
|
243
|
+
# results = client.correct(freeform: "1600 pennsylvania ave washington dc 20500")
|
|
244
|
+
#
|
|
245
|
+
def correct(freeform: nil, country: nil, address1: nil, locality: nil, administrative_area: nil, postal_code: nil)
|
|
246
|
+
post("/api/correct", {
|
|
247
|
+
freeform: freeform,
|
|
248
|
+
country: country,
|
|
249
|
+
address1: address1,
|
|
250
|
+
locality: locality,
|
|
251
|
+
administrative_area: administrative_area,
|
|
252
|
+
postal_code: postal_code
|
|
253
|
+
})
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Global address autocomplete
|
|
257
|
+
#
|
|
258
|
+
# @param q [String] Search query
|
|
259
|
+
# @param country [String] Filter by country code
|
|
260
|
+
# @param limit [Integer] Maximum suggestions
|
|
261
|
+
# @return [Hash] Hash with 'suggestions' array
|
|
262
|
+
#
|
|
263
|
+
def autocomplete(q:, country: nil, limit: nil)
|
|
264
|
+
get("/api/autocomplete", { q: q, country: country, limit: limit })
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# ==========================================================================
|
|
268
|
+
# Helper Methods
|
|
269
|
+
# ==========================================================================
|
|
270
|
+
|
|
271
|
+
class << self
|
|
272
|
+
# Check if US address is deliverable (DPV match code = Y)
|
|
273
|
+
def deliverable?(result)
|
|
274
|
+
result.dig("analysis", "dpv_match_code") == "Y"
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Check if US address is a CMRA (Commercial Mail Receiving Agency)
|
|
278
|
+
def cmra?(result)
|
|
279
|
+
result.dig("analysis", "dpv_cmra") == "Y"
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Check if US address is vacant
|
|
283
|
+
def vacant?(result)
|
|
284
|
+
result.dig("analysis", "dpv_vacant") == "Y"
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Check if international address is verified
|
|
288
|
+
def verified?(result)
|
|
289
|
+
result.dig("analysis", "verification_status") == "Verified"
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Get formatted US address string
|
|
293
|
+
def format_us_address(result)
|
|
294
|
+
"#{result['delivery_line_1']}, #{result['last_line']}"
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Get formatted international address string
|
|
298
|
+
def format_intl_address(result)
|
|
299
|
+
[result["address1"], result["address2"], result["address3"], result["address4"]]
|
|
300
|
+
.compact
|
|
301
|
+
.reject(&:empty?)
|
|
302
|
+
.join(", ")
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
private
|
|
307
|
+
|
|
308
|
+
def build_connection
|
|
309
|
+
Faraday.new(url: base_url) do |f|
|
|
310
|
+
f.request :url_encoded
|
|
311
|
+
f.headers["X-API-Key"] = api_key
|
|
312
|
+
f.headers["User-Agent"] = "TruAddress-Ruby/#{VERSION}"
|
|
313
|
+
f.adapter Faraday.default_adapter
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def get(path, params = {})
|
|
318
|
+
response = @conn.get(path, compact_params(params))
|
|
319
|
+
handle_response(response)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def post(path, body = {})
|
|
323
|
+
response = @conn.post(path) do |req|
|
|
324
|
+
req.headers["Content-Type"] = "application/json"
|
|
325
|
+
req.body = JSON.generate(compact_params(body))
|
|
326
|
+
end
|
|
327
|
+
handle_response(response)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def post_text(path, text, params = {})
|
|
331
|
+
query_string = URI.encode_www_form(compact_params(params))
|
|
332
|
+
full_path = query_string.empty? ? path : "#{path}?#{query_string}"
|
|
333
|
+
|
|
334
|
+
response = @conn.post(full_path) do |req|
|
|
335
|
+
req.headers["Content-Type"] = "text/plain"
|
|
336
|
+
req.body = text
|
|
337
|
+
end
|
|
338
|
+
handle_response(response)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def handle_response(response)
|
|
342
|
+
body = JSON.parse(response.body)
|
|
343
|
+
|
|
344
|
+
case response.status
|
|
345
|
+
when 200..299
|
|
346
|
+
body
|
|
347
|
+
when 401
|
|
348
|
+
raise AuthenticationError.new(body["error"] || "Authentication failed", status_code: response.status)
|
|
349
|
+
when 400, 422
|
|
350
|
+
raise ValidationError.new(body["error"] || "Validation failed", status_code: response.status)
|
|
351
|
+
when 429
|
|
352
|
+
raise RateLimitError.new(body["error"] || "Rate limit exceeded", status_code: response.status)
|
|
353
|
+
when 500..599
|
|
354
|
+
raise ServerError.new(body["error"] || "Server error", status_code: response.status)
|
|
355
|
+
else
|
|
356
|
+
raise Error.new(body["error"] || "Unknown error", status_code: response.status)
|
|
357
|
+
end
|
|
358
|
+
rescue JSON::ParserError
|
|
359
|
+
raise Error.new("Invalid JSON response", status_code: response.status, response_body: response.body)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def compact_params(params)
|
|
363
|
+
params.reject { |_, v| v.nil? }
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def array_param(value)
|
|
367
|
+
return nil if value.nil?
|
|
368
|
+
value.is_a?(Array) ? value.join(",") : value
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Truaddress
|
|
4
|
+
# Base error class for TruAddress errors
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
attr_reader :status_code, :response_body
|
|
7
|
+
|
|
8
|
+
def initialize(message = nil, status_code: nil, response_body: nil)
|
|
9
|
+
@status_code = status_code
|
|
10
|
+
@response_body = response_body
|
|
11
|
+
super(message)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Raised when API key is missing or invalid
|
|
16
|
+
class AuthenticationError < Error; end
|
|
17
|
+
|
|
18
|
+
# Raised when request parameters are invalid
|
|
19
|
+
class ValidationError < Error; end
|
|
20
|
+
|
|
21
|
+
# Raised when API rate limit is exceeded
|
|
22
|
+
class RateLimitError < Error; end
|
|
23
|
+
|
|
24
|
+
# Raised when API returns server error
|
|
25
|
+
class ServerError < Error; end
|
|
26
|
+
end
|
data/lib/truaddress.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "truaddress/version"
|
|
4
|
+
require_relative "truaddress/error"
|
|
5
|
+
require_relative "truaddress/client"
|
|
6
|
+
|
|
7
|
+
# TruAddress Ruby SDK
|
|
8
|
+
# Official SDK for address validation, autocomplete, and geocoding
|
|
9
|
+
module Truaddress
|
|
10
|
+
class << self
|
|
11
|
+
attr_accessor :api_key, :base_url
|
|
12
|
+
|
|
13
|
+
def configure
|
|
14
|
+
yield self
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def client
|
|
18
|
+
@client ||= Client.new(api_key: api_key, base_url: base_url)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Reset the client (useful for testing)
|
|
22
|
+
def reset!
|
|
23
|
+
@client = nil
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Default base URL
|
|
28
|
+
self.base_url = "https://truaddress.net"
|
|
29
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: truaddress
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- TruAddress
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-01-29 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: faraday
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.0'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '3.0'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '1.0'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.0'
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: faraday-net_http
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.0'
|
|
40
|
+
type: :runtime
|
|
41
|
+
prerelease: false
|
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.0'
|
|
47
|
+
- !ruby/object:Gem::Dependency
|
|
48
|
+
name: rake
|
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.0'
|
|
54
|
+
type: :development
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '13.0'
|
|
61
|
+
- !ruby/object:Gem::Dependency
|
|
62
|
+
name: rspec
|
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.0'
|
|
68
|
+
type: :development
|
|
69
|
+
prerelease: false
|
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '3.0'
|
|
75
|
+
- !ruby/object:Gem::Dependency
|
|
76
|
+
name: webmock
|
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '3.0'
|
|
82
|
+
type: :development
|
|
83
|
+
prerelease: false
|
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '3.0'
|
|
89
|
+
description: Ruby SDK for TruAddress API. Validate, autocomplete, and geocode addresses
|
|
90
|
+
worldwide with USPS-grade accuracy.
|
|
91
|
+
email:
|
|
92
|
+
- support@truaddress.net
|
|
93
|
+
executables: []
|
|
94
|
+
extensions: []
|
|
95
|
+
extra_rdoc_files: []
|
|
96
|
+
files:
|
|
97
|
+
- README.md
|
|
98
|
+
- lib/truaddress.rb
|
|
99
|
+
- lib/truaddress/client.rb
|
|
100
|
+
- lib/truaddress/error.rb
|
|
101
|
+
- lib/truaddress/version.rb
|
|
102
|
+
homepage: https://truaddress.net
|
|
103
|
+
licenses:
|
|
104
|
+
- MIT
|
|
105
|
+
metadata:
|
|
106
|
+
homepage_uri: https://truaddress.net
|
|
107
|
+
source_code_uri: https://github.com/truaddress/truaddress-ruby
|
|
108
|
+
documentation_uri: https://truaddress.net/docs
|
|
109
|
+
changelog_uri: https://github.com/truaddress/truaddress-ruby/blob/main/CHANGELOG.md
|
|
110
|
+
post_install_message:
|
|
111
|
+
rdoc_options: []
|
|
112
|
+
require_paths:
|
|
113
|
+
- lib
|
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
115
|
+
requirements:
|
|
116
|
+
- - ">="
|
|
117
|
+
- !ruby/object:Gem::Version
|
|
118
|
+
version: 2.7.0
|
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0'
|
|
124
|
+
requirements: []
|
|
125
|
+
rubygems_version: 3.5.3
|
|
126
|
+
signing_key:
|
|
127
|
+
specification_version: 4
|
|
128
|
+
summary: Official TruAddress SDK for Ruby - Address validation, autocomplete, and
|
|
129
|
+
geocoding
|
|
130
|
+
test_files: []
|