File Coverage

blib/lib/WebService/Async/SmartyStreets.pm
Criterion Covered Total %
statement 79 82 96.3
branch 7 10 70.0
condition 7 15 46.6
subroutine 23 24 95.8
pod 5 9 55.5
total 121 140 86.4


line stmt bran cond sub pod time code
1             package WebService::Async::SmartyStreets;
2              
3             # ABSTRACT: Access SmartyStreet API
4              
5 1     1   123548 use strict;
  1         8  
  1         22  
6 1     1   4 use warnings;
  1         2  
  1         34  
7              
8             our $VERSION = '0.002';
9              
10             =head1 NAME
11              
12             WebService::Async::SmartyStreets - calls the SmartyStreets API and checks for the validity of the address
13              
14             =head1 SYNOPSIS
15              
16             my $ss = WebService::Async::SmartyStreets->new(
17             # Obtain these from your SmartyStreets account page.
18             # These will be used for US lookups
19             us_auth_id => '...',
20             us_token => '...',
21             # For non-US address lookups, you would also need an international token
22             international_auth_id => '...',
23             international_token => '...',
24             );
25             IO::Async::Loop->new->add($ss);
26              
27             print $ss->verify(
28             city => 'Atlanta',
29             country => 'US',
30             geocode => 1
31             )->get->status;
32              
33             =head1 DESCRIPTION
34              
35             This module provides basic support for the L.
36              
37             Note that this module uses L.
38              
39             =cut
40              
41 1     1   332 use parent qw(IO::Async::Notifier);
  1         214  
  1         4  
42              
43 1     1   3363 use mro;
  1         2  
  1         6  
44 1     1   398 no indirect;
  1         817  
  1         3  
45              
46 1     1   495 use URI;
  1         3925  
  1         44  
47 1     1   364 use URI::QueryParam;
  1         655  
  1         30  
48              
49 1     1   429 use Future::AsyncAwait;
  1         2778  
  1         3  
50 1     1   512 use Net::Async::HTTP;
  1         98995  
  1         55  
51 1     1   386 use JSON::MaybeUTF8 qw(:v1);
  1         6825  
  1         127  
52 1     1   445 use Syntax::Keyword::Try;
  1         862  
  1         5  
53 1     1   73 use Scalar::Util qw(blessed);
  1         2  
  1         44  
54              
55 1     1   409 use WebService::Async::SmartyStreets::Address;
  1         2  
  1         36  
56              
57 1     1   6 use Log::Any qw($log);
  1         2  
  1         8  
