line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Dancer2::Plugin::Swagger2; |
2
|
|
|
|
|
|
|
|
3
|
4
|
|
|
4
|
|
647187
|
use strict; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
96
|
|
4
|
4
|
|
|
4
|
|
13
|
use warnings; |
|
4
|
|
|
|
|
4
|
|
|
4
|
|
|
|
|
133
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
# ABSTRACT: A Dancer2 plugin for creating routes from a Swagger2 spec |
7
|
|
|
|
|
|
|
our $VERSION = '0.003'; # VERSION |
8
|
|
|
|
|
|
|
|
9
|
4
|
|
|
4
|
|
912
|
use Dancer2 ':syntax'; |
|
4
|
|
|
|
|
408192
|
|
|
4
|
|
|
|
|
18
|
|
10
|
4
|
|
|
4
|
|
96569
|
use Dancer2::Plugin; |
|
4
|
|
|
|
|
6415
|
|
|
4
|
|
|
|
|
19
|
|
11
|
4
|
|
|
4
|
|
3879
|
use Module::Load; |
|
4
|
|
|
|
|
2866
|
|
|
4
|
|
|
|
|
20
|
|
12
|
4
|
|
|
4
|
|
1592
|
use Swagger2; |
|
4
|
|
|
|
|
215511
|
|
|
4
|
|
|
|
|
32
|
|
13
|
4
|
|
|
4
|
|
140
|
use Swagger2::SchemaValidator; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
4475
|
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
|
16
|
0
|
|
|
0
|
0
|
0
|
sub DEBUG { !!$ENV{SWAGGER2_DEBUG} } |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
register swagger2 => sub { |
20
|
0
|
|
|
0
|
|
0
|
my ( $dsl, %args ) = @_; |
21
|
0
|
|
|
|
|
0
|
my $conf = plugin_setting; |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
### get arguments/config values/defaults ### |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
my $controller_factory = |
26
|
0
|
|
0
|
|
|
0
|
$args{controller_factory} || \&_default_controller_factory; |
27
|
0
|
0
|
|
|
|
0
|
my $url = $args{url} or die "argument 'url' missing"; |
28
|
|
|
|
|
|
|
my $validate_spec = |
29
|
|
|
|
|
|
|
exists $args{validate_spec} ? !!$args{validate_spec} |
30
|
|
|
|
|
|
|
: exists $conf->{validate_spec} ? !!$conf->{validate_spec} |
31
|
0
|
0
|
|
|
|
0
|
: 1; |
|
|
0
|
|
|
|
|
|
32
|
|
|
|
|
|
|
my $validate_requests = |
33
|
|
|
|
|
|
|
exists $args{validate_requests} ? !!$args{validate_requests} |
34
|
|
|
|
|
|
|
: exists $conf->{validate_requests} ? !!$conf->{validate_requests} |
35
|
0
|
0
|
|
|
|
0
|
: $validate_spec; |
|
|
0
|
|
|
|
|
|
36
|
|
|
|
|
|
|
my $validate_responses = |
37
|
|
|
|
|
|
|
exists $args{validate_responses} ? !!$args{validate_responses} |
38
|
|
|
|
|
|
|
: exists $conf->{validate_responses} ? !!$conf->{validate_responses} |
39
|
0
|
0
|
|
|
|
0
|
: $validate_spec; |
|
|
0
|
|
|
|
|
|
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
# parse Swagger2 file |
42
|
0
|
|
|
|
|
0
|
my $spec = Swagger2->new($url)->expand; |
43
|
|
|
|
|
|
|
|
44
|
0
|
0
|
0
|
|
|
0
|
if ( $validate_spec or $validate_requests or $validate_responses ) { |
|
|
|
0
|
|
|
|
|
45
|
0
|
0
|
|
|
|
0
|
if ( my @errors = $spec->validate ) { |
46
|
0
|
0
|
|
|
|
0
|
if ($validate_spec) { |
47
|
0
|
|
|
|
|
0
|
die join "\n" => "Swagger2: Invalid spec:", @errors; |
48
|
|
|
|
|
|
|
} |
49
|
|
|
|
|
|
|
else { |
50
|
0
|
|
|
|
|
0
|
warn "Spec contains errors but" |
51
|
|
|
|
|
|
|
. " request/response validation is enabled!"; |
52
|
|
|
|
|
|
|
} |
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
|
56
|
0
|
|
|
|
|
0
|
my $basePath = $spec->api_spec->get('/basePath'); |
57
|
0
|
|
|
|
|
0
|
my $paths = $spec->api_spec->get('/paths'); # TODO might be undef? |
58
|
|
|
|
|
|
|
|
59
|
0
|
|
|
|
|
0
|
while ( my ( $path => $path_spec ) = each %$paths ) { |
60
|
0
|
|
|
|
|
0
|
my $dancer2_path = $path; |
61
|
|
|
|
|
|
|
|
62
|
0
|
0
|
|
|
|
0
|
$basePath and $dancer2_path = $basePath . $dancer2_path; |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
# adapt Swagger2 syntax for URL path arguments to Dancer2 syntax |
65
|
|
|
|
|
|
|
# '/path/{argument}' -> '/path/:argument' |
66
|
0
|
|
|
|
|
0
|
$dancer2_path =~ s/\{([^{}]+?)\}/:$1/g; |
67
|
|
|
|
|
|
|
|
68
|
0
|
|
|
|
|
0
|
while ( my ( $http_method => $method_spec ) = each %$path_spec ) { |
69
|
0
|
0
|
|
|
|
0
|
my $coderef = $controller_factory->( |
70
|
|
|
|
|
|
|
$method_spec, $http_method, $path, $dsl, $conf, \%args |
71
|
|
|
|
|
|
|
) or next; |
72
|
|
|
|
|
|
|
|
73
|
0
|
0
|
|
|
|
0
|
DEBUG and warn "Add route $http_method $dancer2_path"; |
74
|
|
|
|
|
|
|
|
75
|
0
|
|
|
|
|
0
|
my $params = $method_spec->{parameters}; |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
# Dancer2 DSL keyword is different from HTTP method |
78
|
0
|
0
|
|
|
|
0
|
$http_method eq 'delete' and $http_method = 'del'; |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
$dsl->$http_method( |
81
|
|
|
|
|
|
|
$dancer2_path => sub { |
82
|
0
|
0
|
|
0
|
|
0
|
if ($validate_requests) { |
83
|
0
|
|
|
|
|
0
|
my @errors = |
84
|
|
|
|
|
|
|
_validate_request( $method_spec, $dsl->request ); |
85
|
|
|
|
|
|
|
|
86
|
0
|
0
|
|
|
|
0
|
if (@errors) { |
87
|
0
|
0
|
|
|
|
0
|
DEBUG and warn "Invalid request: @errors\n"; |
88
|
0
|
|
|
|
|
0
|
$dsl->status(400); |
89
|
0
|
|
|
|
|
0
|
return { errors => [ map { "$_" } @errors ] }; |
|
0
|
|
|
|
|
0
|
|
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
|
93
|
0
|
|
|
|
|
0
|
my $result = $coderef->(); |
94
|
|
|
|
|
|
|
|
95
|
0
|
0
|
|
|
|
0
|
if ($validate_responses) { |
96
|
0
|
|
|
|
|
0
|
my @errors = |
97
|
|
|
|
|
|
|
_validate_response( $method_spec, $dsl->response, |
98
|
|
|
|
|
|
|
$result ); |
99
|
|
|
|
|
|
|
|
100
|
0
|
0
|
|
|
|
0
|
if (@errors) { |
101
|
0
|
0
|
|
|
|
0
|
DEBUG and warn "Invalid response: @errors\n"; |
102
|
0
|
|
|
|
|
0
|
$dsl->status(500); |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
# TODO hide details of server-side errors? |
105
|
0
|
|
|
|
|
0
|
return { errors => [ map { "$_" } @errors ] }; |
|
0
|
|
|
|
|
0
|
|
106
|
|
|
|
|
|
|
} |
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
|
109
|
0
|
|
|
|
|
0
|
return $result; |
110
|
|
|
|
|
|
|
} |
111
|
0
|
|
|
|
|
0
|
); |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
}; |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
register_plugin; |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
sub _validate_request { |
119
|
4
|
|
|
4
|
|
3071
|
my ( $method_spec, $request ) = @_; |
120
|
|
|
|
|
|
|
|
121
|
4
|
|
|
|
|
5
|
my @errors; |
122
|
|
|
|
|
|
|
|
123
|
4
|
|
|
|
|
3
|
for my $parameter_spec ( @{ $method_spec->{parameters} } ) { |
|
4
|
|
|
|
|
9
|
|
124
|
4
|
|
|
|
|
5
|
my $in = $parameter_spec->{in}; |
125
|
4
|
|
|
|
|
4
|
my $name = $parameter_spec->{name}; |
126
|
4
|
|
|
|
|
2
|
my $required = $parameter_spec->{required}; |
127
|
|
|
|
|
|
|
|
128
|
4
|
50
|
|
|
|
9
|
if ( $in eq 'body' ) { # complex data structure in HTTP body |
129
|
0
|
|
|
|
|
0
|
my $input = $request->data; |
130
|
0
|
|
|
|
|
0
|
my $schema = $parameter_spec->{schema}; |
131
|
|
|
|
|
|
|
|
132
|
0
|
|
|
|
|
0
|
push @errors, _validator()->validate_input( $input, $schema ); |
133
|
|
|
|
|
|
|
} |
134
|
|
|
|
|
|
|
else { # simple key-value-pair in HTTP header/query/path/form |
135
|
4
|
|
|
|
|
4
|
my $type = $parameter_spec->{type}; |
136
|
4
|
|
|
|
|
1
|
my @values; |
137
|
|
|
|
|
|
|
|
138
|
4
|
50
|
|
|
|
10
|
if ( $in eq 'header' ) { |
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
139
|
0
|
|
|
|
|
0
|
@values = $request->header($name); |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
elsif ( $in eq 'query' ) { |
142
|
4
|
|
|
|
|
14
|
@values = $request->query_parameters->get_all($name); |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
elsif ( $in eq 'path' ) { |
145
|
0
|
|
|
|
|
0
|
@values = $request->route_parameters->get_all($name); |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
elsif ( $in eq 'formData' ) { |
148
|
0
|
|
|
|
|
0
|
@values = $request->body_parameters->get_all($name); |
149
|
|
|
|
|
|
|
} |
150
|
0
|
|
|
|
|
0
|
else { die "Unknown value for property 'in' of parameter '$name'" } |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
# TODO align error messages to output style of SchemaValidator |
153
|
4
|
100
|
100
|
|
|
173
|
if ( @values == 0 and $required ) { |
|
|
100
|
|
|
|
|
|
154
|
1
|
50
|
|
|
|
3
|
$required and push @errors, "No value for parameter '$name'"; |
155
|
1
|
|
|
|
|
2
|
next; |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
elsif ( @values > 1 ) { |
158
|
1
|
|
|
|
|
3
|
push @errors, "Multiple values for parameter '$name'"; |
159
|
1
|
|
|
|
|
2
|
next; |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
|
162
|
2
|
|
|
|
|
3
|
my $value = $values[0]; |
163
|
2
|
|
|
|
|
5
|
my %input = ( $name => $value ); |
164
|
2
|
|
|
|
|
4
|
my %schema = ( properties => { $name => $parameter_spec } ); |
165
|
|
|
|
|
|
|
|
166
|
2
|
50
|
|
|
|
4
|
$required and $schema{required} = [$name]; |
167
|
|
|
|
|
|
|
|
168
|
2
|
|
|
|
|
4
|
push @errors, _validator()->validate_input( \%input, \%schema ); |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
|
172
|
4
|
|
|
|
|
284
|
return @errors; |
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
sub _validate_response { |
176
|
3
|
|
|
3
|
|
1275
|
my ( $method_spec, $response, $result ) = @_; |
177
|
|
|
|
|
|
|
|
178
|
3
|
|
|
|
|
4
|
my $responses = $method_spec->{responses}; |
179
|
3
|
|
|
|
|
17
|
my $status = $response->status; |
180
|
|
|
|
|
|
|
|
181
|
3
|
|
|
|
|
113
|
my @errors; |
182
|
|
|
|
|
|
|
|
183
|
3
|
50
|
33
|
|
|
10
|
if ( my $response_spec = $responses->{$status} || $responses->{default} ) { |
184
|
|
|
|
|
|
|
|
185
|
3
|
|
|
|
|
3
|
my $headers = $response_spec->{headers}; |
186
|
|
|
|
|
|
|
|
187
|
3
|
|
|
|
|
10
|
while ( my ( $name => $header_spec ) = each %$headers ) { |
188
|
2
|
|
|
|
|
8
|
my @values = $response->header($name); |
189
|
|
|
|
|
|
|
|
190
|
2
|
50
|
|
|
|
69
|
if ( $header_spec->{type} eq 'array' ) { |
191
|
0
|
|
|
|
|
0
|
push @errors, |
192
|
|
|
|
|
|
|
_validator()->validate_input( \@values, $header_spec ); |
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
else { |
195
|
2
|
50
|
|
|
|
7
|
if ( @values == 0 ) { |
|
|
50
|
|
|
|
|
|
196
|
0
|
|
|
|
|
0
|
next; # you can't make a header 'required' in Swagger2 |
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
elsif ( @values > 1 ) { |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
# TODO align error message to output style of SchemaValidator |
201
|
0
|
|
|
|
|
0
|
push @errors, "header '$name' has multiple values"; |
202
|
0
|
|
|
|
|
0
|
next; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
2
|
|
|
|
|
4
|
push @errors, |
206
|
|
|
|
|
|
|
_validator()->validate_input( $values[0], $header_spec ); |
207
|
|
|
|
|
|
|
} |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
|
210
|
3
|
100
|
|
|
|
144
|
if ( my $schema = $response_spec->{schema} ) { |
211
|
1
|
|
|
|
|
2
|
push @errors, _validator()->validate_input( $result, $schema ); |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
} |
214
|
|
|
|
|
|
|
else { |
215
|
|
|
|
|
|
|
# TODO Call validate_input($response, {}) like |
216
|
|
|
|
|
|
|
# in Mojolicious::Plugin::Swagger2? |
217
|
|
|
|
|
|
|
# Swagger2-0.71/lib/Mojolicious/Plugin/Swagger2.pm line L315 |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
|
220
|
3
|
|
|
|
|
195
|
return @errors; |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
sub _default_controller_factory { |
225
|
|
|
|
|
|
|
# TODO simplify argument list |
226
|
0
|
|
|
0
|
|
0
|
my ( $method_spec, $http_method, $path, $dsl, $conf, $args, ) = @_; |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
# from Dancer2 app |
229
|
0
|
|
0
|
|
|
0
|
my $namespace = $args->{controller} || $conf->{controller}; |
230
|
0
|
|
|
|
|
0
|
my $app = $dsl->app->name; |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# from Swagger2 file |
233
|
0
|
|
|
|
|
0
|
my $module; |
234
|
0
|
|
|
|
|
0
|
my $method = $method_spec->{operationId}; |
235
|
0
|
0
|
|
|
|
0
|
if ( $method =~ s/^(.+)::// ) { # looks like Perl module |
236
|
0
|
|
|
|
|
0
|
$module = $1; |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
# different candidates possibly reflecting operationId |
240
|
0
|
|
|
|
|
0
|
my @controller_candidates = do { |
241
|
0
|
0
|
|
|
|
0
|
if ($namespace) { |
242
|
0
|
0
|
|
|
|
0
|
if ($module) { $namespace . '::' . $module, $module } |
|
0
|
|
|
|
|
0
|
|
243
|
0
|
|
|
|
|
0
|
else { $namespace } |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
else { |
246
|
0
|
0
|
|
|
|
0
|
if ($module) { |
247
|
|
|
|
|
|
|
( # parens for better layout by Perl::Tidy |
248
|
0
|
|
|
|
|
0
|
$app . '::' . $module, |
249
|
|
|
|
|
|
|
$app . '::Controller::' . $module, |
250
|
|
|
|
|
|
|
$module, # maybe a top level module name? |
251
|
|
|
|
|
|
|
); |
252
|
|
|
|
|
|
|
} |
253
|
0
|
|
|
|
|
0
|
else { $app, $app . '::Controller' } |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
}; |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
# check candidates |
258
|
0
|
|
|
|
|
0
|
for my $controller (@controller_candidates) { |
259
|
0
|
|
|
|
|
0
|
local $@; |
260
|
0
|
|
|
|
|
0
|
eval { load $controller }; |
|
0
|
|
|
|
|
0
|
|
261
|
0
|
0
|
|
|
|
0
|
if ($@) { |
262
|
0
|
0
|
|
|
|
0
|
if ( $@ =~ m/^Can't locate / ) { # module doesn't exist |
263
|
0
|
0
|
|
|
|
0
|
DEBUG and warn "Can't load '$controller'"; |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
# don't do `next` here because controller could be |
266
|
|
|
|
|
|
|
# defined in other package ... |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
else { # module doesn't compile |
269
|
0
|
|
|
|
|
0
|
die $@; |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
} |
272
|
|
|
|
|
|
|
|
273
|
0
|
0
|
|
|
|
0
|
if ( my $cb = $controller->can($method) ) { |
274
|
0
|
|
|
|
|
0
|
return $cb; # confirmed candidate |
275
|
|
|
|
|
|
|
} |
276
|
|
|
|
|
|
|
else { |
277
|
0
|
0
|
|
|
|
0
|
DEBUG and warn "Controller '$controller' can't '$method'"; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
# none found |
282
|
0
|
|
|
|
|
0
|
warn "Can't find any handler for operationId '$method_spec->{operationId}'"; |
283
|
0
|
|
|
|
|
0
|
return; |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
my $validator; |
287
|
5
|
|
66
|
5
|
|
42
|
sub _validator { $validator ||= Swagger2::SchemaValidator->new } |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
1; |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
__END__ |