File Coverage

blib/lib/Amazon/API/Botocore/Shape/Utils.pm
Criterion Covered Total %
statement 36 154 23.3
branch 0 44 0.0
condition 0 14 0.0
subroutine 12 24 50.0
pod 0 11 0.0
total 48 247 19.4


line stmt bran cond sub pod time code
1             package Amazon::API::Botocore::Shape::Utils;
2              
3 3     3   22 use strict;
  3         9  
  3         91  
4 3     3   17 use warnings;
  3         7  
  3         96  
5              
6 3     3   20 use parent qw(Exporter);
  3         17  
  3         17  
7              
8 3     3   178 use Amazon::API::Constants qw(:booleans :chars);
  3         6  
  3         794  
9 3     3   1245 use Amazon::API::Template qw(:all);
  3         10  
  3         370  
10              
11 3     3   22 use Carp;
  3         6  
  3         184  
12 3     3   24 use Data::Dumper;
  3         5  
  3         150  
13 3     3   17 use English qw(-no_match_vars);
  3         6  
  3         20  
14 3     3   1087 use List::Util qw( pairs any );
  3         7  
  3         208  
15 3     3   19 use ReadonlyX;
  3         5  
  3         163  
16 3     3   19 use Scalar::Util qw(reftype blessed );
  3         6  
  3         2014  
