File Coverage

blib/lib/Net/Amazon/DynamoDB/Lite.pm
Criterion Covered Total %
statement 70 208 33.6
branch 1 78 1.2
condition n/a
subroutine 18 33 54.5
pod 13 14 92.8
total 102 333 30.6


line stmt bran cond sub pod time code
1             package Net::Amazon::DynamoDB::Lite;
2 16     16   3413430 use 5.008001;
  16         63  
3 16     16   92 use strict;
  16         28  
  16         347  
4 16     16   80 use warnings;
  16         36  
  16         810  
5              
6             our $VERSION = "0.04";
7              
8 16     16   82 use Carp;
  16         29  
  16         1127  
9 16     16   11890 use Furl;
  16         494204  
  16         477  
10 16     16   11668 use HTTP::Request;
  16         360633  
  16         500  
11 16     16   15522 use JSON;
  16         189974  
  16         81  
12 16     16   14412 use MIME::Base64;
  16         9844  
  16         1072  
13 16     16   12544 use Moo;
  16         233877  
  16         119  
14 16     16   39787 use POSIX qw(setlocale LC_TIME strftime);
  16         106961  
  16         90  
15 16     16   22364 use Scalar::Util qw(reftype);
  16         43  
  16         1280  
16 16     16   13770 use WebService::Amazon::Signature::v4;
  16         678102  
  16         40918  
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   6669 my ($self) = @_;
76 15         80 my $locale = setlocale(LC_TIME);
77 15         111 setlocale(LC_TIME, "C");
78 15         105 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         204 setlocale(LC_TIME, $locale);
84 15         99 $v4;
85             }
86              
87             sub _build_scope {
88 15     15   6526 my ($self) = @_;
89 15         811 join '/', strftime('%Y%m%d', gmtime), $self->region, qw(dynamodb aws4_request);
90             }
91              
92             sub _build_ua {
93 13     13   5648 my ($self) = @_;
94              
95 13         208 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   825 my ($self) = @_;
106 2         25 URI->new('https://dynamodb.' . $self->region . '.amazonaws.com/');
107             }
108              
109             sub make_request {
110 14     14 0 55 my ($self, $target, $content) = @_;
111              
112 14         66 my $req = HTTP::Request->new(
113             POST => $self->uri,
114             );
115 14         16854 my $locale = setlocale(LC_TIME);
116 14         345 setlocale(LC_TIME, "C");
117 14         363 $req->header(host => $self->uri->host);
118 14         3681 my $http_date = strftime('%a, %d %b %Y %H:%M:%S %Z', localtime);
119 14         459 my $amz_date = strftime('%Y%m%dT%H%M%SZ', gmtime);
120 14         82 $req->header(Date => $http_date);
121 14         650 $req->header('x-amz-date' => $amz_date);
122 14         966 $req->header('x-amz-target' => 'DynamoDB_' . $self->api_version . ".$target" );
123 14         858 $req->header('content-type' => 'application/x-amz-json-1.0');
124 14         934 $content = $self->json->encode($content);
125 14         132 $req->content($content);
126 14         380 $req->header('Content-Length' => length($content));
127 14         676 $self->signature->from_http_request($req);
128 14         2977 $req->header(Authorization => $self->signature->calculate_signature);
129 14         22635 setlocale(LC_TIME, $locale);
130 14         38 return $req;
131             }
132              
133             sub list_tables {
134 13     13 1 2598 my ($self, $content) = @_;
135              
136 13 50       68 $content = {} unless $content;
137 13         56 my $req = $self->make_request('ListTables', $content);
138 13         62 my $res = $self->ua->request($req);
139 13         30868 my $decoded = $self->json->decode($res->content);
140 0 0         if ($res->is_success) {
141 0           return $decoded->{TableNames};
142             }
143             else {
144 0           Carp::croak $self->_error_content($res, $decoded);
145             }
146             }
147              
148             sub put_item {
149 0     0 1   my ($self, $content) = @_;
150              
151 0 0         Carp::croak "Item required." unless $content->{Item};
152 0 0         Carp::croak "TableName required." unless $content->{TableName};
153 0           my $req = $self->make_request('PutItem', $content);
154 0           my $res = $self->ua->request($req);
155 0 0         if ($res->is_success) {
156 0           return 1;
157             } else {
158 0           my $decoded = $self->json->decode($res->content);
159 0           Carp::croak $self->_error_content($res, $decoded);
160             }
161             }
162              
163             sub get_item {
164 0     0 1   my ($self, $content) = @_;
165              
166 0 0         Carp::croak "Key required." unless $content->{Key};
167 0 0         Carp::croak "TableName required." unless $content->{TableName};
168 0           my $req = $self->make_request('GetItem', $content);
169 0           my $res = $self->ua->request($req);
170 0           my $decoded = $self->json->decode($res->content);
171 0 0         if ($res->is_success) {
172 0           return _except_type($decoded->{Item});
173             }
174             else {
175 0           Carp::croak $self->_error_content($res, $decoded);
176             }
177              
178             }
179              
180             sub update_item {
181 0     0 1   my ($self, $content) = @_;
182              
183 0 0         Carp::croak "Key required." unless $content->{Key};
184 0 0         Carp::croak "TableName required." unless $content->{TableName};
185 0           my $req = $self->make_request('UpdateItem', $content);
186 0           my $res = $self->ua->request($req);
187 0 0         if ($res->is_success) {
188 0           return 1;
189             } else {
190 0           my $decoded = $self->json->decode($res->content);
191 0           Carp::croak $self->_error_content($res, $decoded);
192             }
193             }
194              
195             sub delete_item {
196 0     0 1   my ($self, $content) = @_;
197              
198 0 0         Carp::croak "Key required." unless $content->{Key};
199 0 0         Carp::croak "TableName required." unless $content->{TableName};
200 0           my $req = $self->make_request('DeleteItem', $content);
201 0           my $res = $self->ua->request($req);
202 0 0         if ($res->is_success) {
203 0           return 1;
204             } else {
205 0           my $decoded = $self->json->decode($res->content);
206 0           Carp::croak $self->_error_content($res, $decoded);
207             }
208             }
209              
210             sub create_table {
211 0     0 1   my ($self, $content) = @_;
212              
213 0 0         Carp::croak "AttributeDefinitions required." unless $content->{AttributeDefinitions};
214 0 0         Carp::croak "KeySchema required." unless $content->{KeySchema};
215 0 0         Carp::croak "ProvisionedThroughput required." unless $content->{ProvisionedThroughput};
216 0 0         Carp::croak "TableName required." unless $content->{TableName};
217              
218 0           my $req = $self->make_request('CreateTable', $content);
219 0           my $res = $self->ua->request($req);
220 0 0         if ($res->is_success) {
221 0           return 1;
222             } else {
223 0           my $decoded = $self->json->decode($res->content);
224 0           Carp::croak $self->_error_content($res, $decoded);
225             }
226             }
227              
228             sub delete_table {
229 0     0 1   my ($self, $content) = @_;
230              
231 0 0         Carp::croak "TableName required." unless $content->{TableName};
232 0           my $req = $self->make_request('DeleteTable', $content);
233 0           my $res = $self->ua->request($req);
234 0 0         if ($res->is_success) {
235 0           return 1;
236             } else {
237 0           my $decoded = $self->json->decode($res->content);
238 0           Carp::croak $self->_error_content($res, $decoded);
239             }
240             }
241              
242             sub describe_table {
243 0     0 1   my ($self, $content) = @_;
244              
245 0 0         Carp::croak "TableName required." unless $content->{TableName};
246 0           my $req = $self->make_request('DescribeTable', $content);
247 0           my $res = $self->ua->request($req);
248 0           my $decoded = $self->json->decode($res->content);
249 0 0         if ($res->is_success) {
250 0           return $decoded->{Table};
251             }
252             else {
253 0           Carp::croak $self->_error_content($res, $decoded);
254             }
255              
256             }
257              
258             sub update_table {
259 0     0 1   my ($self, $content) = @_;
260              
261 0 0         Carp::croak "TableName required." unless $content->{TableName};
262 0           my $req = $self->make_request('UpdateTable', $content);
263 0           my $res = $self->ua->request($req);
264 0 0         if ($res->is_success) {
265 0           return 1;
266             } else {
267 0           my $decoded = $self->json->decode($res->content);
268 0           Carp::croak $self->_error_content($res, $decoded);
269             }
270             }
271              
272             sub query {
273 0     0 1   my ($self, $content) = @_;
274              
275 0 0         Carp::croak "TableName required." unless $content->{TableName};
276 0           my $req = $self->make_request('Query', $content);
277 0           my $res = $self->ua->request($req);
278              
279 0           my $decoded = $self->json->decode($res->content);
280 0 0         if ($res->is_success) {
281 0           return _except_type($decoded->{Items});
282             } else {
283 0           Carp::croak $self->_error_content($res, $decoded);
284             }
285             }
286              
287             sub scan {
288 0     0 1   my ($self, $content) = @_;
289              
290 0 0         Carp::croak "TableName required." unless $content->{TableName};
291 0           my $req = $self->make_request('Scan', $content);
292 0           my $res = $self->ua->request($req);
293 0           my $decoded = $self->json->decode($res->content);
294 0 0         if ($res->is_success) {
295 0           return _except_type($decoded->{Items});
296             } else {
297 0           Carp::croak $self->_error_content($res, $decoded);
298             }
299             }
300              
301             sub batch_get_item {
302 0     0 1   my ($self, $content) = @_;
303              
304 0 0         Carp::croak "RequestItems required." unless $content->{RequestItems};
305 0           my $req = $self->make_request('BatchGetItem', $content);
306 0           my $res = $self->ua->request($req);
307 0           my $decoded = $self->json->decode($res->content);
308 0 0         if ($res->is_success) {
309 0           my $res;
310 0           for my $k (keys %{$decoded->{Responses}}) {
  0            
311 0           push @{$res}, {$k => _except_type($decoded->{Responses}->{$k})};
  0            
312             }
313 0           return $res;
314             } else {
315 0           Carp::croak $self->_error_content($res, $decoded);
316             }
317             }
318              
319             sub batch_write_item {
320 0     0 1   my ($self, $content) = @_;
321              
322 0 0         Carp::croak "RequestItems required." unless $content->{RequestItems};
323 0           my $req = $self->make_request('BatchWriteItem', $content);
324 0           my $res = $self->ua->request($req);
325 0 0         if ($res->is_success) {
326 0           return 1;
327             }
328             else {
329 0           my $decoded = $self->json->decode($res->content);
330 0           Carp::croak $self->_error_content($res, $decoded);
331             }
332             }
333              
334             sub _except_type {
335 0     0     my $v = shift;
336 0           my $res;
337 0 0         if (ref $v eq 'HASH') {
    0          
338 0           for my $key (keys %{$v}) {
  0            
339 0           my $value = $v->{$key};
340 0           $res->{$key} = _rm_type($value);
341             }
342             } elsif (ref $v eq 'ARRAY') {
343 0           for my $w (@{$v}) {
  0            
344 0           push @{$res}, _except_type($w);
  0            
345             }
346             }
347 0           return $res;
348             }
349              
350             sub _rm_type {
351 0     0     my $v = shift;
352 0           my ($type, $value) = %$v;
353 0           my $res;
354 0 0         if ($type eq 'L') {
    0          
    0          
355 0           for my $i (@$value) {
356 0           push @$res, _rm_type($i);
357             }
358 0           return $res;
359             }
360             elsif ($type eq 'M') {
361 0           for my $i (keys %$value) {
362 0           $res->{$i} = _rm_type($value->{$i});
363             }
364 0           return $res;
365             }
366             elsif ($type eq 'B') {
367 0           return MIME::Base64::decode_base64($value);
368             }
369             else {
370 0           return $value;
371             }
372             }
373              
374             sub _error_content {
375 0     0     my ($self, $res, $decoded) = @_;
376              
377 0 0         my $message = $decoded->{Message} ? $decoded->{Message} : $decoded->{message};
378             return "status_code : " . $res->status_line
379             . " __type : " . $decoded->{__type}
380 0           . " message : " . $message;
381             }
382              
383              
384             1;
385             __END__