58              
59             =head2 verify
60              
61             Makes connection to SmartyStreets API and parses the response into WebService::Async::SmartyStreets::Address.
62              
63             my $addr = $ss->verify(%address_to_check)->get;
64              
65             Takes the following named parameters:
66              
67             =over 4
68              
69             =item * C - country (required)
70              
71             =item * C - address line 1
72              
73             =item * C - address line 2
74              
75             =item * C - name of organization (usually building names)
76              
77             =item * C - city
78              
79             =item * C - state
80              
81             =item * C - post code
82              
83             =item * C - true or false
84              
85             =back
86              
87             Returns a L which should resolve to a valid L instance.
88              
89             =cut
90              
91             # keyword 'async' will cause critic test fail. disable it
92             ## no critic (RequireEndWithOne.pm)
93 6     6 1 339 async sub verify {
94 6         20 my ($self, %args) = @_;
95              
96 6         14 my $uri = $self->country_endpoint($args{country})->clone;
97              
98 6         7589 $uri->query_param($_ => $args{$_}) for keys %args;
99             $uri->query_param(
100 6   50     9289 'auth-id' => ($self->auth_id($args{country}) // die 'need an auth ID'),
101             );
102             $uri->query_param(
103 6   50     1886 'auth-token' => ($self->token($args{country}) // die 'need an auth token'),
104             );
105 6         2021 $uri->query_param(
106             'input-id' => $self->next_id,
107             );
108 6         2134 $log->tracef('GET %s', '' . $uri);
109              
110 6         450 my $decoded = await get_decoded_data($self, $uri);
111              
112 3         143 $log->tracef('=> %s', $decoded);
113 3 100       6859 $decoded = [$decoded] unless ref($decoded) eq 'ARRAY';
114              
115 3         7 return map { WebService::Async::SmartyStreets::Address->new(%$_) } @$decoded;
  3         15  
116             }
117              
118             =head2 METHODS - Accessors
119              
120             =cut
121              
122             sub country_endpoint {
123 6     6 0 12 my ($self, $country) = @_;
124 6 50       19 return $self->us_endpoint if uc($country) eq 'US';
125 6         8 return $self->international_endpoint;
126             }
127              
128             sub us_endpoint {
129 0   0 0 0 0 shift->{us_endpoint} //= URI->new('https://us-street.api.smartystreets.com/street-address');
130             }
131              
132             sub international_endpoint {
133 6   66 6 0 27 shift->{international_endpoint} //= URI->new('https://international-street.api.smartystreets.com/verify');
134             }
135              
136             sub auth_id {
137 6     6 1 13 my ($self, $country) = @_;
138 6 50       18 return $self->{us_auth_id} if uc($country) eq 'US';
139 6         21 return $self->{international_auth_id};
140             }
141              
142             sub token {
143 6     6 1 18 my ($self, $country) = @_;
144 6 50       13 return $self->{us_token} if uc($country) eq 'US';
145 6         24 return $self->{international_token};
146             }
147              
148             =head1 METHODS - Internal
149              
150             =head2 get_decoded_data
151              
152             Calls the SmartyStreets API then decode and parses the response give by SmartyStreets
153              
154             my $decoded = await get_decoded_data($self, $uri)
155              
156             Takes the following parameters:
157              
158             =over 4
159              
160             =item * C<$uri> - URI for endpoint
161              
162             =back
163              
164             More information on the response can be seen in L.
165              
166             Returns a L which resolves to an arrayref of L instances.
167              
168             =cut
169              
170 3     3   5 async sub get_decoded_data {
171 3         7 my $self = shift;
172 3         3 my $uri = shift;
173              
174 3         4 my $res;
175             try {
176             $res = await $self->ua->GET($uri);
177 3         7 } catch ($e) {
178             if (blessed($e) and $e->isa('Future::Exception')) {
179             my ($payload) = $e->details;
180              
181             if (blessed($payload) && $payload->can('content')) {
182             if (my $resp = eval { decode_json_utf8($payload->content) }) {
183             my $errors = $resp->{errors} // [];
184             my ($error) = $errors->@*;
185              
186             if ($error && $error->{message}) {
187              
188             # structured response may be useful for further processing
189             die $e;
190             }
191             }
192             }
193             }
194              
195             # throw a generic error
196             die 'Unable to retrieve response.';
197             };
198              
199 0         0 my $response = decode_json_utf8($res->decoded_content);
200              
201 0         0 return $response->[0];
202             }
203              
204             =head2 configure
205              
206             Configures the instance.
207              
208             Takes the following named parameters:
209              
210             =over 4
211              
212             =item * C - auth_id obtained from SmartyStreet
213              
214             =item * C - token obtained from SmartyStreet
215              
216             =item * C - auth_id obtained from SmartyStreet
217              
218             =item * C - token obtained from SmartyStreet
219              
220             =back
221              
222             Note that you can provide US, international or both API tokens - if an API token
223             is not available for a L call, then it will return a failed L.
224              
225             =cut
226              
227             sub configure {
228 4     4 1 33495 my ($self, %args) = @_;
229 4         11 for my $k (qw(international_auth_id international_token us_auth_id us_token)) {
230 16 100       45 $self->{$k} = delete $args{$k} if exists $args{$k};
231             }
232 4         21 $self->next::method(%args);
233             }
234              
235             sub next_id {
236 6   100 6 0 36 ++(shift->{id} //= 'AA00000000');
237             }
238              
239             =head2 ua
240              
241             Accessor for the L instance which will be used for SmartyStreets API requests.
242              
243             =cut
244              
245             sub ua {
246 3     3 1 6 my ($self) = @_;
247 3   33     8 $self->{ua} //= do {
248 3         17 $self->add_child(
249             my $ua = Net::Async::HTTP->new(
250             fail_on_error => 1,
251             decode_content => 1,
252             pipeline => 0,
253             max_connections_per_host => 4,
254             user_agent =>
255             'Mozilla/4.0 (WebService::Async::SmartyStreets; BINARY@cpan.org; https://metacpan.org/pod/WebService::Async::SmartyStreets)',
256             ));
257 3         329 $ua;
258             }
259             }
260              
261             1;
262