17              
18             our @EXPORT_OK = qw(
19             $SHAPE_NAME_TEMPLATE
20             create_shape
21             create_module_name
22             check_pattern
23             check_type
24             flatten
25             get_service_from_class
26             param_n
27             require_class
28             require_shape
29             snake_case
30             );
31              
32             our %EXPORT_TAGS = ( all => [@EXPORT_OK] );
33              
34             Readonly::Scalar our $SHAPE_NAME_TEMPLATE =>
35             'Amazon::API::Botocore::Shape::%s::%s';
36              
37             our $VERSION = '2.0.11'; ## no critic (RequireInterpolationOfMetachars)
38              
39             our $TEMPLATE_START = tell DATA;
40              
41             ########################################################################
42             sub create_module_name {
43             ########################################################################
44 0     0 0   my ($service) = @_;
45              
46 0           my $module_name;
47              
48 0 0         if ( length $service == 3 ) {
49 0           $module_name = uc $service; # SQS SNS IAM STS SES RDS...
50             }
51             else {
52             # Route53, Cloudwatch? SecretsManager? ...well then provide your own damn name!
53 0           $module_name = ucfirst $service;
54             }
55              
56 0           return $module_name;
57             }
58              
59             ########################################################################
60             sub require_class {
61             ########################################################################
62 0     0 0   my ($class) = @_;
63              
64 0           my $class_path = $class;
65              
66 0           $class_path =~ s/::/\//xsmg;
67              
68 0           my $retval = eval { require "$class_path.pm"; };
  0            
69              
70 0 0         return $retval ? $INC{"$class_path.pm"} : undef;
71             }
72              
73             ########################################################################
74             sub get_service_from_class {
75             ########################################################################
76 0     0 0   my ($class) = @_;
77              
78             # find the service name, we'll need then when creating the request class
79 0 0         my $name = ref $class ? ref $class : $class;
80              
81 0 0         if ( $name =~ /::([^:]+)$/xsm ) {
82 0           $name = $1;
83             }
84              
85 0           return $name;
86             }
87              
88             ########################################################################
89             sub require_shape {
90             ########################################################################
91 0     0 0   my ( $shape, $service ) = @_;
92              
93 0           my $class = create_shape_name( $shape, $service );
94              
95 0           my $class_path = require_class $class;
96              
97 0 0         return $class_path ? $class : undef;
98             }
99              
100             ########################################################################
101             # snake_case name
102             #
103             # Attempts to create a snake case name from a CamelCase name
104             # - FooBar => foo_bar
105             # - FOOBar => FOO_Bar
106              
107             ########################################################################
108             sub snake_case {
109             ########################################################################
110 0     0 0   my ($name) = @_;
111              
112 0           while ( $name =~ s/([[:upper:]])([[:lower:]])/lc("_$1").$2/xsme ) { }; # snake_case the CamelCase
  0            
113              
114 0           $name =~ s/^_//xsm;
115              
116 0           $name =~ s/([[:lower:]])([[:upper:]])/$1_$2/gxsm;
117              
118 0           return $name;
119             }
120              
121             ########################################################################
122             sub check_pattern {
123             ########################################################################
124 0     0 0   my ( $value, $pattern );
125              
126 0 0         return $TRUE if !$pattern;
127              
128             ## no critic
129              
130 0           eval {
131 3     3   23 use warnings FATAL => qw( regexp );
  3         7  
  3         4175  
132              
133 0           qr/$pattern/;
134             };
135              
136 0 0         if ( !$EVAL_ERROR ) {
137 0 0         croak "value must match pattern [$pattern]"
138             if $value !~ /$pattern/sm;
139             }
140              
141 0           return $TRUE;
142             }
143              
144             ########################################################################
145             sub check_type {
146             ########################################################################
147 0     0 0   my ( $required_type, $type ) = @_;
148              
149 0 0         if ( ref $required_type ) {
150 0     0     return any {$type} @{$required_type};
  0            
  0            
151             }
152              
153 0   0       $type ||= 'SCALAR';
154              
155 0           return $required_type eq $type;
156              
157             }
158              
159             ########################################################################
160             # flatten('Tag', $tag_list), flatten({ Tag => $tag_list })
161             ########################################################################
162             sub flatten {
163             ########################################################################
164 0     0 0   my (@args) = @_;
165              
166 0           my ( $name, $list ) = @args;
167              
168 0 0         if ( ref $name ) {
169 0           ( $name, $list ) = %{$name};
  0            
170             }
171              
172 0           my $idx = 0;
173              
174 0           my @items = @{$list};
  0            
175              
176 0           my @output;
177              
178 0           foreach my $elem (@items) {
179 0           ++$idx;
180              
181 0 0 0       if ( ref $elem && reftype($elem) eq 'HASH' ) {
182 0           my @kv = %{$elem};
  0            
183              
184 0           foreach my $p ( pairs @kv ) {
185              
186 0           my $key = sprintf '%s.%d.%s', $name, $idx, $p->[0];
187              
188 0           push @output, { $key => $p->[1] };
189             }
190             }
191             else {
192 0           my $key = sprintf '%s.%d', $name, $idx;
193              
194 0           push @output, { $key => $elem };
195             }
196             }
197              
198 0           return \@output;
199             }
200              
201             ########################################################################
202             # param_n() is a rather naive attempt to implement a way to create the
203             # "param n" notation used by some Amazon APIs (most notably APIs using
204             # a "map" shape and expecting a query string - e.g. SQS)
205             #
206             # Note that param_n() and flatten() are related but produce different
207             # output and accept slightly different input. Whereas param_n() can be
208             # sent an object that contains some parameters that might not be
209             # serialized in the param_n() notation, flatten() input should only be
210             # an object that will be serialized in an intermediate param_n() format
211             # suitable for create_urlencoded_content().
212             #
213             # Both must be passed a valid request. A "valid" request is one in
214             # which the parameters represent the serialized version of the "map"
215             # parameter shape. For example to send multiple tags to one of the
216             # APIs that use the param_n notation you would pass an object that
217             # looks like this:
218             #
219             # my $tags = Tag => [ { Key => 'Name', Value => 'foo'},
220             # { Key => 'Env', Value => 'dev }
221             # ];
222             #
223             # The finalize() method of botocore request objects
224             # (Amazon::API::Botocore::Shape) will produce this exact serialized
225             # representation when passed an object that looks like this:
226             #
227             # my $tags = Tag => [ { 'Name' => 'foo' },
228             # { 'Env' => 'dev' },
229             # ];
230             #
231             # It knows knows how to create the former structure by consulting the
232             # botocore metadata for the TagMap "map" type.
233             #
234             # param_n() was written prior to the use of botocore metadata and
235             # assumes the the object being passed was hand rolled by someone with
236             # the knowledge of the required finalized map object.
237             #
238             # flatten($tags) != param_n($tags)
239             #
240             # ...however...surprisingly....
241             #
242             # create_urlencode_content(flatten($tags)) == create_urlencode_content(param_n($tags))
243             #
244             # f(x) <> g(x), but h(f(x)) == h(g(x))
245             #
246             # In other words, create_urlencoded_content() in an isomorphic
247             # transformation function.
248             #
249             ########################################################################
250             sub param_n {
251             ########################################################################
252 0     0 0   my ( $message, $prefix, $idx ) = @_;
253              
254 0 0         if ( !defined $idx ) { # first call, check args
255 0 0         croak 'message argument must be reference'
256             if !ref $message;
257             }
258              
259 0           my @param_n;
260              
261 0 0         if ( ref $message ) {
262 0 0         if ( reftype($message) eq 'HASH' ) {
263 0           foreach my $k ( keys %{$message} ) {
  0            
264             push @param_n,
265 0 0         param_n( $message->{$k}, $prefix ? "$prefix.$k" : $k, $idx );
266             }
267             } ## end if ( reftype($message)...)
268             else {
269 0           $idx = 1;
270 0           foreach my $e ( @{$message} ) {
  0            
271 0 0         push @param_n,
272             param_n( $e, $prefix ? "$prefix.$idx" : $EMPTY, $idx++ );
273             }
274             } ## end else [ if ( reftype($message)...)]
275             } ## end if ( ref $message )
276             else {
277 0 0 0       croak 'missing value'
278             if !defined $message || $message eq $EMPTY;
279              
280 0           return "$prefix=$message";
281             } ## end else [ if ( ref $message ) ]
282              
283 0           return @param_n;
284             } ## end sub param_n
285              
286             ########################################################################
287             sub create_shape_name {
288             ########################################################################
289 0     0 0   my ( $name, $service ) = @_;
290              
291 0           return sprintf $SHAPE_NAME_TEMPLATE, $service, $name;
292             }
293              
294             ########################################################################
295             sub create_shape {
296             ########################################################################
297 0     0 0   my (%args) = @_;
298              
299             my ( $shape_name, $service_description, $service )
300 0           = @args{qw( name service_description service )};
301              
302 0           my $shapes = $service_description->{shapes};
303              
304 0           my $shape = $shapes->{$shape_name};
305              
306 0           my $shape_class = create_shape_name( $shape_name, $service );
307              
308 0           my $shape_template = fetch_template( *DATA, $TEMPLATE_START );
309              
310 0           my $description = html2pod( $shape->{documentation} );
311              
312 0           my $required = join "\n", map {"=item $_\n"} @{ $shape->{required} };
  0            
  0            
313              
314 0           my $type = $shape->{type};
315              
316 0           my @members;
317             my @see_also;
318              
319 0 0         if ( $shape->{member} ) {
    0          
320 0           push @members, sprintf "=item %s\n", $shape->{member}->{shape};
321             }
322             elsif ( $shape->{members} ) {
323 0           foreach my $m ( sort keys %{ $shape->{members} } ) {
  0            
324 0           my $member = $shape->{members}->{$m};
325              
326 0           my $type = $member->{shape};
327 0   0       my $location_name = $member->{locationName} || $m;
328              
329 0           $type = sprintf 'L<%s|Amazon::API::Botocore::Shape::%s::%s/%s>', $type,
330             $service,
331             $type, $type;
332              
333 0           push @see_also, $type;
334              
335 0           my $description = html2pod( $member->{documentation} );
336 0           $description =~ s/\A\n+//xsm;
337 0           $description =~ s/[\n]+\z//xsm;
338              
339 0           my $item = <<'END_OF_ITEM';
340             =item Name: %s
341              
342             =over 10
343              
344             =item Type
345              
346             %s
347              
348             =item Description
349              
350             %s
351              
352             =back
353             END_OF_ITEM
354              
355 0           push @members, sprintf $item, $m, $type, $description;
356             }
357             }
358              
359 0           my @limits;
360              
361 0           foreach (qw( max min pattern )) {
362 0 0         if ( $shape->{$_} ) {
363 0           push @limits, sprintf "=item %s: %s\n", $_, $shape->{$_};
364             }
365             }
366              
367 0           my $lc_name = snake_case $shape_name;
368              
369 0           my %parameters = (
370             to_template_var('see_also') => join( "$COMMA ", @see_also ),
371             to_template_var('lc_name') => $lc_name,
372             to_template_var('package_name') => $shape_name,
373             to_template_var('program_name') => $PROGRAM_NAME,
374             to_template_var('timestamp') => scalar(localtime),
375             to_template_var('class') => $shape_class,
376             to_template_var('shape') => JSON->new->pretty->encode($shape),
377             to_template_var('service') => $service,
378             to_template_var('description') => $description,
379             to_template_var('type') => $type,
380             to_template_var('required') => $required,
381             to_template_var('members') => join( "\n", @members ) . "\n",
382             to_template_var('limits') => join( "\n", @limits ) . "\n",
383             );
384              
385 0           my $synopsis;
386              
387 0 0         if ( !$shape->{members}->{message} ) {
388 0           $synopsis = <<'END_OF_POD';
389             =head1 SYNOPSIS
390              
391             my $@lc_name@ = @class@->new( $parameters );
392             END_OF_POD
393              
394 0           $synopsis = render_template( $synopsis, \%parameters );
395             }
396              
397 0   0       $parameters{ to_template_var('synopsis') } = $synopsis // $EMPTY;
398              
399 0           my $pod = render_template( $shape_template, \%parameters );
400              
401 0           return $pod;
402             }
403              
404             1;
405              
406             __DATA__
407              
408             ########################################################################
409             package @class@;
410             ########################################################################
411              
412             # Autogenerated on @timestamp@
413              
414             use parent qw(Amazon::API::Botocore::Shape);
415              
416             use strict;
417             use warnings;
418              
419             use JSON qw(decode_json);
420              
421             our $SHAPE = <<'SHAPE';
422             @shape@
423             SHAPE
424              
425             our $SHAPE_DEFINITION = decode_json($SHAPE);
426              
427             ########################################################################
428             sub new {
429             ########################################################################
430             my ( $class, $value ) = @_;
431              
432             my $options = $SHAPE_DEFINITION;
433             my $type = $options->{type};
434              
435             # may have to initialize other types (list?) as well
436             if ( ! $value && $type eq 'structure' ) {
437             $value = {};
438             }
439            
440             $options->{_value} = $value;
441             $options->{service} = '@service@';
442            
443             my $self = $class->SUPER::new($options);
444            
445             return $self;
446             }
447              
448              
449             1;
450              
451             =pod
452              
453             =encoding utf8
454              
455             =head1 NAME
456              
457             @class@
458              
459             @synopsis@
460              
461             =head1 DESCRIPTION
462              
463             @description@
464              
465             =head1 PARAMETERS
466              
467             =over 5
468              
469             =item Type: @type@
470              
471             @limits@
472              
473             =back
474              
475             =over 5
476              
477             @members@
478              
479             =back
480              
481             =head1 NOTE
482              
483             You almost never need to actually instantiate these objects
484             manually if you are using the APIs that are built using Botocore
485             support. Data structures required for each API are created
486             automatically for you from simple Perl objects. For example to create
487             an SQS queue the Botocore documentation states that you need to pass the
488             C<QueueName>, C<Attribute> and C<Tag> values.
489              
490             my $result = $sqs->CreateQueue(
491             {
492             QueueName => 'foo',
493             Tag => [ { Name => 'foo' }, { Env => 'dev' } ],
494             Attribute => [ { VisibilityTimeout => 40 }, { DelaySeconds => 60 } ]
495             });
496              
497             Each of these parameters is described in the Botocore metadata as one
498             of several different shapes which ultimately are mapped to one of the
499             data types below.
500              
501             =over 5
502              
503             =item * map
504              
505             A I<map> generally corresponds to an array of hashes which represent key/value pairs.
506              
507             =item * list
508              
509             A I<list> corresponds to an array. Lists can have a minimum or maximum length.
510              
511             =item * string
512              
513             A I<string> corresponds to a SCALAR containing a character string. The
514             string may be constrained by a pattern or can be an enumeration.
515              
516             =item * integer
517              
518             An I<integer> corresponds to a SCALAR containing an integer value.
519             The integer may have a max and minimum value.
520              
521             =item * boolean
522              
523             A I<boolean> values corresponds to a SCALAR containing the values 'true' or 'false'
524              
525             =back
526              
527             L<Amazon::API::Botocore::Shape> handles converting Perl data
528             structures into shapes and eventually back into Perl data structures
529             which can be properly serialized as input to the APIs.
530              
531             =head1 SEE ALSO
532              
533             L<Amazon::API>, L<Amazon::API::Botocore::Shape>
534             @see_also@
535              
536             =head1 AUTHOR
537              
538             Autogenerated by @program_name@ on @timestamp@
539              
540             =cut
541              
542             1;