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   21 use strict;
  3         7  
  3         88  
4 3     3   15 use warnings;
  3         12  
  3         86  
5              
6 3     3   16 use parent qw(Exporter);
  3         6  
  3         14  
7              
8 3     3   170 use Amazon::API::Constants qw(:booleans :chars);
  3         8  
  3         804  
9 3     3   1225 use Amazon::API::Template qw(:all);
  3         11  
  3         371  
10              
11 3     3   24 use Carp;
  3         8  
  3         176  
12 3     3   53 use Data::Dumper;
  3         12  
  3         184  
13 3     3   33 use English qw(-no_match_vars);
  3         6  
  3         23  
14 3     3   1444 use List::Util qw( pairs any );
  3         7  
  3         223  
15 3     3   18 use ReadonlyX;
  3         6  
  3         176  
16 3     3   21 use Scalar::Util qw(reftype blessed );
  3         7  
  3         2033  
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.8'; ## 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   25 use warnings FATAL => qw( regexp );
  3         8  
  3         4067  
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              
434             $options->{_value} = $value;
435             $options->{service} = '@service@';
436            
437             my $self = $class->SUPER::new($options);
438            
439             return $self;
440             }
441              
442              
443             1;
444              
445             =pod
446              
447             =encoding utf8
448              
449             =head1 NAME
450              
451             @class@
452              
453             @synopsis@
454              
455             =head1 DESCRIPTION
456              
457             @description@
458              
459             =head1 PARAMETERS
460              
461             =over 5
462              
463             =item Type: @type@
464              
465             @limits@
466              
467             =back
468              
469             =over 5
470              
471             @members@
472              
473             =back
474              
475             =head1 NOTE
476              
477             You almost never need to actually instantiate these objects
478             manually if you are using the APIs that are built using Botocore
479             support. Data structures required for each API are created
480             automatically for you from simple Perl objects. For example to create
481             an SQS queue the Botocore documentation states that you need to pass the
482             C<QueueName>, C<Attribute> and C<Tag> values.
483              
484             my $result = $sqs->CreateQueue(
485             {
486             QueueName => 'foo',
487             Tag => [ { Name => 'foo' }, { Env => 'dev' } ],
488             Attribute => [ { VisibilityTimeout => 40 }, { DelaySeconds => 60 } ]
489             });
490              
491             Each of these parameters is described in the Botocore metadata as one
492             of several different shapes which ultimately are mapped to one of the
493             data types below.
494              
495             =over 5
496              
497             =item * map
498              
499             A I<map> generally corresponds to an array of hashes which represent key/value pairs.
500              
501             =item * list
502              
503             A I<list> corresponds to an array. Lists can have a minimum or maximum length.
504              
505             =item * string
506              
507             A I<string> corresponds to a SCALAR containing a character string. The
508             string may be constrained by a pattern or can be an enumeration.
509              
510             =item * integer
511              
512             An I<integer> corresponds to a SCALAR containing an integer value.
513             The integer may have a max and minimum value.
514              
515             =item * boolean
516              
517             A I<boolean> values corresponds to a SCALAR containing the values 'true' or 'false'
518              
519             =back
520              
521             L<Amazon::API::Botocore::Shape> handles converting Perl data
522             structures into shapes and eventually back into Perl data structures
523             which can be properly serialized as input to the APIs.
524              
525             =head1 SEE ALSO
526              
527             L<Amazon::API>, L<Amazon::API::Botocore::Shape>
528             @see_also@
529              
530             =head1 AUTHOR
531              
532             Autogenerated by @program_name@ on @timestamp@
533              
534             =cut
535              
536             1;