File Coverage

blib/lib/Mojolicious/Plugin/OpenAPI/Modern.pm
Criterion Covered Total %
statement 69 69 100.0
branch n/a
condition 3 4 75.0
subroutine 19 19 100.0
pod 1 1 100.0
total 92 93 98.9


line stmt bran cond sub pod time code
1 2     2   213591 use strict;
  2         23  
  2         49  
2 2     2   9 use warnings;
  2         3  
  2         81  
3             # vim: set ts=8 sts=2 sw=2 tw=100 et :
4             # ABSTRACT: Mojolicious plugin providing access to an OpenAPI document and parser
5             # KEYWORDS: validation evaluation JSON Schema OpenAPI Swagger HTTP request response
6              
7             our $VERSION = '0.003';
8              
9             use 5.020;
10 2     2   35 use if "$]" >= 5.022, experimental => 're_strict';
  2         7  
11 2     2   9 no if "$]" >= 5.031009, feature => 'indirect';
  2         3  
  2         12  
12 2     2   187 no if "$]" >= 5.033001, feature => 'multidimensional';
  2         4  
  2         9  
13 2     2   65 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  2         5  
  2         12  
14 2     2   71 use Mojo::Base 'Mojolicious::Plugin', -signatures;
  2         9  
  2         9  
15 2     2   798 use Feature::Compat::Try;
  2         289259  
  2         15  
16 2     2   2402 use YAML::PP;
  2         505  
  2         11  
17 2     2   4792 use Path::Tiny;
  2         92924  
  2         83  
18 2     2   1372 use Mojo::JSON 'decode_json';
  2         19263  
  2         97  
19 2     2   819 use Safe::Isa;
  2         36413  
  2         102  
20 2     2   716 use OpenAPI::Modern 0.022;
  2         804  
  2         240  
21 2     2   931 use namespace::clean;
  2         820560  
  2         68  
22 2     2   18  
  2         4  
  2         12  
23             # we store data in two places: on the app (persistent storage, for the OpenAPI::Modern object
24             # itself) and in the controller stash: per-request data like the path info and extracted path items.
25              
26             # the first is $app->openapi or $c->openapi
27             # the second is $c->stash('openapi') which will be initialized to {} on first use.
28             my $stash = Mojo::Util::_stash(openapi => $app);
29 4     4 1 219784  
  4         9  
  4         11  
  4         10  
  4         10  
30 4         16 try {
31             my $schema;
32             if (exists $config->{schema}) {
33             $schema = $config->{schema};
34             }
35             elsif (exists $config->{document_filename}) {
36             if ($config->{document_filename} =~ /\.ya?ml$/) {
37             $schema = YAML::PP->new(boolean => 'JSON::PP')->load_file($config->{document_filename}),
38             }
39             elsif ($config->{document_filename} =~ /\.json$/) {
40             $schema = decode_json(path($config->{document_filename})->slurp_raw);
41             }
42             else {
43             die 'Unsupported file format in filename: ', $config->{document_filename};
44             }
45             }
46             else {
47             die 'missing config: one of schema, filename';
48             }
49              
50             my $openapi = OpenAPI::Modern->new(
51             openapi_uri => $config->{document_filename} // '',
52             openapi_schema => $schema,
53             );
54              
55             # leave room for other keys in our localized stash
56             $stash->{openapi} = $openapi;
57             }
58             catch ($e) {
59             die 'Cannot load OpenAPI document: ', $e;
60 4         57 }
61              
62             $app->helper(openapi => sub ($c) { $stash->{openapi} });
63              
64 4     14   62 $app->helper(validate_request => \&_validate_request);
  14         21  
  14         114  
  14         4389  
  14         28  
65             $app->helper(validate_response => \&_validate_response);
66 4         483 }
67 4         222  
68             my $options = $c->stash->{openapi} //= {};
69             return $c->openapi->validate_request($c->req, $options);
70 5     5   98235 }
  5         11  
  5         9  
71 5   50     16  
72 5         68 my $options = $c->stash->{openapi} //= {};
73             local $options->{request} = $c->req;
74             return $c->openapi->validate_response($c->res, $options);
75 6     6   39462 }
  6         11  
  6         11  
