File Coverage

blib/lib/Whelk/OpenAPI.pm
Criterion Covered Total %
statement 60 61 98.3
branch 18 20 90.0
condition 6 11 54.5
subroutine 10 10 100.0
pod 3 8 37.5
total 97 110 88.1


line stmt bran cond sub pod time code
1             package Whelk::OpenAPI;
2             $Whelk::OpenAPI::VERSION = '1.04';
3 5     5   3482 use Kelp::Base;
  5         12  
  5         34  
4 5     5   1402 use List::Util qw(uniq);
  5         10  
  5         9174  
5              
6             attr openapi_version => '3.0.3';
7              
8             attr info => sub { {} };
9             attr extra => sub { {} };
10             attr tags => sub { [] };
11             attr paths => sub { {} };
12             attr schemas => sub { {} };
13             attr servers => sub { [] };
14              
15             sub default_response_description
16             {
17 57     57 0 453 my ($self, $code) = @_;
18              
19 57         98 state $tries = [
20             ['204' => 'Success (no content).'],
21             ['2..' => 'Success.'],
22             ['4..' => 'Failure, invalid request data.'],
23             ['5..' => 'Failure, server error.'],
24             ];
25              
26 57         109 foreach my $try (@$tries) {
27 168 100       2394 return $try->[1]
28             if $code =~ m{^$try->[0]$};
29             }
30              
31 0         0 return 'Response.';
32             }
33              
34             sub build_path
35             {
36 19     19 0 39 my ($self, $endpoint) = @_;
37 19         61 my %requests;
38             my %responses;
39 19         0 my @parameters;
40              
41             # bulid responses
42 19         34 foreach my $code (keys %{$endpoint->response_schemas}) {
  19         53  
43 57         246 my $schema = $endpoint->response_schemas->{$code};
44 57         411 my $success = $code =~ /2../;
45              
46 57   33     201 $responses{$code} = {
47             description => $schema->description // $self->default_response_description($code),
48             };
49              
50 57 100       215 if (!$schema->empty) {
51             $responses{$code}{content} = {
52 54         146 $endpoint->formatter->full_response_format => {
53             schema => $schema->openapi_schema($self),
54             },
55             };
56             }
57             }
58              
59             # build requests
60 19 100       63 if (my $schema = $endpoint->request) {
61 1         7 foreach my $format (values %{$endpoint->formatter->supported_formats}) {
  1         3  
62 2         16 $requests{content}{$format}{schema} = $schema->openapi_schema($self);
63             }
64             }
65              
66             # build parameters
67 19         128 foreach my $type (qw(path query header cookie)) {
68 76         123 my $method = "${type}_schema";
69 76         186 my $schema = $endpoint->parameters->$method;
70 76 100       751 next if !$schema;
71              
72 8         11 push @parameters, @{$schema->openapi_schema($self, parameters => $type)};
  8         41  
73             }
74              
75             return {
76 19 50       55 ($endpoint->id ? (operationId => $endpoint->id) : ()),
    50          
    100          
    100          
    100          
77             ($endpoint->summary ? (summary => $endpoint->summary) : ()),
78             ($endpoint->description ? (description => $endpoint->description) : ()),
79             tags => [$endpoint->resource->name],
80             responses => \%responses,
81             (@parameters ? (parameters => \@parameters) : ()),
82             (%requests ? (requestBody => \%requests) : ()),
83             };
84             }
85              
86             sub build_paths
87             {
88 5     5 0 13 my ($self, $endpoints) = @_;
89              
90 5         10 my %paths;
91             my @tags;
92 5   50     10 foreach my $endpoint (@{$endpoints // []}) {
  5         22  
93 19         114 $paths{$endpoint->path}{lc $endpoint->route->method} = $self->build_path($endpoint);
94              
95 19         994 push @tags, $endpoint->resource;
96             }
97              
98 5         80 $self->paths(\%paths);
99              
100             @tags = map {
101 5         59 {
102             name => $_->name,
103 7 100       62 ($_->config->{description} ? (description => $_->config->{description}) : ()),
104             }
105             } uniq @tags;
106              
107 5         97 $self->tags(\@tags);
108             }
109              
110             sub build_servers
111             {
112 5     5 0 13 my ($self, $app) = @_;
113              
114 5         56 $self->servers(
115             [
116             {
117             description => 'API for ' . $app->name,
118             url => $app->config('app_url'),
119             },
120             ]
121             );
122             }
123              
124             sub build_schemas
125             {
126 5     5 0 13 my ($self, $schemas) = @_;
127              
128             my %schemas = map {
129 10         40 $_->name => $_->openapi_schema($self, full => 1)
130 5   50     12 } @{$schemas // []};
  5         39  
131              
132 5         50 $self->schemas(\%schemas);
133             }
134              
135             sub parse
136             {
137 5     5 1 34 my ($self, %data) = @_;
138              
139 5   100     60 $self->info($data{info} // {});
140 5   50     71 $self->extra($data{extra} // {});
141              
142 5         28 $self->build_paths($data{endpoints});
143 5         38 $self->build_servers($data{app});
144 5         528 $self->build_schemas($data{schemas});
145             }
146              
147             sub location_for_schema
148             {
149 45     45 1 267 my ($self, $name) = @_;
150              
151 45         366 return "#/components/schemas/$name";
152             }
153              
154             sub generate
155             {
156 4     4 1 37 my ($self) = @_;
157              
158             my %generated = (
159              
160             # extra at the start, to make sure it's not overshadowing keys
161 4         10 %{$self->extra},
  4         14  
162              
163             openapi => $self->openapi_version,
164             info => $self->info,
165             servers => $self->servers,
166             tags => $self->tags,
167             paths => $self->paths,
168             components => {
169             schemas => $self->schemas,
170             },
171             );
172              
173 4         216 return \%generated;
174             }
175              
176             1;
177              
178             __END__
179              
180             =pod
181              
182             =head1 NAME
183              
184             Whelk::OpenAPI - Whelk's default OpenAPI generator class
185              
186             =head1 SYNOPSIS
187              
188             # whelk_config.pl
189             ###################
190             {
191             openapi => {
192             path => '/openapi',
193             class => 'MyOpenAPI',
194             }
195             }
196              
197             # MyOpenAPI.pm
198             ################
199             package MyOpenAPI;
200              
201             use Kelp::Base 'Whelk::OpenAPI';
202              
203             sub parse {
204             my ($self, %data) = @_;
205              
206             # do the parsing differently
207             ...
208             }
209              
210             1;
211              
212             =head1 DESCRIPTION
213              
214             This class generates an OpenAPI document based on the API definition gathered
215             by Whelk. It requires pretty specific setup and should probably not be
216             manipulated by hand. It can be subclassed to change how the document looks.
217              
218             This documentation page describes just the methods which are called from
219             outside of the class. The rest of methods and all attributes are just
220             implementation details.
221              
222             =head1 METHODS
223              
224             =head2 parse
225              
226             $openapi->parse(%data);
227              
228             It's called at build time, after Whelk is finalized. It gets passed a hash
229             C<%data> with a couple of keys containing full data Whelk gathered. It should
230             build most of the parts of the OpenAPI document, so that it will not be
231             terribly slow to generate the document at runtime.
232              
233             =head2 location_for_schema
234              
235             my $location = $openapi->location_for_schema($schema_name);
236              
237             This helper should just return a string which will be put into C<'$ref'> keys
238             of the OpenAPI document to reference named schemas.
239              
240             =head2 generate
241              
242             my $openapi_document_data = $openapi->generate;
243              
244             This method should take all the data prepared by L</parse> and return a hash
245             reference with all the data of the OpenAPI document. This data will be then
246             serialized using formatter declared in C<openapi> configuration.
247