File Coverage

blib/lib/Amazon/MWS/Routines.pm
Criterion Covered Total %
statement 137 203 67.4
branch 30 66 45.4
condition 14 48 29.1
subroutine 20 21 95.2
pod 0 6 0.0
total 201 344 58.4


line stmt bran cond sub pod time code
1             package Amazon::MWS::Routines;
2              
3 8     9   3177 use URI;
  8         38703  
  8         289  
4 8     9   65 use DateTime;
  8         21  
  8         184  
5 8     9   4958 use XML::Simple;
  8         71801  
  8         76  
6 8     9   936 use URI::Escape;
  8         22  
  8         491  
7 8     9   3122 use MIME::Base64;
  8         5362  
  8         490  
8 8     9   2939 use Digest::SHA;
  8         20191  
  8         401  
9 8     9   2510 use HTTP::Request;
  8         85674  
  8         327  
10 8     9   3916 use LWP::UserAgent;
  8         164483  
  8         395  
11 8     9   103 use Digest::MD5 qw(md5_base64);
  8         26  
  8         663  
12 8     9   67 use Amazon::MWS::TypeMap qw(:all);
  8         44  
  8         1286  
13 8     9   3684 use Amazon::MWS::Exception;
  8         40  
  8         296  
14 8     9   911 use Data::Dumper;
  8         13084  
  8         540  
15              
16 8     9   64 use Exporter qw(import);
  8         25  
  8         10270  
