File Coverage

blib/lib/Swagger2/POD.pm
Criterion Covered Total %
statement 190 225 84.4
branch 53 74 71.6
condition 29 56 51.7
subroutine 24 30 80.0
pod 1 1 100.0
total 297 386 76.9


line stmt bran cond sub pod time code
1             package Swagger2::POD;
2 5     5   25 use Mojo::Base -base;
  5         7  
  5         58  
3 5     5   1021 use Mojo::JSON 'encode_json';
  5         8  
  5         353  
4 5     5   569 use Mojo::Message::Response;
  5         39996  
  5         91  
5 5     5   139 use Scalar::Util 'blessed';
  5         8  
  5         305  
6 5     5   25 use constant NO_DESCRIPTION => 'No description.';
  5         7  
  5         17159  
7              
8             require Swagger2;
9              
10             my $MOJO_MESSAGE_RESPONSE = Mojo::Message::Response->new;
11              
12             sub to_string {
13 10     10 1 901 my $self = shift;
14              
15 10         37 join('',
16             $self->_header_to_string, $self->_api_endpoint_to_string,
17             $self->_paths_to_string, $self->_footer_to_string,
18             );
19             }
20              
21             sub _api_endpoint_to_string {
22 10     10   15 my $self = shift;
23 10 50       14 my @schemes = @{$self->{api_spec}->get('/schemes') || []};
  10         33  
24 10         231 my $url = $self->{base_url}->clone;
25 10         372 my $str = "=head1 BASEURL\n\n";
26              
27 10 50       29 unless (@schemes) {
28 0         0 return $str . "No default URL is defined to this application.\n\n";
29             }
30              
31 10         31 while (my $scheme = shift @schemes) {
32 10         38 $url->scheme($scheme);
33 10         63 $str .= sprintf "L<%s>\n\n", $url;
34             }
35              
36 10         1241 return $str;
37             }
38              
39             sub _footer_to_string {
40 10     10   17 my $self = shift;
41 10         52 my $contact = $self->{api_spec}->get('/info/contact');
42 10         322 my $license = $self->{api_spec}->get('/info/license');
43 10         199 my $str = '';
44              
45 10 100       35 unless ($license->{name}) {
46 4         7 $license->{name} = 'BSD';
47 4         8 $license->{url} = 'http://www.linfo.org/bsdlicense.html';
48             }
49              
50 10   100     47 $contact->{name} ||= 'Unknown author';
51              
52 10         40 $str .= sprintf "=head1 COPYRIGHT AND LICENSE\n\n%s", $contact->{name};
53             $str .= sprintf " - %s", $contact->{email} || $contact->{url}
54 10 100 33     75 if $contact->{email} || $contact->{url};
      33        
55 10         25 $str .= sprintf "\n\n%s", $license->{name};
56 10 50       43 $str .= sprintf " - %s", $license->{url} if $license->{url};
57 10         18 $str .= "\n\n=cut\n";
58 10         243 $str;
59             }
60              
61             sub _header_to_string {
62 10     10   17 my $self = shift;
63 10         88 my $info = $self->{api_spec}->get('/info');
64 10         282 my $str = '';
65              
66 10   50     41 $info->{title} ||= 'Noname API';
67 10   100     50 $info->{description} ||= 'This API has no description.';
68 10   50     32 $info->{version} ||= '0.01';
69              
70 10         71 $str .= sprintf "=head1 NAME\n\n%s\n\n", $info->{title};
71 10         35 $str .= sprintf "=head1 VERSION\n\n%s\n\n", $info->{version};
72 10         30 $str .= sprintf "=head1 DESCRIPTION\n\n%s\n\n", $info->{description};
73             $str .= sprintf "=head1 TERMS OF SERVICE\n\n%s\n\n", $info->{termsOfService}
74 10 50       35 if $info->{termsOfService};
75 10         34 $str;
76             }
77              
78             sub _path_request_to_string {
79 37     37   50 my ($self, $info) = @_;
80 37         109 my @table = ([qw( Name In Type Required Description )]);
81 37         41 my $str = '';
82 37         42 my %body;
83              
84 37 100       35 for my $p (@{$info->{parameters} || []}) {
  37         161  
85 29   100     107 $p->{description} ||= NO_DESCRIPTION;
86 29 100       79 if ($p->{in} eq 'body') {
87 10         94 %body = (name => 'body', %$p);
88 10         49 push @table, [$p->{name}, 'body', 'schema', 'Yes', $p->{description}];
89             }
90             else {
91             push @table,
92             [
93             @$p{qw( name in type )}, Swagger2::_is_true($p->{required}) ? 'Yes' : 'No',
94             $p->{description}
95 19 100       97 ];
96             }
97             }
98              
99 37         73 $str .= sprintf "=head3 Parameters\n\n";
100 37 100       122 $str .= (@table == 1) ? "This resource takes no parameters.\n\n" : sprintf "%s\n",
101             _ascii_table(\@table, ' ');
102 37 100       135 $str .= " $body{name}:\n\n" . $self->_schema_to_string_dispatch($body{schema}, 0) . "\n"
103             if %body;
104 37         149 $str;
105             }
106              
107             sub _path_response_to_string {
108 37     37   46 my ($self, $info) = @_;
109 37   50     104 my $responses = $info->{responses} || {};
110 37         38 my $str = '';
111              
112 37         57 $str .= sprintf "=head3 Responses\n\n";
113              
114 37         125 for my $code (sort keys %$responses) {
115 56         82 my $res = $responses->{$code};
116 56         109 $str .= sprintf "=head4 %s\n\n", _status_code_to_string($code);
117 56         110 $str .= $self->_summary_and_description($res);
118 56         118 $str .= $self->_schema_to_string_dispatch($res->{schema}, 0) . "\n";
119             }
120              
121 37         302 return $str;
122             }
123              
124             sub _paths_to_string {
125 11     11   19 my $self = shift;
126 11   50     41 my $paths = $self->{api_spec}->get('/paths') || {};
127 11         219 my $str = "=head1 RESOURCES\n\n";
128 11         18 my %info;
129              
130 11         39 for my $path (keys %$paths) {
131 31         34 for my $method (sort keys %{$paths->{$path}}) {
  31         106  
132 43 100       118 next if $method =~ /^x-/;
133 37   33     112 my $operationId = $paths->{$path}{$method}{operationId} || join ' ', uc $method, $path;
134 37 50       78 $info{$operationId} and die "Overlapping operationId in swagger specification: $operationId";
135 37         29 $info{$operationId} = {%{$paths->{$path}{$method}}, _path => $path, _method => $method,};
  37         357  
136             }
137             }
138              
139 11         117 for my $operationId (sort keys %info) {
140 37         135 my $url = $self->{base_url}->clone;
141 37         1275 my $info = $info{$operationId};
142 37         42 push @{$url->path->parts}, grep { length $_ } split '/', $info->{_path};
  37         79  
  87         2037  
143              
144 37         77 my $ext = $info->{externalDocs};
145 37         36 my $resource_url;
146              
147 37         122 $str .= sprintf "=head2 %s\n\n", $operationId;
148 37 50       98 $str .= " THIS RESOURCE IS DEPRECATED!\n\n" if $info->{deprecated};
149 37         86 $str .= $self->_summary_and_description($info);
150 37 50       78 $str .= sprintf "See also L<%s>\n\n", $ext->{url} if $ext;
151              
152 37 50       82 next METHOD if $info->{deprecated};
153 37         113 $url->query(Mojo::Parameters->new);
154 37         820 $resource_url = $url->to_abs;
155 37         2285 $resource_url =~ s!/%7B([^%]+)%7D!/{$1}!g;
156              
157 37         5861 $str .= sprintf "=head3 Resource URL\n\n";
158 37         204 $str .= sprintf " %s %s\n\n", uc $info->{_method}, $resource_url;
159 37         3244 $str .= $self->_path_request_to_string($info);
160 37         102 $str .= $self->_path_response_to_string($info);
161             }
162              
163 11         184 return $str;
164             }
165              
166             sub _schema_anyof_to_string {
167 0     0   0 my ($self, $schema, $depth) = @_;
168 0         0 my $str = "\n" . _sprintf($depth + 1, "// Any of the below:\n");
169              
170 0         0 for my $s (@{$schema->{anyOf}}) {
  0         0  
171 0         0 $str .= _sprintf($depth + 1, "");
172 0         0 $str .= $self->_schema_to_string_dispatch($s, $depth + 1);
173             }
174              
175 0         0 $str;
176             }
177              
178             sub _schema_allof_to_string {
179 0     0   0 my ($self, $schema, $depth) = @_;
180 0         0 my $str = "\n" . _sprintf($depth + 1, "// All of the below:\n");
181              
182 0         0 for my $s (@{$schema->{allOf}}) {
  0         0  
183 0         0 $str .= _sprintf($depth + 1, "");
184 0         0 $str .= $self->_schema_to_string_dispatch($s, $depth + 1);
185             }
186              
187 0         0 $str;
188             }
189              
190             sub _schema_oneof_to_string {
191 0     0   0 my ($self, $schema, $depth) = @_;
192 0         0 my $str = "\n" . _sprintf($depth + 1, "// One of the below:\n");
193              
194 0         0 for my $s (@{$schema->{oneOf}}) {
  0         0  
195 0         0 $str .= _sprintf($depth + 1, "");
196 0         0 $str .= $self->_schema_to_string_dispatch($s, $depth + 1);
197             }
198              
199 0         0 $str;
200             }
201              
202             sub _schema_array_to_string {
203 29     29   35 my ($self, $schema, $depth) = @_;
204 29         50 my $description = _type_description($schema, qw( minItems maxItems multipleOf uniqueItems ));
205 29         33 my $str = '';
206              
207 29 100       65 $description = $description eq NO_DESCRIPTION ? "" : "// $description";
208              
209 29         67 $str .= _sprintf($depth, "[%s\n", $description);
210 29         83 $str .= $self->_schema_to_string_dispatch($schema->{items}, $depth + 1);
211 29         69 $str .= _sprintf($depth + 1, "...\n");
212 29         66 $str .= _sprintf($depth, "]\n");
213 29         98 $str;
214             }
215              
216             sub _schema_boolean_to_string {
217 0     0   0 my ($self, $schema, $depth) = @_;
218              
219 0         0 sprintf "%s, // %s\n", 'boolean', _type_description($schema);
220             }
221              
222             sub _schema_enum_to_string {
223 0     0   0 my ($self, $schema, $depth) = @_;
224              
225 0         0 sprintf "%s, // %s\n", 'enum', _type_description($schema, qw( enum ));
226             }
227              
228             sub _schema_integer_to_string {
229 26     26   34 my ($self, $schema, $depth) = @_;
230              
231 26   50     79 sprintf "%s, // %s\n", $schema->{format} || 'integer', _type_description($schema, qw( default ));
232             }
233              
234             sub _schema_number_to_string {
235 0     0   0 my ($self, $schema, $depth) = @_;
236              
237 0   0     0 sprintf "%s, // %s\n", $schema->{format} || 'number', _type_description($schema, qw( default ));
238             }
239              
240             sub _schema_file_to_string {
241 10     10   15 my ($self, $schema, $depth) = @_;
242 10   50     53 my $str = $schema->{description} || 'This response contains raw binary or text data.';
243              
244 10         49 return " $str\n";
245             }
246              
247             sub _schema_object_to_string {
248 75     75   82 my ($self, $schema, $depth) = @_;
249 75         120 my $description = _type_description($schema, qw( minProperties maxProperties ));
250 75         86 my $str = '';
251              
252 75 50       124 $description = $description eq NO_DESCRIPTION ? "" : "// $description";
253 75         113 $str .= _sprintf($depth, "{%s\n", $description);
254              
255 75         258 for my $k (sort keys %$schema) {
256 143         232 $str .= _sprintf($depth + 1, qq("%s": ), $k);
257             $str .= $self->_schema_to_string_dispatch($schema->{$k}, $depth + 1)
258 143 50       588 if ref $schema->{$k} eq 'HASH';
259             }
260              
261 75         131 $str .= _sprintf($depth, "},\n");
262 75         244 $str;
263             }
264              
265             sub _schema_string_to_string {
266 94     94   107 my ($self, $schema, $depth) = @_;
267              
268 94   50     346 sprintf "%s, // %s\n", $schema->{format} || 'string',
269             _type_description($schema, qw( minLength maxLength pattern default ));
270             }
271              
272             sub _schema_to_string_dispatch {
273 238     238   263 my ($self, $schema, $depth) = @_;
274 238         257 my $required = $schema->{required};
275 238         173 my $method;
276              
277 238 100       420 if ($schema->{properties}) {
278 75         177 $schema = $schema->{properties};
279             }
280 238 100 100     746 if ($required and ref $required eq 'ARRAY') {
281 64         255 $schema->{$_}{required} = 1 for @$required;
282             }
283              
284 238 50       780 if ($schema->{anyOf}) {
    50          
    50          
    100          
285 0         0 $method = '_schema_anyof_to_string';
286             }
287             elsif ($schema->{allOf}) {
288 0         0 $method = '_schema_allof_to_string';
289             }
290             elsif ($schema->{oneOf}) {
291 0         0 $method = '_schema_oneof_to_string';
292             }
293             elsif (ref $schema->{type} eq 'ARRAY') {
294 4         7 return sprintf "{%s},\n", join ',', @{$schema->{type}};
  4         22  
295             }
296             else {
297 234   100     670 $method = '_schema_' . ($schema->{type} || 'object') . '_to_string';
298             }
299              
300 234 50       756 return "Cannot translate '$schema->{type}' into POD." unless $self->can($method);
301 234         455 return $self->$method($schema, $depth);
302             }
303              
304             sub _summary_and_description {
305 93     93   102 my ($self, $data) = @_;
306 93         106 my $str = '';
307              
308 93 100       226 $str .= "$data->{summary}\n\n" if $data->{summary};
309 93 100       277 $str .= "$data->{description}\n\n" if $data->{description};
310 93 100 66     288 $str .= NO_DESCRIPTION . "\n\n" unless $data->{summary} or $data->{description};
311 93         161 $str;
312             }
313              
314             # FUNCTIONS
315             sub _ascii_table {
316 29     29   42 my ($rows, $pad) = @_;
317 29         31 my $width = 1;
318 29         31 my (@spec, @table);
319              
320 29   50     93 $pad //= '';
321              
322 29         55 for my $row (@$rows) {
323 58         127 for my $i (0 .. $#$row) {
324 290   50     425 $row->[$i] //= '';
325 290         311 $row->[$i] =~ s/[\r\n]//g;
326 290         278 my $len = length $row->[$i];
327 290 100 100     885 $spec[$i] = $len if $len >= ($spec[$i] // 0);
328             }
329             }
330              
331 29         45 my $format = sprintf '%s| %s |', $pad, join ' | ', map { $width += $_ + 3; "\%-${_}s" } @spec;
  145         141  
  145         313  
332 29         56 @table = map { sprintf "$format\n", @$_ } @$rows;
  58         284  
333 29         121 unshift @table, "$pad." . ('-' x ($width - 2)) . ".\n";
334 29         86 splice @table, 2, 0, "$pad|" . ('-' x ($width - 2)) . "|\n";
335 29         72 push @table, "$pad'" . ('-' x ($width - 2)) . "'\n";
336 29         236 return join '', @table;
337             }
338              
339             sub _sprintf {
340 380     380   550 my ($level, $format, @args) = @_;
341              
342 380         1241 sprintf "%s$format", (" " x (($level + 1) * 2)), @args;
343             }
344              
345             sub _status_code_to_string {
346 56     56   63 my ($code) = @_;
347 56         173 my $message = $MOJO_MESSAGE_RESPONSE->code($code)->default_message;
348              
349 56 100       903 return sprintf '%s - %s', $code, $message if $message;
350 19         89 return ucfirst $code;
351             }
352              
353             sub _stringify {
354 93     93   98 my ($k, $obj) = @_;
355 93 50 33     319 return 'required' if $k eq 'required' and Swagger2::_is_true($obj->{$k});
356 0 0 0     0 return "$k=true" if blessed $obj->{$k} and $obj->{$k} eq Mojo::JSON->true;
357 0 0 0     0 return "$k=false" if blessed $obj->{$k} and $obj->{$k} eq Mojo::JSON->false;
358 0 0       0 return sprintf '%s=%s', $k, encode_json $obj->{$k} if ref $obj->{$k};
359 0         0 return sprintf '%s=%s', $k, $obj->{$k};
360             }
361              
362             sub _type_description {
363 224     224   230 my ($schema) = (shift, shift);
364 224 100       439 return $schema->{description} if $schema->{description};
365 208         235 my @keys = grep { defined $schema->{$_} } 'required', @_;
  628         1035  
366 208         209 my @description = map { _stringify($_, $schema) } @keys;
  93         139  
367              
368 208 100 50     795 return $schema->{title} || NO_DESCRIPTION unless @description;
369 93         422 return join ', ', @description;
370             }
371              
372             1;
373              
374             =encoding utf8
375              
376             =head1 NAME
377              
378             Swagger2::POD - Convert swagger API spec to Perl documentation
379              
380             =head1 DEPRECATION WARNING
381              
382             See L.
383              
384             =head1 DESCRIPTION
385              
386             L is a module that can convert from L to L.
387              
388             =head1 SYNOPSIS
389              
390             use Swagger2;
391             my $swagger = Sswagger2->new("file:///path/to/api-spec.yaml");
392              
393             print $swagger->pod->to_string;
394              
395             =head1 METHODS
396              
397             =head2 to_string
398              
399             $str = $self->to_string;
400              
401             Will convert swagger API spec to plain old documentation.
402              
403             =head1 COPYRIGHT AND LICENSE
404              
405             Copyright (C) 2014-2015, Jan Henning Thorsen
406              
407             This program is free software, you can redistribute it and/or modify it under
408             the terms of the Artistic License version 2.0.
409              
410             =head1 AUTHOR
411              
412             Jan Henning Thorsen - C
413              
414             =cut