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