File Coverage

blib/lib/VM/HetznerCloud/Schema.pm
Criterion Covered Total %
statement 82 123 66.6
branch 12 40 30.0
condition 1 7 14.2
subroutine 13 16 81.2
pod 1 1 100.0
total 109 187 58.2


line stmt bran cond sub pod time code
1             package VM::HetznerCloud::Schema;
2              
3             # ABSTRACT: OpenAPI schema for the cloud API
4              
5 4     4   55 use v5.24;
  4         67  
6              
7 4     4   28 use Mojo::Base -strict, -signatures;
  4         9  
  4         35  
8              
9 4     4   1043 use Encode;
  4         10  
  4         375  
10 4     4   2151 use JSON::Validator;
  4         148907  
  4         39  
11 4     4   168 use JSON::Validator::Formats;
  4         11  
  4         103  
12 4     4   30 use List::Util qw(uniq);
  4         9  
  4         257  
13 4     4   39 use Mojo::JSON qw(decode_json);
  4         8  
  4         179  
14 4     4   36 use Mojo::Loader qw(data_section);
  4         14  
  4         213  
15 4     4   28 use Mojo::Util qw(camelize);
  4         23  
  4         198  
16              
17 4     4   58 use utf8;
  4         12  
  4         31  
18              
19 4     4   191 use constant IV_SIZE => eval 'require Config;$Config::Config{ivsize}'; ## no critic
  4         13  
  4         464  