76 6   100     15  
77 6         61 1;
78 6         70  
79              
80             =pod
81              
82             =encoding UTF-8
83              
84             =head1 NAME
85              
86             Mojolicious::Plugin::OpenAPI::Modern - Mojolicious plugin providing access to an OpenAPI document and parser
87              
88             =head1 VERSION
89              
90             version 0.003
91              
92             =head1 SYNOPSIS
93              
94             $app->config({
95             openapi => {
96             document_filename => 'data/openapi.yaml',
97             },
98             ...
99             });
100              
101             $app->plugin('OpenAPI::Modern', $app->config->{openapi});
102              
103             # in a controller...
104             my $result = $c->openapi->validate_request($c->req);
105              
106             =head1 DESCRIPTION
107              
108             This L<Mojolicious> plugin makes an L<OpenAPI::Modern> object available to the application.
109              
110             There are many features to come.
111              
112             =head1 CONFIGURATION OPTIONS
113              
114             =head2 schema
115              
116             The literal, unblessed Perl data structure containing the OpenAPI document. See
117             L<OpenAPI::Modern/openapi_schema>.
118              
119             =head2 document_filename
120              
121             A filename indicating from where to load the OpenAPI document. Supports YAML and json file formats.
122              
123             =head1 METHODS
124              
125             =head2 register
126              
127             Instantiates an L<OpenAPI::Modern> object and provides an accessor to it.
128              
129             =head1 HELPERS
130              
131             These methods are made available on the C<$c> object (the invocant of all controller methods,
132             and therefore other helpers).
133              
134             =for stopwords openapi operationId
135              
136             =head2 openapi
137              
138             The L<OpenAPI::Modern> object.
139              
140             =head2 validate_request
141              
142             my $result = $c->openapi->validate_request;
143              
144             Passes C<< $c->req >> to L<OpenAPI::Modern/validate_request> and returns the
145             L<JSON::Schema::Modern::Result>.
146              
147             Note that the matching L<Mojo::Routes::Route> object for this request is I<not> used to find the
148             OpenAPI path-item that corresponds to this request: only information in the request URI itself is
149             used (although some information in the route may be used in a future feature).
150              
151             =head2 validate_response
152              
153             my $result = $c->openapi->validate_response;
154              
155             Passes C<< $c->res >> and C<< $c->req >> to L<OpenAPI::Modern/validate_response> and returns the
156             L<JSON::Schema::Modern::Result>.
157              
158             Can only be called in the areas of the dispatch flow where the response has already been rendered; a
159             good place to call this would be in an L<after_dispatch|Mojolicious/after_dispatch> hook.
160              
161             Note that the matching L<Mojo::Routes::Route> object for this request is I<not> used to find the
162             OpenAPI path-item that corresponds to this request and response: only information in the request URI
163             itself is used (although some information in the route may be used in a future feature).
164              
165             =head1 STASH VALUES
166              
167             This plugin stores all its data under the C<openapi> hashref, e.g.:
168              
169             my $operation_id = $c->stash->{openapi}{operation_id};
170              
171             Keys starting with underscore are for I<internal use only> and should not be relied upon to behave
172             consistently across release versions. Values that may be used by controllers and templates are:
173              
174             =over 4
175              
176             =item *
177              
178             C<path_template>: Set by the first call to L</validate_request> or L</validate_response>. A string representing the request URI, with placeholders in braces (e.g. C</pets/{petId}>); see L<https://spec.openapis.org/oas/v3.1.0#paths-object>.
179              
180             =item *
181              
182             C<path_captures>: Set by the first call to L</validate_request> or L</validate_response>. A hashref mapping placeholders in the path to their actual values in the request URI.
183              
184             =item *
185              
186             C<operation_id>: Set by the first call to L</validate_request> or L</validate_response>. Contains the corresponding L<operationId|https://swagger.io/docs/specification/paths-and-operations/#operationid> of the current endpoint.
187              
188             =item *
189              
190             C<method>: Set by the first call to L</validate_request> or L</validate_response>. The HTTP method used by the request, lower-cased.
191              
192             =back
193              
194             =head1 SEE ALSO
195              
196             =over 4
197              
198             =item *
199              
200             L<OpenAPI::Modern>
201              
202             =item *
203              
204             L<JSON::Schema::Modern::Document::OpenAPI>
205              
206             =item *
207              
208             L<JSON::Schema::Modern>
209              
210             =item *
211              
212             L<https://json-schema.org>
213              
214             =item *
215              
216             L<https://www.openapis.org/>
217              
218             =item *
219              
220             L<https://oai.github.io/Documentation/>
221              
222             =item *
223              
224             L<https://spec.openapis.org/oas/v3.1.0>
225              
226             =back
227              
228             =head1 SUPPORT
229              
230             Bugs may be submitted through L<https://github.com/karenetheridge/Mojolicious-Plugin-OpenAPI-Modern/issues>.
231              
232             I am also usually active on irc, as 'ether' at C<irc.perl.org> and C<irc.libera.chat>.
233              
234             You can also find me on the L<JSON Schema Slack server|https://json-schema.slack.com> and L<OpenAPI Slack
235             server|https://open-api.slack.com>, which are also great resources for finding help.
236              
237             =head1 AUTHOR
238              
239             Karen Etheridge <ether@cpan.org>
240              
241             =head1 COPYRIGHT AND LICENCE
242              
243             This software is copyright (c) 2021 by Karen Etheridge.
244              
245             This is free software; you can redistribute it and/or modify it under
246             the same terms as the Perl 5 programming language system itself.
247              
248             =cut