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   20 use Mojo::Base -base;
  5         5  
  5         37  
3 5     5   848 use Mojo::JSON 'encode_json';
  5         6  
  5         295  
4 5     5   425 use Mojo::Message::Response;
  5         29334  
  5         74  
5 5     5   116 use Scalar::Util 'blessed';
  5         5  
  5         265  
6 5     5   20 use constant NO_DESCRIPTION => 'No description.';
  5         5  
  5         13555  
7              
8             require Swagger2;
9              
10             my $MOJO_MESSAGE_RESPONSE = Mojo::Message::Response->new;
11              
12             sub to_string {
13 10     10 1 805 my $self = shift;
14              
15 10         28 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   12 my $self = shift;
23 10 50       19 my @schemes = @{$self->{api_spec}->get('/schemes') || []};
  10         27  
24 10         180 my $url = $self->{base_url}->clone;
25 10         299 my $str = "=head1 BASEURL\n\n";
26              
27 10 50       27 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         22 $url->scheme($scheme);
33 10         53 $str .= sprintf "L<%s>\n\n", $url;
34             }
35              
36 10         1016 return $str;
37             }
38              
39             sub _footer_to_string {
40 10     10   14 my $self = shift;
41 10         38 my $contact = $self->{api_spec}->get('/info/contact');
42 10         231 my $license = $self->{api_spec}->get('/info/license');
43 10         147 my $str = '';
44              
45 10 100       29 unless ($license->{name}) {
46 4         7 $license->{name} = 'BSD';
47 4         7 $license->{url} = 'http://www.linfo.org/bsdlicense.html';
48             }
49              
50 10   100     39 $contact->{name} ||= 'Unknown author';
51              
52 10         35 $str .= sprintf "=head1 COPYRIGHT AND LICENSE\n\n%s", $contact->{name};
53             $str .= sprintf " - %s", $contact->{email} || $contact->{url}
54 10 100 33     62 if $contact->{email} || $contact->{url};
      33        
55 10         22 $str .= sprintf "\n\n%s", $license->{name};
56 10 50       33 $str .= sprintf " - %s", $license->{url} if $license->{url};
57 10         23 $str .= "\n\n=cut\n";
58 10         177 $str;
59             }
60              
61             sub _header_to_string {
62 10     10   18 my $self = shift;
63 10         46 my $info = $self->{api_spec}->get('/info');
64 10         193 my $str = '';
65              
66 10   50     36 $info->{title} ||= 'Noname API';
67 10   100     41 $info->{description} ||= 'This API has no description.';
68 10   50     27 $info->{version} ||= '0.01';
69              
70 10         59 $str .= sprintf "=head1 NAME\n\n%s\n\n", $info->{title};
71 10         31 $str .= sprintf "=head1 VERSION\n\n%s\n\n", $info->{version};
72 10         31 $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       34 if $info->{termsOfService};
75 10         28 $str;
76             }
77              
78             sub _path_request_to_string {
79 37     37   42 my ($self, $info) = @_;
80 37         75 my @table = ([qw( Name In Type Required Description )]);
81 37         35 my $str = '';
82 37         40 my %body;
83              
84 37 100       35 for my $p (@{$info->{parameters} || []}) {
  37         129  
85 29   100     90 $p->{description} ||= NO_DESCRIPTION;
86 29 100       73 if ($p->{in} eq 'body') {
87 10         64 %body = (name => 'body', %$p);
88 10         34 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       71 ];
96             }
97             }
98              
99 37         56 $str .= sprintf "=head3 Parameters\n\n";
100 37 100       91 $str .= (@table == 1) ? "This resource takes no parameters.\n\n" : sprintf "%s\n",
101             _ascii_table(\@table, ' ');
102 37 100       101 $str .= " $body{name}:\n\n" . $self->_schema_to_string_dispatch($body{schema}, 0) . "\n"
103             if %body;
104 37         120 $str;
105             }
106              
107             sub _path_response_to_string {
108 37     37   36 my ($self, $info) = @_;
109 37   50     83 my $responses = $info->{responses} || {};
110 37         37 my $str = '';
111              
112 37         41 $str .= sprintf "=head3 Responses\n\n";
113              
114 37         104 for my $code (sort keys %$responses) {
115 56         66 my $res = $responses->{$code};
116 56         74 $str .= sprintf "=head4 %s\n\n", _status_code_to_string($code);
117 56         81 $str .= $self->_summary_and_description($res);
118 56         97 $str .= $self->_schema_to_string_dispatch($res->{schema}, 0) . "\n";
119             }
120              
121 37         218 return $str;
122             }
123              
124             sub _paths_to_string {
125 11     11   20 my $self = shift;
126 11   50     38 my $paths = $self->{api_spec}->get('/paths') || {};
127 11         193 my $str = "=head1 RESOURCES\n\n";
128 11         15 my %info;
129              
130 11         38 for my $path (keys %$paths) {
131 31         22 for my $method (sort keys %{$paths->{$path}}) {
  31         91  
132 43 100       99 next if $method =~ /^x-/;
133 37   33     96 my $operationId = $paths->{$path}{$method}{operationId} || join ' ', uc $method, $path;
134 37 50       65 $info{$operationId} and die "Overlapping operationId in swagger specification: $operationId";
135 37         28 $info{$operationId} = {%{$paths->{$path}{$method}}, _path => $path, _method => $method,};
  37         267  
136             }
137             }
138              
139 11         42 for my $operationId (sort keys %info) {
140 37         115 my $url = $self->{base_url}->clone;
141 37         1391 my $info = $info{$operationId};
142 37         33 push @{$url->path->parts}, grep { length $_ } split '/', $info->{_path};
  37         70  
  87         1650  
143              
144 37         61 my $ext = $info->{externalDocs};
145 37         28 my $resource_url;
146              
147 37         106 $str .= sprintf "=head2 %s\n\n", $operationId;
148 37 50       81 $str .= " THIS RESOURCE IS DEPRECATED!\n\n" if $info->{deprecated};
149 37         92 $str .= $self->_summary_and_description($info);
150 37 50       61 $str .= sprintf "See also L<%s>\n\n", $ext->{url} if $ext;
151              
152 37 50       60 next METHOD if $info->{deprecated};
153 37         93 $url->query(Mojo::Parameters->new);
154 37         681 $resource_url = $url->to_abs;
155 37         1776 $resource_url =~ s!/%7B([^%]+)%7D!/{$1}!g;
156              
157 37         4550 $str .= sprintf "=head3 Resource URL\n\n";
158 37         149 $str .= sprintf " %s %s\n\n", uc $info->{_method}, $resource_url;
159 37         2500 $str .= $self->_path_request_to_string($info);
160 37         87 $str .= $self->_path_response_to_string($info);
161             }
162              
163 11         118 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   31 my ($self, $schema, $depth) = @_;
204 29         41 my $description = _type_description($schema, qw( minItems maxItems multipleOf uniqueItems ));
205 29         29 my $str = '';
206              
207 29 100       56 $description = $description eq NO_DESCRIPTION ? "" : "// $description";
208              
209 29         54 $str .= _sprintf($depth, "[%s\n", $description);
210 29         70 $str .= $self->_schema_to_string_dispatch($schema->{items}, $depth + 1);
211 29         60 $str .= _sprintf($depth + 1, "...\n");
212 29         47 $str .= _sprintf($depth, "]\n");
213 29         72 $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   27 my ($self, $schema, $depth) = @_;
230              
231 26   50     67 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   11 my ($self, $schema, $depth) = @_;
242 10   50     40 my $str = $schema->{description} || 'This response contains raw binary or text data.';
243              
244 10         38 return " $str\n";
245             }
246              
247             sub _schema_object_to_string {
248 75     75   62 my ($self, $schema, $depth) = @_;
249 75         103 my $description = _type_description($schema, qw( minProperties maxProperties ));
250 75         75 my $str = '';
251              
252 75 50       95 $description = $description eq NO_DESCRIPTION ? "" : "// $description";
253 75         88 $str .= _sprintf($depth, "{%s\n", $description);
254              
255 75         200 for my $k (sort keys %$schema) {
256 143         177 $str .= _sprintf($depth + 1, qq("%s": ), $k);
257             $str .= $self->_schema_to_string_dispatch($schema->{$k}, $depth + 1)
258 143 50       461 if ref $schema->{$k} eq 'HASH';
259             }
260              
261 75         104 $str .= _sprintf($depth, "},\n");
262 75         242 $str;
263             }
264              
265             sub _schema_string_to_string {
266 94     94   72 my ($self, $schema, $depth) = @_;
267              
268 94   50     279 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   190 my ($self, $schema, $depth) = @_;
274 238         225 my $required = $schema->{required};
275 238         141 my $method;
276              
277 238 100       321 if ($schema->{properties}) {
278 75         75 $schema = $schema->{properties};
279             }
280 238 100 100     588 if ($required and ref $required eq 'ARRAY') {
281 64         193 $schema->{$_}{required} = 1 for @$required;
282             }
283              
284 238 50       580 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         6 return sprintf "{%s},\n", join ',', @{$schema->{type}};
  4         16  
295             }
296             else {
297 234   100     527 $method = '_schema_' . ($schema->{type} || 'object') . '_to_string';
298             }
299              
300 234 50       606 return "Cannot translate '$schema->{type}' into POD." unless $self->can($method);
301 234         359 return $self->$method($schema, $depth);
302             }
303              
304             sub _summary_and_description {
305 93     93   83 my ($self, $data) = @_;
306 93         83 my $str = '';
307              
308 93 100       175 $str .= "$data->{summary}\n\n" if $data->{summary};
309 93 100       209 $str .= "$data->{description}\n\n" if $data->{description};
310 93 100 66     220 $str .= NO_DESCRIPTION . "\n\n" unless $data->{summary} or $data->{description};
311 93         155 $str;
312             }
313              
314             # FUNCTIONS
315             sub _ascii_table {
316 29     29   30 my ($rows, $pad) = @_;
317 29         29 my $width = 1;
318 29         23 my (@spec, @table);
319              
320 29   50     78 $pad //= '';
321              
322 29         35 for my $row (@$rows) {
323 58         103 for my $i (0 .. $#$row) {
324 290   50     326 $row->[$i] //= '';
325 290         266 $row->[$i] =~ s/[\r\n]//g;
326 290         218 my $len = length $row->[$i];
327 290 100 100     702 $spec[$i] = $len if $len >= ($spec[$i] // 0);
328             }
329             }
330              
331 29         41 my $format = sprintf '%s| %s |', $pad, join ' | ', map { $width += $_ + 3; "\%-${_}s" } @spec;
  145         105  
  145         235  
332 29         45 @table = map { sprintf "$format\n", @$_ } @$rows;
  58         243  
333 29         104 unshift @table, "$pad." . ('-' x ($width - 2)) . ".\n";
334 29         70 splice @table, 2, 0, "$pad|" . ('-' x ($width - 2)) . "|\n";
335 29         52 push @table, "$pad'" . ('-' x ($width - 2)) . "'\n";
336 29         159 return join '', @table;
337             }
338              
339             sub _sprintf {
340 380     380   368 my ($level, $format, @args) = @_;
341              
342 380         918 sprintf "%s$format", (" " x (($level + 1) * 2)), @args;
343             }
344              
345             sub _status_code_to_string {
346 56     56   51 my ($code) = @_;
347 56         138 my $message = $MOJO_MESSAGE_RESPONSE->code($code)->default_message;
348              
349 56 100       711 return sprintf '%s - %s', $code, $message if $message;
350 19         66 return ucfirst $code;
351             }
352              
353             sub _stringify {
354 93     93   74 my ($k, $obj) = @_;
355 93 50 33     229 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   171 my ($schema) = (shift, shift);
364 224 100       325 return $schema->{description} if $schema->{description};
365 208         179 my @keys = grep { defined $schema->{$_} } 'required', @_;
  628         775  
366 208         181 my @description = map { _stringify($_, $schema) } @keys;
  93         100  
367              
368 208 100 50     600 return $schema->{title} || NO_DESCRIPTION unless @description;
369 93         291 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