20              
21             our $VERSION = '0.0.3'; # VERSION
22              
23 7     7 1 17 sub validate ( $class, $operation, $params = {} ) {
  7         14  
  7         17  
  7         10  
  7         12  
24 7         28 my ($spec, $validator) = _get_params($operation);
25              
26             my %check_params = map {
27 7         36 my $camelized = camelize $_;
  6         23  
28 6         133 $camelized =~ s{Id$}{ID};
29              
30             $spec->{param_names}->{$camelized} ?
31             ($camelized => $params->{$_}) :
32 6 50       35 ($_ => $params->{$_});
33             } keys $params->%*;
34              
35 7         36 my @errors = $validator->validate(
36             \%check_params,
37             );
38              
39 7 50       2262 if ( @errors ) {
40 0         0 return ( undef, @errors );
41             }
42              
43 7         14 my %request_params;
44              
45 7         39 for my $param_name ( sort keys $spec->{param_names}->%* ) {
46 19         37 my $key = $spec->{param_names}->{$param_name};
47              
48 19 100       51 next if !$check_params{$param_name};
49              
50 4         17 $request_params{$key}->{$param_name} = $check_params{$param_name};
51             }
52              
53 7         55 return \%request_params;
54             }
55              
56 0     0   0 sub _get_subnmame ( $parts, $method ) {
  0         0  
  0         0  
  0         0  
57 0 0 0     0 my $all = ( $parts->@* && $parts->[-1] =~ m{\{} ) ? 0 : 1;
58 0         0 my @static_parts = grep { $_ !~ m/\{/ } $parts->@*;
  0         0  
59              
60 0 0       0 my $suffix = @static_parts ? ( join '_', '', @static_parts ) : '';
61              
62 0 0       0 if ( $all ) {
63 0 0       0 return 'list' . $suffix if $method eq 'get';
64 0 0       0 return 'create' . $suffix if $method eq 'post';
65             }
66              
67 0         0 return $method . $suffix;
68             }
69              
70 7     7   9 sub _get_params ($operation) {
  7         14  
  7         12  
71 7         10 state %operation_params;
72              
73 7         17 my $op_data = $operation_params{$operation};
74 7 100       29 return $op_data->@* if $op_data;
75              
76 2         14 my $api_spec = data_section(__PACKAGE__, 'openapi.json');
77 2         98532 $api_spec = encode_utf8( $api_spec );
78              
79 2         35 my $data = decode_json( $api_spec );
80              
81 2   50     1632848 my ($path, $method) = split '#', $operation // '1#1';
82 2         16 my $op = $data->{$path}->{$method};
83              
84 2 50       11 return if !$op;
85              
86 2         9 my $params = $op->{parameters};
87              
88 2         10 my %properties;
89             my @required;
90 2         0 my %param_names;
91              
92             PARAM:
93 2         16 for my $param ( $params->@* ) {
94 5 50       23 next PARAM if $param->{name} eq 'Auth-API-Token';
95              
96 5         13 my $name = $param->{name};
97              
98 5 100       78 if ( $param->{required} ) {
99 1         14 push @required, $name;
100             }
101              
102 5         54 $param_names{$name} = $param->{in};
103 5         20 $properties{$name} = $param;
104             }
105              
106 2         7 my ($content_type, $body_required);
107 2 50       13 if ( $op->{requestBody} ) {
108 0         0 my $body = $op->{requestBody}->{content};
109 0         0 $body_required = $op->{requestBody}->{required};
110              
111 0         0 ($content_type) = sort keys $body->%*;
112              
113 0 0       0 if ( $content_type eq 'application/json' ) {
    0          
114 0         0 my $schema = $body->{$content_type}->{schema};
115 0   0     0 my $prop = $schema->{properties} || {};
116 0         0 for my $property ( keys $prop->%* ) {
117 0         0 $properties{$property} = delete $prop->{$property};
118 0         0 $param_names{$property} = 'body';
119             }
120             }
121             elsif ( $content_type eq 'text/plain' ) {
122 0         0 $properties{text} = { type => "string" };
123 0         0 $param_names{text} = 'body';
124             }
125             }
126              
127 2         21 my $spec = {
128             type => 'object',
129             required => \@required,
130             body_required => $body_required,
131             properties => \%properties,
132             param_names => \%param_names,
133             content_type => $content_type,
134             };
135              
136 2         45 my $validator = JSON::Validator->new->schema($spec);
137              
138             $validator->formats->{uint32} = sub {
139 0     0   0 my ($sub) = JSON::Validator::Formats->can('_match_number');
140              
141 0         0 $sub->( unint32 => $_[0], 'L' );
142 2         6626 };
143              
144             $validator->formats->{uint64} = sub {
145 0     0   0 my ($sub) = JSON::Validator::Formats->can('_match_number');
146              
147 0         0 $sub->( unint32 => $_[0], IV_SIZE >= 8 ? 'Q' : '' );
148 2         233 };
149              
150 2 50       24 if ( $spec->{properties}->{'$ref'} ) {
151 0         0 my @ref = $spec->{properties}->{'$ref'};
152 0         0 while ( my $ref = shift @ref ) {
153 0         0 $ref =~ s{^#}{};
154              
155 0         0 my $data = $validator->get( $ref );
156 0 0       0 if ( $data->{properties} ) {
157 0         0 for my $property ( keys $data->{properties}->%* ) {
158 0 0       0 next if $data->{properties}->{$property}->{readOnly};
159              
160 0         0 $spec->{param_names}->{$property} = 'body';
161             }
162             }
163              
164 0 0       0 if ( $data->{allOf} ) {
165 0         0 push @ref, map{ $_->{'$ref'} } $data->{allOf}->@*;
  0         0  
166 0 0       0 if ( $data->{required} ) {
167 0         0 push $spec->{required}->@*, $data->{required}->@*;
168             }
169             }
170             }
171             }
172              
173 2         15 $spec->{required}->@* = uniq $spec->{required}->@*;
174              
175 2         10 $operation_params{$operation} = [ $spec, $validator ];
176 2         9369 return $operation_params{$operation}->@*;
177             }
178              
179             1;
180              
181             =pod
182              
183             =encoding UTF-8
184              
185             =head1 NAME
186              
187             VM::HetznerCloud::Schema - OpenAPI schema for the cloud API
188              
189             =head1 VERSION
190              
191             version 0.0.3
192              
193             =head1 SYNOPSIS
194              
195             =head1 METHODS
196              
197             =head2 validate
198              
199             =head1 AUTHOR
200              
201             Renee Baecker
202              
203             =head1 COPYRIGHT AND LICENSE
204              
205             This software is Copyright (c) 2018 by Renee Baecker.
206              
207             This is free software, licensed under:
208              
209             The Artistic License 2.0 (GPL Compatible)
210              
211             =cut
212              
213             __DATA__