File Coverage

blib/lib/Net/Amazon/DynamoDB/Lite.pm
Criterion Covered Total %
statement 77 227 33.9
branch 4 96 4.1
condition n/a
subroutine 19 34 55.8
pod 13 14 92.8
total 113 371 30.4


line stmt bran cond sub pod time code
1             package Net::Amazon::DynamoDB::Lite;
2 16     16   523620 use 5.008001;
  16         60  
3 16     16   86 use strict;
  16         27  
  16         337  
4 16     16   80 use warnings;
  16         34  
  16         726  
5              
6             our $VERSION = "0.05";
7              
8 16     16   77 use Carp;
  16         27  
  16         1109  
9 16     16   11524 use Furl;
  16         494067  
  16         481  
10 16     16   13118 use HTTP::Request;
  16         382310  
  16         484  
11 16     16   15675 use JSON;
  16         193289  
  16         82  
12 16     16   14413 use MIME::Base64;
  16         10213  
  16         1064  
13 16     16   14851 use Moo;
  16         260737  
  16         124  
14 16     16   41797 use POSIX qw(setlocale LC_TIME strftime);
  16         108944  
  16         93  
15 16     16   22260 use Scalar::Util qw(reftype);
  16         34  
  16         1325  
16 16     16   13465 use WebService::Amazon::Signature::v4;
  16         685024  
  16         44957  