17             our @EXPORT_OK = qw(define_api_method new sign_request convert force_array);
18             our %EXPORT_TAGS = ( all => \@EXPORT_OK );
19              
20 474 50   474 0 2042 sub slurp_kwargs { ref $_[0] eq 'HASH' ? shift : { @_ } }
21              
22             sub define_api_method {
23 472     472 0 971 my $method_name = shift;
24 472         1055 my $spec = slurp_kwargs(@_);
25 472         1019 my $params = $spec->{parameters};
26              
27             my $method = sub {
28              
29 2     2   1953 my $self = shift;
30 2         9 my $args = slurp_kwargs(@_);
31 2         7 my $body = '';
32              
33             my %form = (
34             Action => $method_name,
35             AWSAccessKeyId => $self->{access_key_id},
36             Merchant => $self->{merchant_id},
37             SellerId => $self->{merchant_id},
38 2         31 SignatureVersion => 2,
39             SignatureMethod => 'HmacSHA256',
40             Timestamp => to_amazon('datetime', DateTime->now),
41             );
42              
43 2         1585 foreach my $name (keys %$params) {
44 4         10 my $param = $params->{$name};
45 4 100       13 unless (exists $args->{$name}) {
46 1 50       10 Amazon::MWS::Exception::MissingArgument->throw(name => $name) if $param->{required};
47 1         3 next;
48             }
49              
50 3         8 my $type = $param->{type};
51 3         6 my $array_names = $param->{array_names};
52 3         7 my $value = $args->{$name};
53              
54 3 50       10 if ($type =~ /^List$/) {
55 0         0 my %valuehash;
56 0         0 @valuehash{@{$param->{values}}}=();
  0         0  
57 0 0       0 Amazon::MWS::Exception::Invalid->throw(field => $name, value=>$value) unless (exists ($valuehash{$value}));
58 0         0 $form{$name} = $value;
59 0         0 next;
60             }
61              
62             # Odd 'structured list' notation handled here
63 3 50       11 if ($type =~ /(\w+)List/) {
64 0         0 my $list_type = $1;
65 0 0       0 Amazon::MWS::Exception::Invalid->throw(field => $name, value=>$value, message=>"$name should be of type ARRAY") unless (ref $value eq 'ARRAY');
66 0         0 my $counter = 1;
67              
68 0         0 foreach my $sub_value (@$value) {
69 0         0 my $listKey = "$name.$list_type." . $counter++;
70 0         0 $form{$listKey} = $sub_value;
71             }
72 0         0 next;
73             }
74              
75 3 50       8 if ($type =~ /(\w+)Array/) {
76 0 0       0 Amazon::MWS::Exception::Invalid->throw(field => $name, value=>$value, message=>"$name should be of type ARRAY") unless (ref $value eq 'ARRAY');
77 0         0 my $list_type = $1;
78 0         0 my $counter = 0;
79 0         0 foreach my $sub_value (@$value) {
80 0         0 $counter++;
81 0         0 my $arr_col=0;
82 0         0 foreach my $array_name (@{$array_names}) {
  0         0  
83 0 0       0 if ( ! defined $sub_value->[$arr_col] ) { next; }
  0         0  
84 0         0 my $listKey = "$name.$list_type." . $counter;
85 0         0 $listKey .= ".$array_name";
86 0         0 $form{$listKey} = $sub_value->[$arr_col++];
87             }
88             }
89 0         0 next;
90             }
91 3 100       9 if ($type eq 'HTTP-BODY') {
92 1         3 $body = $value;
93             }
94             else {
95 2         6 $form{$name} = to_amazon($type, $value);
96             }
97             }
98              
99 2   50     15 $form{Version} = $spec->{version} || '2010-01-01';
100              
101 2 100       13 my $endpoint = ( $spec->{service} ) ? "$self->{endpoint}$spec->{service}" : $self->{endpoint};
102              
103 2         15 my $uri = URI->new($endpoint);
104              
105 2         13091 my $request = HTTP::Request->new;
106 2         165 $request->protocol('HTTP/1.0');
107              
108 2         20 my ($response, $content);
109              
110 2 50       13 $spec->{method} = 'GET' unless $spec->{method};
111              
112 2 50       11 if ($spec->{method} eq 'POST') {
    100          
113 0         0 $request->uri($uri);
114 0         0 $request->method('POST');
115 0         0 $request->content($body);
116 0   0     0 $request->content_type($args->{content_type}||'application/x-www-form-urlencoded');
117 0         0 my $signature = $self->sign_request($request, %form);
118              
119 0         0 $response = $self->agent->request($request);
120 0         0 $content = $response->content;
121             } elsif ($body) {
122 1         5 $request->uri($uri);
123 1         118 $request->method('POST');
124 1         16 $request->content($body);
125 1         57 $request->header('Content-MD5' => md5_base64($body) . '==');
126 1   50     167 $request->content_type($args->{content_type}||'text/plain');
127              
128 1         54 $self->sign_request($request, %form);
129 1         6 $request->content($body);
130 1         31 $response = $self->agent->request($request);
131 1         364 $content = $response->content;
132             } else {
133 1         6 $uri->query_form(\%form);
134 1         292 $request->uri($uri);
135 1         35 $request->method('GET');
136              
137 1         12 $self->sign_request($request);
138            
139 1         5 $response = $self->agent->request($request);
140 1         670173 $content = $response->content;
141              
142             }
143              
144              
145 2 50       41 if ($self->{debug}) {
146 0         0 open LOG, ">>$self->{logfile}";
147 0         0 print LOG Dumper($response);
148             }
149              
150 2         24 my $xs = XML::Simple->new( KeepRoot => 1 );
151              
152 2 50 33     195 if ($response->code == 400 || $response->code == 403) {
153 0         0 my $hash = $xs->xml_in($content);
154 0         0 my $root = $hash->{ErrorResponse};
155 0         0 force_array($root, 'Error');
156 0         0 Amazon::MWS::Exception::Response->throw(errors => $root->{Error}, xml => $content);
157             }
158              
159 2 50       66 if ($response->code == 503) {
160 0         0 my $hash = $xs->xml_in($content);
161 0         0 my $root = $hash->{ErrorResponse};
162 0         0 force_array($root, 'Error');
163 0         0 Amazon::MWS::Exception::Throttled->throw(errors => $root->{Error}, xml => $content);
164             }
165              
166 2 50       25 unless ($response->is_success) {
167 0         0 Amazon::MWS::Exception::Transport->throw(request => $request, response => $response);
168             }
169              
170 2 50       31 if (my $md5 = $response->header('Content-MD5')) {
171 0 0       0 Amazon::MWS::Exception::BedChecksum->throw(response => $response)
172             unless ($md5 eq md5_base64($content) . '==');
173             }
174              
175 2 50 33     167 return $content if ($spec->{raw_body} || $args->{raw_body});
176              
177 2         15 my $hash = $xs->xml_in($content);
178              
179             my $root = $hash->{$method_name . 'Response'}
180 2         185743 ->{$method_name . 'Result'};
181              
182 2         18 return $spec->{respond}->($root);
183 472         4263 };
184              
185 472   100     2022 my $module_name = $spec->{module_name} || 'Amazon::MWS::Client';
186 472         1337 my $fqn = join '::', "$module_name", $method_name;
187              
188 8     9   75 no strict 'refs';
  8         26  
  8         7056  
189 472         3610 *$fqn = $method;
190              
191             }
192              
193             sub force_array {
194              
195 0     0 0 0 my ($hash, $key) = @_;
196 0         0 my $val = $hash->{$key};
197              
198 0 0       0 if (!defined $val) {
    0          
199 0         0 $val = [];
200             }
201             elsif (ref $val ne 'ARRAY') {
202 0         0 $val = [ $val ];
203             }
204              
205 0         0 $hash->{$key} = $val;
206             }
207              
208             sub sign_request {
209 2     2 0 18 my ($self, $request, %form) = @_;
210              
211 2         11 my $uri = $request->uri;
212 2 100       40 my %params = ($request->method eq 'GET' ) ? $uri->query_form : %form;
213              
214             my $canonical = join '&', map {
215 2         203 my $param = uri_escape($_);
  18         45  
216 18         240 my $value = uri_escape($params{$_});
217 18         282 "$param=$value";
218             } sort keys %params;
219              
220 2   100     18 my $path = $uri->path || '/';
221 2         36 my $string = $request->method . "\n"
222             . $uri->authority . "\n"
223             . $path . "\n"
224             . $canonical;
225              
226 2         160 $params{Signature} = Digest::SHA::hmac_sha256_base64($string, $self->{secret_key});
227 2         15 while (length($params{Signature}) % 4) {
228 2         8 $params{Signature} .= '=';
229             }
230              
231 2 50 66     15 if ($request->{_method} eq 'GET' || $request->{_content} ) {
232 2         14 $uri->query_form(\%params);
233             } else {
234 0         0 $request->{_content} = "$canonical&Signature=$params{Signature}";
235             }
236 2         523 $request->uri($uri);
237 2         61 return $request;
238              
239             }
240              
241             sub convert {
242 1     1 0 4 my ($hash, $key, $type) = @_;
243 1         8 $hash->{$key} = from_amazon($type, $hash->{$key});
244             }
245              
246              
247             sub new {
248            
249 2     2 0 300 my($pkg, %opts) = @_;
250              
251 2   50     24 $opts{configfile} ||= 'amazon.xml';
252              
253 2 50       28 if (-r $opts{configfile} ) {
254              
255 0         0 my $xmlconfig = XML::Simple::XMLin("$opts{configfile}");
256              
257 0   0     0 $opts{access_key_id} ||= $xmlconfig->{access_key_id};
258 0   0     0 $opts{secret_key} ||= $xmlconfig->{secret_key};
259 0   0     0 $opts{merchant_id} ||= $xmlconfig->{merchant_id};
260 0   0     0 $opts{marketplace_id} ||= $xmlconfig->{marketplace_id};
261 0   0     0 $opts{endpoint} ||= $xmlconfig->{endpoint};
262 0   0     0 $opts{debug} ||= $xmlconfig->{debug};
263 0   0     0 $opts{logfile} ||= $xmlconfig->{logfile};
264             }
265              
266 2         9 my $attr = $opts->{agent_attributes};
267 2         7 $attr->{Language} = 'Perl';
268              
269 2         10 my $attr_str = join ';', map { "$_=$attr->{$_}" } keys %$attr;
  2         15  
270 2   50     16 my $appname = $opts{Application} || 'Amazon::MWS::Client';
271 2   50     14 my $version = $opts{Version} || 0.5;
272              
273 2         21 my $agent_string = "$appname/$version ($attr_str)";
274              
275 2 50       8 die 'No access key id' unless $opts{access_key_id};
276 2 50       8 die 'No secret key' unless $opts{secret_key};
277 2 50       6 die 'No merchant id' unless $opts{merchant_id};
278 2 50       8 die 'No marketplace id' unless $opts{marketplace_id};
279              
280 2 50       7 if ($opts{debug}) {
281 0 0       0 open LOG, ">$opts{logfile}" or die "Cannot open logfile.";
282 0         0 print LOG DateTime->now();
283 0         0 print LOG "\nNew instance created. \n";
284 0         0 print LOG Dumper(\%opts);
285 0         0 close LOG;
286             }
287              
288             # https://github.com/interchange/Amazon-MWS/issues/9
289 2   50     12 $opts{endpoint} ||= 'https://mws.amazonaws.com';
290             # strip the trailing slashes
291 2         10 $opts{endpoint} =~ s/\/+\z//;
292              
293             bless {
294             package => "$pkg",
295             agent => LWP::UserAgent->new(agent => $agent_string),
296             endpoint => $opts{endpoint},
297             access_key_id => $opts{access_key_id},
298             secret_key => $opts{secret_key},
299             merchant_id => $opts{merchant_id},
300             marketplace_id => $opts{marketplace_id},
301             debug => $opts{debug},
302             logfile => $opts{logfile},
303 2         21 }, $pkg;
304              
305             }
306              
307             1;
308              
309             # Local Variables:
310             # tab-width: 8
311             # End:
312