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