17              
18             has signature => (
19             is => 'lazy',
20             );
21              
22             has scope => (
23             is => 'lazy',
24             );
25              
26             has ua => (
27             is => 'lazy',
28             );
29              
30             has uri => (
31             is => 'lazy',
32             );
33              
34             has access_key => (
35             is => 'ro',
36             );
37              
38             has secret_key => (
39             is => 'ro',
40             );
41              
42             has region => (
43             is => 'ro',
44             );
45              
46             has api_version => (
47             is => 'ro',
48             default => sub {
49             '20120810',
50             },
51             );
52              
53             has ca_path => (
54             is => 'rw',
55             default => sub {
56             '/etc/ssl/certs',
57             },
58             );
59              
60             has connection_timeout => (
61             is => 'rw',
62             default => sub {
63             1,
64             },
65             );
66              
67             has json => (
68             is => 'rw',
69             default => sub {
70             JSON->new,
71             },
72             );
73              
74             sub _build_signature {
75 15     15   6458 my ($self) = @_;
76 15         83 my $locale = setlocale(LC_TIME);
77 15         73 setlocale(LC_TIME, "C");
78 15         101 my $v4 = WebService::Amazon::Signature::v4->new(
79             scope => $self->scope,
80             access_key => $self->access_key,
81             secret_key => $self->secret_key,
82             );
83 15         207 setlocale(LC_TIME, $locale);
84 15         102 $v4;
85             }
86              
87             sub _build_scope {
88 15     15   6344 my ($self) = @_;
89 15         833 join '/', strftime('%Y%m%d', gmtime), $self->region, qw(dynamodb aws4_request);
90             }
91              
92             sub _build_ua {
93 13     13   5705 my ($self) = @_;
94              
95 13         204 my $ua = Furl->new(
96             agent => 'Net::Amazon::DynamoDB::Lite ' . $VERSION,
97             timeout => $self->connection_timeout,
98             ssl_opts => {
99             SSL_ca_path => $self->ca_path,
100             },
101             );
102             }
103              
104             sub _build_uri {
105 2     2   810 my ($self) = @_;
106 2         25 URI->new('https://dynamodb.' . $self->region . '.amazonaws.com/');
107             }
108              
109             sub make_request {
110 14     14 0 119 my ($self, $target, $content) = @_;
111              
112 14         67 my $req = HTTP::Request->new(
113             POST => $self->uri,
114             );
115 14         16794 my $locale = setlocale(LC_TIME);
116 14         335 setlocale(LC_TIME, "C");
117 14         365 $req->header(host => $self->uri->host);
118 14         3629 my $http_date = strftime('%a, %d %b %Y %H:%M:%S %Z', localtime);
119 14         445 my $amz_date = strftime('%Y%m%dT%H%M%SZ', gmtime);
120 14         85 $req->header(Date => $http_date);
121 14         657 $req->header('x-amz-date' => $amz_date);
122 14         954 $req->header('x-amz-target' => 'DynamoDB_' . $self->api_version . ".$target" );
123 14         778 $req->header('content-type' => 'application/x-amz-json-1.0');
124 14         1016 $content = $self->json->encode($content);
125 14         132 $req->content($content);
126 14         348 $req->header('Content-Length' => length($content));
127 14         728 $self->signature->from_http_request($req);
128 14         3042 $req->header(Authorization => $self->signature->calculate_signature);
129 14         10665 setlocale(LC_TIME, $locale);
130 14         44 return $req;
131             }
132              
133             sub list_tables {
134 13     13 1 2524 my ($self, $content) = @_;
135              
136 13 50       65 $content = {} unless $content;
137 13         57 my $req = $self->make_request('ListTables', $content);
138 13         60 my $res = $self->ua->request($req);
139 13         23916 my $decoded;
140 13         30 eval {
141 13         98 $decoded = $self->json->decode($res->content);
142             };
143 13 50       627 Carp::croak $self->_error_content($res, {Message => $res->content}) if $@;
144 0 0       0 if ($res->is_success) {
145 0         0 return $decoded->{TableNames};
146             }
147             else {
148 0         0 Carp::croak $self->_error_content($res, $decoded);
149             }
150             }
151              
152             sub put_item {
153 0     0 1 0 my ($self, $content) = @_;
154              
155 0 0       0 Carp::croak "Item required." unless $content->{Item};
156 0 0       0 Carp::croak "TableName required." unless $content->{TableName};
157 0         0 my $req = $self->make_request('PutItem', $content);
158 0         0 my $res = $self->ua->request($req);
159 0 0       0 if ($res->is_success) {
160 0         0 return 1;
161             } else {
162 0         0 $self->_err_res_json_decode($res);
163             }
164             }
165              
166             sub get_item {
167 0     0 1 0 my ($self, $content) = @_;
168              
169 0 0       0 Carp::croak "Key required." unless $content->{Key};
170 0 0       0 Carp::croak "TableName required." unless $content->{TableName};
171 0         0 my $req = $self->make_request('GetItem', $content);
172 0         0 my $res = $self->ua->request($req);
173 0         0 my $decoded;
174 0         0 eval {
175 0         0 $decoded = $self->json->decode($res->content);
176             };
177 0 0       0 Carp::croak $self->_error_content($res, {Message => $res->content}) if $@;
178 0 0       0 if ($res->is_success) {
179 0         0 return _except_type($decoded->{Item});
180             }
181             else {
182 0         0 Carp::croak $self->_error_content($res, $decoded);
183             }
184              
185             }
186              
187             sub update_item {
188 0     0 1 0 my ($self, $content) = @_;
189              
190 0 0       0 Carp::croak "Key required." unless $content->{Key};
191 0 0       0 Carp::croak "TableName required." unless $content->{TableName};
192 0         0 my $req = $self->make_request('UpdateItem', $content);
193 0         0 my $res = $self->ua->request($req);
194 0 0       0 if ($res->is_success) {
195 0         0 return 1;
196             } else {
197 0         0 $self->_err_res_json_decode($res);
198             }
199             }
200              
201             sub delete_item {
202 0     0 1 0 my ($self, $content) = @_;
203              
204 0 0       0 Carp::croak "Key required." unless $content->{Key};
205 0 0       0 Carp::croak "TableName required." unless $content->{TableName};
206 0         0 my $req = $self->make_request('DeleteItem', $content);
207 0         0 my $res = $self->ua->request($req);
208 0 0       0 if ($res->is_success) {
209 0         0 return 1;
210             } else {
211 0         0 $self->_err_res_json_decode($res);
212             }
213             }
214              
215             sub create_table {
216 0     0 1 0 my ($self, $content) = @_;
217              
218 0 0       0 Carp::croak "AttributeDefinitions required." unless $content->{AttributeDefinitions};
219 0 0       0 Carp::croak "KeySchema required." unless $content->{KeySchema};
220 0 0       0 Carp::croak "ProvisionedThroughput required." unless $content->{ProvisionedThroughput};
221 0 0       0 Carp::croak "TableName required." unless $content->{TableName};
222              
223 0         0 my $req = $self->make_request('CreateTable', $content);
224 0         0 my $res = $self->ua->request($req);
225 0 0       0 if ($res->is_success) {
226 0         0 return 1;
227             } else {
228 0         0 $self->_err_res_json_decode($res);
229             }
230             }
231              
232             sub delete_table {
233 0     0 1 0 my ($self, $content) = @_;
234              
235 0 0       0 Carp::croak "TableName required." unless $content->{TableName};
236 0         0 my $req = $self->make_request('DeleteTable', $content);
237 0         0 my $res = $self->ua->request($req);
238 0 0       0 if ($res->is_success) {
239 0         0 return 1;
240             } else {
241 0         0 $self->_err_res_json_decode($res);
242             }
243             }
244              
245             sub describe_table {
246 0     0 1 0 my ($self, $content) = @_;
247              
248 0 0       0 Carp::croak "TableName required." unless $content->{TableName};
249 0         0 my $req = $self->make_request('DescribeTable', $content);
250 0         0 my $res = $self->ua->request($req);
251 0         0 my $decoded;
252 0         0 eval {
253 0         0 $decoded = $self->json->decode($res->content);
254             };
255 0 0       0 Carp::croak $self->_error_content($res, {Message => $res->content}) if $@;
256 0 0       0 if ($res->is_success) {
257 0         0 return $decoded->{Table};
258             } else {
259 0         0 Carp::croak $self->_error_content($res, $decoded);
260             }
261              
262             }
263              
264             sub update_table {
265 0     0 1 0 my ($self, $content) = @_;
266              
267 0 0       0 Carp::croak "TableName required." unless $content->{TableName};
268 0         0 my $req = $self->make_request('UpdateTable', $content);
269 0         0 my $res = $self->ua->request($req);
270 0 0       0 if ($res->is_success) {
271 0         0 return 1;
272             } else {
273 0         0 $self->_err_res_json_decode($res);
274             }
275             }
276              
277             sub query {
278 0     0 1 0 my ($self, $content) = @_;
279              
280 0 0       0 Carp::croak "TableName required." unless $content->{TableName};
281 0         0 my $req = $self->make_request('Query', $content);
282 0         0 my $res = $self->ua->request($req);
283              
284 0         0 my $decoded;
285 0         0 eval {
286 0         0 $decoded = $self->json->decode($res->content);
287             };
288 0 0       0 Carp::croak $self->_error_content($res, {Message => $res->content}) if $@;
289 0 0       0 if ($res->is_success) {
290 0         0 return _except_type($decoded->{Items});
291             } else {
292 0         0 Carp::croak $self->_error_content($res, $decoded);
293             }
294             }
295              
296             sub scan {
297 0     0 1 0 my ($self, $content) = @_;
298              
299 0 0       0 Carp::croak "TableName required." unless $content->{TableName};
300 0         0 my $req = $self->make_request('Scan', $content);
301 0         0 my $res = $self->ua->request($req);
302 0         0 my $decoded;
303 0         0 eval {
304 0         0 $decoded = $self->json->decode($res->content);
305             };
306 0 0       0 Carp::croak $self->_error_content($res, {Message => $res->content}) if $@;
307 0 0       0 if ($res->is_success) {
308 0         0 return _except_type($decoded->{Items});
309             } else {
310 0         0 Carp::croak $self->_error_content($res, $decoded);
311             }
312             }
313              
314             sub batch_get_item {
315 0     0 1 0 my ($self, $content) = @_;
316              
317 0 0       0 Carp::croak "RequestItems required." unless $content->{RequestItems};
318 0         0 my $req = $self->make_request('BatchGetItem', $content);
319 0         0 my $res = $self->ua->request($req);
320 0         0 my $decoded;
321 0         0 eval {
322 0         0 $decoded = $self->json->decode($res->content);
323             };
324 0 0       0 Carp::croak $self->_error_content($res, {Message => $res->content}) if $@;
325 0 0       0 if ($res->is_success) {
326 0         0 my $res;
327 0         0 for my $k (keys %{$decoded->{Responses}}) {
  0         0  
328 0         0 push @{$res}, {$k => _except_type($decoded->{Responses}->{$k})};
  0         0  
329             }
330 0         0 return $res;
331             } else {
332 0         0 Carp::croak $self->_error_content($res, $decoded);
333             }
334             }
335              
336             sub batch_write_item {
337 0     0 1 0 my ($self, $content) = @_;
338              
339 0 0       0 Carp::croak "RequestItems required." unless $content->{RequestItems};
340 0         0 my $req = $self->make_request('BatchWriteItem', $content);
341 0         0 my $res = $self->ua->request($req);
342 0 0       0 if ($res->is_success) {
343 0         0 return 1;
344             }
345             else {
346 0         0 $self->_err_res_json_decode($res);
347             }
348             }
349              
350             sub _except_type {
351 0     0   0 my $v = shift;
352 0         0 my $res;
353 0 0       0 if (ref $v eq 'HASH') {
    0          
354 0         0 for my $key (keys %{$v}) {
  0         0  
355 0         0 my $value = $v->{$key};
356 0         0 $res->{$key} = _rm_type($value);
357             }
358             } elsif (ref $v eq 'ARRAY') {
359 0         0 for my $w (@{$v}) {
  0         0  
360 0         0 push @{$res}, _except_type($w);
  0         0  
361             }
362             }
363 0         0 return $res;
364             }
365              
366             sub _rm_type {
367 0     0   0 my $v = shift;
368 0         0 my ($type, $value) = %$v;
369 0         0 my $res;
370 0 0       0 if ($type eq 'L') {
    0          
    0          
371 0         0 for my $i (@$value) {
372 0         0 push @$res, _rm_type($i);
373             }
374 0         0 return $res;
375             }
376             elsif ($type eq 'M') {
377 0         0 for my $i (keys %$value) {
378 0         0 $res->{$i} = _rm_type($value->{$i});
379             }
380 0         0 return $res;
381             }
382             elsif ($type eq 'B') {
383 0         0 return MIME::Base64::decode_base64($value);
384             }
385             else {
386 0         0 return $value;
387             }
388             }
389              
390             sub _err_res_json_decode {
391 0     0   0 my ($self, $res) = @_;
392 0         0 my $decoded;
393 0         0 eval {
394 0         0 $decoded = $self->json->decode($res->content);
395             };
396 0 0       0 if ($@) {
397 0 0       0 Carp::croak $self->_error_content($res, {Message => $res->content}) if $@;
398             } else {
399 0         0 Carp::croak $self->_error_content($res, $decoded);
400             }
401             }
402              
403             sub _error_content {
404 13     13   99 my ($self, $res, $decoded) = @_;
405              
406 13 50       68 my $message = $decoded->{Message} ? $decoded->{Message} : $decoded->{message};
407 13 50       50 my $type = $decoded->{__type} ? $decoded->{__type} : "";
408 13         68 return "status_code : " . $res->status_line
409             . " __type : " . $type
410             . " message : " . $message;
411             }
412              
413              
414             1;
415             __END__