PaxHeader/Mojolicious-Plugin-OpenAPI-5.09000755 000765 000024 00000000210 14374621224 021474 xustar00jhthorsenstaff000000 000000 30 mtime=1676878484.835468198 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/000755 000765 000024 00000000000 14374621224 017604 5ustar00jhthorsenstaff000000 000000 Mojolicious-Plugin-OpenAPI-5.09/PaxHeader/Changes000644 000765 000024 00000000210 14374621223 023040 xustar00jhthorsenstaff000000 000000 30 mtime=1676878483.945890892 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/Changes000644 000765 000024 00000037754 14374621223 021116 0ustar00jhthorsenstaff000000 000000 Revision history for perl distribution Mojolicious-Plugin-OpenAPI 5.09 2023-02-20T16:34:43 - Will only run t/00-project.t when developing #241 5.08 2022-12-09T09:58:39 - Fix not coercing body parameter for OpenAPIv2 in JSON::Validator v5.13 - Specified Perl version - Updated basic repository files - Updated contributors list 5.07 2022-08-18T07:41:35+0900 - Bumped J::V version to support DefaultResponse in $ref path object #236 5.06 2022-08-17T20:15:46+0900 - Fix incompatability with OpenAPIv2 spec regarding default "collectionFormat" 5.05 2022-03-25T10:00:49+0900 - Fix collectionFormat in header 5.04 2022-03-23T07:58:50+0900 - Depends on JSON::Validator 5.07 5.03 2022-03-23T07:44:41+0900 - Updated documentation to make it more obvious that you can pass in a schema - Updated the SYNOPSIS making it more obvious to read the guides 5.02 2021-11-21T09:22:33+0900 - Fix reading request body as string if not form data or JSON #227 5.01 2021-11-20T13:39:28+0900 - Avoid uninitialized warnings #224 - Bump JSON::Validator to 5.03 5.00 2021-10-02T10:16:28+0900 - Fix "version_from_class" uses the VERSION from $app by default - Compatible with JSON::Validator 5.00 - Removed support for "allow_invalid_ref" - Changed render_spec() to require a JSON::Validator::Schema::OpenAPIv2 object 4.06 2021-09-14T14:08:26+0200 - Add support for adding $route->to(...) programmatically - Fix link to Convos example spec #222 Contributor: Roy Storey - Cleaned up CPAN distribution files 4.05 2021-07-10T15:56:19+0900 - Allow string in OpenAPIv2 request body #219 4.04 2021-06-17T11:16:04+0900 - Depending on JSON::Validator 4.18 4.03 2021-04-28T11:32:17+0900 - Depending on JSON::Validator 4.17 - Add support for "skip_validating_specification" - Reverted back to supporting "default_response" - Moved "DefaultResponse" for v2 from "definitions" to "responses" - Improved documentation: "schema" is no longer needed 4.02 2021-03-24T11:37:27+0900 - Add support for passing in constraints (such as format) using x-mojo-to 4.01 2021-03-24T09:14:22+0900 - Using routes() from JSON::Validator::Schema::OpenAPIv2 and ::OpenAPIv3 - Fix generating correct OpenAPIv2 spec #199 - Fix not adding basePath to OpenAPIv3 spec #200 - Fix compatibility with Mojolicious 9.11 #204 4.00 2021-02-17T09:23:35+0900 - Using JSON::Validator::Schema::OpenAPIv2 and OpenAPIv2 schema API #160 - Compatible with Mojolicious 9.0 - Depends on JSON::Validator 4.13 3.41 2021-01-24T16:44:53+0900 - Add announcement about the new JSON::Validator based API - Add missing documentation about Text::Markdown - Add permalinks to headers in SpecRenderer - SpecRenderer adds openapi.rich_text helper - Compatible with Mojolicious 8.67 - Bump JSON::Validator to 4.11 - Removed $c->openapi->cors_simple() helper 3.40 2020-10-10T16:23:05+0900 - Compatible with JSON::Validator 4.06 3.39 2020-10-08T15:52:42+0900 - Add support optional requestBody in OpenAPIv3 #170 #194 Contributor: Ji-Hyeon Gim 3.38 2020-10-07T12:39:18+0900 - Fix failing CPAN testers tests - Fix uninitialized warnings - Fix not coercing body with array into object 3.37 2020-10-06T13:47:47+0900 - Can also render doc for spec with $ref inside paths - Can specify SpecRenderer logo - Can specify SpecRenderer theme color - Will block less when rendering big JSON documents in browser - Bump JSON::Validator to 4.05 3.36 2020-09-22T09:42:10+0900 - Fix writeOnly handling OpenAPI v3 #191 3.35 2020-08-11T11:32:59+0900 - Add support for v3 object parameters #184 Contributor: SebMourlhou - Add support for passing in custom spec to $c->openapi->render_spec #189 - Fix handling 404 and 501 in v3 #179 - Fix issue when "nullable" is stored inside JSON::Validator::Ref #183 - Fix $c->openapi->validate helper #187 3.34 2020-08-05T16:36:41+0900 - Can now set custom status code from a security callback #186 Contributor: Stephan Hradek 3.33 2020-06-08T15:28:12+0900 - Forgot to bump JSON::Validator to 4.00 3.32 2020-06-08T10:42:08+0900 - Compatible with JSON::Validator 4.00 3.31 2020-03-31T20:42:25+0900 - Fix SpecRenderer should not add ".json" to the "servers" URL #174 3.30 2020-03-24T10:24:44+0900 - Fix support for file uploads in OpenAPI v3 #171 3.29 2020-03-23T15:09:35+0900 - Add basic support for file uploads in OpenAPI v3 #171 3.28 2020-03-10T19:20:23+0900 - Fix setting "schemes" in OpenAPIv2 when rendering the spec - Fix links to parameters in SpecRenderer - Hiding "About" header on desktop 3.27 2020-03-09T09:33:44+0900 - Fix "up" button in SpecRenderer 3.26 2020-03-09T09:24:33+0900 - Add "up" button to SpecRenderer - Fix some rendering issues for jsonhtmlify - Merged "renderjson" and "scrollspy" into "javascript.html.ep" - Split "References" into "Parameters", "Definitions" and "Components" 3.25 2020-03-08T18:38:25+0900 - Switched to jsonhtmlify in SpecRenderer #167 - SpecRenderer styling is using colors from the OpenAPI logo - Changed to rendering operationId, instead of method/path in headings - Documented demo page and template structure for SpecRenderer - Moved "Base URL", "Contact" and "License" in SpecRenderer 3.24 2020-03-06T13:30:16+0900 - SpecRenderer can be used standalone - New default CSS styling for SpecRenderer - Add more templates for SpecRenderer * mojolicious/plugin/openapi/foot.html.ep * mojolicious/plugin/openapi/head.html.ep * mojolicious/plugin/openapi/renderjson.html.ep * mojolicious/plugin/openapi/scrollspy.html.ep * mojolicious/plugin/openapi/style.html.ep 2.23 2020-03-03T15:57:42+0900 - Started slowly to deprecate $c->validation->output #162 - Bump JSON::Validator version #163 2.22 2020-03-02T17:09:49+0900 - Improved links to v3.x documentation 2.21 2020-01-24T12:34:04+0900 - Will not detect invalid route names on startup - Add support for v3 array parameters #149 #154 Contributor: Sebastien Mourlhou 2.20 2019-12-12T21:17:07+0100 - Depends on YAML::XS because it's a nicer way to write the spec and I have made too many failed releases that depend on YAML::XS #153 2.19 2019-12-04T17:19:08+0100 - Add support for parameter defaults in OpenAPI v3 #115 - Override generate_definitions_path() in order to render proper OpenAPIv3 spec #152 - Update of OpenAPI3 guide #152 2.18 2019-10-28T14:18:33+0900 - Fix /servers/url for OpenAPI v3 in SpecRenderer #148 - Fix OpenAPI v3 parameter type #137 #147 Contributor: SebMourlhou 2.17 2019-10-17T08:12:29+0900 - Add tuturial for OpenAPI v3 #142 Contributor: Henrik Andersen - The internal doc renderer now supports OpenAPI v3 #144 Contributor: Henrik Andersen - Fixed failing tests #143 Contributor: Henrik Andersen - Fixed rendering OpenAPI v3 spec #141 Contributor: Henrik Andersen - Fixed failing integration with OpenAPI::Client #135 Contributor: Roy Storey 2.16 2019-08-02T09:07:24+0200 - Fix t/v3-body.t when YAML::XS is not available 2.15 2019-08-01T20:18:05+0200 - Add support for v3 schema from https://spec.openapis.org/oas/3.0/schema/2019-04-02 - Add support for handling of securitySchemes in OpenAPI v3 #129 Contributor: Ilya Rassadin - Fix default responses for OpenAPI v3 #129 Contributor: Ilya Rassadin - Compatible with new Mojo::Exception # 133 Contributor: Roy Storey 2.14 2019-05-05T14:11:06+0700 - Fix "coerce(1) will be deprecated" #130 - Changed OPTIONS response to be a draft-04 response - Need to bundle all responses from SpecRenderer to make OPTIONS render in a more human friendly way. - Require Mojolicious 8.00 #122 2.13 2019-03-13T17:12:52+0800 - Fix issue in OpenAPI::Security when used from OpenAPI::Client, or another UserAgent with an IOLoop that is not the singleton. #121 - Fix issue in SYNOPSIS that gave confusing output for /api Contributor: Bernhard Graf 2.12 2019-02-14T20:12:16+0100 - Fix HEAD requests #105 - Fix using /servers/0/url as basePath for OpenAPI v3 #110 Note: This could be breaking change - Fix getting basePath when using under #107 - Add support for "nullable" in OpenAPI 3.0 #106 - Improved handling of Accept header in OpenAPI v3 #104 Can now handle wildcards, such as application/* and */*, even though not defined in the specification. - Bump JSON::Validator to 3.06 2.11 2019-01-26T11:37:15+0900 - Fix allowing regular requests with "openapi_cors_allowed_origins" #103 2.10 2019-01-25T12:49:55+0900 - Add "plugins" as a documented feature for register() - Add Mojolicious::Plugin::OpenAPI::SpecRenderer - Add the possibility to turn off automatic rendering of specification using OPTIONS and from /:basePath route - Add EXPERIMENTAL "openapi_routes_added" hook - Add support for Preflight CORS requests #99 - Fix Simple CORS requests with "GET" and no Content-Type #99 - Fix writing a list of headers back after validated - Marked $c->openapi->simple_cors as DEPRECATED 2.09 2019-01-21T09:51:56+0900 - Using formats from JSON::Validator 3.04 2.08 2019-01-07T10:00:52+0900 - Fix Data::Validate::IP is an optional module for the test suite #100 - Bumping JSON::Validator to 3.01 2.07 2018-12-15T11:50:30+0900 - Merged JSON::Validator::OpenAPI into JSON::Validator::OpenAPI::Mojolicious - Compatible with "formats" in JSON::Validator 3.x 2.06 2018-12-07T14:14:24+0900 - Made YAML::XS and v3 optional 2.05 2018-12-07T14:02:49+0900 - Moved JSON::Validator::OpenAPI::Mojolicious from JSON-Validator 2.04 2018-11-15T16:13:55+0900 - Use data:///file.json in SYNOPSIS to make it work with morbo 2.03 2018-11-14T15:42:27+0900 - Improved human readable documentation rendering 2.02 2018-11-14T13:13:13+0900 - Mention EXPERIMENTAL support for OpenAPI v3 #75 2.01 2018-10-26T11:58:10+0900 - Fix default error template lookup by mode #93 Contributor: Doug Bell - Bumped JSON::Validator version to 2.14 2.00 2018-09-30T21:53:28+0900 - Add support for "default_response_codes" #66 #80 - Add support for "default_response_name" #66 #80 - Add support for plack and other servers that does not start the IOLoop #82 - Add detection for invalid x-mojo-name on startup #87 - Changed "message" in JSON response for 404, 500 and 501 - Changed "path" is not required in default error response - Removed default "default_response" #80 - Removed "Using default_handler to render..." warning since it was confusing - Bump Mojolicious version to 8.0 1.30 2018-06-06T00:20:46+0800 - Fix exception handling in an action, with the security plugin enabled 1.29 2018-06-03T20:32:21+0800 - Fix "No security callback for $name." error object - Fix "status" icompatibility with Mojolicious 7.82 #78 1.28 2018-04-21T11:03:02+0200 - Add support for Simple Cross-Origin Resource Sharing requests (CORS) #14 - Bumped JSON::Validator version - Changed placeholders from () to <> to support Mojolicious 7.75 #73 1.27 2018-04-09T09:05:10-0700 - Add EXPERIMENTAL route name for OPTIONS routes #69 - Add Text::Markdown as an optional module for rendering documentation snippets #63 Contributor: Lars Thegler 1.26 2018-03-08T21:15:52+0100 - Fix skipping yaml.t, unless correct version of YAML::XS is available #67 Contributor: Søren Lund 1.25 2018-01-29T10:00:59+0100 - Removed YAML::Syck test #60 - Change register() to return the plugin instance 1.24 2018-01-19T10:37:28+0100 - Require JSON::Validator 2.00 which fixes "enum" bug 1.23 2017-12-25T10:50:28+0100 - Fix setting default values #53 #55 - Can specify schema when loading plugin 1.22 2017-11-19T20:25:16+0100 - Compatible with JSON::Validator 1.06 - Deprecated "reply.openapi" helper - Moved security handling to separate module - Started on plugin support #14 1.21 2017-07-24T21:46:37+0200 - "path" is not required in default error document 1.20 2017-07-24T21:41:01+0200 - Add "default_response" parameter to register() 1.19 2017-07-10T22:44:19+0200 - Add support for "security" and "securityDefinitions" Contributor: Joel Berger 1.18 2017-07-04T09:23:48+0200 - Fix rendering of documentation does not die when "parameters" are under a path - Fix generating routes with "parameters" under a path #42 - Fix other documentation renderers, when "parameters" under a pth #42 1.17 2017-06-12T20:58:57+0200 - Add support for fetching API spec in route chain - Add "exception" stash variable on internal server error #38 Contributor: Manuel Mausz 1.16 2017-05-18T11:23:52+0200 - Can override status code in "renderer" function 1.15 2017-05-15T09:15:14+0200 - Fix "renderer" will also be called for internal errors #34 #35 - Removed openapi.not_implemented helper 1.14 2017-05-13T11:55:37+0200 - Fix automatically coercing values #33 Contributor: Nick Logan - Add openapi.render_spec helper - Add example for how to use a M::P::Swagger2 powered app with M::P::OpenAPI - Bump JSON::Validator version 1.13 2017-03-03T00:35:26+0100 - Forgot to bump JSON::Validator version in cpanfile #32 1.12 2017-03-02T23:10:18+0100 - Compatible with JSON::Validator 0.95 1.11 2017-03-01T19:42:58+0100 - Fix adding routes with wildcards after routes without wildcards - Add fallback to default renderer, unless "openapi" is set in stash 1.10 2017-02-21T15:35:45+0100 - Fix resolve of specification twice #19 - Require JSON::Validator 0.94 #30 1.09 2017-01-30T13:11:52+0000 - Prevent stomping of status in before_render hook 1.08 2017-01-25T17:27:12+0100 - Add EXPERIMENTAL openapi.not_implemented helper 1.07 2016-12-11T11:39:46+0100 - Compatible with JSON::Validator 0.90 1.06 2016-11-18T15:57:26+0100 - Will rewrite basePath in generated spec, relative to base URL - Documented x-mojo-placeholder #16 1.05 2016-10-26T13:23:38+0200 - Add support for path parameters #11 - Fix typos in tutorial regarding example snippets #13 - Fix default OPTIONS path, when it has placeholders 1.04 2016-10-06T21:39:06+0200 - Fix responding with an empty string #9 - Fix responding with null 1.03 2016-09-27T23:58:41+0200 - Bumped required JSON::Validator version to 0.85 #8 1.02 2016-09-27T09:52:02+0200 - Fix bug for collectionFormat handling in JSON::Validator - Add support for "version_from_class" - Add TOC to .html rendering of API 1.01 2016-09-21T16:07:45+0200 - Fix documentation regarding the "reply.openapi" helper #7 1.00 2016-09-04T15:08:56+0200 - Removed EXPERIMENTAL 0.14 2016-08-20T14:04:58+0200 - Fix rendering UTF-8 characters 0.13 2016-08-16T19:54:48+0200 - Removed $c->openapi->invalid_input() - Add support for rendering specification on OPTIONS #1 0.12 2016-08-10T21:16:54+0200 - Add support for $c->render(openapi => $data); - Started DEPRECATING $c->reply->openapi() 0.11 2016-08-09T13:35:16+0200 - Add support for retrieving the complete API spec - Improved tutorial 0.10 2016-08-07T22:16:38+0200 - Add $c->openapi->validate() - Deprecated $c->openapi->invalid_input() - Fix validating YAML specifications #3 #4 Contributor: Ilya Rassadin 0.09 2016-08-04T09:30:23+0200 - Add basic support for rendering spec as HTML - Add check for $ref in the right place in the input specification Contributor: Lari Taskula 0.08 2016-07-29T14:33:14+0200 - Add check for unique operationId and route names - All route names will have "spec_route_name." as prefix 0.07 2016-07-26T21:53:56+0200 - Add support for serving binary data 0.06 2016-07-26T18:56:50+0200 - Add support for naming baseUrl (specification) route - Add openapi.valid_input helper - Fix loading the plugin twice 0.05 2016-07-26T15:04:25+0200 - Fix "false" must be false and not true - Make sure 404 is returned as default format and not html 0.04 2016-07-25T15:03:31+0200 - Fix setting default values in JSON::Validator::OpenAPI 0.76 - Fix registering correct HTTP method for action in a class 0.03 2016-07-25T11:25:43+0200 - Add openapi.invalid_input helper - Add Mojolicious::Plugin::OpenAPI::Guides::Tutorial - Remove openapi.validate helper - Remove openapi.input helper - Will store validated data into $c->validation->output 0.02 2016-06-11T07:32:51-0700 - Improved documentation - Add support for MOJO_OPENAPI_LOG_LEVEL=error 0.01 2016-06-10T19:34:35-0700 - Add logging of request/response errors - Add rendering of API spec from base URL - Exceptions returns structured JSON data instead of HTML - Making an improved version of Mojolicious::Plugin::Swagger2 - Started project Mojolicious-Plugin-OpenAPI-5.09/PaxHeader/MANIFEST000644 000765 000024 00000000207 14374621224 022705 xustar00jhthorsenstaff000000 000000 29 mtime=1676878484.86395804 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/MANIFEST000644 000765 000024 00000004100 14374621224 020730 0ustar00jhthorsenstaff000000 000000 Changes lib/Mojolicious/Plugin/OpenAPI.pm lib/Mojolicious/Plugin/OpenAPI/Cors.pm lib/Mojolicious/Plugin/OpenAPI/Guides/OpenAPIv2.pod lib/Mojolicious/Plugin/OpenAPI/Guides/OpenAPIv3.pod lib/Mojolicious/Plugin/OpenAPI/Guides/Swagger2.pod lib/Mojolicious/Plugin/OpenAPI/Parameters.pm lib/Mojolicious/Plugin/OpenAPI/Security.pm lib/Mojolicious/Plugin/OpenAPI/SpecRenderer.pm Makefile.PL MANIFEST This list of files t/00-project.t t/basic-404-501.t t/basic-autorender.t t/basic-bundle.t t/basic-coerce.t t/basic-correct-order-of-paths.t t/basic-custom-formats.t t/basic-custom-renderer.t t/basic-custom-validation.t t/basic-empty-response.t t/basic-invalid-json-input.t t/basic-legacy-swagger2.t t/basic-mojo-placeholder.t t/basic-mojo-route-names.t t/basic-path-parameters.t t/basic-register-plugin.t t/basic-under-route-authenticate.t t/data/image.jpeg t/jv-recursion.t t/plugin-cors.t t/plugin-security-extended-status.t t/plugin-security-rules-not-defined.t t/plugin-security-v2.t t/plugin-security-v3.t t/plugin-spec-renderer-doc.t t/plugin-spec-renderer-options.t t/plugin-spec-renderer-standalone.t t/plugin-spec-renderer-v3.t t/spec/bundlecheck.json t/spec/swagger/parameters/body.yaml t/spec/swagger/paths/ref.yaml t/spec/swagger/responses/ok.yaml t/spec/swagger/swagger.yaml t/spec/v2-petstore.json t/spec/v3-invalid_file_refs.yaml t/spec/v3-invalid_file_refs_no_path.yaml t/spec/v3-invalid_include.yaml t/spec/v3-petstore.json t/spec/v3-valid_file_refs.yaml t/spec/v3-valid_include.yaml t/v2-basic.t t/v2-body.t t/v2-collectionformat.t t/v2-defaults.t t/v2-discriminator.t t/v2-file.t t/v2-formats.t t/v2-headers.t t/v2-id-prop.t t/v2-readonly.t t/v2-swagger.t t/v2-tutorial.t t/v2-validate-schema.t t/v3-basic.t t/v3-body.t t/v3-bundle.t t/v3-defaults.t t/v3-file.t t/v3-invalid_file_refs.t t/v3-invalid_file_refs_no_path.t t/v3-nullable.t t/v3-style-array.t t/v3-style-object.t t/v3-tutorial.t t/v3-valid_file_refs.t t/v3-writeonly.t META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) Mojolicious-Plugin-OpenAPI-5.09/PaxHeader/t000755 000765 000024 00000000210 14374621224 021737 xustar00jhthorsenstaff000000 000000 30 mtime=1676878484.574967712 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/000755 000765 000024 00000000000 14374621224 020047 5ustar00jhthorsenstaff000000 000000 Mojolicious-Plugin-OpenAPI-5.09/PaxHeader/META.yml000664 000765 000024 00000000210 14374621224 023021 xustar00jhthorsenstaff000000 000000 30 mtime=1676878484.685071123 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/META.yml000664 000765 000024 00000004060 14374621224 021057 0ustar00jhthorsenstaff000000 000000 --- abstract: 'OpenAPI / Swagger plugin for Mojolicious' author: - 'Jan Henning Thorsen ' build_requires: ExtUtils::MakeMaker: '0' Test::More: '0.88' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'ExtUtils::MakeMaker version 7.64, CPAN::Meta::Converter version 2.150010' license: artistic_2 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Mojolicious-Plugin-OpenAPI no_index: directory: - t - inc - examples - t recommends: Text::Markdown: v1.0.31 requires: JSON::Validator: '5.13' Mojolicious: '9.00' perl: '5.016' resources: IRC: url: irc://irc.libera.chat/#perl-openapi web: https://web.libera.chat/#perl-openapi bugtracker: https://github.com/jhthorsen/mojolicious-plugin-openapi/issues homepage: https://github.com/jhthorsen/mojolicious-plugin-openapi license: http://www.opensource.org/licenses/artistic-license-2.0 repository: https://github.com/jhthorsen/mojolicious-plugin-openapi.git version: '5.09' x_contributors: - 'Bernhard Graf ' - 'Doug Bell ' - 'Ed J ' - 'Henrik Andersen ' - 'Ilya Rassadin ' - 'Jan Henning Thorsen ' - 'Ji-Hyeon Gim ' - 'Joel Berger ' - 'Krasimir Berov ' - 'Lars Thegler ' - 'Lee Johnson ' - 'Linn-Hege Kristensen ' - 'Manuel ' - 'Martin Renvoize ' - 'Mohammad S Anwar ' - 'Nick Morrott ' - 'Renee ' - 'Roy Storey ' - 'SebMourlhou <35918953+SebMourlhou@users.noreply.github.com>' - 'Søren Lund ' - 'Stephan Hradek ' - 'Stephan Hradek ' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' Mojolicious-Plugin-OpenAPI-5.09/PaxHeader/lib000755 000765 000024 00000000210 14374621224 022242 xustar00jhthorsenstaff000000 000000 30 mtime=1676878484.553779746 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/lib/000755 000765 000024 00000000000 14374621224 020352 5ustar00jhthorsenstaff000000 000000 Mojolicious-Plugin-OpenAPI-5.09/PaxHeader/Makefile.PL000644 000765 000024 00000000152 14344504052 023521 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/Makefile.PL000644 000765 000024 00000005106 14344504052 021554 0ustar00jhthorsenstaff000000 000000 use 5.016; use strict; use warnings; use utf8; use ExtUtils::MakeMaker; my $GITHUB_URL = 'https://github.com/jhthorsen/mojolicious-plugin-openapi'; my %WriteMakefileArgs = ( NAME => 'Mojolicious::Plugin::OpenAPI', AUTHOR => 'Jan Henning Thorsen ', LICENSE => 'artistic_2', ABSTRACT_FROM => 'lib/Mojolicious/Plugin/OpenAPI.pm', VERSION_FROM => 'lib/Mojolicious/Plugin/OpenAPI.pm', TEST_REQUIRES => {'Test::More' => '0.88'}, PREREQ_PM => {'JSON::Validator' => '5.13', 'Mojolicious' => '9.00'}, META_MERGE => { 'dynamic_config' => 0, 'meta-spec' => {version => 2}, 'no_index' => {directory => [qw(examples t)]}, 'prereqs' => {runtime => {recommends => {'Text::Markdown' => 'v1.0.31'}, requires => {perl => '5.016'}}}, 'resources' => { bugtracker => {web => "$GITHUB_URL/issues"}, homepage => $GITHUB_URL, license => ['http://www.opensource.org/licenses/artistic-license-2.0'], repository => {type => 'git', url => "$GITHUB_URL.git", web => $GITHUB_URL}, x_IRC => { url => 'irc://irc.libera.chat/#perl-openapi', web => 'https://web.libera.chat/#perl-openapi' }, }, 'x_contributors' => [ 'Bernhard Graf ', 'Doug Bell ', 'Ed J ', 'Henrik Andersen ', 'Ilya Rassadin ', 'Jan Henning Thorsen ', 'Ji-Hyeon Gim ', 'Joel Berger ', 'Krasimir Berov ', 'Lars Thegler ', 'Lee Johnson ', 'Linn-Hege Kristensen ', 'Manuel ', 'Martin Renvoize ', 'Mohammad S Anwar ', 'Nick Morrott ', 'Renee ', 'Roy Storey ', 'SebMourlhou <35918953+SebMourlhou@users.noreply.github.com>', 'Søren Lund ', 'Stephan Hradek ', 'Stephan Hradek ', ], }, test => {TESTS => (-e 'META.yml' ? 't/*.t' : 't/*.t xt/*.t')}, ); unless (eval { ExtUtils::MakeMaker->VERSION('6.63_03') }) { my $test_requires = delete $WriteMakefileArgs{TEST_REQUIRES}; @{$WriteMakefileArgs{PREREQ_PM}}{keys %$test_requires} = values %$test_requires; } WriteMakefile(%WriteMakefileArgs); Mojolicious-Plugin-OpenAPI-5.09/PaxHeader/META.json000664 000765 000024 00000000210 14374621224 023171 xustar00jhthorsenstaff000000 000000 30 mtime=1676878484.834046673 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/META.json000664 000765 000024 00000005632 14374621224 021235 0ustar00jhthorsenstaff000000 000000 { "abstract" : "OpenAPI / Swagger plugin for Mojolicious", "author" : [ "Jan Henning Thorsen " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.64, CPAN::Meta::Converter version 2.150010", "license" : [ "artistic_2" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Mojolicious-Plugin-OpenAPI", "no_index" : { "directory" : [ "t", "inc", "examples", "t" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "recommends" : { "Text::Markdown" : "v1.0.31" }, "requires" : { "JSON::Validator" : "5.13", "Mojolicious" : "9.00", "perl" : "5.016" } }, "test" : { "requires" : { "Test::More" : "0.88" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/jhthorsen/mojolicious-plugin-openapi/issues" }, "homepage" : "https://github.com/jhthorsen/mojolicious-plugin-openapi", "license" : [ "http://www.opensource.org/licenses/artistic-license-2.0" ], "repository" : { "type" : "git", "url" : "https://github.com/jhthorsen/mojolicious-plugin-openapi.git", "web" : "https://github.com/jhthorsen/mojolicious-plugin-openapi" }, "x_IRC" : { "url" : "irc://irc.libera.chat/#perl-openapi", "web" : "https://web.libera.chat/#perl-openapi" } }, "version" : "5.09", "x_contributors" : [ "Bernhard Graf ", "Doug Bell ", "Ed J ", "Henrik Andersen ", "Ilya Rassadin ", "Jan Henning Thorsen ", "Ji-Hyeon Gim ", "Joel Berger ", "Krasimir Berov ", "Lars Thegler ", "Lee Johnson ", "Linn-Hege Kristensen ", "Manuel ", "Martin Renvoize ", "Mohammad S Anwar ", "Nick Morrott ", "Renee ", "Roy Storey ", "SebMourlhou <35918953+SebMourlhou@users.noreply.github.com>", "Søren Lund ", "Stephan Hradek ", "Stephan Hradek " ], "x_serialization_backend" : "JSON::PP version 4.07" } Mojolicious-Plugin-OpenAPI-5.09/lib/PaxHeader/Mojolicious000755 000765 000024 00000000210 14374621224 024536 xustar00jhthorsenstaff000000 000000 30 mtime=1676878484.553814204 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/000755 000765 000024 00000000000 14374621224 022646 5ustar00jhthorsenstaff000000 000000 Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/PaxHeader/Plugin000755 000765 000024 00000000210 14374621224 025774 xustar00jhthorsenstaff000000 000000 30 mtime=1676878484.570443056 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/000755 000765 000024 00000000000 14374621224 024104 5ustar00jhthorsenstaff000000 000000 Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/PaxHeader/OpenAPI.pm000644 000765 000024 00000000210 14374621223 027636 xustar00jhthorsenstaff000000 000000 30 mtime=1676878483.926084701 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI.pm000644 000765 000024 00000050121 14374621223 025673 0ustar00jhthorsenstaff000000 000000 package Mojolicious::Plugin::OpenAPI; use Mojo::Base 'Mojolicious::Plugin'; use JSON::Validator; use Mojo::JSON; use Mojo::Util; use Mojolicious::Plugin::OpenAPI::Parameters; use constant DEBUG => $ENV{MOJO_OPENAPI_DEBUG} || 0; our $VERSION = '5.09'; has route => sub {undef}; has validator => sub { JSON::Validator::Schema->new; }; sub register { my ($self, $app, $config) = @_; $self->validator(JSON::Validator->new->schema($config->{url} || $config->{spec})->schema); $self->validator->coerce($config->{coerce}) if defined $config->{coerce}; if (my $class = $config->{version_from_class} // ref $app) { $self->validator->data->{info}{version} = sprintf '%s', $class->VERSION if $class->VERSION; } my $errors = $config->{skip_validating_specification} ? [] : $self->validator->errors; die @$errors if @$errors; unless ($app->defaults->{'openapi.base_paths'}) { $app->helper('openapi.spec' => \&_helper_get_spec); $app->helper('openapi.valid_input' => \&_helper_valid_input); $app->helper('openapi.validate' => \&_helper_validate); $app->helper('reply.openapi' => \&_helper_reply); $app->hook(before_render => \&_before_render); $app->renderer->add_handler(openapi => \&_render); } $self->{log_level} = $ENV{MOJO_OPENAPI_LOG_LEVEL} || $config->{log_level} || 'warn'; $self->_build_route($app, $config); # This plugin is required my @plugins = (Mojolicious::Plugin::OpenAPI::Parameters->new->register($app, $config)); for my $plugin (@{$config->{plugins} || [qw(+Cors +SpecRenderer +Security)]}) { $plugin = "Mojolicious::Plugin::OpenAPI::$plugin" if $plugin =~ s!^\+!!; eval "require $plugin;1" or Carp::confess("require $plugin: $@"); push @plugins, $plugin->new->register($app, {%$config, openapi => $self}); } my %default_response = %{$config->{default_response} || {}}; $default_response{name} ||= $config->{default_response_name} || 'DefaultResponse'; $default_response{status} ||= $config->{default_response_codes} || [400, 401, 404, 500, 501]; $default_response{location} = 'definitions'; $self->validator->add_default_response(\%default_response) if @{$default_response{status}}; $self->_add_routes($app, $config); return $self; } sub _add_routes { my ($self, $app, $config) = @_; my $op_spec_to_route = $config->{op_spec_to_route} || '_op_spec_to_route'; my (@routes, %uniq); for my $route ($self->validator->routes->each) { my $op_spec = $self->validator->get([paths => @$route{qw(path method)}]); my $name = $op_spec->{'x-mojo-name'} || $op_spec->{operationId}; my $r; die qq([OpenAPI] operationId "$op_spec->{operationId}" is not unique) if $op_spec->{operationId} and $uniq{o}{$op_spec->{operationId}}++; die qq([OpenAPI] Route name "$name" is not unique.) if $name and $uniq{r}{$name}++; if (!$op_spec->{'x-mojo-to'} and $name) { $r = $self->route->root->find($name); warn "[OpenAPI] Found existing route by name '$name'.\n" if DEBUG and $r; $self->route->add_child($r) if $r; } if (!$r) { my $http_method = $route->{method}; my $route_path = $self->_openapi_path_to_route_path(@$route{qw(method path)}); $name ||= $op_spec->{operationId}; warn "[OpenAPI] Creating new route for '$route_path'.\n" if DEBUG; $r = $self->route->$http_method($route_path); $r->name("$self->{route_prefix}$name") if $name; } $r->to(format => undef, 'openapi.method' => $route->{method}, 'openapi.path' => $route->{path}); $self->$op_spec_to_route($op_spec, $r, $config); warn "[OpenAPI] Add route $route->{method} @{[$r->to_string]} (@{[$r->name // '']})\n" if DEBUG; push @routes, $r; } $app->plugins->emit_hook(openapi_routes_added => $self, \@routes); } sub _before_render { my ($c, $args) = @_; return unless _self($c); my $handler = $args->{handler} || 'openapi'; # Call _render() for response data return if $handler eq 'openapi' and exists $c->stash->{openapi} or exists $args->{openapi}; # Fallback to default handler for things like render_to_string() return $args->{handler} = $c->app->renderer->default_handler unless exists $args->{handler}; # Call _render() for errors my $status = $args->{status} || $c->stash('status') || '200'; if ($handler eq 'openapi' and ($status eq '404' or $status eq '500')) { $args->{handler} = 'openapi'; $args->{status} = $status; $c->stash( status => $args->{status}, openapi => { errors => [{message => $c->res->default_message($args->{status}) . '.', path => '/'}], status => $args->{status}, } ); } } sub _build_route { my ($self, $app, $config) = @_; my $validator = $self->validator; my $base_path = $validator->base_url->path->to_string; my $route = $config->{route}; $route = $route->any($base_path) if $route and !$route->pattern->unparsed; $route = $app->routes->any($base_path) unless $route; $base_path = $route->to_string; $base_path =~ s!/$!!; push @{$app->defaults->{'openapi.base_paths'}}, [$base_path, $self]; $route->to({format => undef, handler => 'openapi', 'openapi.object' => $self}); $validator->base_url($base_path); if (my $spec_route_name = $config->{spec_route_name} || $validator->get('/x-mojo-name')) { $self->{route_prefix} = "$spec_route_name."; } $self->{route_prefix} //= ''; $self->route($route); } sub _helper_get_spec { my $c = shift; my $path = shift // 'for_current'; my $self = _self($c); # Get spec by valid JSON pointer return $self->validator->get($path) if ref $path or $path =~ m!^/! or !length $path; # Find spec by current request my ($stash) = grep { $_->{'openapi.path'} } reverse @{$c->match->stack}; return undef unless $stash; my $jp = [paths => $stash->{'openapi.path'}]; push @$jp, $stash->{'openapi.method'} if $path ne 'for_path'; # Internal for now return $self->validator->get($jp); } sub _helper_reply { my $c = shift; my $status = ref $_[0] ? 200 : shift; my $output = shift; my @args = @_; Mojo::Util::deprecated( '$c->reply->openapi() is DEPRECATED in favor of $c->render(openapi => ...)'); if (UNIVERSAL::isa($output, 'Mojo::Asset')) { my $h = $c->res->headers; if (!$h->content_type and $output->isa('Mojo::Asset::File')) { my $types = $c->app->types; my $type = $output->path =~ /\.(\w+)$/ ? $types->type($1) : undef; $h->content_type($type || $types->type('bin')); } return $c->reply->asset($output); } push @args, status => $status if $status; return $c->render(@args, openapi => $output); } sub _helper_valid_input { my $c = shift; return undef if $c->res->code; return $c unless my @errors = _helper_validate($c); $c->stash(status => 400) ->render(data => $c->openapi->build_response_body({errors => \@errors, status => 400})); return undef; } sub _helper_validate { my $c = shift; my $self = _self($c); my @errors = $self->validator->validate_request([@{$c->stash}{qw(openapi.method openapi.path)}], $c->openapi->build_schema_request); $c->openapi->coerce_request_parameters( delete $c->stash->{'openapi.evaluated_request_parameters'}); return @errors; } sub _log { my ($self, $c, $dir) = (shift, shift, shift); my $log_level = $self->{log_level}; $c->app->log->$log_level( sprintf 'OpenAPI %s %s %s %s', $dir, $c->req->method, $c->req->url->path, Mojo::JSON::encode_json(@_) ); } sub _op_spec_to_route { my ($self, $op_spec, $r, $config) = @_; my $op_to = $op_spec->{'x-mojo-to'} // []; my @args = ref $op_to eq 'ARRAY' ? @$op_to : ref $op_to eq 'HASH' ? %$op_to : $op_to ? ($op_to) : (); # x-mojo-to: controller#action $r->to(shift @args) if @args and $args[0] =~ m!#!; my ($constraints, @to) = ($r->pattern->constraints); $constraints->{format} //= $config->{format} if $config->{format}; while (my $arg = shift @args) { if (ref $arg eq 'ARRAY') { %$constraints = (%$constraints, @$arg) } elsif (ref $arg eq 'HASH') { push @to, %$arg } elsif (!ref $arg and @args) { push @to, $arg, shift @args } } $r->to(@to) if @to; } sub _render { my ($renderer, $c, $output, $args) = @_; my $stash = $c->stash; return unless exists $stash->{openapi}; return unless my $self = _self($c); my $status = $args->{status} || $stash->{status} || 200; my $method_path_status = [@$stash{qw(openapi.method openapi.path)}, $status]; my $op_spec = $method_path_status->[0] && $self->validator->parameters_for_response($method_path_status); my @errors; delete $args->{encoding}; $args->{status} = $status; $stash->{format} ||= 'json'; if ($op_spec) { @errors = $self->validator->validate_response($method_path_status, $c->openapi->build_schema_response); $c->openapi->coerce_response_parameters( delete $stash->{'openapi.evaluated_response_parameters'}); $args->{status} = $errors[0]->path eq '/header/Accept' ? 400 : 500 if @errors; } elsif (ref $stash->{openapi} eq 'HASH' and ref $stash->{openapi}{errors} eq 'ARRAY') { $args->{status} ||= $stash->{openapi}{status}; @errors = @{$stash->{openapi}{errors}}; } else { $args->{status} = 501; @errors = ({message => qq(No response rule for "$status".)}); } $self->_log($c, '>>>', \@errors) if @errors; $stash->{status} = $args->{status}; $$output = $c->openapi->build_response_body( @errors ? {errors => \@errors, status => $args->{status}} : $stash->{openapi}); } sub _openapi_path_to_route_path { my ($self, $http_method, $path) = @_; my %params = map { ($_->{name}, $_) } grep { $_->{in} eq 'path' } @{$self->validator->parameters_for_request([$http_method, $path])}; $path =~ s/{([^}]+)}/{ my $name = $1; my $type = $params{$name}{'x-mojo-placeholder'} || ':'; "<$type$name>"; }/ge; return $path; } sub _self { my $c = shift; my $self = $c->stash('openapi.object'); return $self if $self; my $path = $c->req->url->path->to_string; return +(map { $_->[1] } grep { $path =~ /^$_->[0]/ } @{$c->stash('openapi.base_paths')})[0]; } 1; =encoding utf8 =head1 NAME Mojolicious::Plugin::OpenAPI - OpenAPI / Swagger plugin for Mojolicious =head1 SYNOPSIS # It is recommended to use Mojolicious::Plugin::OpenAPI with a "full app". # See the links after this example for more information. use Mojolicious::Lite; # Because the route name "echo" matches the "x-mojo-name", this route # will be moved under "basePath", resulting in "POST /api/echo" post "/echo" => sub { # Validate input request or return an error document my $c = shift->openapi->valid_input or return; # Generate some data my $data = {body => $c->req->json}; # Validate the output response and render it to the user agent # using a custom "openapi" handler. $c->render(openapi => $data); }, "echo"; # Load specification and start web server plugin OpenAPI => {url => "data:///swagger.yaml"}; app->start; __DATA__ @@ swagger.yaml swagger: "2.0" info: { version: "0.8", title: "Echo Service" } schemes: ["https"] basePath: "/api" paths: /echo: post: x-mojo-name: "echo" parameters: - { in: "body", name: "body", schema: { type: "object" } } responses: 200: description: "Echo response" schema: { type: "object" } See L or L for more in depth information about how to use L with a "full app". Even with a "lite app" it can be very useful to read those guides. Looking at the documentation for L can be especially useful. (The logic is the same for OpenAPIv2 and OpenAPIv3) =head1 DESCRIPTION L is L that add routes and input/output validation to your L application based on a OpenAPI (Swagger) specification. This plugin supports both version L<2.0|/schema> and L<3.x|/schema>, though 3.x I have some missing features. Have a look at the L for references to plugins and other useful documentation. Please report in L or open pull requests to enhance the 3.0 support. =head1 HELPERS =head2 openapi.spec $hash = $c->openapi->spec($json_pointer) $hash = $c->openapi->spec("/info/title") $hash = $c->openapi->spec; Returns the OpenAPI specification. A JSON Pointer can be used to extract a given section of the specification. The default value of C<$json_pointer> will be relative to the current operation. Example: { "paths": { "/pets": { "get": { // This datastructure is returned by default } } } } =head2 openapi.validate @errors = $c->openapi->validate; Used to validate a request. C<@errors> holds a list of L objects or empty list on valid input. Note that this helper is only for customization. You probably want L in most cases. =head2 openapi.valid_input $c = $c->openapi->valid_input; Returns the L object if the input is valid or automatically render an error document if not and return false. See L for example usage. =head1 HOOKS L will emit the following hooks on the L object. =head2 openapi_routes_added Emitted after all routes have been added by this plugin. $app->hook(openapi_routes_added => sub { my ($openapi, $routes) = @_; for my $route (@$routes) { ... } }); This hook is EXPERIMENTAL and subject for change. =head1 RENDERER This plugin register a new handler called C. The special thing about this handler is that it will validate the data before sending it back to the user agent. Examples: $c->render(json => {foo => 123}); # without validation $c->render(openapi => {foo => 123}); # with validation This handler will also use L to format the output data. The code below shows the default L which generates JSON data: $app->plugin( OpenAPI => { renderer => sub { my ($c, $data) = @_; return Mojo::JSON::encode_json($data); } } ); =head1 ATTRIBUTES =head2 route $route = $openapi->route; The parent L object for all the OpenAPI endpoints. =head2 validator $jv = $openapi->validator; Holds either a L or a L object. =head1 METHODS =head2 register $openapi = $openapi->register($app, \%config); $openapi = $app->plugin(OpenAPI => \%config); Loads the OpenAPI specification, validates it and add routes to L<$app|Mojolicious>. It will also set up L and adds a L hook for auto-rendering of error documents. The return value is the object instance, which allow you to access the L after you load the plugin. C<%config> can have: =head3 coerce See L for possible values that C can take. Default: booleans,numbers,strings The default value will include "defaults" in the future, once that is stable enough. =head3 default_response Instructions for L. (Also used for OpenAPIv3) =head3 format Set this to a default list of file extensions that your API accepts. This value can be overwritten by L. This config parameter is EXPERIMENTAL and subject for change. =head3 log_level C is used when logging invalid request/response error messages. Default: "warn". =head3 op_spec_to_route C can be provided if you want to add route definitions without using "x-mojo-to". Example: $app->plugin(OpenAPI => {op_spec_to_route => sub { my ($plugin, $op_spec, $route) = @_; # Here are two ways to customize where to dispatch the request $route->to(cb => sub { shift->render(openapi => ...) }); $route->to(ucfirst "$op_spec->{operationId}#handle_request"); }}); This feature is EXPERIMENTAL and might be altered and/or removed. =head3 plugins A list of OpenAPI classes to extend the functionality. Default is: L, L and L. $app->plugin(OpenAPI => {plugins => [qw(+Cors +SpecRenderer +Security)]}); You can load your own plugins by doing: $app->plugin(OpenAPI => {plugins => [qw(+SpecRenderer My::Cool::OpenAPI::Plugin)]}); =head3 renderer See L. =head3 route C can be specified in case you want to have a protected API. Example: $app->plugin(OpenAPI => { route => $app->routes->under("/api")->to("user#auth"), url => $app->home->rel_file("cool.api"), }); =head3 skip_validating_specification Used to prevent calling L for the specification. =head3 spec_route_name Name of the route that handles the "basePath" part of the specification and serves the specification. Defaults to "x-mojo-name" in the specification at the top level. =head3 spec, url See L for the different C formats that is accepted. C is an alias for "url", which might make more sense if your specification is written in perl, instead of JSON or YAML. Here are some common uses: $app->plugin(OpenAPI => {url => $app->home->rel_file('openapi.yaml')); $app->plugin(OpenAPI => {url => 'https://example.com/swagger.json'}); $app->plugin(OpenAPI => {spec => JSON::Validator::Schema::OpenAPIv3->new(...)}); $app->plugin(OpenAPI => {spec => {swagger => "2.0", paths => {...}, ...}}); =head3 version_from_class Can be used to overridden C in the API specification, from the return value from the C method in C. Defaults to the current C<$app>. This can be disabled by setting the "version_from_class" to zero (0). =head1 AUTHORS =head2 Project Founder Jan Henning Thorsen - C =head2 Contributors =over 2 =item * Bernhard Graf =item * Doug Bell =item * Ed J =item * Henrik Andersen =item * Henrik Andersen =item * Ilya Rassadin =item * Jan Henning Thorsen =item * Jan Henning Thorsen =item * Ji-Hyeon Gim =item * Joel Berger =item * Krasimir Berov =item * Lars Thegler =item * Lee Johnson =item * Linn-Hege Kristensen =item * Manuel =item * Martin Renvoize =item * Mohammad S Anwar =item * Nick Morrott =item * Renee =item * Roy Storey =item * SebMourlhou <35918953+SebMourlhou@users.noreply.github.com> =item * SebMourlhou =item * SebMourlhou =item * Søren Lund =item * Stephan Hradek =item * Stephan Hradek =back =head1 COPYRIGHT AND LICENSE Copyright (C) Jan Henning Thorsen This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version 2.0. =head1 SEE ALSO =over 2 =item * L Guide for how to use this plugin with OpenAPI version 2.0 spec. =item * L Guide for how to use this plugin with OpenAPI version 3.0 spec. =item * L Plugin to add Cross-Origin Resource Sharing (CORS). =item * L Plugin for handling security definitions in your schema. =item * L Plugin for exposing your spec in human readable or JSON format. =item * L Official OpenAPI website. =back =cut Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/PaxHeader/OpenAPI000755 000765 000024 00000000210 14374621224 027227 xustar00jhthorsenstaff000000 000000 30 mtime=1676878484.575397916 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/000755 000765 000024 00000000000 14374621224 025337 5ustar00jhthorsenstaff000000 000000 Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/PaxHeader/Cors.pm000644 000765 000024 00000000210 14374621223 030544 xustar00jhthorsenstaff000000 000000 30 mtime=1676878483.929153291 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/Cors.pm000644 000765 000024 00000032775 14374621223 026620 0ustar00jhthorsenstaff000000 000000 package Mojolicious::Plugin::OpenAPI::Cors; use Mojo::Base -base; require Mojolicious::Routes::Route; my $methods = Mojolicious::Routes::Route->can('methods') ? 'methods' : 'via'; use constant DEBUG => $ENV{MOJO_OPENAPI_DEBUG} || 0; our %SIMPLE_METHODS = map { ($_ => 1) } qw(GET HEAD POST); our %SIMPLE_CONTENT_TYPES = map { ($_ => 1) } qw(application/x-www-form-urlencoded multipart/form-data text/plain); our %SIMPLE_HEADERS = map { (lc $_ => 1) } qw(Accept Accept-Language Content-Language Content-Type DPR Downlink Save-Data Viewport-Width Width); our %PREFLIGHTED_CONTENT_TYPES = %SIMPLE_CONTENT_TYPES; our %PREFLIGHTED_METHODS = map { ($_ => 1) } qw(CONNECT DELETE OPTIONS PATCH PUT TRACE); my $X_RE = qr{^x-}; sub register { my ($self, $app, $config) = @_; my $openapi = $config->{openapi}; if ($config->{add_preflighted_routes}) { $app->plugins->once(openapi_routes_added => sub { $self->_add_preflighted_routes($app, @_) }); } my %defaults = ( openapi_cors_allowed_origins => [], openapi_cors_default_exchange_callback => \&_default_cors_exchange_callback, openapi_cors_default_max_age => 1800, ); $app->defaults($_ => $defaults{$_}) for grep { !$app->defaults($_) } keys %defaults; $app->helper('openapi.cors_exchange' => sub { $self->_exchange(@_) }); } sub _add_preflighted_routes { my ($self, $app, $openapi, $routes) = @_; my $c = $app->build_controller; my $match = Mojolicious::Routes::Match->new(root => $app->routes); for my $route (@$routes) { my $route_path = $route->to_string; next if $self->_takeover_exchange_route($route); next if $match->find($c, {method => 'options', path => $route_path}); # Make a given action also handle OPTIONS push @{$route->$methods}, 'OPTIONS'; $route->to->{'openapi.cors_preflighted'} = 1; warn "[OpenAPI] Add route options $route_path (@{[$route->name // '']})\n" if DEBUG; } } sub _default_cors_exchange_callback { my $c = shift; my $allowed = $c->stash('openapi_cors_allowed_origins') || []; my $origin = $c->req->headers->origin // ''; return scalar(grep { $origin =~ $_ } @$allowed) ? undef : '/Origin'; } sub _exchange { my ($self, $c) = (shift, shift); my $cb = shift || $c->stash('openapi_cors_default_exchange_callback'); # Not a CORS request unless (defined $c->req->headers->origin) { my $method = $c->req->method; _render_bad_request($c, 'OPTIONS is only for preflighted CORS requests.') if $method eq 'OPTIONS' and $c->match->endpoint->to->{'openapi.cors_preflighted'}; return $c; } my $type = $self->_is_simple_request($c) || $self->_is_preflighted_request($c) || 'real'; $c->stash(openapi_cors_type => $type); my $errors = $c->$cb; return _render_bad_request($c, $errors) if $errors; _set_default_headers($c); return $type eq 'preflighted' ? $c->tap('render', data => '', status => 200) : $c; } sub _is_preflighted_request { my ($self, $c) = @_; my $req_h = $c->req->headers; return undef unless $c->req->method eq 'OPTIONS'; return 'preflighted' if $req_h->header('Access-Control-Request-Headers'); return 'preflighted' if $req_h->header('Access-Control-Request-Method'); my $ct = lc($req_h->content_type || ''); return 'preflighted' if $ct and $PREFLIGHTED_CONTENT_TYPES{$ct}; return undef; } sub _is_simple_request { my ($self, $c) = @_; return undef unless $SIMPLE_METHODS{$c->req->method}; my $req_h = $c->req->headers; my @names = grep { !$SIMPLE_HEADERS{lc($_)} } @{$req_h->names}; return undef if @names; my $ct = lc $req_h->content_type || ''; return undef if $ct and $SIMPLE_CONTENT_TYPES{$ct}; return 'simple'; } sub _render_bad_request { my ($c, $errors) = @_; $errors = [{message => "Invalid $1 header.", path => $errors}] if !ref $errors and $errors =~ m!^/([\w-]+)!; $errors = [{message => $errors, path => '/'}] unless ref $errors; return $c->tap('render', openapi => {errors => $errors, status => 400}, status => 400); } sub _set_default_headers { my $c = shift; my $req_h = $c->req->headers; my $res_h = $c->res->headers; unless ($res_h->access_control_allow_origin) { $res_h->access_control_allow_origin($req_h->origin); } return unless $c->stash('openapi_cors_type') eq 'preflighted'; unless ($res_h->header('Access-Control-Allow-Headers')) { $res_h->header( 'Access-Control-Allow-Headers' => $req_h->header('Access-Control-Request-Headers') // ''); } unless ($res_h->header('Access-Control-Allow-Methods')) { my $op_spec = $c->openapi->spec('for_path'); my @methods = sort grep { !/$X_RE/ } keys %{$op_spec || {}}; $res_h->header('Access-Control-Allow-Methods' => uc join ', ', @methods); } unless ($res_h->header('Access-Control-Max-Age')) { $res_h->header('Access-Control-Max-Age' => $c->stash('openapi_cors_default_max_age')); } } sub _takeover_exchange_route { my ($self, $route) = @_; my $defaults = $route->to; return 0 if $defaults->{controller}; return 0 unless $defaults->{action} and $defaults->{action} eq 'openapi_plugin_cors_exchange'; return 0 unless grep { $_ eq 'OPTIONS' } @{$route->$methods}; $defaults->{cb} = sub { my $c = shift; $c->openapi->valid_input or return; $c->req->headers->origin or return _render_bad_request($c, '/Origin'); $c->stash(openapi_cors_type => 'preflighted'); _set_default_headers($c); $c->render(data => '', status => 200); }; return 1; } 1; =encoding utf8 =head1 NAME Mojolicious::Plugin::OpenAPI::Cors - OpenAPI plugin for Cross-Origin Resource Sharing =head1 SYNOPSIS =head2 Application Set L to 1, if you want "Preflighted" CORS requests to be sent to your already existing actions. $app->plugin(OpenAPI => {add_preflighted_routes => 1, %openapi_parameters}); See L for what C<%openapi_parameters> might contain. =head2 Simple exchange The following example will automatically set default CORS response headers after validating the request against L: package MyApp::Controller::User; sub get_user { my $c = shift->openapi->cors_exchange->openapi->valid_input or return; # Will only run this part if both the cors_exchange and valid_input was successful. $c->render(openapi => {user => {}}); } =head2 Using the specification It's possible to enable preflight and simple CORS support directly in the specification. Here is one example: "/user/{id}/posts": { "parameters": [ { "in": "header", "name": "Origin", "type": "string", "pattern": "https?://example.com" } ], "options": { "x-mojo-to": "#openapi_plugin_cors_exchange", "responses": { "200": { "description": "Cors exchange", "schema": { "type": "string" } } } }, "put": { "x-mojo-to": "user#add_post", "responses": { "200": { "description": "Add a new post.", "schema": { "type": "object" } } } } } The special part can be found in the "OPTIONS" request It has the C key set to "#openapi_plugin_cors_exchange". This will enable L to take over the route and add a custom callback to validate the input headers using regular OpenAPI rules and respond with a "200 OK" and the default headers as listed under L if the input is valid. The only extra part that needs to be done in the C action is this: sub add_post { my $c = shift->openapi->valid_input or return; # Need to respond with a "Access-Control-Allow-Origin" header if # the input "Origin" header was validated $c->res->headers->access_control_allow_origin($c->req->headers->origin) if $c->req->headers->origin; # Do the rest of your custom logic $c->respond(openapi => {}); } =head2 Custom exchange If you need full control, you must pass a callback to L: package MyApp::Controller::User; sub get_user { # Validate incoming CORS request with _validate_cors() my $c = shift->openapi->cors_exchange("_validate_cors")->openapi->valid_input or return; # Will only run this part if both the cors_exchange and valid_input was # successful. $c->render(openapi => {user => {}}); } # This method must return undef on success. Any true value will be used as an error. sub _validate_cors { my $c = shift; my $req_h = $c->req->headers; my $res_h = $c->res->headers; # The following "Origin" header check is the same for both simple and # preflighted. return "/Origin" unless $req_h->origin =~ m!^https?://whatever.example.com!; # The following checks are only valid if preflighted... # Check the Access-Control-Request-Headers header my $headers = $req_h->header('Access-Control-Request-Headers'); return "Bad stuff." if $headers and $headers =~ /X-No-Can-Do/; # Check the Access-Control-Request-Method header my $method = $req_h->header('Access-Control-Request-Methods'); return "Not cool." if $method and $method eq "DELETE"; # Set the following header for both simple and preflighted on success # or just let the auto-renderer handle it. $c->res->headers->access_control_allow_origin($req_h->origin); # Set Preflighted response headers, instead of using the default if ($c->stash("openapi_cors_type") eq "preflighted") { $c->res->headers->header("Access-Control-Allow-Headers" => "X-Whatever, X-Something"); $c->res->headers->header("Access-Control-Allow-Methods" => "POST, GET, OPTIONS"); $c->res->headers->header("Access-Control-Max-Age" => 86400); } # Return undef on success. return undef; } =head1 DESCRIPTION L is a plugin for accepting Preflighted or Simple Cross-Origin Resource Sharing requests. See L for more details. This plugin is loaded by default by L. Note that this plugin currently EXPERIMENTAL! Please comment on L if you have any feedback or create a new issue. =head1 STASH VARIABLES The following "stash variables" can be set in L, L or L. =head2 openapi_cors_allowed_origins This variable should hold an array-ref of regexes that will be matched against the "Origin" header in case the default L is used. Examples: $app->defaults(openapi_cors_allowed_origins => [qr{^https?://whatever.example.com}]); $c->stash(openapi_cors_allowed_origins => [qr{^https?://whatever.example.com}]); =head2 openapi_cors_default_exchange_callback This value holds a default callback that will be used by L, unless you pass on a C<$callback>. The default provided by this plugin will simply validate the C header against L. Here is an example to allow every "Origin" $app->defaults(openapi_cors_default_exchange_callback => sub { my $c = shift; $c->res->headers->header("Access-Control-Allow-Origin" => "*"); return undef; }); =head2 openapi_cors_default_max_age Holds the default value for the "Access-Control-Max-Age" response header set by L. Examples: $app->defaults(openapi_cors_default_max_age => 86400); $c->stash(openapi_cors_default_max_age => 86400); Default value is 1800. =head2 openapi_cors_type This stash variable is available inside the callback passed on to L. It will be either "preflighted", "real" or "simple". "real" is the type that comes after "preflighted" when the actual request is sent to the server, but with "Origin" header set. =head1 HELPERS =head2 openapi.cors_exchange $c = $c->openapi->cors_exchange($callback); $c = $c->openapi->cors_exchange("MyApp::cors_validator"); $c = $c->openapi->cors_exchange("_some_controller_method"); $c = $c->openapi->cors_exchange(sub { ... }); $c = $c->openapi->cors_exchange; Used to validate either a simple CORS request, preflighted CORS request or a real request. It will be called as soon as the "Origin" request header is seen. The C<$callback> will be called with the current L object and must return an error or C on success: my $error = $callback->($c); The C<$error> must be in one of the following formats: =over 2 =item * C Returning C means that the CORS request is valid. =item * A string starting with "/" Shortcut for generating a 400 Bad Request response with a header name. Example: return "/Access-Control-Request-Headers"; =item * Any other string Used to generate a 400 Bad Request response with a completely custom message. =item * An array-ref Used to generate a completely custom 400 Bad Request response. Example: return [{message => "Some error!", path => "/Whatever"}]; return [{message => "Some error!"}]; return [JSON::Validator::Error->new]; =back On success, the following headers will be set, unless already set by C<$callback>: =over 2 =item * Access-Control-Allow-Headers Set to the header of the incoming "Access-Control-Request-Headers" header. =item * Access-Control-Allow-Methods Set to the list of HTTP methods defined in the OpenAPI spec for this path. =item * Access-Control-Allow-Origin Set to the "Origin" header in the request. =item * Access-Control-Max-Age Set to L. =back =head1 METHODS =head2 register Called by L. =head1 SEE ALSO L. =cut Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/PaxHeader/Security.pm000644 000765 000024 00000000210 14374621223 031445 xustar00jhthorsenstaff000000 000000 30 mtime=1676878483.932151215 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/Security.pm000644 000765 000024 00000015624 14374621223 027513 0ustar00jhthorsenstaff000000 000000 package Mojolicious::Plugin::OpenAPI::Security; use Mojo::Base -base; my %DEF_PATH = ('openapiv2' => '/securityDefinitions', 'openapiv3' => '/components/securitySchemes'); sub register { my ($self, $app, $config) = @_; my $openapi = $config->{openapi}; my $handlers = $config->{security} or return; return unless $openapi->validator->get($DEF_PATH{$openapi->validator->moniker}); return $openapi->route( $openapi->route->under('/')->to(cb => $self->_build_action($openapi, $handlers))); } sub _build_action { my ($self, $openapi, $handlers) = @_; my $global = $openapi->validator->get('/security') || []; my $definitions = $openapi->validator->get($DEF_PATH{$openapi->validator->moniker}); return sub { my $c = shift; return 1 if $c->req->method eq 'OPTIONS' and $c->match->stack->[-1]{'openapi.default_options'}; my $spec = $c->openapi->spec || {}; my @security_or = @{$spec->{security} || $global}; my ($sync_mode, $n_checks, %res) = (1, 0); my $security_completed = sub { my ($i, $status, @errors) = (0, 401); SECURITY_AND: for my $security_and (@security_or) { my @e; for my $name (sort keys %$security_and) { my $error_path = sprintf '/security/%s/%s', $i, _pointer_escape($name); push @e, ref $res{$name} ? $res{$name} : {message => $res{$name}, path => $error_path} if defined $res{$name}; } # Authenticated # Cannot call $c->continue() in case this callback was called # synchronously, since it will result in an infinite loop. unless (@e) { return if eval { $sync_mode || $c->continue || 1 }; chomp $@; $c->app->log->error($@); @errors = ({message => 'Internal Server Error.', path => '/'}); $status = 500; last SECURITY_AND; } # Not authenticated push @errors, @e; $i++; } $status = $c->stash('status') || $status if $status < 500; $c->render(openapi => {errors => \@errors}, status => $status); $n_checks = -1; # Make sure we don't render twice }; for my $security_and (@security_or) { for my $name (sort keys %$security_and) { my $security_cb = $handlers->{$name}; if (!$security_cb) { $res{$name} = {message => "No security callback for $name."} unless exists $res{$name}; } elsif (!exists $res{$name}) { $res{$name} = undef; $n_checks++; # $security_cb is obviously called synchronously, but the callback # might also be called synchronously. We need the $sync_mode guard # to make sure that we do not call continue() if that is the case. $c->$security_cb( $definitions->{$name}, $security_and->{$name}, sub { $res{$name} //= $_[1]; $security_completed->() if --$n_checks == 0; } ); } } } # If $security_completed was called already, then $n_checks will zero and # we return "1" which means we are in synchronous mode. When running async, # we need to asign undef() to $sync_mode, since it is used inside # $security_completed to call $c->continue() return $sync_mode = $n_checks ? undef : 1; }; } sub _pointer_escape { local $_ = shift; s/~/~0/g; s!/!~1!g; $_; } 1; =encoding utf8 =head1 NAME Mojolicious::Plugin::OpenAPI::Security - OpenAPI plugin for securing your API =head1 DESCRIPTION This plugin will allow you to use the security features provided by the OpenAPI specification. Note that this is currently EXPERIMENTAL! Please let me know if you have any feedback. See L for a complete discussion. =head1 TUTORIAL =head2 Specification Here is an example specification that use L and L from the OpenAPI spec: { "swagger": "2.0", "info": { "version": "0.8", "title": "Super secure" }, "schemes": [ "https" ], "basePath": "/api", "securityDefinitions": { "dummy": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "dummy" } }, "paths": { "/protected": { "post": { "x-mojo-to": "super#secret_resource", "security": [{"dummy": []}], "parameters": [ { "in": "body", "name": "body", "schema": { "type": "object" } } ], "responses": { "200": {"description": "Echo response", "schema": { "type": "object" }}, "401": {"description": "Sorry mate", "schema": { "type": "array" }} } } } } } =head2 Application The specification above can be dispatched to handlers inside your L application. The do so, add the "security" key when loading the plugin, and reference the "securityDefinitions" name inside that to a callback. In this example, we have the "dummy" security handler: package Myapp; use Mojo::Base "Mojolicious"; sub startup { my $app = shift; $app->plugin(OpenAPI => { url => "data:///security.json", security => { dummy => sub { my ($c, $definition, $scopes, $cb) = @_; return $c->$cb() if $c->req->headers->authorization; return $c->$cb('Authorization header not present'); } } }); } 1; C<$c> is a L object. C<$definition> is the security definition from C. C<$scopes> is the Oauth scopes, which in this case is just an empty array ref, but it will contain the value for "security" under the given HTTP method. Call C<$cb> with C or no argument at all to indicate pass. Call C<$cb> with a defined value (usually a string) to indicate that the check has failed. When none of the sets of security restrictions are satisfied, the standard OpenAPI structure is built using the values passed to the callbacks as the messages and rendered to the client with a status of 401. Note that the callback must be called or the dispatch will hang. See also L for example L application. =head2 Controller Your controllers and actions are unchanged. The difference in behavior is that the action simply won't be called if you fail to pass the security tests. =head2 Exempted routes All of the routes created by the plugin are protected by the security definitions with the following exemptions. The base route that renders the spec/documentation is exempted. Additionally, when a route does not define its own C handler a documentation endpoint is generated which is exempt as well. =head1 METHODS =head2 register Called by L. =head1 SEE ALSO L. =cut Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/PaxHeader/Guides000755 000765 000024 00000000210 14374621224 030447 xustar00jhthorsenstaff000000 000000 30 mtime=1676878484.571108298 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/Guides/000755 000765 000024 00000000000 14374621224 026557 5ustar00jhthorsenstaff000000 000000 Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/PaxHeader/SpecRenderer.pm000644 000765 000024 00000000210 14374621223 032217 xustar00jhthorsenstaff000000 000000 30 mtime=1676878483.936994618 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/SpecRenderer.pm000644 000765 000024 00000105215 14374621223 030261 0ustar00jhthorsenstaff000000 000000 package Mojolicious::Plugin::OpenAPI::SpecRenderer; use Mojo::Base 'Mojolicious::Plugin'; use JSON::Validator; use Mojo::JSON; use Scalar::Util qw(blessed); use constant DEBUG => $ENV{MOJO_OPENAPI_DEBUG} || 0; use constant MARKDOWN => eval 'require Text::Markdown;1'; sub register { my ($self, $app, $config) = @_; $app->defaults(openapi_spec_renderer_logo => '/mojolicious/plugin/openapi/logo.png'); $app->defaults(openapi_spec_renderer_theme_color => '#508a25'); $self->{standalone} = $config->{openapi} ? 0 : 1; $app->helper('openapi.render_spec' => sub { $self->_render_spec(@_) }); $app->helper('openapi.rich_text' => \&_helper_rich_text); # EXPERIMENTAL $app->helper('openapi.spec_iterator' => \&_helper_iterator); unless ($app->{'openapi.render_specification'}++) { push @{$app->renderer->classes}, __PACKAGE__; push @{$app->static->classes}, __PACKAGE__; } $self->_register_with_openapi($app, $config) unless $self->{standalone}; } sub _helper_iterator { my ($c, $obj) = @_; return unless $obj; unless ($c->{_helper_iterator}{$obj}) { my $x_re = qr{^x-}; $c->{_helper_iterator}{$obj} = [map { [$_, $obj->{$_}] } sort { lc $a cmp lc $b } grep { !/$x_re/ } keys %$obj]; } my $items = $c->{_helper_iterator}{$obj}; my $item = shift @$items; delete $c->{_helper_iterator}{$obj} unless $item; return $item ? @$item : (); } sub _register_with_openapi { my ($self, $app, $config) = @_; my $openapi = $config->{openapi}; if ($config->{render_specification} // 1) { my $spec_route = $openapi->route->get( '/', [format => [qw(html json)]], {format => undef}, sub { shift->openapi->render_spec(@_) } ); my $name = $config->{spec_route_name} || $openapi->validator->get('/x-mojo-name'); $spec_route->name($name) if $name; } if ($config->{render_specification_for_paths} // 1) { $app->plugins->once(openapi_routes_added => sub { $self->_add_documentation_routes(@_) }); } } sub _add_documentation_routes { my ($self, $openapi, $routes) = @_; my %dups; for my $route (@$routes) { my $route_path = $route->to_string; next if $dups{$route_path}++; my $openapi_path = $route->to->{'openapi.path'}; my $doc_route = $openapi->route->options($route->pattern->unparsed, {'openapi.default_options' => 1}); $doc_route->to(cb => sub { $self->_render_spec(shift, $openapi_path) }); $doc_route->name(join '_', $route->name, 'openapi_documentation') if $route->name; warn "[OpenAPI] Add route options $route_path (@{[$doc_route->name // '']})\n" if DEBUG; } } sub _helper_rich_text { return Mojo::ByteStream->new(MARKDOWN ? Text::Markdown::markdown($_[1]) : $_[1]); } sub _render_partial_spec { my ($self, $c, $path, $custom) = @_; my $method = $c->param('method'); my $validator = $custom || Mojolicious::Plugin::OpenAPI::_self($c)->validator; return $c->render(json => {errors => [{message => 'No spec defined.'}]}, status => 404) unless my $schema = $validator->get([paths => $path, $method ? ($method) : ()]); return $c->render( json => { '$schema' => 'http://json-schema.org/draft-04/schema#', title => $validator->get([qw(info title)]) || '', description => $validator->get([qw(info description)]) || '', %{$validator->bundle({schema => $schema})->data}, $method ? (parameters => $validator->parameters_for_request([$method, $path])) : (), } ); } sub _render_spec { my ($self, $c, $path, $custom) = @_; return $self->_render_partial_spec($c, $path, $custom) if $path; my $openapi = $custom || $self->{standalone} ? undef : Mojolicious::Plugin::OpenAPI::_self($c); my $format = $c->stash('format') || 'json'; my $validator = $custom; if (!$validator and $openapi) { $validator = $openapi->{bundled} ||= $openapi->validator->bundle; $validator->base_url($c->req->url->to_abs->path($c->url_for($validator->base_url->path))); } return $c->render(json => {errors => [{message => 'No specification to render.'}]}, status => 500) unless $validator; my $operations = $validator->routes->each(sub { my $path_item = $_; $path_item->{op_spec} = $validator->get([paths => @$path_item{qw(path method)}]); $path_item->{operation_id} //= ''; $path_item; }); return $c->render(json => $validator->data) unless $format eq 'html'; return $c->render( base_url => $validator->base_url, handler => 'ep', template => 'mojolicious/plugin/openapi/layout', operations => [sort { $a->{operation_id} cmp $b->{operation_id} } @$operations], serialize => \&_serialize, slugify => sub { join '-', map { s/\W/-/g; lc } map {"$_"} @_; }, spec => $validator->data, ); } sub _serialize { Mojo::JSON::encode_json(@_) } 1; =encoding utf8 =head1 NAME Mojolicious::Plugin::OpenAPI::SpecRenderer - Render OpenAPI specification =head1 SYNOPSIS =head2 With Mojolicious::Plugin::OpenAPI $app->plugin(OpenAPI => { plugins => [qw(+SpecRenderer)], render_specification => 1, render_specification_for_paths => 1, %openapi_parameters, }); See L for what C<%openapi_parameters> might contain. =head2 Standalone use Mojolicious::Lite; plugin "Mojolicious::Plugin::OpenAPI::SpecRenderer"; # Some specification to render my $petstore = app->home->child("petstore.json"); get "/my-spec" => sub { my $c = shift; my $path = $c->param('path') || '/'; state $custom = JSON::Validator->new->schema($petstore->to_string)->schema->bundle; $c->openapi->render_spec($path, $custom); }; =head1 DESCRIPTION L will enable L to render the specification in both HTML and JSON format. It can also be used L if you just want to render the specification, and not add any API routes to your application. See L to see how you can override parts of the rendering. The human readable format focus on making the documentation printable, so you can easily share it with third parties as a PDF. If this documentation format is too basic or has missing information, then please L suggestions for enhancements. See L for a demo. =head1 HELPERS =head2 openapi.render_spec $c = $c->openapi->render_spec; $c = $c->openapi->render_spec($json_path); $c = $c->openapi->render_spec($json_path, $openapi_v2_schema_object); $c = $c->openapi->render_spec("/user/{id}"); Used to render the specification as either "html" or "json". Set the L variable "format" to change the format to render. Will render the whole specification by default, but can also render documentation for a given OpenAPI path. =head2 openapi.rich_text $bytestream = $c->openapi->rich_text($text); Used to render the "description" in the specification with L if it is installed. Will just return the text if the module is not available. =head1 METHODS =head2 register $doc->register($app, $openapi, \%config); Adds the features mentioned in the L. C<%config> is the same as passed on to L. The following keys are used by this plugin: =head3 render_specification Render the whole specification as either HTML or JSON from "/:basePath". Example if C in your specification is "/api": GET https://api.example.com/api.html GET https://api.example.com/api.json Disable this feature by setting C to C<0>. =head3 render_specification_for_paths Render the specification from individual routes, using the OPTIONS HTTP method. Example: OPTIONS https://api.example.com/api/some/path.json OPTIONS https://api.example.com/api/some/path.json?method=post Disable this feature by setting C to C<0>. =head1 TEMPLATING Overriding templates is EXPERIMENTAL, but not very likely to break in a bad way. L uses many template files to make up the human readable version of the spec. Each of them can be overridden by creating a file in your templates folder. mojolicious/plugin/openapi/layout.html.ep |- mojolicious/plugin/openapi/head.html.ep | '- mojolicious/plugin/openapi/style.html.ep |- mojolicious/plugin/openapi/header.html.ep | |- mojolicious/plugin/openapi/logo.html.ep | '- mojolicious/plugin/openapi/toc.html.ep |- mojolicious/plugin/openapi/intro.html.ep |- mojolicious/plugin/openapi/resources.html.ep | '- mojolicious/plugin/openapi/resource.html.ep | |- mojolicious/plugin/openapi/human.html.ep | |- mojolicious/plugin/openapi/parameters.html.ep | '- mojolicious/plugin/openapi/response.html.ep | '- mojolicious/plugin/openapi/human.html.ep |- mojolicious/plugin/openapi/references.html.ep |- mojolicious/plugin/openapi/footer.html.ep |- mojolicious/plugin/openapi/javascript.html.ep '- mojolicious/plugin/openapi/foot.html.ep See the DATA section in the source code for more details on styling and markup structure. L Variables available in the templates: %= $serialize->($data_structure) %= $slugify->(@str) %= $spec->{info}{title} In addition, there is a logo in "header.html.ep" that can be overridden by either changing the static file "mojolicious/plugin/openapi/logo.png" or set "openapi_spec_renderer_logo" in L to a custom URL. =head1 SEE ALSO L =cut __DATA__ @@ mojolicious/plugin/openapi/header.html.ep

<%= $spec->{info}{title} || 'No title' %>

Version <%= $spec->{info}{version} %> - OpenAPI <%= $spec->{swagger} || $spec->{openapi} %>

@@ mojolicious/plugin/openapi/intro.html.ep

About

% if ($spec->{info}{description}) {
%== $c->openapi->rich_text($spec->{info}{description})
% } % my $contact = $spec->{info}{contact}; % my $license = $spec->{info}{license};

License

% if ($license->{name}) {

<%= $license->{name} %>

% } else {

No license specified.

% }

Contact information

% if ($contact->{email}) {

<%= $contact->{email} %>

% } % if ($contact->{url}) {

<%= $contact->{url} %>

% } % if (exists $spec->{openapi}) {

Servers

    % for my $server (@{$spec->{servers}}){
  • <%= $server->{url} %><%= $server->{description} ? ' - '.$server->{description} : '' %>
  • % }
% } else { % my $schemes = $spec->{schemes} || ["http"]; % my $url = Mojo::URL->new("http://$spec->{host}");

Base URL

    % for my $scheme (@$schemes) { % $url->scheme($scheme);
  • <%= $url %>
  • % }
% } % if ($spec->{info}{termsOfService}) {

Terms of service

%= $spec->{info}{termsOfService}

% } @@ mojolicious/plugin/openapi/foot.html.ep @@ mojolicious/plugin/openapi/footer.html.ep @@ mojolicious/plugin/openapi/head.html.ep <%= $spec->{info}{title} || 'No title' %> %= include 'mojolicious/plugin/openapi/style' @@ mojolicious/plugin/openapi/human.html.ep % if ($op_spec->{summary}) {

<%= $op_spec->{summary} %>

% } % if ($op_spec->{description}) {
<%== $c->openapi->rich_text($op_spec->{description}) %>
% } % if (!$op_spec->{description} and !$op_spec->{summary}) {

This resource is not documented.

% } @@ mojolicious/plugin/openapi/parameters.html.ep % my $has_parameters = @{$op_spec->{parameters} || []}; % my $body;

Parameters

% if ($has_parameters) { % } % for my $p (@{$op_spec->{parameters} || []}) { % $body = $p->{schema} if $p->{in} eq 'body'; % if ($spec->{parameters}{$p->{name}}) { % } else { % } % } % if ($has_parameters) {
Name In Type Required Description
<%= $p->{name} %><%= $p->{name} %><%= $p->{in} %> <%= $p->{type} || $p->{schema}{type} %> <%= $p->{required} ? "Yes" : "No" %> <%== $p->{description} ? $c->openapi->rich_text($p->{description}) : "" %>
% } else {

This resource has no input parameters.

% } % if ($body) {

Body

<%= $serialize->($body) %>
% } % if ($op_spec->{requestBody}) {

requestBody

<%= $serialize->($op_spec->{requestBody}{content}) %>
% } @@ mojolicious/plugin/openapi/response.html.ep % while (my ($code, $res) = $c->openapi->spec_iterator($op_spec->{responses})) {

Response <%= $code %>

%= include 'mojolicious/plugin/openapi/human', op_spec => $res
<%= $serialize->($res->{schema} || $res->{content}) %>
% } @@ mojolicious/plugin/openapi/resource.html.ep % my $id = $slugify->(op => $method, $path);

"> <%= $operation_id %>

% if ($op_spec->{deprecated}) {

This resource is deprecated!

% } %= include 'mojolicious/plugin/openapi/human', op_spec => $op_spec %= include 'mojolicious/plugin/openapi/parameters', op_spec => $op_spec %= include 'mojolicious/plugin/openapi/response', op_spec => $op_spec @@ mojolicious/plugin/openapi/references.html.ep % if ($spec->{parameters}) {

Parameters

% while (my ($key, $schema) = $c->openapi->spec_iterator($spec->{parameters})) { % my $id = lc $slugify->(qw(ref parameters), $key);

<%= $key %>

<%= $serialize->($schema) %>
% } % } % if ($spec->{components}) {

Components

% while (my ($type, $comp_group) = $c->openapi->spec_iterator($spec->{components})) { % while (my ($key, $comp) = $c->openapi->spec_iterator($comp_group)) {
  • <%= $key %>
  • % } % } % } % if ($spec->{definitions}) {

    Definitions

    % while (my ($key, $schema) = $c->openapi->spec_iterator($spec->{definitions})) { % my $id = lc $slugify->(qw(ref definitions), $key);

    <%= $key %>

    <%= $serialize->($schema) %>
    % } % } @@ mojolicious/plugin/openapi/resources.html.ep

    Resources

    % for my $op (@$operations) { %= include 'mojolicious/plugin/openapi/resource', %$op; % } @@ mojolicious/plugin/openapi/toc.html.ep
      % if ($spec->{info}{description}) {
    1. About
      1. License
      2. Contact
      3. Base URL
      4. % if ($spec->{info}{termsOfService}) {
      5. Terms of service
      6. % }
    2. % }
    3. Resources
        % for my $op (@$operations) {
      1. <%= $op->{operation_id} %>
      2. % }
    4. % if ($spec->{parameters}) {
    5. Parameters
        % while (my ($key) = $c->openapi->spec_iterator($spec->{parameters})) {
      1. <%= $key %>
      2. % }
    6. % } % if ($spec->{components}) {
    7. Components
        % while (my ($type, $comp_group) = $c->openapi->spec_iterator($spec->{components})) { % while (my ($key, $comp) = $c->openapi->spec_iterator($comp_group)) {
      1. <%= $key %>
      2. % } % }
    8. % } % if ($spec->{definitions}) {
    9. Definitions
        % while (my ($key) = $c->openapi->spec_iterator($spec->{definitions})) {
      1. <%= $key %>
      2. % }
    10. % }
    @@ mojolicious/plugin/openapi/layout.html.ep %= include 'mojolicious/plugin/openapi/head'
    %= include 'mojolicious/plugin/openapi/header'
    %= include 'mojolicious/plugin/openapi/intro'
    %= include 'mojolicious/plugin/openapi/resources'
    %= include 'mojolicious/plugin/openapi/references'
    %= include 'mojolicious/plugin/openapi/footer'
    %= include "mojolicious/plugin/openapi/javascript" %= include "mojolicious/plugin/openapi/foot" @@ mojolicious/plugin/openapi/javascript.html.ep @@ mojolicious/plugin/openapi/style.html.ep @@ mojolicious/plugin/openapi/logo.png (base64) iVBORw0KGgoAAAANSUhEUgAAAMgAAAA5CAMAAABESJQQAAAABGdBTUEAALGPC/xhBQAAACBjSFJN AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAC+lBMVEVHcExXYExNTE5GREZH RkhLSUxZXFMAAElLSkxEQ0VDQkRDQkNDQkRDQkRFRUZMSk1LSkxJR0pRU09DQkREQ0VOT0tUVFFE Q0RHRkd8gHxNS05CQUNUVFF2tk95qlByqkt2rU5vp0hfZ09ZZkdLS0xDQkRCQUN2qU5wqUptp0Zt p0ZupkZspkRwqEhLXDJNXDNOXTNOXTROXTRSYDdYZz5nkVNEQ0VNTE9wqEpup0ZrpUNupEZQXzVM WzFSYThSYjhgYVyArlZwp0htpkZNXDFNXDNRYDdeZk1RUU1EQ0VDQkSi01KVyEhtp0VSYDdCQUNL SkuWyT+XykJ0q0xNXDJYXVBYWlVFREZFREZCQkOtx22YykWWyUCVyD2VyD6Wx0ltpkZMS05FREZJ SEmXykKWyUB8rE1NXDNFREZCQUNGRUdFREZfX19XZztOXTRth0KWyUGUyD2XykRRYjdOTU9PXTRw iUWVyD+Mu0xtp0dNXDJPXjVMS01PXjSWyUGYykN2qlBspkRRUFJbXlhPXjSUyT5PTlBRYDdYZz1N XDFspkRRYTdNXDFSXj1tg0CVyT6Zy0VaX1JEQ0VQXzZDQURSYjlxoU1wqElSYDdSYTmXykdSYThT YTlJSEpJR0lCQUNJSEpEREVRUVNKSEtYWFhPTlFOTlBNTE1GRkdISElHR0lxcHJxbXFEREVcXFVh YGJycXJ7e354eHxsa25JSUpFQ0Y/PkpBQUNIR0hAQEdIR0lKSUtHRkiJrmVEQUSenpyhs46Vm4Nv qElwqUpGRUdEQ0VIR0lKSEpHRkiWykGVyD5CQUNCQUNHRkdJSUqn1l6Vxklvp0dTU1VBQUVEQERE Q0VKSkyYy0hrpkNfX2JGRUdPT1B0pk9rpkRHRkhFREVJSEtEREVLSkyVyD+WyUB1pFBZWVqVxU91 olBup0hnZ2ptpkZDQ0NKSkuXyUN0plBMWzKXxkyXyERXV1lKSUtTYThPTVBKSktDQUNCQUNrpUNM WzGUyD3///+AqxLvAAAA+XRSTlMAE0FicjwRAkfV7/j97cgzYVkE8eEqDLmiAiTkIgoTGi0yDi43 9Ps4h7HU4+txPe7l1biNQAb7TWTN/HlD/m4xCg+n6P3osRoZ2egTI8V90W/GlV7YHxfNvBMDU9P+ 7D+uILAmp7w/3cH++MUDWsNBrPxpOxzJNd852uI0Z8+ykE7xZAmu812JVfTukvl1JvljJt2o7Jgm k4SiN2hRVYORie5ZajsvD5vV/Mw6PRcHKz1PSDRQnhHLjyTovakIWg0aBp7+muXpvpOdy9T833wM R7SNOzzzy2D+a+67Q/d3z83JseZxProqSn9N3nbBgVf3G3pes1/gzccUdOrEAAAAAWJLR0T9SwmT 6QAAAAd0SU1FB+QDCAM0E/7HrXsAAApxSURBVGje1ZprWFTHGcdnUVhgF8EFFg5FRQQ0iiIqaGiI a4jCgiKKclOkrEbdsl6IKEYR0BXUghcQ74JUAzEaVxPB0IpNtRCNF6RNjW1isEZtbLRKbdpm2f3Q M3NuM3sL0SfPMf8PsDNnzpz3NzPvO5dzAPhBkjj16dvH2UX6w+564SR1dXOXyWUe/TzFtuQ5Obzc jYzc+yvENuZ5OFy9jZx8fMW25jmk9DMK8qfENufZFfAzDMQ9UGxzeqMBAwcFDR4UPCRQosQaPiQU AwlzFdvI79fQYS8NHxEePnJUxOjIMWPHRXH5ztEYiHy82GY6VlTQhJd7kGJ+/ooJKjby1YkqdM3T GwOROYttqkNNei0upocEMZlenzwlHl5MUGMgicFi2+pASVOn9fASQGhFJEtoZ5+OdUiK2MY60NQZ M1NjbIOYZs0GaelyASQjU2xr7SprztzseTm/sA0ya5Q0NwMLWZr5YptrXwMXmM3mNxaG2wJZNAQk LNbSBNH+/RJ/mZjnpRTbWrvSLVm6bHm+Of/NYdbOHjtrBRWAODy8FMqCgAKV2NY60IrJsStXvWE2 F65+ywpkzVrgwvRH0Qu/VJwdSRu8bkhxvrmkdL3FPDJrLQhMR6uSDUAntqHfI/3GWDT10X1SlvMS CbJmIpCgeBVdrgjeFPX8D/sxtXkLM4ign8wo/RUOErkC5C6G/eFRREkqKre+0IOLWse59aJtxdt3 TIgRQNZUSV2qkX+U61Q7Xzctmi22sY44tgoTxtJlC+auHs6DRK6QBqD5o6ZIIdlVSS9XksW21oES Zgkgset2FxenxsQM31M6KH7vmCqQkM7EK51qH/Qj0+R4sc21r1e2YGsR08plJfsnLJyErmRJmfmj hvbzfZXMCvKA2Oba1yYToXXbkvhLmXmoPzbQHLHs5TEvrLtHjSZBIg4CIC2orTtU39+5H/SPX5dT wbsqucujq8Q22J7GkRyVr9J5/X3gzCEPQ/1xWBc8JZa/HntQbIPtiNprMbLSADiC7c5rNkglQn/Q 2iu2xXak30mC0M78dpjAEX0YKKcQBTaKbbEdScYQZtLhVYntPIxuLiA+kiixVC+2ybYVv5I0MwoE +GAgoQ1AOSYWV6QLe6e08e13jr777rH6BiV/qK2bffw9TscDTxiYXMXJ9widqqKAruoUXQSbldJO vf/+B/jCIf44KipEybTTQg0nE7iH0k88dVppBbKRAp4eGIixCVC+o3CtZWpWNGl8tOzwUzc7s+df Zz7EbtW6p7cUwNzf4Ed7UL9NAyfQCUC1cBx+CKbPChzBTHuGupIl+BZWtzrBzHMosv6OdvaPfo/r HAAh7vgN/cH5C3/ANRxNlsFtcqLaFIM1CFS7p9QGyMcciNFPQZh5UQApZ1vjkm0QuOM+Qj/0o2Ms SNInl3HNPA9827HC0SFggLCRh0Ig8/PkFpU2n7EJYnQLcAgiv6S3DaJv5ZpCaQ/E6F2Lg9D7QkyF SSAzHSurTgMDXyZApg0AQJnHmu9+RR3KxDhts4EHuRqGxJRplbIgYYKu8SBGb1fbIIG8p3YQIFqm BmZU16QJINeXEyD5nYBKEcJvGF1LUDgB8scsoGRaK7HNSalTuDS1omknLIXiQP70Ka0/j29JhAmZ Jwuy4VNengoexKj2tQlSBLv8Bsyb7oKDfIYqcO2LFrPGOh0PQs3EObLLbtLm8ONf69cIqGF/IUD2 cF6U6MQ+QNEfHab6zOdA/specEWEfVmQk0S05EGMGY02QPRucNylfE7/9QjBQb5gEwVuyMWUPAiY g3GUfdI58hb9kBbmmDe0ORdQC78kOHpSgRR1iIdwjG3oQsO9wxJEifaWl3SOQWRdlDWIqww1TStb rzUI6ICp22cEkOv5/LAq3B/0Vk8cdOaqi+7e3n8LoSOOhYf0rB8MTqLnt+BmnYU5d/QWIIavYKpN YR8Edf3V8VJLEGkbTGmU5XCUf26wAUJtgKkrmQIIWMBy3L3XeYHer8fdl8ByabkJMDIqdg/vsfB1 aQryM+KFaBMcRLJACxAJChstbI+ctgHydxnygkBLkNxqmCqSMv+PSG30SAtMpWM9AuZsR6Oq+OtU eOwQXnogYnMUM79R179ekfwgjvCRfwAFaq08FW7WfBSyywkQqTJFzpjDgFRf4dWWxYJ8w4SNVr0F iBcKaHTg9Wc8wQrE4IQiiUaFgUwtpJ18RmfqNOgMcaV7t5h2zinpTMq63lly+W7nrrH3H+IjK4iN 8K3ES8QTaphXz4J80fXo0aOudxaj5g4NtJ5H/nmeA8m9htykgwQxoNeVdZyvtM8XQK510HU/qtcg DrqvMJCsBdnzHgftQT4d/mRrt8m0ZAbv/jNWral4MELok9cooEJPOUR8NJCJzDpkY0K82gEcgQDP GhQ5nAgQXzhSveG7JBfYQtoiAQSX9pgeYCDgXyU5I5lWj3uSTHOszJknzCtPk7s33eL7JHwwACoN cmGiRzKvMP5vDZJxwjGIoV6LBnsaBkJdgr/dEuDvOvjTJ9gmiE8IIEDAbvYFT1xpMr2Fqjz4OF+I yHdXR5p28aPrAr0WM6Bxm0Hs3XPRPJxiBSJrhdYwIMf8eNXzPkJvtTWIRNMsgBQgF/dHB7QNcAmr rbUBcvVoCGUBMogBiUuF/WFatKoQnyJnjovo3vSAOdt+uBu2F9NIxFGdM5witSEsSGgNUvtt/1oU EuxHrW/gOEJmy9oFkCboGHKnTKhctLr1N3Ag0R5M5WpNX9RjBAgYCk+u425tRVvaKU8tFi30HrLi PvST9beYyQo2odYLd5I6mOXTyIJsRSZknlGxRRyDSA/L+FZmQFDnaBmDa9BFnwAO5CxTd2Yje6BO gmRN+LJnxJNN6ICrctllAsQ8d9loU3fFA3o+GXYOlVYdRUO4gDeK+bhD26ZjQf4NSDkGAVS9nAAp IPYRSPIjHMi3FnWTIOD8sBGlB7rR1mrK/nkkyPY3/0P31NhbDy9wpWvRqtLvBJukfBFZqBN4NhCQ loeDUC1Ga90x9A4ETBrK9Iepe0lJNglivruQ3rVXVuweyBtxG94c1m886l7DEbWcG8fPBgI82zGQ tOk2QOB7/V6BAP1eBmTR/e1mSxVv3mLaUoGFKVdmOgpb3NGnT91RFHWMbnCd4Qjkv//j9FlrIwkC vEIFEBQ4jEe/5XQWXaPDfe9AgGEzOnKseGzFYS7LqYg4gL8Alday+3ott30yquGe1iEIJn7PzoGo 2rQ8SAv8KQtRcSq4A69U5/YWBEiHbKw0Ray6bA2SPXfbRPLrP12TWotbFpaHOHoL8rElCEi4zYHE o93UYpXwLOQzYV69BgEgeMnKfZ1lNjh2nAOWCriUKMSU6g4Jk/vMIKybXAQU2tvQuzFBVSgCa3QI 5KvegNAoksfz8gmK/LLLO5JsfWVGBXbd8fG4cSOx+mJRApepbP6OlpNF0QLNd6Q09IzjD3808GUU XTDdAvQt8L8f8UUCyspQ9YX/6izqPt8Gc218dDVg/73lhRxL9t3ipzkDgT0ZZjeEhDQkvLAvGqis pJyb9+7du9mZRP2Ev/j7ier/2PE6aEE6GrEAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDMtMDhU MDM6NTI6MTYrMDA6MDC7y1oBAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTAzLTA4VDAzOjUyOjE1 KzAwOjAw+374IAAAAABJRU5ErkJggg== Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/PaxHeader/Parameters.pm000644 000765 000024 00000000210 14374621223 031741 xustar00jhthorsenstaff000000 000000 30 mtime=1676878483.940038125 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/Parameters.pm000644 000765 000024 00000015327 14374621223 030007 0ustar00jhthorsenstaff000000 000000 package Mojolicious::Plugin::OpenAPI::Parameters; use Mojo::Base 'Mojolicious::Plugin'; use JSON::Validator::Util qw(is_bool schema_type); use Mojo::JSON qw(encode_json decode_json); sub register { my ($self, $app, $config) = @_; $app->helper('openapi.build_response_body' => $config->{renderer} || \&_helper_build_response_body); $app->helper('openapi.build_schema_request' => \&_helper_build_schema_request); $app->helper('openapi.build_schema_response' => \&_helper_build_schema_response); $app->helper('openapi.coerce_request_parameters' => \&_helper_coerce_request_parameters); $app->helper('openapi.coerce_response_parameters' => \&_helper_coerce_response_parameters); $app->helper('openapi.parse_request_body' => \&_helper_parse_request_body); } sub _bool { return map { !is_bool($_) ? $_ : $_ ? 'true' : 'false' } @_; } sub _helper_build_response_body { my $c = shift; return $_[0]->slurp if UNIVERSAL::isa($_[0], 'Mojo::Asset'); $c->res->headers->content_type('application/json;charset=UTF-8') unless $c->res->headers->content_type; return encode_json($_[0]); } sub _helper_build_schema_request { my $c = shift; my $req = $c->req; $c->stash->{'openapi.evaluated_request_parameters'} = \my @evaluated; return { body => sub { $evaluated[@evaluated] = $c->openapi->parse_request_body($_[1]); }, formData => sub { my $name = shift; my $value = $req->body_params->every_param($name); my $n = @$value; return $evaluated[@evaluated] = {exists => 1, value => $n > 1 ? $value : $value->[0]} if $n > 0; $value = $req->upload($name); return $evaluated[@evaluated] = {exists => !!$value, value => $value && $value->size}; }, header => sub { my $name = shift; my $value = $req->headers->every_header($name); return $evaluated[@evaluated] = {exists => !!@$value, value => $value}; }, path => sub { my $name = shift; my $stash = $c->match->stack->[-1]; return $evaluated[@evaluated] = {exists => exists $stash->{$name}, value => $stash->{$name}}; }, query => sub { return $evaluated[@evaluated] = {exists => 1, value => $req->url->query->to_hash} unless my $name = shift; my $value = $req->url->query->every_param($name); my $n = @$value; return $evaluated[@evaluated] = {exists => !!$n, value => $n > 1 ? $value : $value->[0]}; }, }; } sub _helper_build_schema_response { my $c = shift; my $res = $c->res; $c->stash->{'openapi.evaluated_response_parameters'} = \my @evaluated; return { body => sub { my $res = $c->stash('openapi'); return $evaluated[@evaluated] = {accept => $c->req->headers->accept, exists => !!$res, value => $res}; }, header => sub { my $name = shift; my $value = $res->headers->every_header($name); return $evaluated[@evaluated] = {exists => !!@$value, value => $value}; }, }; } sub _helper_coerce_request_parameters { my ($c, $evaluated) = @_; my $output = $c->validation->output; my $req = $c->req; for my $i (@$evaluated) { next unless $i->{valid}; $output->{$i->{name}} = $i->{value}; $c->stash(@$i{qw(name value)}) if $i->{in} eq 'path'; $req->headers->header($i->{name}, ref $i->{value} eq 'ARRAY' ? @{$i->{value}} : $i->{value}) if $i->{in} eq 'header'; $req->url->query->merge(@$i{qw(name value)}) if $i->{in} eq 'query'; $req->params->merge(@$i{qw(name value)}) if $i->{in} eq 'query'; $req->params->merge(@$i{qw(name value)}) if $i->{in} eq 'formData'; $req->body_params->merge(@$i{qw(name value)}) if $i->{in} eq 'formData'; } } sub _helper_coerce_response_parameters { my ($c, $evaluated) = @_; my $res = $c->res; for my $i (@$evaluated) { next unless $i->{valid}; $c->stash(openapi_negotiated_content_type => $i->{content_type}) if $i->{in} eq 'body'; $res->headers->header($i->{name}, _bool(ref $i->{value} eq 'ARRAY' ? @{$i->{value}} : $i->{value})) if $i->{in} eq 'header'; } } sub _helper_parse_request_body { my ($c, $param) = @_; my $content_type = $c->req->headers->content_type || ''; my $res = {content_type => $content_type, exists => !!$c->req->body_size}; eval { $res->{value} //= $c->req->body_params->to_hash if grep { $content_type eq $_ } qw(application/x-www-form-urlencoded multipart/form-data); # Trying to use the already parsed json() or fallback to manually decoding the request # since it will make the eval {} fail on invalid json. $res->{value} //= $c->req->json // decode_json $c->req->body; 1; } or do { $res->{value} = $c->req->body; }; return $res; } 1; =encoding utf8 =head1 NAME Mojolicious::Plugin::OpenAPI::Parameters - Methods for transforming data from/to JSON::Validator::Schema =head1 DESCRIPTION L adds helpers to your L application, required by L. These helpers can be redefined in case you have special needs. =head1 HELPERS =head2 openapi.build_response_body $bytes = $c->openapi->build_response_body(Mojo::Asset->new); $bytes = $c->openapi->build_response_body($data); Takes validated data and turns it into bytes that will be used as HTTP response body. This method is useful to override, in case you want to render some other structure than JSON. =head2 openapi.build_schema_request $hash_ref = $c->openapi->build_schema_request; Builds input data for L. =head2 openapi.build_schema_response $hash_ref = $c->openapi->build_schema_response; Builds input data for L. =head2 openapi.coerce_request_parameters $c->openapi->coerce_request_parameters(\@evaluated_parameters); Used by L to write the validated data back to L and L. =head2 openapi.coerce_response_parameters $c->openapi->coerce_response_parameters(\@evaluated_parameters); Used by L to write the validated data to L. =head2 openapi.parse_request_body $hash_ref = $c->openapi->parse_request_body; Returns a structure representing the request body. The default is to parse the input as JSON: {content_type => "application/json", exists => !!$c->req->body_size, value => $c->req->json}; This method is useful to override, in case you want to parse some other structure than JSON. =head1 METHODS =head2 register $self->register($app, \%config); This method will add the L to your L C<$app>. =head1 SEE ALSO L. =cut Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/Guides/PaxHeader/Swagger2.pod000644 000765 000024 00000000152 13742714631 032716 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/Guides/Swagger2.pod000644 000765 000024 00000003366 13742714631 030757 0ustar00jhthorsenstaff000000 000000 =head1 NAME Mojolicious::Plugin::OpenAPI::Guides::Swagger2 - Swagger2 back compat guide =head1 OVERVIEW This guide is useful if your application is already using L. The old plugin used to pass on C<$args> and C<$cb> to the action. This can be emulated using an L hook. The L below contains example code that you can use to make your old controllers and actions work with L. =head1 SYNOPSIS package MyApp; use Mojo::Base "Mojolicious"; sub startup { my $self = shift; # Load your specification $self->plugin("OpenAPI" => {url => $app->home->rel_file("myapi.json")}); $self->hook(around_action => sub { my ($next, $c, $action, $last) = @_; # Do not call the action with ($args, $cb) unless it is an # OpenAPI endpoint. return $next->() unless $last; return $next->() unless $c->openapi->spec; # Render error document unless the input is valid return unless $c->openapi->valid_input; my $cb = sub { my ($c, $data, $code) = @_; $c->render(openapi => $data, status => $code); }; # Call the action with ($args, $cb) # NOTE! $c->validation->output will be removed in the future return $c->$action($c->validation->output, $cb); }); } =head1 MOVING FORWARD Note that the C hook above does not prevent you from writing new actions using the standard L API. In the new actions, you can simply drop using C<$args> and C<$cb> and it will work as expected as well. =head1 SEE ALSO L L. =cut Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/Guides/PaxHeader/OpenAPIv2.pod000644 000765 000024 00000000207 14026520760 032733 xustar00jhthorsenstaff000000 000000 29 mtime=1616552432.63407311 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/Guides/OpenAPIv2.pod000644 000765 000024 00000020462 14026520760 030767 0ustar00jhthorsenstaff000000 000000 =head1 NAME Mojolicious::Plugin::OpenAPI::Guides::OpenAPIv2 - Mojolicious <3 OpenAPI v2 (Swagger) =head1 OVERVIEW This guide will give you an introduction to how to use L with OpenAPI version v2. =head1 TUTORIAL =head2 Specification This plugin reads an L and generate routes and input/output rules from it. See L for L. { "swagger": "2.0", "info": { "version": "1.0", "title": "Some awesome API" }, "basePath": "/api", "paths": { "/pets": { "get": { "operationId": "getPets", "x-mojo-name": "get_pets", "x-mojo-to": "pet#list", "summary": "Finds pets in the system", "parameters": [ {"in": "body", "name": "body", "schema": {"type": "object"}}, {"in": "query", "name": "age", "type": "integer"} ], "responses": { "200": { "description": "Pet response", "schema": { "type": "object", "properties": { "pets": { "type": "array", "items": { "type": "object" } } } } } } } } } } The complete HTTP request for getting the "pet list" will be C The first part of the path ("/api") comes from C, the second part comes from the keys under C, and the HTTP method comes from the keys under C. The different parts of the specification can also be retrieved as JSON using the "OPTIONS" HTTP method. Example: OPTIONS /api/pets OPTIONS /api/pets?method=get Note that the use of "OPTIONS" is EXPERIMENTAL, and subject to change. Here are some more details about the different keys: =over 2 =item * swagger and info These two sections are required to make the specification valid. Check out L for a complete reference to the specification. =item * host, schemes, consumes, produces, security and securityDefinitions These keys are currently not in use. "host" will be replaced by the "Host" header in the request. The rest of the keys are currently not in use. Submit an L if you have ideas on what to use these keys for. =item * basePath The C will also be used to add a route that renders back the specification either as JSON or HTML. Examples: =over 2 =item * http://example.com/api.html Retrieve the expanded version of the API in human readable format. The formatting is currently a bit rough, but should be easier than reading the JSON spec. =item * http://example.com/api.json Retrieve the expanded version of the API, useful for JavaScript clients and other client side applications. =back =item * parameters and responses C and C will be used to define input and output validtion rules, which is used by L and when rendering the response back to the client, using C<< render(openapi => ...) >>. Have a look at L for more details about output rendering. =item * operationId and x-mojo-name See L
    . =item * x-mojo-placeholder C can be used inside a parameter definition to instruct Mojolicious to parse a path part in a certain way. Example: "parameters": [ { "x-mojo-placeholder": "#", "in": "path", "name": "email", "type": "string" } ] See L for more information about "standard", "relaxed" and "wildcard" placeholders. The default is to use the "standard" ("/:foo") placeholder. =item * x-mojo-to The non-standard part in the spec above is "x-mojo-to". The "x-mojo-to" key can be either a plain string, object (hash) or an array. The string and hash will be passed directly to L, while the array ref will be flatten. Examples: "x-mojo-to": "pet#list" $route->to("pet#list"); "x-mojo-to": {"controller": "pet", "action": "list", "foo": 123} $route->to({controller => "pet", action => "list", foo => 123); "x-mojo-to": ["pet#list", {"foo": 123}, ["format": ["json"]]] $route->to("pet#list", {foo => 123}); $route->pattern->constraints->{format} = ["json"]; =back =head2 Application package Myapp; use Mojo::Base "Mojolicious"; sub startup { my $app = shift; $app->plugin("OpenAPI" => {url => $app->home->rel_file("myapi.json")}); } 1; The first thing in your code that you need to do is to load this plugin and the L. See L for information about what the plugin config can be. See also L for example L application. =head2 Controller package Myapp::Controller::Pet; use Mojo::Base "Mojolicious::Controller"; sub list { # Do not continue on invalid input and render a default 400 # error document. my $c = shift->openapi->valid_input or return; # You might want to introspect the specification for the current route my $spec = $c->openapi->spec; unless ($spec->{'x-opening-hour'} == (localtime)[2]) { return $c->render(openapi => [], status => 498); } my $age = $c->param("age"); my $body = $c->req->json; # $output will be validated by the OpenAPI spec before rendered my $output = {pets => [{name => "kit-e-cat"}]}; $c->render(openapi => $output); } 1; The input will be validated using L while the output is validated through then L handler. =head2 Route names Routes will get its name from either L or from L if defined in the specification. The route name can also be used the other way around, to find already defined routes. This is especially useful for L apps. Note that if L then all the route names will have that value as prefix: spec_route_name = "my_cool_api" operationId or x-mojo-name = "Foo" Route name = "my_cool_api.Foo" You can also set "x-mojo-name" in the spec, instead of passing L to L: { "swagger": "2.0", "info": { "version": "1.0", "title": "Some awesome API" }, "x-mojo-name": "my_cool_api" } =head2 Default response schema A default response definition will be added to the API spec, unless it's already defined. This schema will at least be used for invalid input (400 - Bad Request) and invalid output (500 - Internal Server Error), but can also be used in other cases. See L and L for more details on how to configure these settings. The response schema will be added to your spec like this, unless already defined: { ... "definitions": { ... "DefaultResponse": { "type": "object", "required": ["errors"], "properties": { "errors": { "type": "array", "items": { "type": "object", "required": ["message"], "properties": {"message": {"type": "string"}, "path": {"type": "string"}} } } } } } } The "errors" key will contain one element for all the invalid data, and not just the first one. The useful part for a client is mostly the "path", while the "message" is just to add some human readable debug information for why this request/response failed. =head2 Rendering binary data Rendering assets and binary data should be accomplished by using the standard L tools: sub get_image { my $c = shift->openapi->valid_input or return; my $asset = Mojo::Asset::File->new(path => "image.jpeg"); $c->res->headers->content_type("image/jpeg"); $c->reply->asset($asset); } =head1 SEE ALSO L, L. =cut Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/Guides/PaxHeader/OpenAPIv3.pod000644 000765 000024 00000000210 14026520224 032721 xustar00jhthorsenstaff000000 000000 30 mtime=1616552084.174866619 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/lib/Mojolicious/Plugin/OpenAPI/Guides/OpenAPIv3.pod000644 000765 000024 00000024411 14026520224 030761 0ustar00jhthorsenstaff000000 000000 =head1 NAME Mojolicious::Plugin::OpenAPI::Guides::OpenAPIv3 - Mojolicious <3 OpenAPI v3 =head1 OVERVIEW This guide will give you an introduction on how to use L with OpenAPI version v3.x. =head1 TUTORIAL =head2 Specification This plugin reads an L and generates routes and input/output rules from it. See L for L. { "openapi": "3.0.2", "info": { "version": "1.0", "title": "Some awesome API" }, "paths": { "/pets": { "get": { "operationId": "getPets", "x-mojo-name": "get_pets", "x-mojo-to": "pet#list", "summary": "Finds pets in the system", "parameters": [ { "in": "query", "name": "age", "schema": { "type": "integer" } } ], "requestBody": { "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "Pet response", "content": { "application/json": { "schema": { "type": "object", "properties": { "pets": { "type": "array", "items": { "type": "object" } } } } } } } } } } }, "servers": [ { "url": "/api" } ] } The complete HTTP request for getting the "pet list" will be C The first part of the path ("/api") comes from C, the second part comes from the keys under C, and the HTTP method comes from the keys under C. The different parts of the specification can also be retrieved as JSON using the "OPTIONS" HTTP method. Example: OPTIONS /api/pets OPTIONS /api/pets?method=get Note that the use of "OPTIONS" is EXPERIMENTAL, and subject to change. Here are some more details about the different keys: =over 2 =item * openapi, info and paths These three sections are required to make the specification valid. Check out L for a complete reference to the specification. =item * parameters, requestBody and responses C, C and C will be used to define input and output validation rules, which is used by L and when rendering the response back to the client, using C<< render(openapi => ...) >>. Here OpenAPIv3 input differs from the v2 spec, where C is used for input in the path or query of the request. The C is used for input passed in the body. Have a look at L for more details about output rendering. =item * operationId and x-mojo-name See L. =item * x-mojo-placeholder C can be used inside a parameter definition to instruct Mojolicious to parse a path part in a certain way. Example: "parameters": [ { "x-mojo-placeholder": "#", "in": "path", "name": "email", "type": "string" } ] See L for more information about "standard", "relaxed" and "wildcard" placeholders. The default is to use the "standard" ("/:foo") placeholder. =item * x-mojo-to The non-standard part in the spec above is "x-mojo-to". The "x-mojo-to" key can be either a plain string, object (hash) or an array. The string and hash will be passed directly to L, while the array ref will be flattened. Examples: "x-mojo-to": "pet#list" $route->to("pet#list"); "x-mojo-to": {"controller": "pet", "action": "list", "foo": 123} $route->to({controller => "pet", action => "list", foo => 123); "x-mojo-to": ["pet#list", {"foo": 123}, ["format": ["json"]]] $route->to("pet#list", {foo => 123}); $route->pattern->constraints->{format} = ["json"]; =item * security and securitySchemes The securityScheme is added under components, where one way is to have the client place an apiKey in the header of the request { ... "components": { "securitySchemes": { "apiKey": { "name": "X-Api-Key", "in": "header", "type": "apiKey" } } } } It is then referenced under the path object as security like this { ... "paths": { "/pets": { "get": { "operationId": "getPets", ... "security": [ { "apiKey": [] } ] } } } } You can then utilize security, by adding a security callback when loading the plugin $self->plugin( OpenAPI => { spec => $self->static->file("openapi.json")->path, security => { apiKey => sub { my ($c, $definition, $scopes, $cb) = @_; if (my $key = $c->tx->req->content->headers->header('X-Api-Key')) { if (got_valid_api_key()) { return $c->$cb(); } else { return $c->$cb('Api Key not valid'); } } else { return $c->$cb('Api Key header not present'); } } } } ); =back =head3 References with files Only a file reference like "$ref": "my-other-cool-component.json#/components/schemas/inputSchema" Is supported, though a valid path must be used for both the reference and in the referenced file, in order to produce a valid spec output. See L for unsupported file references =head2 Application package Myapp; use Mojo::Base "Mojolicious"; sub startup { my $app = shift; $app->plugin("OpenAPI" => {url => $app->home->rel_file("myapi.json")}); } 1; The first thing in your code that you need to do is to load this plugin and the L. See L for information about what the plugin config can be. See also L for example L application. =head2 Controller package Myapp::Controller::Pet; use Mojo::Base "Mojolicious::Controller"; sub list { # Do not continue on invalid input and render a default 400 # error document. my $c = shift->openapi->valid_input or return; # You might want to introspect the specification for the current route my $spec = $c->openapi->spec; unless ($spec->{'x-opening-hour'} == (localtime)[2]) { return $c->render(openapi => [], status => 498); } my $age = $c->param("age"); my $body = $c->req->json; # $output will be validated by the OpenAPI spec before rendered my $output = {pets => [{name => "kit-e-cat"}]}; $c->render(openapi => $output); } 1; The input will be validated using L while the output is validated through then L handler. =head2 Route names Routes will get its name from either L or from L if defined in the specification. The route name can also be used the other way around, to find already defined routes. This is especially useful for L apps. Note that if L is used then all the route names will have that value as prefix: spec_route_name = "my_cool_api" operationId or x-mojo-name = "Foo" Route name = "my_cool_api.Foo" You can also set "x-mojo-name" in the spec, instead of passing L to L: { "openapi": "3.0.2", "info": { "version": "1.0", "title": "Some awesome API" }, "x-mojo-name": "my_cool_api" } =head2 Default response schema A default response definition will be added to the API spec, unless it's already defined. This schema will at least be used for invalid input (400 - Bad Request) and invalid output (500 - Internal Server Error), but can also be used in other cases. See L and L for more details on how to configure these settings. The response schema will be added to your spec like this, unless already defined: { ... "components": { ... "schemas": { ... "DefaultResponse": { "type": "object", "required": ["errors"], "properties": { "errors": { "type": "array", "items": { "type": "object", "required": ["message"], "properties": {"message": {"type": "string"}, "path": {"type": "string"}} } } } } } } } The "errors" key will contain one element for all the invalid data, and not just the first one. The useful part for a client is mostly the "path", while the "message" is just to add some human readable debug information for why this request/response failed. =head2 Rendering binary data Rendering assets and binary data should be accomplished by using the standard L tools: sub get_image { my $c = shift->openapi->valid_input or return; my $asset = Mojo::Asset::File->new(path => "image.jpeg"); $c->res->headers->content_type("image/jpeg"); $c->reply->asset($asset); } =head1 OpenAPIv2 to OpenAPIv3 conversion Both online and offline tools are available. One example is of this is L =head1 Known issues =head2 File references Relative file references like the following "$ref": "my-cool-component.json#" "$ref": "my-cool-component.json" Will also be placed under '#/definitions/...', again producing a spec output which will not pass validation. =head1 SEE ALSO L, L. =cut Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/basic-mojo-route-names.t000644 000765 000024 00000000207 14120106511 026446 xustar00jhthorsenstaff000000 000000 29 mtime=1631620425.69316639 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/basic-mojo-route-names.t000644 000765 000024 00000006210 14120106511 024475 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; app->routes->namespaces(['MyApp::Controller']); get '/whatever' => sub { die 'Oh noes!' }, 'Whatever'; plugin OpenAPI => {url => 'data://main/lite.json'}; my $t = Test::Mojo->new; my $r = $t->app->routes; ok $r->find('Whatever'), 'Whatever is defined'; { local $TODO = 'This default route name might change in the future'; ok $r->find('my_api.whatever_options'), 'my_api.whatever_options is defined'; } eval { plugin OpenAPI => {url => 'data://main/unique-route.json'} }; like $@, qr{Route name "Whatever" is not unique}, 'unique route names'; eval { plugin OpenAPI => {url => 'data://main/unique-op.json'} }; like $@, qr{operationId "Whatever" is not unique}, 'unique operationId'; $t = Test::Mojo->new(Mojolicious->new); $r = $t->app->routes->namespaces(['MyApp::Controller']); $t->app->plugin(OpenAPI => {spec_route_name => 'my_api', url => 'data://main/full.json'}); ok $r->lookup('my_api'), 'my_api is defined'; $r = $r->lookup('my_api')->parent; ok $r->find('my_api.Whatever'), 'my_api.Whatever is defined'; done_testing; sub define_controller { eval <<'HERE' or die; package MyApp::Controller::Dummy; use Mojo::Base 'Mojolicious::Controller'; sub whatever {} 1; HERE } package main; __DATA__ @@ full.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Test route names" }, "basePath" : "/api", "paths" : { "/whatever" : { "get" : { "operationId" : "Whatever", "x-mojo-to": "dummy#whatever", "responses" : { "200": { "description": "response", "schema": { "type": "object" } } } } }, "/no-endpoint": { "get" : { "operationId" : "NoEndpoint", "responses" : { "200": { "description": "response", "schema": { "type": "object" } } } } } } } @@ lite.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Test route names" }, "basePath" : "/api", "paths" : { "/whatever" : { "get" : { "operationId" : "Whatever", "responses" : { "200": { "description": "response", "schema": { "type": "object" } } } } } } } @@ unique-op.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Test unique operationId" }, "basePath" : "/api", "paths" : { "/r" : { "get" : { "operationId": "Whatever", "responses": { "200": { "description": "response", "schema": { "type": "object" } } } }, "post" : { "operationId": "Whatever", "responses": { "200": { "description": "response", "schema": { "type": "object" } } } } } } } @@ unique-route.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Test unique route names" }, "basePath" : "/api", "paths" : { "/r" : { "get" : { "x-mojo-name": "Whatever", "responses": { "200": { "description": "response", "schema": { "type": "object" } } } }, "post" : { "x-mojo-name": "Whatever", "responses": { "200": { "description": "response", "schema": { "type": "object" } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v3-style-object.t000644 000765 000024 00000000210 14146075215 025130 xustar00jhthorsenstaff000000 000000 30 mtime=1637382797.225411499 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v3-style-object.t000644 000765 000024 00000023536 14146075215 023177 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; get '/pets' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'getPets'; get '/petsBySimpleId/:id' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'getPetsBySimpleId'; get '/petsByExplodedSimpleId/:id' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'getPetsByExplodedSimpleId'; get '/petsByMatrixId#id' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'getPetsByMatrixId'; get '/petsByExplodedMatrixId#id' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'getPetsByExplodedMatrixId'; get '/petsByLabelId#id' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'getPetsByLabelId'; get '/petsByExplodedLabelId#id' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'getPetsByExplodedLabelId'; plugin OpenAPI => {url => 'data:///parameters.json'}; my $t = Test::Mojo->new; subtest 'style: deepObject' => sub { $t->get_ok('/api/pets')->status_is(200)->json_is('/do', undef); $t->get_ok('/api/pets?do[name]=birdy&do[birth-date][gte]=1970-01-01&do[numbers][0]=5') ->status_is(200) ->json_is('/do', {name => 'birdy', 'birth-date' => {gte => '1970-01-01'}, numbers => [5]}); $t->get_ok('/api/pets?do[numbers][0]=5&do[numbers][1]=10')->status_is(200) ->json_is('/do', {numbers => [5, 10]}); $t->get_ok('/api/pets?do[numbers][]=5&do[numbers][]=10')->status_is(200) ->json_is('/do', {numbers => [5, 10]}); $t->get_ok('/api/pets?do[numbers]=5&do[numbers]=10')->status_is(200) ->json_is('/do', {numbers => [5, 10]}); }; subtest 'style: form, explode: false' => sub { $t->get_ok('/api/pets')->status_is(200)->json_is('/ff', undef); $t->get_ok('/api/pets?ff=')->status_is(200)->json_is('/ff', {}); $t->get_ok('/api/pets?ff=name,birdy,age,3')->status_is(200) ->json_is('/ff', {name => 'birdy', age => 3}); }; subtest 'style: form, explode: true' => sub { $t->get_ok('/api/pets')->status_is(200)->json_is('/ft', {}); $t->get_ok('/api/pets?name=birdy&age=3')->status_is(200) ->json_is('/ft', {name => ['birdy'], age => 3}); $t->get_ok('/api/pets?name=birdy&age=3&name=birdy2')->status_is(200) ->json_is('/ft', {name => ['birdy', 'birdy2'], age => 3}); }; subtest 'style: spaceDelimited' => sub { $t->get_ok('/api/pets')->status_is(200)->json_is('/sf', undef); $t->get_ok('/api/pets?sf=')->status_is(200)->json_is('/sf', {}); $t->get_ok('/api/pets?sf=name%20birdy%20age%203')->status_is(200) ->json_is('/sf', {name => 'birdy', age => 3}); }; subtest 'style: pipeDelimited' => sub { $t->get_ok('/api/pets')->status_is(200)->json_is('/pf', undef); $t->get_ok('/api/pets?pf=')->status_is(200)->json_is('/pf', {}); $t->get_ok('/api/pets?pf=name|birdy|age|3')->status_is(200) ->json_is('/pf', {name => 'birdy', age => 3}); }; subtest 'style: simple, explode: false' => sub { $t->get_ok('/api/petsBySimpleId/category,bird,name,birdy')->status_is(200) ->json_is('/id', {category => 'bird', name => 'birdy'}); }; subtest 'style: simple, explode: true' => sub { $t->get_ok('/api/petsByExplodedSimpleId/category=bird,name=birdy')->status_is(200) ->json_is('/id', {category => 'bird', name => 'birdy'}); }; subtest 'style: matrix, explode: false' => sub { $t->get_ok('/api/petsByMatrixId;id=category,bird,name,birdy')->status_is(200) ->json_is('/id', {category => 'bird', name => 'birdy'}); }; subtest 'style: matrix, explode: true' => sub { $t->get_ok('/api/petsByExplodedMatrixId;category=bird;name=birdy')->status_is(200) ->json_is('/id', {category => 'bird', name => 'birdy'}); }; subtest 'style: label, explode: false' => sub { $t->get_ok('/api/petsByLabelId.category.bird.name.birdy')->status_is(200) ->json_is('/id', {category => 'bird', name => 'birdy'}); }; subtest 'style: label, explode: true' => sub { $t->get_ok('/api/petsByExplodedLabelId.category=bird.name=birdy')->status_is(200) ->json_is('/id', {category => 'bird', name => 'birdy'}); }; done_testing; __DATA__ @@ parameters.json { "openapi": "3.0.0", "info": { "license": { "name": "MIT" }, "title": "Swagger Petstore", "version": "1.0.0" }, "servers": [ { "url": "/api" } ], "paths": { "/pets": { "get": { "operationId": "getPets", "parameters": [ { "name": "do", "in": "query", "style": "deepObject", "explode": true, "schema": { "type": "object" } }, { "name": "ff", "in": "query", "style": "form", "explode": false, "schema": { "type": "object" } }, { "name": "ft", "in": "query", "style": "form", "explode": true, "schema": { "type": "object", "properties": { "name": {"type": "array", "items": {"type": "string"}} } } }, { "name": "sf", "in": "query", "style": "spaceDelimited", "explode": false, "schema": { "type": "object" } }, { "name": "pf", "in": "query", "style": "pipeDelimited", "explode": false, "schema": { "type": "object" } } ], "responses": { "200": { "description": "pet response", "content": { "*/*": { "schema": { "type": "object" } } } } } } }, "/petsBySimpleId/{id}": { "get": { "operationId": "getPetsBySimpleId", "parameters": [ { "name": "id", "in": "path", "required": true, "style": "simple", "explode": false, "schema": { "type": "object" } } ], "responses": { "200": { "description": "pet response", "content": { "*/*": { "schema": { "type": "object" } } } } } } }, "/petsByExplodedSimpleId/{id}": { "get": { "operationId": "getPetsByExplodedSimpleId", "parameters": [ { "name": "id", "in": "path", "required": true, "style": "simple", "explode": true, "schema": { "type": "object" } } ], "responses": { "200": { "description": "pet response", "content": { "*/*": { "schema": { "type": "object" } } } } } } }, "/petsByMatrixId/{id}": { "get": { "operationId": "getPetsByMatrixId", "parameters": [ { "name": "id", "in": "path", "required": true, "style": "matrix", "explode": false, "schema": { "type": "object" } } ], "responses": { "200": { "description": "pet response", "content": { "*/*": { "schema": { "type": "object" } } } } } } }, "/petsByExplodedMatrixId/{id}": { "get": { "operationId": "getPetsByExplodedMatrixId", "parameters": [ { "name": "id", "in": "path", "required": true, "style": "matrix", "explode": true, "schema": { "type": "object" } } ], "responses": { "200": { "description": "pet response", "content": { "*/*": { "schema": { "type": "object" } } } } } } }, "/petsByLabelId/{id}": { "get": { "operationId": "getPetsByLabelId", "parameters": [ { "name": "id", "in": "path", "required": true, "style": "label", "explode": false, "schema": { "type": "object" } } ], "responses": { "200": { "description": "pet response", "content": { "*/*": { "schema": { "type": "object" } } } } } } }, "/petsByExplodedLabelId/{id}": { "get": { "operationId": "getPetsByExplodedLabelId", "parameters": [ { "name": "id", "in": "path", "required": true, "style": "label", "explode": true, "schema": { "type": "object" } } ], "responses": { "200": { "description": "pet response", "content": { "*/*": { "schema": { "type": "object" } } } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v3-nullable.t000644 000765 000024 00000000210 14013056427 024317 xustar00jhthorsenstaff000000 000000 30 mtime=1613520151.422170931 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v3-nullable.t000644 000765 000024 00000004200 14013056427 022351 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; my %data = (id => 42); get '/nullable-data' => \&action_null, 'withNullable'; get '/nullable-ref' => \&action_null, 'withNullableRef'; plugin OpenAPI => {url => 'data:///nullable.json'}; my $t = Test::Mojo->new; $t->get_ok('/v1/nullable-data')->status_is(500); $data{name} = undef; $t->get_ok('/v1/nullable-data')->status_is(200); $data{name} = 'batgirl'; $t->get_ok('/v1/nullable-data')->status_is(200); $t->get_ok('/v1/nullable-ref')->status_is(200); done_testing; sub action_null { my $c = shift->openapi->valid_input or return; $c->render(openapi => \%data); } __DATA__ @@ nullable.json { "openapi": "3.0.0", "info": { "license": { "name": "MIT" }, "title": "Swagger Petstore", "version": "1.0.0" }, "servers": [ { "url": "http://petstore.swagger.io/v1" } ], "paths": { "/nullable-data": { "get": { "operationId": "withNullable", "summary": "Dummy", "responses": { "200": { "description": "type:[null, string, ...] does the same", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WithNullable" } } } } } } }, "/nullable-ref": { "get": { "operationId": "withNullableRef", "summary": "Dummy", "responses": { "200": { "description": "type:[null, string, ...] does the same", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WithNullableRef" } } } } } } } }, "components": { "schemas": { "WithNullable": { "required": [ "id", "name" ], "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string", "nullable": true } } }, "WithNullableRef": { "properties": { "name": { "$ref": "#/components/schemas/WithNullable/properties/name" } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/plugin-security-v3.t000644 000765 000024 00000000210 14125731256 025670 xustar00jhthorsenstaff000000 000000 30 mtime=1633137326.287367582 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/plugin-security-v3.t000644 000765 000024 00000031353 14125731256 023733 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; post '/global' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 1}); }, 'global'; post('/fail_escape' => sub { shift->render(openapi => {ok => 1}) }, 'fail_escape'); post '/simple' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 1}); }, 'simple'; options '/options' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 1}); }, 'options'; post '/fail_or_pass' => sub { my $c = shift->openapi->valid_input or return; die 'Could not connect to dummy database error message' if $ENV{DUMMY_DB_ERROR}; $c->render(openapi => {ok => 1}); }, 'fail_or_pass'; post '/fail_and_pass' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 1}); }, 'fail_and_pass'; post '/multiple_fail' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 1}); }, 'multiple_fail'; post '/multiple_and_fail' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 1}); }, 'multiple_and_fail'; post '/cache' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 1}); }, 'cache'; post '/die' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 1}); }, 'die'; our %checks; plugin OpenAPI => { url => 'data://main/sec.json', security => { pass1 => sub { my ($c, $def, $scopes, $cb) = @_; $checks{pass1}++; $c->$cb; }, pass2 => sub { my ($c, $def, $scopes, $cb) = @_; $checks{pass2}++; $c->$cb; }, fail1 => sub { my ($c, $def, $scopes, $cb) = @_; $checks{fail1}++; # This deferment causes multiple_and_fail to report # out of order unless order is carefully maintained Mojo::IOLoop->next_tick(sub { $c->$cb('Failed fail1') }); }, fail2 => sub { my ($c, $def, $scopes, $cb) = @_; $checks{fail2}++; my %res = %$def; $res{message} = 'Failed fail2'; $c->$cb(\%res); }, '~fail/escape' => sub { my ($c, $def, $scopes, $cb) = @_; $checks{'~fail/escape'}++; $c->$cb('Failed ~fail/escape'); }, die => sub { my ($c, $def, $scopes, $cb) = @_; $checks{die}++; die 'Argh!'; }, }, }; my %security_definition = (description => 'fail2', in => 'header', name => 'Authorization', type => 'apiKey'); my $t = Test::Mojo->new; subtest 'post global' => sub { local %checks; $t->post_ok('/api/global' => json => {})->status_is(200)->json_is('/ok' => 1); is_deeply \%checks, {pass1 => 1}, 'expected checks occurred'; }; subtest 'options global' => sub { # global does not define an options handler, so it gets the default # which is allowed through the security local %checks; $t->options_ok('/api/global')->status_is(200); is_deeply \%checks, {}, 'expected checks occurred'; }; subtest 'post simple' => sub { local %checks; $t->post_ok('/api/simple' => json => {})->status_is(200)->json_is('/ok' => 1); is_deeply \%checks, {pass2 => 1}, 'expected checks occurred'; }; subtest 'options options' => sub { # route defined with an options handler so it must use the defined security local %checks; $t->options_ok('/api/options' => json => {})->status_is(200)->json_is('/ok' => 1); is_deeply \%checks, {pass1 => 1}, 'expected checks occurred'; }; subtest 'post fail_or_pass' => sub { local %checks; $t->post_ok('/api/fail_or_pass' => json => {})->status_is(200)->json_is('/ok' => 1); is_deeply \%checks, {fail1 => 1, pass1 => 1}, 'expected checks occurred'; }; subtest 'post fail_or_pass - env' => sub { local $ENV{DUMMY_DB_ERROR} = 1; $t->post_ok('/api/fail_or_pass' => json => {})->status_is(500) ->json_is('/errors/0/message', 'Internal Server Error.')->json_is('/errors/0/path', '/'); }; subtest 'post fail_and_pass' => sub { local %checks; $t->post_ok('/api/fail_and_pass' => json => {})->status_is(401) ->json_is( {errors => [{message => 'Failed fail1', path => '/security/0/fail1'}], status => 401}); is_deeply \%checks, {fail1 => 1, pass1 => 1}, 'expected checks occurred'; }; subtest 'post multiple_fail' => sub { local %checks; $t->post_ok('/api/multiple_fail' => json => {})->status_is(401)->json_is({ status => 401, errors => [ {message => 'Failed fail1', path => '/security/0/fail1'}, {message => 'Failed fail2', %security_definition}, ] }); is_deeply \%checks, {fail1 => 1, fail2 => 1}, 'expected checks occurred'; }; subtest 'post multiple_and_fail' => sub { local %checks; $t->post_ok('/api/multiple_and_fail' => json => {})->status_is(401)->json_is({ status => 401, errors => [ {message => 'Failed fail1', path => '/security/0/fail1'}, {message => 'Failed fail2', %security_definition} ] }); is_deeply \%checks, {fail1 => 1, fail2 => 1}, 'expected checks occurred'; }; subtest 'post fail_escape' => sub { local %checks; $t->post_ok('/api/fail_escape' => json => {})->status_is(401)->json_is({ errors => [{message => 'Failed ~fail/escape', path => '/security/0/~0fail~1escape'}], status => 401 }); is_deeply \%checks, {'~fail/escape' => 1}, 'expected checks occurred'; }; subtest 'post cache' => sub { local %checks; $t->post_ok('/api/cache' => json => {})->status_is(200)->json_is('/ok' => 1); is_deeply \%checks, {fail1 => 1, pass1 => 1, pass2 => 1}, 'expected checks occurred'; }; subtest 'post die' => sub { local %checks; $t->post_ok('/api/die' => json => {})->status_is(500)->json_has('/errors/0/message'); is_deeply \%checks, {die => 1}, 'expected checks occurred'; }; done_testing; __DATA__ @@ sec.json { "openapi": "3.0.0", "info": { "version": "0.8", "title": "Pets" }, "servers": [ { "url": "http://petstore.swagger.io/api" } ], "components": { "responses": { "defaultResponse": { "description": "default response", "content": { "application/json": { "schema": { "type": "object", "properties": { "errors": { "type": "array", "items": { "type": "object", "properties": { "message": { "type": "string" }, "path": { "type": "string" } }, "required": ["message"] } } }, "required": ["errors"] } } } } }, "securitySchemes": { "pass1": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "pass1" }, "pass2": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "pass2" }, "fail1": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "fail1" }, "fail2": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "fail2" }, "~fail/escape": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "dummy" }, "die": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "die" } } }, "security": [{"pass1": []}], "paths": { "/global": { "post": { "x-mojo-name": "global", "requestBody": { "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": {"description": "Echo response", "content": { "application/json": { "schema": { "type": "object" } } }} } } }, "/simple": { "post": { "x-mojo-name": "simple", "security": [{"pass2": []}], "requestBody": { "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": {"description": "Echo response", "content": { "application/json": { "schema": { "type": "object" } } }} } } }, "/options": { "options": { "x-mojo-name": "options", "security": [{"pass1": []}], "requestBody": { "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": {"description": "Echo response", "content": { "application/json": { "schema": { "type": "object" } } }} } } }, "/fail_or_pass": { "post": { "x-mojo-name": "fail_or_pass", "security": [ {"fail1": []}, {"pass1": []} ], "requestBody": { "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": {"description": "Echo response", "content": { "application/json": { "schema": { "type": "object" } } }} } } }, "/fail_and_pass": { "post": { "x-mojo-name": "fail_and_pass", "security": [ { "fail1": [], "pass1": [] } ], "requestBody": { "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": {"description": "Echo response", "content": { "application/json": { "schema": { "type": "object" } } }} } } }, "/multiple_fail": { "post": { "x-mojo-name": "multiple_fail", "security": [ { "fail1": [] }, { "fail2": [] } ], "requestBody": { "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": {"description": "Echo response", "content": { "application/json": { "schema": { "type": "object" } } }} } } }, "/multiple_and_fail": { "post": { "x-mojo-name": "multiple_and_fail", "security": [ { "fail1": [], "fail2": [] } ], "requestBody": { "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": {"description": "Echo response", "content": { "application/json": { "schema": { "type": "object" } } }} } } }, "/fail_escape": { "post": { "x-mojo-name": "fail_escape", "security": [{"~fail/escape": []}], "requestBody": { "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": {"description": "Echo response", "content": { "application/json": { "schema": { "type": "object" } } }} } } }, "/cache": { "post": { "x-mojo-name": "cache", "security": [ { "fail1": [], "pass1": [] }, { "pass1": [], "pass2": [] } ], "requestBody": { "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": {"description": "Echo response", "content": { "application/json": { "schema": { "type": "object" } } }} } } }, "/die": { "post": { "x-mojo-name": "die", "security": [ {"die": []}, {"pass1": []} ], "requestBody": { "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": {"description": "Echo response", "content": { "application/json": { "schema": { "type": "object" } } }} } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v2-id-prop.t000644 000765 000024 00000000210 14013056427 024072 xustar00jhthorsenstaff000000 000000 30 mtime=1613520151.418442697 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v2-id-prop.t000644 000765 000024 00000002262 14013056427 022132 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; my $id = 'foo'; get '/user' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {email => 'jhthorsen@cpan.org', id => $id}); }, 'getUser'; plugin OpenAPI => {url => 'data://main/schema.json'}; my $t = Test::Mojo->new; $t->get_ok('/api/user')->status_is(500) ->json_is('/errors/0', {message => 'Expected integer - got string.', path => '/body/id'}); $id = 42; $t->get_ok('/api/user')->status_is(200)->json_is('/email', 'jhthorsen@cpan.org') ->json_is('/id', 42); done_testing; __DATA__ @@ schema.json { "swagger": "2.0", "info": { "version": "0.8", "title": "Test" }, "basePath": "/api", "paths": { "/user": { "get": { "operationId": "getUser", "responses": { "200": { "description": "ok", "examples": { "application/json": {"id": "42"} }, "schema": { "type": "object", "properties": { "email": {"type": "string"}, "id": {"type": "integer"} } } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v2-headers.t000644 000765 000024 00000000210 14277145725 024147 xustar00jhthorsenstaff000000 000000 30 mtime=1660734421.481588065 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v2-headers.t000644 000765 000024 00000004661 14277145725 022214 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; my $what_ever; get '/headers' => sub { my $c = shift->openapi->valid_input or return; my $args = $c->validation->output; $c->res->headers->header('what-ever' => ref $what_ever ? @$what_ever : $what_ever); $c->res->headers->header('x-bool' => $args->{'x-bool'}) if exists $args->{'x-bool'}; $c->render(openapi => $args); }, 'dummy'; plugin OpenAPI => {url => 'data://main/headers.json'}; my $t = Test::Mojo->new; $t->get_ok('/api/headers' => {'x-number' => 'x', 'x-string' => '123'})->status_is(400) ->json_is('/errors/0', {'path' => '/x-number', 'message' => 'Expected number - got string.'}); $what_ever = '123'; $t->get_ok('/api/headers' => {'x-number' => 42.3, 'x-string' => '123'})->status_is(200) ->json_is('/x-number', 42.3)->header_is('what-ever', '123'); $what_ever = [qw(1 2 3)]; $t->get_ok('/api/headers' => {'x-array' => '42,24'})->status_is(200)->json_is('/x-array', [42, 24]) ->header_is('what-ever', '1, 2, 3'); for my $bool (qw(true false 1 0)) { my $s = $bool =~ /true|1/ ? 'true' : 'false'; $what_ever = '123'; $t->get_ok('/api/headers' => {'x-bool' => $bool})->status_is(200)->content_like(qr{"x-bool":$s}) ->header_is('x-bool', $s); } done_testing; __DATA__ @@ headers.json { "swagger" : "2.0", "info" : { "version": "9.1", "title" : "Test API for body parameters" }, "consumes" : [ "application/json" ], "produces" : [ "application/json" ], "schemes" : [ "http" ], "basePath" : "/api", "paths" : { "/headers" : { "get" : { "x-mojo-name": "dummy", "parameters" : [ { "in": "header", "name": "x-bool", "type": "boolean", "description": "desc..." }, { "in": "header", "name": "x-number", "type": "number", "description": "desc..." }, { "in": "header", "name": "x-string", "type": "string", "description": "desc..." }, { "in": "header", "name": "x-array", "items": { "type": "string" }, "type": "array", "description": "desc..." } ], "responses" : { "200" : { "description": "this is required", "headers": { "x-bool": { "type": "boolean" }, "what-ever": { "type": "array", "items": { "type": "string" }, "minItems": 1 } }, "schema": { "type" : "object" } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v3-bundle.t000644 000765 000024 00000000210 14125731256 023776 xustar00jhthorsenstaff000000 000000 30 mtime=1633137326.288279402 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v3-bundle.t000644 000765 000024 00000010006 14125731256 022031 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Mojo::File 'path'; use Test::Mojo; use Test::More; use Mojolicious::Lite; get '/pets' => sub { my $c = shift->openapi->valid_input or return; $c->res->headers->header('x-next' => $c->param('limit') // 0); $c->render(openapi => $c->param('limit') ? [] : {}); }, 'listPets'; plugin OpenAPI => {url => 'data:///spec.json'}; my $t = Test::Mojo->new; $t->get_ok('/v1.json')->status_is(200)->json_is('/openapi' => '3.0.0') ->json_is('/info/title' => 'Swagger Petstore')->json_like('/servers/0/url' => qr{^http://.*/v1$}) ->json_is('/security/0/pass1', [])->json_is('/components/securitySchemes/apiKey/type' => 'http') ->json_is('/components/schemas/DefaultResponse/properties/errors/items/properties/message/type', 'string')->json_is('/components/schemas/Pet/required/0', 'id') ->json_is('/components/schemas/Pets/type', 'array') ->json_is('/paths/~1pets~1{petId}/get/parameters/0/schema/type', 'string') ->json_is('/paths/~1pets~1{petId}/get/responses/500/content/application~1json/schema/$ref', '#/components/schemas/DefaultResponse') ->json_hasnt('/paths/~1pets~1{petId}/get/parameters/0/type')->json_hasnt('/basePath'); done_testing; __DATA__ @@ spec.json { "openapi": "3.0.0", "info": { "license": { "name": "MIT" }, "title": "Swagger Petstore", "version": "1.0.0" }, "servers": [ { "url": "http://petstore.swagger.io/v1" } ], "security": [{"pass1": []}], "paths": { "/pets/{petId}": { "get": { "operationId": "showPetById", "tags": [ "pets" ], "summary": "Info for a specific pet", "parameters": [ { "description": "The id of the pet to retrieve", "in": "path", "name": "petId", "required": true, "schema": { "type": "string" } }, { "description": "Indicates if the age is wanted in the response object", "in": "query", "name": "wantAge", "schema": { "type": "boolean" } } ], "responses": { "200": { "description": "Expected response to a valid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } }, "application/xml": { "schema": { "$ref": "#/components/schemas/Pet" } } } } } } }, "/pets": { "get": { "operationId": "listPets", "summary": "List all pets", "tags": [ "pets" ], "parameters": [ { "description": "How many items to return at one time (max 100)", "in": "query", "name": "limit", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "An paged array of pets", "headers": { "x-next": { "schema": { "type": "string" }, "description": "A link to the next page of responses" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pets" } }, "application/xml": { "schema": { "$ref": "#/components/schemas/Pets" } } } } } } } }, "components": { "securitySchemes": { "apiKey": { "type": "http", "scheme": "basic" } }, "schemas": { "Pets": { "type": "array", "items": { "$ref": "#/components/schemas/Pet" } }, "Pet": { "required": [ "id", "name" ], "properties": { "tag": { "type": "string" }, "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" }, "age": { "type": "integer" } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/basic-path-parameters.t000644 000765 000024 00000000152 13736770604 026367 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/basic-path-parameters.t000644 000765 000024 00000002126 13736770604 024421 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; post '/user/:id' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {id => $c->param('id')}); }, 'user'; plugin OpenAPI => {url => "data://main/path-parameters.json"}; my $t = Test::Mojo->new; $t->post_ok('/api/user/foo' => json => {})->status_is(400); $t->post_ok('/api/user/42a' => json => {})->status_is(400); $t->post_ok('/api/user/42' => json => {})->status_is(200)->json_is('/id', 42); done_testing; __DATA__ @@ path-parameters.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Path parameters" }, "schemes" : [ "http" ], "basePath" : "/api", "paths" : { "/user/{id}" : { "parameters" : [ { "in": "path", "name": "id", "type": "integer", "required": true } ], "post" : { "x-mojo-name" : "user", "responses" : { "200": { "description": "User response", "schema": { "type": "object" } }, "400": { "description": "Invalid input", "schema": { "type": "object" } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/basic-bundle.t000644 000765 000024 00000000210 14013056427 024523 xustar00jhthorsenstaff000000 000000 30 mtime=1613520151.414994418 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/basic-bundle.t000644 000765 000024 00000001326 14013056427 022563 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Mojo::File 'path'; use Test::More; use JSON::Validator::Schema::OpenAPIv2; # This test mimics what Mojolicious::Plugin::OpenAPI does when loading # a spec from a file that Mojolicious locates with a '..' # It checks that a $ref to something that's under /responses doesn't # get picked as remote, or if so that it doesn't make an invalid spec! my $validator = JSON::Validator::Schema::OpenAPIv2->new; my $bundlecheck_path = path(path(__FILE__)->dirname, 'spec', File::Spec->updir, 'spec', 'bundlecheck.json'); my $bundled = $validator->data($bundlecheck_path)->bundle->data; eval { JSON::Validator->new->load_and_validate_schema($bundled) }; is $@, '', 'bundled schema is valid'; done_testing; Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/basic-mojo-placeholder.t000644 000765 000024 00000000207 14124037631 026503 xustar00jhthorsenstaff000000 000000 29 mtime=1632649113.45213411 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/basic-mojo-placeholder.t000644 000765 000024 00000003417 14124037631 024540 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Mojo::Util 'monkey_patch'; use Test::Mojo; use Test::More; my $t = Test::Mojo->new(make_app()); monkey_patch 'Myapp::Controller::Pet' => one => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {username => $c->stash('username')}); }; $t->app->plugin(OpenAPI => {url => 'data://main/echo.json'}); $t->get_ok('/api/jhthorsen@cpan.org')->status_is(200)->json_is('/username' => 'jhthorsen@cpan.org'); $t->options_ok('/api/jhthorsen@cpan.org?method=get')->status_is(200) ->json_is('/parameters/0/x-mojo-placeholder' => '#')->json_is('/parameters/0/in' => 'path') ->json_is('/parameters/0/name' => 'username')->json_is('/parameters/1/in' => 'query') ->json_is('/parameters/1/name' => 'fields')->json_hasnt('/x-all-parameters'); # make sure rendering doesn't croak when "parameters" are under a path # Not a HASH reference at template mojolicious/plugin/openapi/resource.html.ep $t->get_ok('/api.html')->status_is(200); done_testing; sub make_app { eval <<"HERE"; package Myapp; use Mojo::Base 'Mojolicious'; sub startup { } 1; package Myapp::Controller::Pet; use Mojo::Base 'Mojolicious::Controller'; 1; HERE return Myapp->new; } __DATA__ @@ echo.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Pets" }, "schemes" : [ "http" ], "basePath" : "/api", "paths" : { "/{username}" : { "parameters": [ { "x-mojo-placeholder": "#", "in": "path", "name": "username", "required": true, "type": "string" } ], "get" : { "x-mojo-to" : "pet#one", "parameters" : [ { "in": "query", "name": "fields", "type": "string" } ], "responses" : { "200": { "description": "Echo response", "schema": { "type": "object" } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/plugin-spec-renderer-doc.t000644 000765 000024 00000000210 14126206205 026764 xustar00jhthorsenstaff000000 000000 30 mtime=1633225861.031040503 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/plugin-spec-renderer-doc.t000644 000765 000024 00000006117 14126206205 025027 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious; sub VERSION {1.42} { my $app = Mojolicious->new; my $under = $app->routes->under('/my-api' => sub {1}); add_routes($app, 'cool_spec_path'); $app->plugin( OpenAPI => {route => $under, url => 'data://main/reply.json', version_from_class => 'main'}); my $t = Test::Mojo->new($app); $t->get_ok('/url')->status_is(200)->content_is('/my-api'); $t->get_ok('/my-api')->status_is(200)->json_is('/basePath', '/my-api') ->json_unlike('/host', qr{api\.thorsen\.pm})->json_like('/host', qr{.:\d+$}) ->json_is('/info/version', 1.42); } { my $app = Mojolicious->new; add_routes($app, 'my.cool.api'); $app->plugin( OpenAPI => { spec_route_name => 'my.cool.api', url => 'data://main/reply.json', version_from_class => 'main' } ); my $t = Test::Mojo->new($app); $t->get_ok('/url')->status_is(200)->content_is('/api'); $t->get_ok('/api')->status_is(200)->json_is('/info/version', 1.42); $t->get_ok('/api.html')->status_is(200)->text_is('title', 'Test reply spec') ->text_is('h1#title', 'Test reply spec')->text_is('h3#op-post--pets a', 'addPet'); $t->get_ok('/api/docs')->status_is(200)->json_is('/info/version', 1.42) ->json_is('/basePath', '/api'); $t->get_ok('/api/docs.html')->status_is(200)->text_is('h3#op-post--pets a', 'addPet') ->text_like('style', qr{font-family:}s) ->text_like('script', qr{SpecRenderer\.prototype\.jsonhtmlify}s) ->content_like(qr{new SpecRenderer\(\).setup\(\)}); SKIP: { skip 'Text::Markdown is not installed', 2 unless eval 'require Text::Markdown;1'; $t->text_is('div.spec-description p', 'pet response') ->text_is('div.spec-description code', 'markdown'); } } sub add_routes { my ($app, $name) = @_; $app->routes->get('/url' => sub { $_[0]->render(text => $_[0]->url_for($name)) }); $app->routes->get( '/docs', [format => [qw(html json)]], {format => undef}, sub { shift->openapi->render_spec } )->name('docs'); $app->routes->post('/pets', sub { shift->render(openapi => {}) })->name('addPet'); return $app; } done_testing; __DATA__ @@ reply.json { "swagger": "2.0", "info": { "version": "0", "title": "Test reply spec" }, "consumes": [ "application/json" ], "produces": [ "application/json" ], "x-mojo-name": "cool_spec_path", "schemes": [ "http" ], "host": "api.thorsen.pm", "basePath": "/api", "paths": { "/docs": { "get": { "operationId": "docs", "responses": { "200": { "description": "pet response\n\nwith `markdown` content", "schema": { "type": "object" } } } } }, "/pets": { "$ref": "#/x-path/Pets" } }, "x-path": { "Pets": { "post": { "operationId": "addPet", "parameters": [ { "in": "body", "name": "body", "schema": { "type": "object" } } ], "responses": { "200": { "description": "pet response", "schema": { "type": "object" } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v2-validate-schema.t000644 000765 000024 00000000210 14125731256 025553 xustar00jhthorsenstaff000000 000000 30 mtime=1633137326.287958032 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v2-validate-schema.t000644 000765 000024 00000002607 14125731256 023616 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::More; use Mojolicious::Lite; eval { plugin OpenAPI => {url => 'data://main/invalid.json'} }; like $@, qr{/info: Missing property}si, 'missing spec elements'; eval { plugin OpenAPI => {url => 'data://main/swagger2/issues/89.json'} }; like $@, qr{/definitions/\$ref}si, 'ref in the wrong place'; eval { plugin OpenAPI => {skip_validating_specification => 1, url => 'data://main/invalid.json'} }; ok !$@, 'skip_validating_specification=1' or diag $@; done_testing; __DATA__ @@ invalid.json { "swagger" : "2.0", "paths" : {} } @@ swagger2/issues/89.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Test auto response" }, "paths" : { "$ref": "#/x-def/paths" }, "definitions": { "$ref": "#/x-def/defs" }, "x-responses": { "with_ref": { "post": {"$ref": "#/x-responses/with_get_ref"} }, "with_get_ref": { "responses": { "201": { "description": "response", "schema": { "type": "object" } } } } }, "x-def": { "defs": { "foo": { "properties": {} } }, "paths": { "/with-ref": {"$ref": "#/x-responses/with_ref"}, "/with-get-ref": { "get": {"$ref": "#/x-responses/with_get_ref"} }, "/auto" : { "post" : { "responses" : { "200": { "description": "response", "schema": { "type": "object" } } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/basic-correct-order-of-paths.t000644 000765 000024 00000000152 13736770604 027563 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/basic-correct-order-of-paths.t000644 000765 000024 00000002346 13736770604 025621 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojo::Util 'encode'; use Mojolicious::Lite; post '/decode' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {decode => 1}); }, 'decode'; post '/:id' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {id => $c->param('id')}); }, 'id'; plugin OpenAPI => {url => "data://main/correct-order.json"}; my $t = Test::Mojo->new; $t->post_ok('/api/foo')->status_is(200)->json_is('/id', 'foo')->content_like(qr{id}); $t->post_ok('/api/decode')->status_is(200)->json_is('/decode', 1)->content_like(qr{decode}); done_testing; __DATA__ @@ correct-order.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "File" }, "schemes" : [ "http" ], "basePath" : "/api", "paths" : { "/decode": { "post": { "x-mojo-name": "decode", "responses": { "200": { "description": "Success" } } } }, "/{id}": { "post": { "x-mojo-name": "id", "parameters": [ { "name": "id", "in": "path", "required": true, "type": "string" } ], "responses": { "200": { "description": "Success" } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/plugin-spec-renderer-v3.t000644 000765 000024 00000000210 14013056427 026553 xustar00jhthorsenstaff000000 000000 30 mtime=1613520151.417698959 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/plugin-spec-renderer-v3.t000644 000765 000024 00000000740 14013056427 024612 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Mojo::File 'path'; use Test::Mojo; use Test::More; use Mojolicious::Lite; plugin OpenAPI => {url => path(__FILE__)->dirname->child(qw(spec v3-petstore.json))}; my $t = Test::Mojo->new; $t->get_ok('/v1')->status_is(200)->json_like('/servers/0/url', qr{:\d+/v1$}); $t->get_ok('/v1.json')->status_is(200)->json_like('/servers/0/url', qr{:\d+/v1$}); $t->get_ok('/v1.html')->status_is(200)->element_exists('ul.unstyled li a[href$="/v1"]'); done_testing; Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v2-defaults.t000644 000765 000024 00000000210 14026516210 024322 xustar00jhthorsenstaff000000 000000 30 mtime=1616551048.827176316 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v2-defaults.t000644 000765 000024 00000012761 14026516210 022367 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; package Test::Controller::Echo; use Mojo::Base 'Mojolicious::Controller'; sub any { my $c = shift->openapi->valid_input or return; my $name = $c->stash('name') ? {param => $c->param('name'), stash => $c->stash('name')} : {controller => $c->param('name'), form => $c->req->body_params->param('name')}; $c->render( openapi => { days => {controller => $c->param('days'), url => $c->req->query_params->param('days')}, format => $c->stash('format'), name => $name, route => { bar => $c->stash('bar'), constraints => $c->match->endpoint->pattern->constraints, foo => $c->stash('foo'), namespace => $c->stash('namespace') }, x_foo => {header => $c->req->headers->header('X-Foo')}, validation => $c->validation->output, } ); } package main; use Mojolicious::Lite; get '/echo' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {bool => $c->param('bool')}); }, 'echo'; get '/echo/:whatever' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {this_stack => $c->match->stack->[-1], whatever => $c->param('whatever')}); }, 'whatever'; get '/param-has-ref' => sub { my $c = shift->openapi->valid_input or return; my $params = $c->validation->output; $c->render(status => 200, openapi => $params->{pcversion}); }, 'ParamsHasRef'; plugin OpenAPI => {url => 'data://main/def.json'}; my $t = Test::Mojo->new; $t->get_ok('/api/echo?bool=false')->status_is(200)->json_is('/bool' => Mojo::JSON->false); $t->get_ok('/api/echo?bool=true')->status_is(200)->json_is('/bool' => Mojo::JSON->true); $t->get_ok('/api/echo')->status_is(200)->json_is('/bool' => Mojo::JSON->true); $t->get_ok('/api/echo/something')->status_is(200)->json_is('/this_stack/whatever' => 'something') ->json_is('/whatever' => 'something'); $t->get_ok('/api/param-has-ref?x=42')->status_is(200)->content_is('"10.1.0"'); $t->post_ok('/api/echo-controller.js')->status_is(200) ->json_is('/days' => {controller => 42, url => 42})->json_is('/format', 'js') ->json_is('/name', {controller => 'batman', form => 'batman'})->json_is( '/route', { bar => 42, constraints => {format => [qw(js txt)]}, foo => [42], namespace => 'Test::Controller' } )->json_is('/x_foo', {header => 'yikes'}) ->json_is('/validation', {days => 42, name => 'batman', 'X-Foo' => 'yikes', enumParam => '10.1.0'}); $t->get_ok('/api/echo-controller/batman')->status_is(200) ->json_is('/days' => {controller => 42, url => 42}) ->json_is('/name', {param => 'batman', stash => 'batman'}); ok !$t->tx->res->json->{x_foo}{header}, 'x_foo header is not set'; done_testing; __DATA__ @@ def.json { "swagger": "2.0", "info": { "version": "0.8", "title": "Pets" }, "schemes": [ "http" ], "basePath": "/api", "parameters": { "PCVersion": { "name": "pcversion", "in": "query", "type": "string", "enum": [ "9.6.1", "10.1.0" ], "default": "10.1.0", "description": "version of commands which will run on backend" } }, "paths": { "/echo/{whatever}": { "get": { "x-mojo-name": "whatever", "parameters": [ { "in": "path", "name": "whatever", "type": "string", "required": true } ], "responses": { "200": { "description": "Echo response", "schema": { "type": "object" } } } } }, "/echo": { "get": { "x-mojo-name": "echo", "parameters": [ { "in": "query", "name": "bool", "type": "boolean", "default": true } ], "responses": { "200": { "description": "Echo response", "schema": { "type": "object" } } } } }, "/echo-controller": { "post": { "x-mojo-to": ["echo#any", {"foo": [42]}, "bar", "42", ["format", ["js", "txt"]], "namespace", "Test::Controller"], "parameters": [ { "in": "query", "name": "days", "type": "number", "default": 42 }, { "in": "formData", "name": "name", "type": "string", "default": "batman" }, { "in": "query", "name": "enumParam", "type": "string", "default": "10.1.0", "enum": [ "9.6.1", "10.1.0" ] }, { "in": "header", "name": "X-Foo", "type": "string", "default": "yikes" } ], "responses": { "200": { "description": "Echo response", "schema": { "type": "object" } } } } }, "/echo-controller/{name}": { "get": { "x-mojo-to": ["namespace", "Test::Controller", "controller", "echo", "action", "any"], "parameters": [ { "in": "path", "name": "name", "type": "string", "required": true }, { "in": "query", "name": "days", "type": "number", "default": 42 } ], "responses": { "200": { "description": "Echo response", "schema": { "type": "object" } } } } }, "/param-has-ref": { "get": { "operationId": "ParamsHasRef", "parameters": [ { "$ref": "#/parameters/PCVersion" }, { "name": "x", "in": "query", "type": "string", "description": "x" } ], "responses": { "200": { "description": "thing", "schema": { "type": "string" } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/jv-recursion.t000644 000765 000024 00000000210 14125731256 024625 xustar00jhthorsenstaff000000 000000 30 mtime=1633137326.286984546 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/jv-recursion.t000644 000765 000024 00000003747 14125731256 022676 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use JSON::Validator::Schema::OpenAPIv2; TODO: { todo_skip 'At this moment in spacetime, I do not know how to suppport both a recursive schema and a recusrive data structure', 2; my ($data, @errors) = ({}); $data->{rec} = $data; eval { local $SIG{ALRM} = sub { die 'Recursion!' }; alarm 2; @errors = JSON::Validator::Schema::Draft4->new('data://main/spec.json')->validate({top => $data}); }; is $@, '', 'no error'; is_deeply(\@errors, [], 'avoided recursion'); } note 'This part of the test checks that we don\'t go into an infite loop'; eval { my $validator = JSON::Validator::Schema::OpenAPIv2->new; $validator->data('data://main/user.json')->errors; $validator->data($validator->data)->errors; }; ok !$@, 'handle $schema with recursion'; done_testing; __DATA__ @@ spec.json { "properties": { "top": { "$ref": "#/definitions/again" } }, "definitions": { "again": { "anyOf": [ {"type": "string"}, { "type": "object", "properties": { "rec": {"$ref": "#/definitions/again"} } } ] } } } @@ user.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "User schema" }, "schemes" : [ "http" ], "basePath" : "/api", "paths" : { "/user" : { "post" : { "operationId" : "User", "parameters": [{ "name": "data", "in": "body", "required": true, "schema": { "$ref": "#/definitions/user" } }], "responses" : { "200": { "description": "response", "schema": { "type": "object" } } } } } }, "definitions": { "user": { "type": "object", "properties": { "name": { "type": "string" }, "siblings": { "type": "array", "items": { "$ref": "#/definitions/user" } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/basic-custom-renderer.t000644 000765 000024 00000000210 14013056427 026370 xustar00jhthorsenstaff000000 000000 30 mtime=1613520151.415438286 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/basic-custom-renderer.t000644 000765 000024 00000004361 14013056427 024432 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; my $age = 43; get '/user' => sub { my $c = shift->openapi->valid_input or return; die "no age!\n" unless defined $age; $c->render(openapi => {age => $age}); }, 'get_user'; post '/user' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {age => $c->param('age')}); }, 'create_user'; plugin OpenAPI => {renderer => \&custom_openapi_renderer, url => 'data://main/user.json'}; my $t = Test::Mojo->new; $t->get_ok('/api/user')->status_is(200)->json_is('/age', 43)->json_is('/t', $^T); $age = 'invalid output!'; note "age = $age"; $t->get_ok('/api/user')->status_is(500)->json_is('/messages/0/path', '/body/age') ->json_is('/t', $^T); $t->post_ok('/api/user', form => {age => 'invalid input'})->status_is(400) ->json_is('/messages/0/path', '/age')->json_is('/t', $^T); undef $age; note 'age = undef'; $t->get_ok('/api/user')->status_is(500)->json_is('/messages/0/message', 'Internal Server Error.') ->json_is('/exception', "no age!\n")->json_is('/t', $^T); $t->get_ok('/api/nope')->status_is(404)->json_is('/messages/0/message', 'Not Found.') ->json_is('/t', $^T); done_testing; sub custom_openapi_renderer { my ($c, $data) = @_; $data->{messages} = delete $data->{errors} if $data->{errors}; $data->{t} = $^T if ref $data eq 'HASH'; $data->{exception} = $c->stash('exception')->message if $c->stash('exception'); return Mojo::JSON::encode_json($data); } __DATA__ @@ user.json { "swagger": "2.0", "info": { "version": "0.8", "title": "Pets" }, "schemes": [ "http" ], "basePath": "/api", "paths": { "/user": { "get": { "x-mojo-name": "get_user", "responses": { "200": { "description": "User", "schema": { "type": "object", "properties": { "age": { "type": "integer"} } } } } }, "post": { "x-mojo-name": "create_user", "parameters": [ { "in": "formData", "name": "age", "type": "integer" } ], "responses": { "400": { "description": "Error", "schema": { "type": "object" } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/plugin-spec-renderer-standalone.t000644 000765 000024 00000000210 14125731256 030357 xustar00jhthorsenstaff000000 000000 30 mtime=1633137326.287590245 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/plugin-spec-renderer-standalone.t000644 000765 000024 00000003725 14125731256 026424 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Mojo::File 'path'; use Mojolicious; use Test::Mojo; use Test::More; sub VERSION {1.42} my $petstore = path(__FILE__)->dirname->child(qw(spec v2-petstore.json)); my $app = Mojolicious->new; # All options are ignored when loaded as standalone plugin $app->plugin('Mojolicious::Plugin::OpenAPI::SpecRenderer' => {url => $petstore->to_string, spec_route_name => 'my.cool.api', version_from_class => 'main'}); my $custom_spec = JSON::Validator->new->schema($petstore->to_string)->schema->bundle; $app->routes->get('/my-unknown-doc' => sub { shift->openapi->render_spec }); $app->routes->get( '/my-cool-doc' => [format => [qw(html json)]], {format => undef}, sub { $_[0]->openapi->render_spec($_[0]->param('path'), $custom_spec) } ); my $t = Test::Mojo->new($app); $t->get_ok('/my-cool-doc.json')->status_is(200)->json_is('/basePath', '/v1') ->json_is('/host', 'petstore.swagger.io')->json_is('/info/version', '1.0.0'); $t->get_ok('/my-cool-doc.json?path=/pets/{petId}')->status_is(200) ->json_is('/$schema', 'http://json-schema.org/draft-04/schema#') ->json_is('/title', 'Swagger Petstore')->json_is('/description', '') ->json_is('/get/operationId', 'showPetById') ->json_is('/get/responses/200/schema/$ref', '#/definitions/Pets') ->json_is('/definitions/Pets/type', 'array'); $t->get_ok('/my-cool-doc.json?method=get&path=/pets/{petId}')->status_is(200) ->json_is('/$schema', 'http://json-schema.org/draft-04/schema#') ->json_is('/title', 'Swagger Petstore')->json_is('/operationId', 'showPetById'); $t->get_ok('/my-unknown-doc')->status_is(500) ->json_is('/errors/0/message', 'No specification to render.'); $t->get_ok('/my-cool-doc.html')->status_is(200)->text_is('h3#op-post--pets a', 'createPets'); SKIP: { skip 'Text::Markdown is not installed', 2 unless eval 'require Text::Markdown;1'; $t->text_is('div.spec-description p', 'Null response'); } done_testing; Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/plugin-security-extended-status.t000644 000765 000024 00000000210 14013056427 030455 xustar00jhthorsenstaff000000 000000 30 mtime=1613520151.416857014 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/plugin-security-extended-status.t000644 000765 000024 00000005767 14013056427 026532 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; get '/securitytest' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {message => "ok"}, status => 200); }, 'securitytest'; plugin OpenAPI => { url => 'data://main/sec.yaml', security => { api_key => sub { my ($self, $definition, $scopes, $cb) = @_; my $apikey = $self->req->headers->header('apikey'); my $apiuser = $self->req->headers->header('apiuser'); return $self->$cb("apikey/apiuser missing") unless $apikey and $apiuser; if ($apikey eq "authenticated" and $apiuser eq "authenticated") { $self->stash(status => 403); return $self->$cb("Permission denied"); } if ($apikey eq "authorized" and $apiuser eq "authorized") { return $self->$cb(); } return $self->$cb("Unauthorized"); }, }, }; my $t = Test::Mojo->new; $t->get_ok('/api/securitytest' => {apikey => 'authorized', apiuser => 'authorized'}) ->status_is(200); $t->get_ok('/api/securitytest' => {apikey => 'authenticated', apiuser => 'authenticated'}) ->status_is(403); $t->get_ok('/api/securitytest' => {apikey => 'unknown', apiuser => 'unknown'})->status_is(401); $t->get_ok('/api/securitytest')->status_is(401); done_testing; __DATA__ @@ sec.yaml openapi: 3.0.2 info: title: CSApi version: "1.0" description: API test servers: - url: /api security: - api_key: [] api_user: [] paths: /securitytest: get: x-mojo-name: securitytest summary: test security description: > Will grant authenticated and authorized apikeys access responses: 200: $ref: "#/components/responses/200_OK_message" 403: $ref: "#/components/responses/403_Forbidden" components: securitySchemes: api_key: type: apiKey description: API key to authorize requests. name: apikey in: header api_user: type: apiKey description: Username going with that key. name: apiuser in: header schemas: Error: type: object required: - errors properties: errors: type: array items: type: object required: - message properties: message: type: string path: type: string responses: 200_OK_message: description: OK content: application/json: schema: type: object required: - message properties: message: type: string path: type: string 403_Forbidden: description: Insufficient priviliges content: application/json: schema: $ref: "#/components/schemas/Error" DefaultResponse: description: Default Response content: application/json: schema: $ref: "#/components/schemas/Error" Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v3-invalid_file_refs.t000644 000765 000024 00000000210 14125731256 026171 xustar00jhthorsenstaff000000 000000 30 mtime=1633137326.288564773 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v3-invalid_file_refs.t000644 000765 000024 00000001254 14125731256 024231 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; get '/test' => sub { my $c = shift->openapi->valid_input or return; $c->render(status => 200, openapi => $c->param('pcversion')); }, 'File'; plugin OpenAPI => {url => app->home->rel_file('spec/v3-invalid_file_refs.yaml')}; my $t = Test::Mojo->new; $t->get_ok('/api')->status_is(200)->json_hasnt('/PCVersion/name')->json_has('/components/schemas') ->content_like(qr/v3-invalid_include_yaml-PCVersion/); my $validator = JSON::Validator::Schema::OpenAPIv3->new($t->get_ok('/api')->tx->res->body); like $validator->errors->[0], qr/Properties not allowed/, 'invalid bundled spec'; done_testing; Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/basic-under-route-authenticate.t000644 000765 000024 00000000152 13754115043 030205 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/basic-under-route-authenticate.t000644 000765 000024 00000003330 13754115043 026235 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; my $auth = app->routes->under('/api')->to( cb => sub { my $c = shift; my $spec = $c->openapi->spec; # skip authentication return 1 if $spec->{'x-no-auth'}; # really bad authentication return 1 if $c->param('unsafe_token'); # not authenticated $c->render(openapi => {errors => [{message => 'not logged in'}]}, status => 401); return; } ); get '/login' => sub { shift->render(openapi => {id => 123}, status => 200) }, 'login'; get '/protected' => sub { shift->render(openapi => {protected => 'secret'}, status => 200) }, 'protected'; plugin OpenAPI => {route => $auth, url => 'data://main/api.json'}; my $t = Test::Mojo->new; $t->get_ok('/api')->status_is(401)->json_is('/errors/0/message', 'not logged in'); $t->get_ok('/api?unsafe_token=1')->status_is(200)->json_is('/swagger', '2.0'); $t->get_ok('/api/login')->status_is(200)->json_is('/id', 123); $t->get_ok('/api/protected')->status_is(401)->json_is('/errors/0/message', 'not logged in'); $t->get_ok('/api/protected?unsafe_token=1')->status_is(200)->json_is('/protected', 'secret'); done_testing; __DATA__ @@ api.json { "swagger": "2.0", "info": { "version": "0.8", "title": "Test protected api" }, "basePath": "/api", "paths": { "/login": { "get": { "x-no-auth": true, "x-mojo-name": "login", "responses": { "200": { "description": "response", "schema": { "type": "object" } } } } }, "/protected": { "get": { "x-mojo-name": "protected", "responses": { "200": { "description": "response", "schema": { "type": "object" } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/spec000755 000765 000024 00000000207 14374621224 022677 xustar00jhthorsenstaff000000 000000 29 mtime=1676878484.57518271 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/000755 000765 000024 00000000000 14374621224 021001 5ustar00jhthorsenstaff000000 000000 Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v2-tutorial.t000644 000765 000024 00000000210 14026515753 024371 xustar00jhthorsenstaff000000 000000 30 mtime=1616550891.194062742 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v2-tutorial.t000644 000765 000024 00000004155 14026515753 022434 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; make_app(); make_controller(); my $t = Test::Mojo->new('Myapp'); $t->get_ok('/api')->status_is(200)->json_is('/info/title', 'Some awesome API'); $t->get_ok('/api/pets')->status_is(200)->json_is('/pets/0/name', 'kit-e-cat'); done_testing; sub make_app { eval <<'HERE' or die $@; package Myapp; use Mojo::Base "Mojolicious"; sub startup { my $app = shift; $app->plugin("OpenAPI" => {url => "data://main/myapi.json"}); } $ENV{"Myapp.pm"} = 1; HERE } sub make_controller { eval <<'HERE' or die $@; package Myapp::Controller::Pet; use Mojo::Base "Mojolicious::Controller"; sub list { # Do not continue on invalid input and render a default 400 # error document. my $c = shift->openapi->valid_input or return; # $c->openapi->valid_input copies valid data to validation object, # and the normal Mojolicious api works as well. my $input = $c->validation->output; my $age = $c->param("age"); # same as $input->{age} my $body = $c->req->json; # same as $input->{body} # $output will be validated by the OpenAPI spec before rendered my $output = {pets => [{name => "kit-e-cat"}]}; $c->render(openapi => $output); } $ENV{"Myapp/Controller/Pet.pm"} = 1; HERE } __DATA__ @@ myapi.json { "swagger": "2.0", "info": { "version": "1.0", "title": "Some awesome API" }, "basePath": "/api", "paths": { "/pets": { "get": { "operationId": "getPets", "x-mojo-name": "get_pets", "x-mojo-to": "pet#list", "summary": "Finds pets in the system", "parameters": [ {"in": "body", "name": "body", "schema": {"type": "object"}}, {"in": "query", "name": "age", "type": "integer"} ], "responses": { "200": { "description": "Pet response", "schema": { "type": "object", "properties": { "pets": { "type": "array", "items": { "type": "object" } } } } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/basic-register-plugin.t000644 000765 000024 00000000210 14215037764 026401 xustar00jhthorsenstaff000000 000000 30 mtime=1647591412.361632569 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/basic-register-plugin.t000644 000765 000024 00000017105 14215037764 024443 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Mojo::JSON 'true'; use Test::Mojo; use Test::More; use Mojolicious::Lite; get( '/no-default-options/:id' => sub { $_[0]->render(openapi => {id => $_[0]->stash('id')}) }, 'Dummy' ); options( '/perl/no-default-options/:id' => sub { $_[0]->render(json => {options => $_[0]->stash('id')}) }); post('/user' => sub { shift->render(openapi => {}) }, 'User'); my $obj = plugin OpenAPI => {route => app->routes->any('/one'), url => 'data://main/one.json'}; plugin OpenAPI => {default_response_name => 'DefErr', url => 'data://main/two.json'}; plugin OpenAPI => { default_response_codes => [], spec => { swagger => '2.0', info => {version => '0.8', title => 'Test schema in perl'}, schemes => ['http'], basePath => '/perl', paths => { '/no-default-options/{id}' => { get => { operationId => 'Dummy', parameters => [{in => 'path', name => 'id', type => 'string', required => true}], responses => {200 => {description => 'response', schema => {type => 'object'}}} } }, '/user' => { post => { operationId => 'User', responses => {200 => {description => 'response', schema => {type => 'object'}}} } } } } }; plugin OpenAPI => { spec => { openapi => '3.0.0', info => { title => 'Sample API', description => 'Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML.', version => '0.1.9' }, servers => [ { url => 'http://api.example.com/oa3', description => 'Optional server description, e.g. Main (production) server' }, { url => 'http://staging-api.example.com', description => 'Optional server description, e.g. Internal staging server for testing' } ], components => {schemas => {jobs => {type => 'array', items => {type => 'string'}}}}, paths => { '/users' => { get => { summary => 'Returns a list of users.', description => 'Optional extended description in CommonMark or HTML.', responses => { '200' => { description => 'A JSON array of user names', content => {'application/json' => {schema => {type => 'array', items => {type => 'string'}}}} } } } }, '/jobs' => { get => { summary => 'Returns a list of jobs.', description => 'Optional extended description in CommonMark or HTML.', responses => { '200' => { description => 'A JSON array of job types', content => {'application/json' => {schema => {'$ref' => '#/components/schemas/jobs'}}} } } } } } } }; plugin OpenAPI => {url => 'data://main/programmatically.json', op_spec_to_route => \&op_spec_to_route}; get('/injected' => {text => 'injected'}, 'injected'); my $schema = JSON::Validator::Schema::OpenAPIv2->new('data://main/schema-object.json'); $schema->data->{paths}{'/injected'} = { get => { operationId => 'injected', responses => {200 => {description => 'response', schema => {type => 'array'}}}, } }; plugin OpenAPI => {spec => $schema}; ok $obj->route->find('cool_api'), 'found api endpoint'; isa_ok($obj->route, 'Mojolicious::Routes::Route'); isa_ok($obj->validator, 'JSON::Validator::Schema::OpenAPIv2'); my $t = Test::Mojo->new; $t->get_ok('/one')->status_is(200) ->json_is('/definitions/DefaultResponse/properties/errors/type', 'array') ->json_is('/info/title', 'Test schema one'); $t->options_ok('/oa3/users?method=get')->status_is(200) ->json_is('/responses/200/description', 'A JSON array of user names') ->json_is('/responses/400/description', 'Default response.') ->json_is('/responses/400/content/application~1json/schema/$ref', '#/components/schemas/DefaultResponse'); $t->options_ok('/oa3/jobs?method=get')->status_is(200) ->json_is('/responses/200/description', 'A JSON array of job types') ->json_is('/responses/400/description', 'Default response.') ->json_is('/responses/200/content/application~1json/schema/$ref', '#/components/schemas/jobs'); $t->options_ok('/one/user?method=post')->status_is(200) ->json_is('/responses/200/description', 'ok') ->json_is('/responses/400/description', 'Default response.') ->json_is('/responses/400/schema/$ref', '#/definitions/DefaultResponse') ->json_is('/responses/500/description', 'err'); $t->get_ok('/two')->status_is(200)->json_is('/definitions/DefaultResponse', undef) ->json_is('/definitions/DefErr/required', [qw(errors something_else)]) ->json_is('/info/title', 'Test schema two'); $t->options_ok('/two/user?method=post')->status_is(200) ->json_is('/responses/400/schema/$ref', '#/definitions/DefErr') ->json_is('/responses/default/description', 'whatever'); $t->get_ok('/perl')->status_is(200)->json_is('/info/title', 'Test schema in perl'); $t->options_ok('/perl/user?method=post')->status_is(200) ->json_is('/responses/500/description', undef); note 'Override options'; $t->get_ok('/perl/no-default-options/42')->status_is(200)->json_is('/id', 42); $t->options_ok('/perl/no-default-options/42')->status_is(200)->json_is('/options', 42); note 'programmatically'; $t->get_ok('/api/programmatically')->status_is(200)->json_is('/operationId', 'getStuff') ->json_is('/responses/200/schema/type', 'array'); $t->post_ok('/api/programmatically')->status_is(200)->json_is('/operationId', 'postStuff') ->json_is('/responses/200/schema/type', 'boolean'); $t->get_ok('/schema-object/injected')->status_is(200)->content_is('injected'); done_testing; sub op_spec_to_route { my ($plugin, $op_spec, $route) = @_; $route->to(cb => sub { shift->render(json => $op_spec) }); } __DATA__ @@ one.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Test schema one" }, "schemes" : [ "http" ], "basePath" : "/api", "x-mojo-name": "cool_api", "paths" : { "/user" : { "post" : { "operationId" : "User", "responses" : { "200": { "description": "ok", "schema": { "type": "object" } }, "500": { "description": "err", "schema": { "type": "object" } } } } } } } @@ two.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Test schema two" }, "schemes" : [ "http" ], "basePath" : "/two", "paths" : { "/user" : { "post" : { "operationId" : "User", "responses" : { "200": { "description": "response", "schema": { "type": "object" } }, "default": { "description": "whatever", "schema": { "type": "array" } } } } } }, "definitions": { "DefErr": { "type": "object", "required": ["errors", "something_else"] } } } @@ schema-object.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Object" }, "basePath" : "/schema-object", "paths" : {} } @@ programmatically.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Test unique route names" }, "basePath" : "/api", "paths" : { "/programmatically" : { "get" : { "operationId" : "getStuff", "responses": { "200": { "description": "response", "schema": { "type": "array" } } } }, "post" : { "operationId" : "postStuff", "responses": { "200": { "description": "response", "schema": { "type": "boolean" } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/basic-custom-validation.t000644 000765 000024 00000000152 13742714631 026727 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/basic-custom-validation.t000644 000765 000024 00000001540 13742714631 024760 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; my $age = 43; get '/custom' => sub { my $c = shift; my @errors = $c->openapi->validate; return $c->render(text => sprintf '%s errors', int @errors) if @errors; return $c->render(text => 'cool beans'); }, 'get_custom'; plugin OpenAPI => {url => 'data://main/custom.json'}; my $t = Test::Mojo->new; $t->get_ok('/api/custom?i=42')->content_is('cool beans'); $t->get_ok('/api/custom?i=nok')->content_is('1 errors'); done_testing; __DATA__ @@ custom.json --- swagger: "2.0" info: version: "1.0" title: Custom validation basePath: /api paths: /custom: get: operationId: get_custom parameters: - name: i in: query type: integer responses: 200: description: ok schema: type: file Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/basic-custom-formats.t000644 000765 000024 00000000152 13742714631 026250 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/basic-custom-formats.t000644 000765 000024 00000002707 13742714631 024307 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; my $what_ever; get '/custom-format' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'custom_format'; my $oap = plugin OpenAPI => {url => 'data://main/custom-format.json'}; $oap->validator->formats->{need_to_be_x} = sub { $_[0] eq 'x' ? undef : 'Not x.' }; my $t = Test::Mojo->new; $t->get_ok('/api/custom-format' => json => {str => 'x'})->status_is(200) ->content_like(qr{"str":"x"}); $t->get_ok('/api/custom-format' => json => {str => 'y'})->status_is(400) ->content_like(qr{"errors"}); done_testing; __DATA__ @@ custom-format.json { "swagger" : "2.0", "info" : { "version": "9.1", "title" : "Test API for custom formats" }, "consumes" : [ "application/json" ], "produces" : [ "application/json" ], "schemes" : [ "http" ], "basePath" : "/api", "paths" : { "/custom-format" : { "get" : { "x-mojo-name": "custom_format", "parameters" : [ {"in": "body", "name": "body", "schema": {"$ref": "Body"}} ], "responses" : { "200" : { "description": "this is required", "schema": { "type" : "object" } } } } } }, "definitions": { "Body": { "required": ["str"], "properties": { "str": { "type": "string", "format": "need_to_be_x" } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/basic-autorender.t000644 000765 000024 00000000210 14013060620 025410 xustar00jhthorsenstaff000000 000000 30 mtime=1613521296.145807215 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/basic-autorender.t000644 000765 000024 00000005464 14013060620 023457 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; my %inline; { use Mojolicious::Lite; app->routes->namespaces(['MyApp::Controller']); get('/die' => sub { die 'Oh noes!' }, 'Die'); get('/inline' => sub { shift->render(%inline) }, 'Inline'); get '/not-found' => sub { shift->render(openapi => {this_is_fine => 1}, status => 404) }, 'NotFound'; plugin OpenAPI => {url => 'data://main/hook.json'}; } my $t = Test::Mojo->new; $t->app->mode('development'); # Exception $t->get_ok('/api/die')->status_is(500)->json_is('/errors/0/message', 'Internal Server Error.'); # Not implemented $t->get_ok('/api/todo')->status_is(404)->json_is('/errors/0/message', 'Not Found.'); # Implemented, but Not Found define_controller(); $t->get_ok('/api/todo')->status_is(404)->json_is('/errors/0/message', 'Not Found.'); $t->post_ok('/api/todo')->status_is(200)->json_is('/todo', 42); # Custom Not Found response $t->get_ok('/api/not-found')->status_is(404)->json_is('/this_is_fine', 1); # Custom Not Found template (mode) $t->get_ok('/THIS_IS_NOT_FOUND')->status_is(404)->content_like(qr{Not found development}); # Fallback to default renderer $inline{template} = 'inline'; $t->get_ok('/api/inline')->status_is(200); #->content_like(qr{Too cool}); $inline{openapi} = 'openapi is cool'; $t->get_ok('/api/inline')->status_is(200)->content_like(qr{openapi is cool}); done_testing; sub define_controller { eval <<'HERE' or die; package MyApp::Controller::Dummy; use Mojo::Base 'Mojolicious::Controller'; sub todo { my $c = shift->openapi->valid_input or return; $c->render(openapi => {todo => 42}); } 1; HERE } package main; __DATA__ @@ hook.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Test before_render hook" }, "basePath" : "/api", "paths" : { "/die" : { "get" : { "operationId" : "Die", "responses" : { "200": { "description": "response", "schema": { "type": "object" } } } } }, "/inline" : { "get" : { "operationId" : "Inline", "responses" : { "200": { "description": "response", "schema": { "type": "string" } } } } }, "/not-found" : { "get" : { "operationId" : "NotFound", "responses" : { "404": { "description": "response", "schema": { "type": "object" } } } } }, "/todo" : { "post" : { "x-mojo-to": "dummy#todo", "operationId" : "Auto", "parameters" : [ { "in": "body", "name": "body", "schema": { "type" : "object" } } ], "responses" : { "200": { "description": "response", "schema": { "type": "object" } } } } } } } @@ inline.html.ep Too cool @@ not_found.html.ep Not found @@ not_found.development.html.ep Not found development Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v3-body.t000644 000765 000024 00000000210 14146310313 023450 xustar00jhthorsenstaff000000 000000 30 mtime=1637454027.544846884 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v3-body.t000644 000765 000024 00000007624 14146310313 021517 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; post '/test' => sub { my $c = shift; $c->openapi->valid_input or return; $c->render(json => undef, status => 200); }, 'test'; post '/test/string' => sub { my $c = shift; $c->openapi->valid_input or return; $c->render(text => $c->req->body, status => 200); }, 'string'; post '/test/optional/explicitly' => sub { my $c = shift; $c->openapi->valid_input or return; $c->render(openapi => undef, status => 200); }, 'test2'; post '/test/optional/implicitly' => sub { my $c = shift; $c->openapi->valid_input or return; $c->render(openapi => undef, status => 200); }, 'test3'; plugin OpenAPI => {url => 'data:///api.yml'}; my $t = Test::Mojo->new(); note 'Valid request should be ok'; $t->post_ok('/test', json => {foo => 'bar'})->status_is(200); note 'Missing property should fail'; $t->post_ok('/test', json => {})->status_is(400)->json_is('/errors/0/message', 'Missing property.'); note 'Array should fail'; $t->post_ok('/test', json => [])->status_is(400) ->json_is('/errors/0/message', 'Expected object - got array.'); note 'Null should fail'; $t->post_ok('/test', json => undef)->status_is(400) ->json_is('/errors/0/message', 'Expected object - got null.'); note 'Invalid JSON should fail'; $t->post_ok('/test', {'Content-Type' => 'application/json'} => 'invalid_json')->status_is(400) ->json_is('/errors/0/message', 'Expected object - got string.'); note 'Invalid Content-Type should fail'; $t->post_ok('/test', {'Content-Type' => 'application/xml'} => '') ->status_is(400) ->json_is('/errors/0/message', 'Expected application/json - got application/xml.'); note 'empty requestBody with "required: false"'; $t->post_ok('/test/optional/explicitly')->status_is(200); note 'requestBody with "required: false"'; $t->post_ok('/test/optional/explicitly', json => {foo => 'bar'})->status_is(200); note 'empty requestBody without "required: false"'; $t->post_ok('/test/optional/implicitly')->status_is(200); note 'requestBody without "required: false"'; $t->post_ok('/test/optional/implicitly', json => {foo => 'bar'})->status_is(200); note 'requestBody as plain string'; $t->post_ok('/test/string', {'Content-Type' => 'text/plain'}, 'cool beans')->status_is(200) ->content_is('cool beans'); note 'not really the first, since "content" is an object, but predictable when there is only one'; $t->post_ok('/test/string', 'first content-type')->status_is(200)->content_is('first content-type'); done_testing; __DATA__ @@ api.yml openapi: 3.0.0 info: title: Test version: 0.0.0 paths: /test: post: x-mojo-name: test requestBody: required: true content: application/json: schema: type: object properties: foo: type: string required: - foo responses: '200': description: ok /test/string: post: x-mojo-name: string requestBody: required: true content: text/plain: schema: type: string responses: '200': description: ok /test/optional/explicitly: post: x-mojo-name: test2 requestBody: required: false content: application/json: schema: type: object properties: foo: type: string required: - foo responses: '200': description: ok /test/optional/implicitly: post: x-mojo-name: test3 requestBody: content: application/json: schema: type: object properties: foo: type: string required: - foo responses: '200': description: ok Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v3-file.t000644 000765 000024 00000000210 14062523525 023442 xustar00jhthorsenstaff000000 000000 30 mtime=1623893845.848019391 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v3-file.t000644 000765 000024 00000003172 14062523525 021503 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; post '/upload' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {size => $c->req->upload('image')->size}); }, 'upload'; plugin OpenAPI => {url => 'data://main/openapi.yaml'}; my $t = Test::Mojo->new; $t->post_ok('/api/upload', form => {foo => 42})->status_is(400) ->json_is('/errors/0', {message => 'Missing property.', path => '/body/image'}); my $image = Mojo::Asset::Memory->new->add_chunk('smileyface'); $t->post_ok( '/api/upload', {Accept => 'application/json'}, form => {id => 1, image => {file => $image}} )->content_like(qr{"size"})->status_is(200); done_testing; __DATA__ @@ openapi.yaml --- openapi: 3.0.0 info: title: Upload test version: 1.0.0 servers: - url: http://example.com/api paths: /upload: post: operationId: upload requestBody: required: true content: application/x-www-form-urlencoded: schema: required: [ image ] properties: id: type: string image: type: string format: binary multipart/form-data: schema: required: [ image ] properties: image: type: string format: binary responses: 200: description: Accepted content: application/json: schema: required: [ size ] properties: size: type: integer Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/plugin-cors.t000644 000765 000024 00000000207 14003223727 024441 xustar00jhthorsenstaff000000 000000 29 mtime=1611474903.04129164 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/plugin-cors.t000644 000765 000024 00000014763 14003223727 022504 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; our $cors_callback = 'main::cors_exchange'; use Mojolicious::Lite; get '/user' => sub { my $c = shift->openapi->cors_exchange($cors_callback)->openapi->valid_input or return; $c->render(json => {cors => 'cors_exchange', origin => $c->stash('origin')}); }, 'getUser'; put '/user' => sub { my $c = shift->openapi->cors_exchange->openapi->valid_input or return; $c->render(json => {created => time}); }, 'addUser'; put '/headers' => sub { my $c = shift->openapi->valid_input or return; $c->res->headers->access_control_allow_origin($c->req->headers->origin) if $c->req->headers->origin; $c->render(json => {h => 42}); }, 'headerValidation'; plugin OpenAPI => {url => 'data://main/cors.json', add_preflighted_routes => 1}; my $t = Test::Mojo->new; note 'Simple'; $t->get_ok('/api/user', {'Content-Type' => 'text/plain', Origin => 'http://bar.example'}) ->status_is(400)->json_is('/errors/0/message', 'Invalid Origin header.'); $t->get_ok('/api/user', {'Content-Type' => 'text/plain', Origin => 'http://foo.example'}) ->status_is(200)->header_is('Access-Control-Allow-Origin' => 'http://foo.example') ->json_is('/cors', 'cors_exchange')->json_is('/origin', 'http://foo.example'); $t->get_ok('/api/user', {Origin => 'http://foo.example'})->status_is(200) ->header_is('Access-Control-Allow-Origin' => 'http://foo.example'); note 'Preflighted'; $t->options_ok('/api/user', {'Content-Type' => 'text/plain', Origin => 'http://bar.example'}) ->status_is(400)->json_is('/errors/0/message', 'Invalid Origin header.'); $t->options_ok('/api/user', {'Content-Type' => 'text/plain', Origin => 'http://foo.example'}) ->status_is(200)->header_is('Access-Control-Allow-Origin' => 'http://foo.example') ->header_is('Access-Control-Allow-Headers' => 'X-Whatever, X-Something') ->header_is('Access-Control-Allow-Methods' => 'POST, GET, OPTIONS') ->header_is('Access-Control-Max-Age' => 86400)->content_is(''); $t->options_ok( '/api/user', { 'Access-Control-Request-Headers' => 'X-Foo, X-Bar', 'Access-Control-Request-Method' => 'GET', 'Content-Type' => 'text/plain', 'Origin' => 'http://foo.example' } )->status_is(200)->header_is('Access-Control-Allow-Origin' => 'http://foo.example') ->header_is('Access-Control-Allow-Headers' => 'X-Foo, X-Bar') ->header_is('Access-Control-Allow-Methods' => 'GET, PUT') ->header_is('Access-Control-Max-Age' => 1800)->content_is(''); note 'Default cors exchange'; $cors_callback = undef; $t->app->defaults(openapi_cors_allowed_origins => [qr{bar\.example}]); $t->app->defaults(openapi_cors_default_max_age => 42); $t->options_ok('/api/user', {'Origin' => 'http://bar.example', 'Access-Control-Request-Method' => 'GET'})->status_is(200) ->header_is('Access-Control-Allow-Origin' => 'http://bar.example') ->header_is('Access-Control-Max-Age' => 42)->content_is(''); note 'Actual request'; $t->options_ok('/api/user')->status_is(400) ->json_is('/errors/0/message', 'OPTIONS is only for preflighted CORS requests.'); $t->put_ok('/api/user', {'Origin' => 'http://bar.example'})->status_is(200) ->header_is('Access-Control-Allow-Origin' => 'http://bar.example')->json_has('/created'); $t->get_ok('/api/user')->status_is(200)->header_is('Access-Control-Allow-Origin' => undef) ->json_is('/origin', undef); $t->put_ok('/api/user')->status_is(200)->header_is('Access-Control-Allow-Origin' => undef) ->json_has('/created'); $t->put_ok('/api/headers')->status_is(200)->header_is('Access-Control-Allow-Origin' => undef) ->json_is('/h' => 42); note 'Using the spec'; $t->options_ok('/api/headers')->status_is(400)->json_is('/errors/0/path' => '/Origin'); $t->put_ok('/api/headers', {'Origin' => 'https://foo.example'})->status_is(400) ->json_is('/errors/0/path' => '/Origin'); $t->options_ok('/api/headers', {'Origin' => 'http://foo.example'})->status_is(400) ->json_is('/errors/0/path' => '/Origin'); $t->options_ok('/api/headers', {'Origin' => 'http://bar.example'})->status_is(200) ->header_is('Access-Control-Allow-Origin' => 'http://bar.example') ->header_is('Access-Control-Max-Age' => 42)->content_is(''); $t->put_ok('/api/headers', {'Origin' => 'https://bar.example'})->status_is(200) ->header_is('Access-Control-Allow-Origin' => 'https://bar.example')->json_is('/h' => 42); done_testing; sub cors_exchange { my $c = shift; my $req_h = $c->req->headers; my $headers = $req_h->header('Access-Control-Request-Headers'); my $method = $req_h->header('Access-Control-Request-Methods'); my $origin = $req_h->header('Origin'); return '/Origin' unless $origin eq 'http://foo.example'; return '/X-No-Can-Do' if $headers and $headers =~ /X-No-Can-Do/; return '/Access-Control-Request-Method' if $method and $method eq 'DELETE'; $c->stash(origin => $origin); # Set required Preflighted response header $c->res->headers->header('Access-Control-Allow-Origin' => $origin); # Set Preflighted response headers, instead of using the default $c->res->headers->header('Access-Control-Allow-Headers' => 'X-Whatever, X-Something') unless $c->req->headers->header('Access-Control-Request-Headers'); $c->res->headers->header('Access-Control-Allow-Methods' => 'POST, GET, OPTIONS') unless $c->req->headers->header('Access-Control-Request-Method'); $c->res->headers->header('Access-Control-Max-Age' => 86400) unless $c->req->headers->header('Access-Control-Request-Method'); return undef; } __DATA__ @@ cors.json { "swagger": "2.0", "info": { "version": "0.8", "title": "Test cors response" }, "basePath": "/api", "paths": { "/user": { "get": { "operationId": "getUser", "responses": { "200": { "description": "Get user", "schema": { "type": "object" } } } }, "put": { "operationId": "addUser", "responses": { "200": { "description": "Create user", "schema": { "type": "object" } } } } }, "/headers": { "parameters": [ { "in": "header", "name": "Origin", "type": "string", "pattern": "https?://bar.example" } ], "options": { "x-mojo-to": "#openapi_plugin_cors_exchange", "responses": { "200": { "description": "Cors exchange", "schema": { "type": "object" } } } }, "put": { "operationId": "headerValidation", "responses": { "200": { "description": "Cors put", "schema": { "type": "object" } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/basic-legacy-swagger2.t000644 000765 000024 00000000152 13736770604 026255 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/basic-legacy-swagger2.t000644 000765 000024 00000002466 13736770604 024316 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; post '/echo' => sub { my ($c, $data, $cb) = @_; $c->$cb({body => $data->{body}}, 200); }, 'echo'; get '/' => {text => 'test123'}; plugin OpenAPI => {url => 'data://main/echo.json'}; my $t = Test::Mojo->new; hook around_action => sub { my ($next, $c, $action, $last) = @_; return $next->() unless $last; return $next->() unless $c->openapi->spec; return unless $c->openapi->valid_input; my $cb = sub { my ($c, $data, $code) = @_; $c->render(openapi => $data, status => $code); }; return $c->$action($c->validation->output, $cb); }; $t->get_ok('/')->status_is(200)->content_is('test123'); $t->post_ok('/api/echo' => json => {foo => 123})->status_is(200)->json_is('/body/foo' => 123); done_testing; __DATA__ @@ echo.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Pets" }, "schemes" : [ "http" ], "basePath" : "/api", "paths" : { "/echo" : { "post" : { "x-mojo-name" : "echo", "parameters" : [ { "in": "body", "name": "body", "schema": { "type" : "object" } } ], "responses" : { "200": { "description": "Echo response", "schema": { "type": "object" } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v3-valid_file_refs.t000644 000765 000024 00000000210 14125731256 025642 xustar00jhthorsenstaff000000 000000 30 mtime=1633137326.289206473 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v3-valid_file_refs.t000644 000765 000024 00000001130 14125731256 023673 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; get '/test' => sub { my $c = shift->openapi->valid_input or return; $c->render(status => 200, openapi => $c->param('pcversion')); }, 'File'; plugin OpenAPI => {url => app->home->rel_file('spec/v3-valid_file_refs.yaml')}; my $t = Test::Mojo->new; $t->get_ok('/api')->status_is(200)->json_is('/components/parameters/PCVersion/name', 'pcversion'); my $validator = JSON::Validator::Schema::OpenAPIv3->new($t->get_ok('/api')->tx->res->body); is $validator->errors->[0], undef, 'valid bundled spec'; done_testing; Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v2-formats.t000644 000765 000024 00000000210 14013056427 024173 xustar00jhthorsenstaff000000 000000 30 mtime=1613520151.418159827 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v2-formats.t000644 000765 000024 00000007465 14013056427 022245 0ustar00jhthorsenstaff000000 000000 use lib '.'; use JSON::Validator::Schema::OpenAPIv2; use JSON::Validator::Util qw(E); use Test::More; my $schema = {type => 'object', properties => {v => {type => 'string'}}}; my $validator = JSON::Validator::Schema::OpenAPIv2->new; sub validate_ok { my ($data, $schema, @expected) = @_; my $descr = @expected ? "errors: @expected" : "valid: " . Mojo::JSON::encode_json($data); my @errors = $validator->data($schema)->validate($data); is_deeply [map { $_->TO_JSON } sort { $a->path cmp $b->path } @errors], [map { $_->TO_JSON } sort { $a->path cmp $b->path } @expected], $descr or Test::More::diag(Mojo::JSON::encode_json(\@errors)); } { $schema->{properties}{v}{format} = 'byte'; validate_ok {v => 'amh0aG9yc2Vu'}, $schema; validate_ok {v => "\0"}, $schema, E('/v', 'Does not match byte format.'); } { $schema->{properties}{v}{format} = 'date'; validate_ok {v => '2014-12-09'}, $schema; validate_ok {v => '0000-00-00'}, $schema, E('/v', 'Month out of range.'); validate_ok {v => '0000-01-00'}, $schema, E('/v', 'Day out of range.'); validate_ok {v => '2014-12-09T20:49:37Z'}, $schema, E('/v', 'Does not match date format.'); validate_ok {v => '0-0-0'}, $schema, E('/v', 'Does not match date format.'); validate_ok {v => '09-12-2014'}, $schema, E('/v', 'Does not match date format.'); validate_ok {v => '09-DEC-2014'}, $schema, E('/v', 'Does not match date format.'); validate_ok {v => '09/12/2014'}, $schema, E('/v', 'Does not match date format.'); } { $schema->{properties}{v}{format} = 'date-time'; validate_ok {v => '2014-12-09T20:49:37Z'}, $schema; validate_ok {v => '0000-00-00T00:00:00Z'}, $schema, E('/v', 'Month out of range.'); validate_ok {v => '0000-01-00T00:00:00Z'}, $schema, E('/v', 'Day out of range.'); validate_ok {v => '20:46:02'}, $schema, E('/v', 'Does not match date-time format.'); } { local $schema->{properties}{v}{type} = 'number'; local $schema->{properties}{v}{format} = 'double'; local $TODO = "cannot test double, since input is already rounded"; validate_ok {v => 1.1000000238418599085576943252817727625370025634765626}, $schema; } { local $schema->{properties}{v}{format} = 'email'; validate_ok {v => 'jhthorsen@cpan.org'}, $schema; validate_ok {v => 'foo'}, $schema, E('/v', 'Does not match email format.'); } { local $schema->{properties}{v}{type} = 'number'; local $schema->{properties}{v}{format} = 'float'; validate_ok {v => -1.10000002384186}, $schema; validate_ok {v => 1.10000002384186}, $schema; local $TODO = 'No idea how to test floats'; validate_ok {v => 0.10000000000000}, $schema, E('/v', 'Does not match float format.'); } { local $TODO = eval 'require Data::Validate::IP;1' ? undef : 'Missing module'; local $schema->{properties}{v}{format} = 'ipv4'; validate_ok {v => '255.100.30.1'}, $schema; validate_ok {v => '300.0.0.0'}, $schema, E('/v', 'Does not match ipv4 format.'); } { local $schema->{properties}{v}{type} = 'integer'; local $schema->{properties}{v}{format} = 'int32'; validate_ok {v => -2147483648}, $schema; validate_ok {v => 2147483647}, $schema; validate_ok {v => 2147483648}, $schema, E('/v', 'Does not match int32 format.'); } if (JSON::Validator::Formats::IV_SIZE >= 8) { local $schema->{properties}{v}{type} = 'integer'; local $schema->{properties}{v}{format} = 'int64'; validate_ok {v => -9223372036854775808}, $schema; validate_ok {v => 9223372036854775807}, $schema; validate_ok {v => 9223372036854775808}, $schema, E('/v', 'Does not match int64 format.'); } { local $schema->{properties}{v}{format} = 'password'; validate_ok {v => 'whatever'}, $schema; } { local $schema->{properties}{v}{format} = 'unknown'; validate_ok {v => 'whatever'}, $schema; } done_testing; Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v2-readonly.t000644 000765 000024 00000000210 14013056427 024335 xustar00jhthorsenstaff000000 000000 30 mtime=1613520151.418992897 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v2-readonly.t000644 000765 000024 00000002532 14013056427 022375 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; post '/user' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'createUser'; plugin OpenAPI => {url => 'data://main/readonly.json'}; my $t = Test::Mojo->new; $t->post_ok('/api/user', json => {age => 42})->status_is(400) ->json_is('/errors/0', {message => 'Read-only.', path => '/body/age'}); $t->post_ok('/api/user', json => {something => 'else'})->status_is(500) ->json_is('/errors/0', {message => 'Missing property.', path => '/body/age'}); done_testing; __DATA__ @@ readonly.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Test readonly" }, "schemes" : [ "http" ], "basePath" : "/api", "paths" : { "/user" : { "post" : { "operationId" : "createUser", "parameters" : [ { "name":"body", "in":"body", "schema": { "$ref": "#/definitions/User" } } ], "responses" : { "200": { "description": "ok", "schema": { "$ref": "#/definitions/User" } } } } } }, "definitions": { "User": { "type" : "object", "required": ["age"], "properties": { "age": { "type": "integer", "readOnly": true } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/plugin-security-rules-not-defined.t000644 000765 000024 00000000152 13736770604 030700 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/plugin-security-rules-not-defined.t000644 000765 000024 00000002050 13736770604 026726 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; post '/global' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 'checks disabled'}); }, 'global'; plugin OpenAPI => {url => 'data://main/sec.json'}; my $t = Test::Mojo->new; $t->post_ok('/api/global' => json => {})->status_is(200)->json_is('/ok' => 'checks disabled'); done_testing; __DATA__ @@ sec.json { "swagger": "2.0", "info": { "version": "0.8", "title": "Pets" }, "schemes": [ "http" ], "basePath": "/api", "securityDefinitions": { "fail1": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "fail1" } }, "security": [{"fail1": []}], "paths": { "/global": { "post": { "x-mojo-name": "global", "parameters": [ { "in": "body", "name": "body", "schema": { "type": "object" } } ], "responses": { "200": {"description": "Echo response", "schema": { "type": "object" }} } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/plugin-spec-renderer-options.t000644 000765 000024 00000000152 13742714631 027731 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/plugin-spec-renderer-options.t000644 000765 000024 00000007146 13742714631 025772 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; get '/spec' => sub { my $c = shift->openapi->valid_input or return; $c->render(json => {info => $c->openapi->spec('/info'), op_spec => $c->openapi->spec}); }, 'Spec'; get('/user/:id' => sub { shift->render(openapi => {}) }, 'user'); plugin OpenAPI => {url => 'data://main/spec.json'}; my $t = Test::Mojo->new; $t->get_ok('/api')->status_is(200)->json_is('/swagger', '2.0') ->json_is('/definitions/DefaultResponse/properties/errors/items/properties/message/type', 'string')->json_is('/definitions/SpecResponse/type', 'object') ->json_is('/paths/~1spec/get/operationId', 'Spec'); $t->get_ok('/api/spec')->status_is(200) ->json_is('/op_spec/responses/200/description', 'Spec response.') ->json_is('/info/version', '0.8'); $t->get_ok('/api/user/1')->status_is(200)->content_is('{}'); $t->options_ok('/api/spec')->status_is(200) ->json_is('/$schema', 'http://json-schema.org/draft-04/schema#') ->json_is('/title', 'Test spec response')->json_is('/description', '') ->json_is('/get/operationId', 'Spec') ->json_is('/get/responses/200/schema/$ref', '#/definitions/SpecResponse') ->json_is('/definitions/DefaultResponse/properties/errors/items/properties/message/type', 'string')->json_is('/definitions/SpecResponse/type', 'object'); $t->options_ok('/api/spec?method=get')->status_is(200) ->json_is('/$schema', 'http://json-schema.org/draft-04/schema#') ->json_is('/title', 'Test spec response')->json_is('/description', '') ->json_is('/operationId', 'Spec')->json_is('/definitions/SpecResponse/type', 'object'); eval { JSON::Validator->new->load_and_validate_schema($t->tx->res->json); ok 1, 'api/spec return valid schema'; } or do { ok 0, "api/spec return valid schema: $@"; }; $t->options_ok('/api/spec?method=post')->status_is(404) ->json_is('/errors/0/message', 'No spec defined.'); $t->options_ok('/api/user/1')->status_is(200) ->json_is('/$schema', 'http://json-schema.org/draft-04/schema#') ->json_is('/title', 'Test spec response')->json_is('/get/operationId', 'user') ->json_is('/definitions/DefaultResponse/properties/errors/items/properties/message/type', 'string'); $t->get_ok('/api')->status_is(200)->json_is('/basePath', '/api'); $t->head_ok('/api')->status_is(200); $t->head_ok('/api/user/1')->status_is(200)->content_is(''); hook before_dispatch => sub { my $c = shift; $c->req->url->base->path('/whatever'); }; $t->get_ok('/api')->status_is(200)->json_is('/basePath', '/whatever/api'); done_testing; __DATA__ @@ spec.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Test spec response" }, "basePath" : "/api", "paths" : { "/spec" : { "get" : { "operationId" : "Spec", "parameters" : [ { "in": "body", "name": "body", "schema": { "type" : "object" } } ], "responses" : { "200": { "description": "Spec response.", "schema": { "$ref": "#/definitions/SpecResponse" } } } } }, "/user/{id}" : { "parameters" : [ { "in": "path", "name": "id", "type": "integer", "required": true } ], "get" : { "operationId" : "user", "responses" : { "200": { "description": "User response.", "schema": { "type": "object" } } } } } }, "definitions": { "Object": { "type": "object" }, "SpecResponse": { "type": "object", "properties": { "get": { "$ref": "#/definitions/Object" } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v3-writeonly.t000644 000765 000024 00000000210 14013056427 024555 xustar00jhthorsenstaff000000 000000 30 mtime=1613520151.424114026 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v3-writeonly.t000644 000765 000024 00000002365 14013056427 022621 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; post '/required' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {id => 1}, status => 201); }, 'with_required'; plugin OpenAPI => {url => 'data:///schema.json'}; my $t = Test::Mojo->new; $t->post_ok('/required' => json => {app_id => 1})->status_is(201); done_testing; sub post_test { } __DATA__ @@ schema.json { "openapi": "3.0.3", "info": { "title": "Test", "version": "0.0.0" }, "paths": { "/required": { "post": { "operationId": "with_required", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Required" } } } }, "responses": { "201": { "description": "ok", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Required" } } } } } } } }, "components": { "schemas": { "Required": { "required": ["app_id", "id"], "properties": { "app_id": { "type": "integer", "writeOnly": true }, "id": { "type": "integer", "readOnly": true } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v3-basic.t000644 000765 000024 00000000210 14062524545 023607 xustar00jhthorsenstaff000000 000000 30 mtime=1623894373.006929815 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v3-basic.t000644 000765 000024 00000007041 14062524545 021647 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Mojo::File 'path'; use Test::Mojo; use Test::More; use Mojolicious::Lite; get '/pets/:petId' => sub { my $c = shift->openapi->valid_input or return; my $input = $c->validation->output; my $output = {id => $input->{petId}, name => 'Cow'}; $output->{age} = 6 if $input->{wantAge}; $c->render(openapi => $output); }, 'showPetById'; get '/pets' => sub { my $c = shift->openapi->valid_input or return; $c->res->headers->header('x-next' => $c->param('limit') // 0); $c->render(openapi => $c->param('limit') ? [] : {}); }, 'listPets'; post '/pets' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => '', status => 201); }, 'createPets'; my $openapi_plugin = plugin OpenAPI => { url => path(__FILE__)->dirname->child(qw(spec v3-petstore.json)), renderer => sub { my ($c, $data) = @_; my $ct = $c->stash('openapi_negotiated_content_type') || 'application/json'; return '' if $c->stash('status') == 201; $c->res->headers->content_type($ct); return '' if $ct =~ m!^application/xml!; return Mojo::JSON::encode_json($data); } }; my $t = Test::Mojo->new; $t->get_ok('/v1.json')->status_is(200)->json_like('/servers/0/url', qr{^http://[^/]+/v1$}) ->json_hasnt('/basePath'); ok !$openapi_plugin->validator->data->{basePath}, 'basePath was not added'; $t->get_ok('/v1/pets?limit=invalid', {Accept => 'application/json'})->status_is(400) ->json_is('/errors/0/path', '/limit') ->json_is('/errors/0/message', 'Expected integer - got string.'); $t->get_ok('/v1/pets?limit=5', {Accept => 'application/json'})->status_is(200) ->header_is('x-next', 5)->content_is('[]'); $t->get_ok('/v1/pets?limit=10', {Accept => 'not/*'})->status_is(400) ->json_is('/errors/0/message', 'Expected application/json, application/xml - got not/*.'); $t->get_ok('/v1/pets?limit=0', {Accept => 'application/json'})->status_is(500) ->json_is('/errors/0/message', 'Expected array - got object.'); $t->get_ok('/v1/pets?limit=10', {Accept => 'application/json'})->status_is(200) ->header_like('Content-Type' => qr{^application/json})->content_is('[]'); $t->get_ok('/v1/pets?limit=10', {Accept => 'application/*'})->status_is(200) ->header_like('Content-Type' => qr{^application/json})->content_is('[]'); $t->get_ok('/v1/pets?limit=10', {Accept => 'text/html,application/xml;q=0.9,*/*;q=0.8'}) ->status_is(200)->header_like('Content-Type' => qr{^application/xml})->content_is(''); $t->get_ok('/v1/pets?limit=10', {Accept => 'text/html,*/*;q=0.8'})->status_is(200) ->header_like('Content-Type' => qr{^application/json})->content_is('[]'); $t->get_ok('/v1/pets?limit=10', {Accept => 'application/json'})->status_is(200)->content_is('[]'); $t->post_ok('/v1/pets', {Accept => 'application/json', Cookie => 'debug=foo'})->status_is(400) ->json_is('/errors/0/message', 'Missing property.')->json_is('/errors/0/path', '/body'); $t->post_ok('/v1/pets', {Cookie => 'debug=1'}, json => {id => 1, name => 'Supercow'}) ->status_is(201)->content_is(''); $t->post_ok('/v1/pets', form => {id => 1, name => 'Supercow'})->status_is(201)->content_is(''); $t->get_ok('/v1/pets/23?wantAge=yes', {Accept => 'application/json'})->status_is(400) ->json_is('/errors/0/message', 'Expected boolean - got string.'); $t->get_ok('/v1/pets/23?wantAge=true', {Accept => 'application/json'})->status_is(200) ->json_is('/id', 23)->json_is('/age', 6); $t->get_ok('/v1/pets/23?wantAge=false', {Accept => 'application/json'})->status_is(200) ->json_is('/id', 23)->json_is('/age', undef); done_testing; Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v2-discriminator.t000644 000765 000024 00000000152 13736770604 025407 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v2-discriminator.t000644 000765 000024 00000006305 13736770604 023444 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; post '/pets' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->req->json); }, 'addPet'; plugin OpenAPI => {url => 'data://main/discriminator.json'}; exit app->start(@ARGV) if @ARGV and $ARGV[0] eq 'routes'; my $t = Test::Mojo->new; my %cat = (name => 'kit-e-cat', petType => 'Cat', huntingSkill => "adventurous"); my %dog = (name => 'dog-e-dog', petType => 'Dog', packSize => 4); # jhthorsen: The error message is not very good. # I think this must be fixed in JSON::Validator. # {"errors":[{"message":"allOf failed: Missing property.","path":"\/body"}]} $t->post_ok('/api/pets' => json => {%cat, petType => 'Dog'})->status_is(400) ->json_like('/errors/0/message', qr{Missing property}); $t->post_ok('/api/pets' => json => {%cat})->status_is(200); $t->post_ok('/api/pets' => json => {%dog, petType => 'Cat'})->status_is(400) ->json_like('/errors/0/message', qr{Missing property}); $t->post_ok('/api/pets' => json => {%dog})->status_is(200); $t->post_ok('/api/pets' => json => {%dog, petType => ''})->status_is(400) ->json_is('/errors/0/message', 'Discriminator petType has no value.'); $t->post_ok('/api/pets' => json => {%dog, petType => 'Hamster'})->status_is(400) ->json_is('/errors/0/message', 'No definition for discriminator Hamster.'); done_testing; __DATA__ @@ discriminator.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Test discriminator" }, "consumes" : [ "application/json" ], "produces" : [ "application/json" ], "schemes" : [ "http" ], "basePath" : "/api", "paths" : { "/pets" : { "post" : { "operationId" : "addPet", "parameters" : [ { "in": "body", "name": "body", "schema": { "$ref" : "#/definitions/Pet" } } ], "responses" : { "200": { "description": "pet response", "schema": { "type": "object" } } } } } }, "definitions": { "Pet": { "type": "object", "discriminator": "petType", "required": [ "name", "petType" ], "properties": { "name": { "type": "string" }, "petType": { "type": "string" } } }, "Cat": { "description": "A representation of a cat", "allOf": [ { "$ref": "#/definitions/Pet" }, { "type": "object", "required": [ "huntingSkill" ], "properties": { "huntingSkill": { "type": "string", "description": "The measured skill for hunting", "default": "lazy", "enum": [ "clueless", "lazy", "adventurous", "aggressive" ] } } } ] }, "Dog": { "description": "A representation of a dog", "allOf": [ { "$ref": "#/definitions/Pet" }, { "type": "object", "required": [ "packSize" ], "properties": { "packSize": { "type": "integer", "format": "int32", "description": "the size of the pack the dog is from", "default": 0, "minimum": 0 } } } ] } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/basic-empty-response.t000644 000765 000024 00000000152 13736770604 026264 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/basic-empty-response.t000644 000765 000024 00000002156 13736770604 024321 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; my $t = Test::Mojo->new; my ($res, $status); get '/string' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $res, status => $status); }, 'File'; plugin OpenAPI => {url => 'data://main/file.json'}; ($res, $status) = ('', 200); $t->get_ok('/api/string')->status_is(200)->content_is('""'); ($res, $status) = (undef, 200); $t->get_ok('/api/string')->status_is(200)->content_is('null'); ($res, $status) = ('', 204); $t->get_ok('/api/string')->status_is(204)->content_is(''); done_testing; package main; __DATA__ @@ file.json { "swagger": "2.0", "info": {"version": "0.8", "title": "Test empty response"}, "schemes": ["http"], "basePath": "/api", "paths": { "/string": { "get": { "operationId": "File", "responses": { "200": { "description": "response", "schema": {"type": ["null", "string"]} }, "204": { "description": "empty", "schema": {"type": ["string"]} } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v2-basic.t000644 000765 000024 00000000210 14277263743 023616 xustar00jhthorsenstaff000000 000000 30 mtime=1660774371.305490733 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v2-basic.t000644 000765 000024 00000002117 14277263743 021655 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Mojo::File 'path'; use Test::Mojo; use Test::More; use Mojolicious::Lite; get '/pets/:petId' => sub { my $c = shift->openapi->valid_input or return; my $input = $c->validation->output; my $output = {id => $input->{petId}, name => 'Cow'}; $output->{age} = 6 if $input->{wantAge}; $c->render(openapi => $output); }, 'showPetById'; get '/pets' => sub { my $c = shift->openapi->valid_input or return; $c->res->headers->header('x-next' => $c->param('limit') // 0); $c->render(openapi => $c->param('limit') ? [] : {}); }, 'listPets'; plugin OpenAPI => {url => path(__FILE__)->dirname->child(qw(spec v2-petstore.json))}; my $t = Test::Mojo->new; $t->get_ok('/v1.json')->status_is(200)->json_has('/basePath'); $t->get_ok('/v1/pets?limit=invalid', {Accept => 'application/json'})->status_is(400) ->json_is('/errors/0/path', '/limit') ->json_is('/errors/0/message', 'Expected integer - got string.'); $t->get_ok('/v1/pets?limit=5', {Accept => 'application/json'})->status_is(200) ->header_is('x-next', 5)->content_is('[]'); done_testing; Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v2-swagger.t000644 000765 000024 00000000210 14277266256 024176 xustar00jhthorsenstaff000000 000000 30 mtime=1660775598.463937541 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v2-swagger.t000644 000765 000024 00000001260 14277266256 022233 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Mojo::File 'path'; use Test::Mojo; use Test::More; use Mojolicious::Lite; subtest 'check that we can load swagger.yaml' => sub { eval { plugin OpenAPI => {url => path(__FILE__)->dirname->child(qw(spec swagger swagger.yaml))}; ok 1, 'spec loaded'; } or do { diag $@; ok 0, 'spec loaded'; }; }; my $t = Test::Mojo->new; subtest 'check that we can add DefaultResponse to paths/ref.yaml' => sub { $t->get_ok('/swagger.json')->status_is(200) ->json_is('/paths/~1external~1ref/get/responses/200/description', 'Ref response') ->json_is('/paths/~1external~1ref/get/responses/500/description', 'Default response.'); }; done_testing; Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/basic-invalid-json-input.t000644 000765 000024 00000000210 14013056427 027004 xustar00jhthorsenstaff000000 000000 30 mtime=1613520151.415906612 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/basic-invalid-json-input.t000644 000765 000024 00000001643 14013056427 025046 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; post '/invalid' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {x => 42}); }, 'invalid'; plugin OpenAPI => {url => 'data://main/spec.json', default_response => undef}; my $t = Test::Mojo->new; $t->post_ok('/api/invalid')->status_is(400)->json_is('/errors/0/message', 'Missing property.'); done_testing; __DATA__ @@ spec.json { "swagger" : "2.0", "info" : { "version": "0.1", "title" : "Test response codes" }, "basePath" : "/api", "paths" : { "/invalid": { "post" : { "operationId" : "invalid", "parameters": [ {"in": "body", "name": "body", "required": true, "schema": {"type": "object"}} ], "responses" : { "200": { "description": "Info", "schema": { "type": "object" } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/00-project.t000644 000765 000024 00000000152 14374621055 024070 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/00-project.t000644 000765 000024 00000003173 14374621055 022125 0ustar00jhthorsenstaff000000 000000 use Test::More; use File::Find; plan skip_all => 'No such directory: .git' unless $ENV{TEST_ALL} or -d '.git'; plan skip_all => 'HARNESS_PERL_SWITCHES =~ /Devel::Cover/' if +($ENV{HARNESS_PERL_SWITCHES} || '') =~ /Devel::Cover/; for (qw( Test::CPAN::Changes::changes_file_ok+VERSION!4 Test::Pod::Coverage::pod_coverage_ok+VERSION!1 Test::Pod::pod_file_ok+VERSION!1 Test::Spelling::pod_file_spelling_ok+has_working_spellchecker!1 )) { my ($fqn, $module, $sub, $check, $skip_n) = /^((.*)::(\w+))\+(\w+)!(\d+)$/; next if eval "use $module;$module->$check"; no strict qw(refs); *$fqn = sub { SKIP: { skip "$sub(@_) ($module is required)", $skip_n } }; } my @files; find({wanted => sub { /\.pm$/ and push @files, $File::Find::name }, no_chdir => 1}, -e 'blib' ? 'blib' : 'lib'); plan tests => @files * 4 + 4; Test::Spelling::add_stopwords() if Test::Spelling->can('has_working_spellchecker') && Test::Spelling->has_working_spellchecker; for my $file (@files) { my $module = $file; $module =~ s,\.pm$,,; $module =~ s,.*/?lib/,,; $module =~ s,/,::,g; ok eval "use $module; 1", "use $module" or diag $@; Test::Pod::pod_file_ok($file); Test::Pod::Coverage::pod_coverage_ok($module, {also_private => [qr/^[A-Z_]+$/]}); Test::Spelling::pod_file_spelling_ok($file); } Test::CPAN::Changes::changes_file_ok(); __DATA__ Anwar Bernhard Berov CORS Gim Graf Hege Henning Hradek Hyeon Ji Krasimir Kristensen Linn Lund Mojolicious Morrott Oauth OpenAPI OpenAPIv Preflighted RENDERER Rassadin Renvoize SebMourlhou Søren Thegler Thorsen basePath html mojo openapi preflight preflighted renderer securityDefinitions validator Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v3-tutorial.t000644 000765 000024 00000000210 14013056427 024364 xustar00jhthorsenstaff000000 000000 30 mtime=1613520151.423293705 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v3-tutorial.t000644 000765 000024 00000005117 14013056427 022426 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; make_app(); make_controller(); my $t = Test::Mojo->new('Myapp'); $t->get_ok('/api')->status_is(200)->json_is('/info/title', 'Some awesome API'); $t->get_ok('/api/pets' => {'Content-Type' => 'application/json'})->status_is(200) ->json_is('/pets/0/name', 'kit-e-cat'); done_testing; sub make_app { eval <<'HERE' or die $@; package Myapp; use Mojo::Base "Mojolicious"; sub startup { my $app = shift; $app->plugin("OpenAPI" => {url => "data://main/myapi.json"}); } $ENV{"Myapp.pm"} = 1; HERE } sub make_controller { eval <<'HERE' or die $@; package Myapp::Controller::Pet; use Mojo::Base "Mojolicious::Controller"; sub list { # Do not continue on invalid input and render a default 400 # error document. my $c = shift; $c = $c->openapi->valid_input or return; # $c->openapi->valid_input copies valid data to validation object, # and the normal Mojolicious api works as well. my $input = $c->validation->output; my $age = $c->param("age"); # same as $input->{age} my $body = $c->req->json; # same as $input->{body} # $output will be validated by the OpenAPI spec before rendered my $output = {pets => [{name => "kit-e-cat"}]}; $c->render(openapi => $output); } $ENV{"Myapp/Controller/Pet.pm"} = 1; HERE } __DATA__ @@ myapi.json { "openapi": "3.0.2", "info": { "version": "1.0", "title": "Some awesome API" }, "paths": { "/pets": { "get": { "operationId": "getPets", "x-mojo-name": "get_pets", "x-mojo-to": "pet#list", "summary": "Finds pets in the system", "parameters": [ { "in": "query", "name": "age", "schema": { "type": "integer" } } ], "requestBody": { "content": { "application/json": { "schema": { "type": "object" } } } }, "responses": { "200": { "description": "Pet response", "content": { "application/json": { "schema": { "type": "object", "properties": { "pets": { "type": "array", "items": { "type": "object" } } } } } } } } } } }, "servers": [ { "url": "/api" } ] } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v2-collectionformat.t000644 000765 000024 00000000210 14217212065 026062 xustar00jhthorsenstaff000000 000000 30 mtime=1648170037.106896966 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v2-collectionformat.t000644 000765 000024 00000016345 14217212065 024131 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; get '/header' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'getHeader'; get '/pets' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'getPets'; post '/pets' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'postPets'; get '/pets/:id' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'getPetsById'; plugin OpenAPI => {url => 'data://main/discriminator.json'}; my $t = Test::Mojo->new; subtest 'Expected array - got null' => sub { $t->get_ok('/api/pets')->status_is(400)->json_is('/errors/0/path', '/ri'); }; subtest 'Expected integer - got number.' => sub { $t->get_ok('/api/pets?ri=1.3')->status_is(400)->json_is('/errors/0/path', '/ri/0'); }; subtest 'Not enough items: 1\/2' => sub { $t->get_ok('/api/pets?ri=3&ml=5')->status_is(400)->json_is('/errors/0/path', '/ml'); }; subtest 'Valid' => sub { $t->get_ok('/api/pets?ri=3&ml=4&ml=2')->status_is(200)->json_is('/ml', [4, 2]) ->json_is('/ri', [3]); }; subtest 'In path' => sub { $t->get_ok('/api/pets/ilm,a,r,i')->status_is(200)->json_is('/id', [qw(ilm a r i)]); }; subtest 'In query' => sub { $t->post_ok('/api/pets?idq=ilm,a,r,i')->status_is(200)->json_is('/idq', [qw(ilm a r i)]); $t->post_ok("/api/pets?idq-tsv=ilm\ta\tr\ti")->status_is(200) ->json_is('/idq-tsv', [qw(ilm a r i)]); $t->post_ok('/api/pets?idq-ssv=ilm a r i')->status_is(200)->json_is('/idq-ssv', [qw(ilm a r i)]); $t->post_ok('/api/pets?idq-pipes=ilm|a|r|i')->status_is(200) ->json_is('/idq-pipes', [qw(ilm a r i)]); }; subtest 'In formData' => sub { $t->post_ok('/api/pets' => form => {idf => 'ilm,a,r,i'})->status_is(200) ->json_is('/idf', [qw(ilm a r i)]); $t->post_ok('/api/pets' => form => {'idf-tsv' => "ilm\ta\tr\ti"})->status_is(200) ->json_is('/idf-tsv', [qw(ilm a r i)]); $t->post_ok('/api/pets' => form => {'idf-ssv' => 'ilm a r i'})->status_is(200) ->json_is('/idf-ssv', [qw(ilm a r i)]); $t->post_ok('/api/pets' => form => {'idf-pipes' => 'ilm|a|r|i', 'a' => 'b'})->status_is(200) ->json_is('/idf-pipes', [qw(ilm a r i)]); $t->post_ok('/api/pets' => {'Content-Type' => 'application/x-www-form-urlencoded'} => 'idf-multi=ilm&idf-multi=a')->status_is(200)->json_is('/idf-multi', [qw(ilm a)]); }; subtest 'In header' => sub { $t->get_ok('/api/header')->status_is(200)->content_is('{}'); $t->get_ok('/api/header', {'X-Collection' => ''})->status_is(200)->json_is('/X-Collection' => []); $t->get_ok('/api/header', {'X-Collection' => 'a,b'})->status_is(200) ->json_is('/X-Collection' => [qw(a b)]); }; done_testing; __DATA__ @@ discriminator.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Test collectionFormat" }, "basePath": "/api", "paths" : { "/header" : { "get" : { "operationId" : "getHeader", "parameters" : [ { "name":"X-Collection", "in":"header", "type":"array", "collectionFormat":"csv", "items":{"type":"string"}, "minItems":0 } ], "responses" : { "200": { "description": "response", "schema": { "type": "object" } } } } }, "/pets/{id}" : { "get" : { "operationId" : "getPetsById", "parameters" : [ { "name":"id", "in":"path", "type":"array", "collectionFormat":"csv", "items":{"type":"string"}, "minItems":0, "required":true } ], "responses" : { "200": { "description": "pet response", "schema": { "type": "object" } } } } }, "/pets" : { "get" : { "operationId" : "getPets", "parameters" : [ { "name":"no", "in":"query", "type":"array", "collectionFormat":"multi", "items":{"type":"integer"}, "minItems":0 }, { "name":"ml", "in":"query", "type":"array", "collectionFormat":"multi", "items":{"type":"integer"}, "minItems":2 }, { "name":"ri", "in":"query", "type":"array", "collectionFormat":"multi", "required":true, "items":{"type":"integer"}, "minItems":1 } ], "responses" : { "200": { "description": "pet response", "schema": { "type": "object" } } } }, "post" : { "operationId" : "postPets", "parameters" : [ { "name":"idq", "in":"query", "type":"array", "collectionFormat":"csv", "items":{"type":"string"}, "minItems":0, "required":false }, { "name":"idq-tsv", "in":"query", "type":"array", "collectionFormat":"tsv", "items":{"type":"string"}, "minItems":0, "required":false }, { "name":"idq-ssv", "in":"query", "type":"array", "collectionFormat":"ssv", "items":{"type":"string"}, "minItems":0, "required":false }, { "name":"idq-pipes", "in":"query", "type":"array", "collectionFormat":"pipes", "items":{"type":"string"}, "minItems":0, "required":false }, { "name":"idf", "in":"formData", "type":"array", "collectionFormat":"csv", "items":{"type":"string"}, "minItems":0, "required":false }, { "name":"idf-tsv", "in":"formData", "type":"array", "collectionFormat":"tsv", "items":{"type":"string"}, "minItems":0, "required":false }, { "name":"idf-ssv", "in":"formData", "type":"array", "collectionFormat":"ssv", "items":{"type":"string"}, "minItems":0, "required":false }, { "name":"idf-pipes", "in":"formData", "type":"array", "collectionFormat":"pipes", "items":{"type":"string"}, "minItems":0, "required":false }, { "name":"idf-multi", "in":"formData", "type":"array", "collectionFormat":"multi", "items":{"type":"string"}, "minItems":0, "required":false } ], "responses" : { "200": { "description": "pet response", "schema": { "type": "object" } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/plugin-security-v2.t000644 000765 000024 00000000210 14013056427 025663 xustar00jhthorsenstaff000000 000000 30 mtime=1613520151.417158509 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/plugin-security-v2.t000644 000765 000024 00000024103 14013056427 023721 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; post '/global' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 1}); }, 'global'; post('/fail_escape' => sub { shift->render(openapi => {ok => 1}) }, 'fail_escape'); post '/simple' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 1}); }, 'simple'; options '/options' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 1}); }, 'options'; post '/fail_or_pass' => sub { my $c = shift->openapi->valid_input or return; die 'Could not connect to dummy database error message' if $ENV{DUMMY_DB_ERROR}; $c->render(openapi => {ok => 1}); }, 'fail_or_pass'; post '/fail_and_pass' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 1}); }, 'fail_and_pass'; post '/multiple_fail' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 1}); }, 'multiple_fail'; post '/multiple_and_fail' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 1}); }, 'multiple_and_fail'; post '/cache' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 1}); }, 'cache'; post '/die' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {ok => 1}); }, 'die'; our %checks; plugin OpenAPI => { url => 'data://main/sec.json', security => { pass1 => sub { my ($c, $def, $scopes, $cb) = @_; $checks{pass1}++; $c->$cb; }, pass2 => sub { my ($c, $def, $scopes, $cb) = @_; $checks{pass2}++; $c->$cb; }, fail1 => sub { my ($c, $def, $scopes, $cb) = @_; $checks{fail1}++; # This deferment causes multiple_and_fail to report # out of order unless order is carefully maintained Mojo::IOLoop->next_tick(sub { $c->$cb('Failed fail1') }); }, fail2 => sub { my ($c, $def, $scopes, $cb) = @_; $checks{fail2}++; my %res = %$def; $res{message} = 'Failed fail2'; $c->$cb(\%res); }, '~fail/escape' => sub { my ($c, $def, $scopes, $cb) = @_; $checks{'~fail/escape'}++; $c->$cb('Failed ~fail/escape'); }, die => sub { my ($c, $def, $scopes, $cb) = @_; $checks{die}++; die 'Argh!'; }, }, }; my %security_definition = (description => 'fail2', in => 'header', name => 'Authorization', type => 'apiKey'); my $t = Test::Mojo->new; { local %checks; $t->post_ok('/api/global' => json => {})->status_is(200)->json_is('/ok' => 1); is_deeply \%checks, {pass1 => 1}, 'expected checks occurred'; } { # global does not define an options handler, so it gets the default # which is allowed through the security local %checks; $t->options_ok('/api/global')->status_is(200); is_deeply \%checks, {}, 'expected checks occurred'; } { local %checks; $t->post_ok('/api/simple' => json => {})->status_is(200)->json_is('/ok' => 1); is_deeply \%checks, {pass2 => 1}, 'expected checks occurred'; } { # route defined with an options handler so it must use the defined security local %checks; $t->options_ok('/api/options' => json => {})->status_is(200)->json_is('/ok' => 1); is_deeply \%checks, {pass1 => 1}, 'expected checks occurred'; } { local %checks; $t->post_ok('/api/fail_or_pass' => json => {})->status_is(200)->json_is('/ok' => 1); is_deeply \%checks, {fail1 => 1, pass1 => 1}, 'expected checks occurred'; } { local $ENV{DUMMY_DB_ERROR} = 1; $t->post_ok('/api/fail_or_pass' => json => {})->status_is(500) ->json_is('/errors/0/message', 'Internal Server Error.')->json_is('/errors/0/path', '/'); } { local %checks; $t->post_ok('/api/fail_and_pass' => json => {})->status_is(401) ->json_is( {errors => [{message => 'Failed fail1', path => '/security/0/fail1'}], status => 401}); is_deeply \%checks, {fail1 => 1, pass1 => 1}, 'expected checks occurred'; } { local %checks; $t->post_ok('/api/multiple_fail' => json => {})->status_is(401)->json_is({ status => 401, errors => [ {message => 'Failed fail1', path => '/security/0/fail1'}, {message => 'Failed fail2', %security_definition}, ] }); is_deeply \%checks, {fail1 => 1, fail2 => 1}, 'expected checks occurred'; } { local %checks; $t->post_ok('/api/multiple_and_fail' => json => {})->status_is(401)->json_is({ status => 401, errors => [ {message => 'Failed fail1', path => '/security/0/fail1'}, {message => 'Failed fail2', %security_definition} ] }); is_deeply \%checks, {fail1 => 1, fail2 => 1}, 'expected checks occurred'; } { local %checks; $t->post_ok('/api/fail_escape' => json => {})->status_is(401)->json_is( { errors => [{message => 'Failed ~fail/escape', path => '/security/0/~0fail~1escape'}], status => 401 } ); is_deeply \%checks, {'~fail/escape' => 1}, 'expected checks occurred'; } { local %checks; $t->post_ok('/api/cache' => json => {})->status_is(200)->json_is('/ok' => 1); is_deeply \%checks, {fail1 => 1, pass1 => 1, pass2 => 1}, 'expected checks occurred'; } { local %checks; $t->post_ok('/api/die' => json => {})->status_is(500)->json_has('/errors/0/message'); is_deeply \%checks, {die => 1}, 'expected checks occurred'; } done_testing; __DATA__ @@ sec.json { "swagger": "2.0", "info": { "version": "0.8", "title": "Pets" }, "schemes": [ "http" ], "basePath": "/api", "securityDefinitions": { "pass1": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "pass1" }, "pass2": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "pass2" }, "fail1": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "fail1" }, "fail2": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "fail2" }, "~fail/escape": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "dummy" }, "die": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "die" } }, "security": [{"pass1": []}], "paths": { "/global": { "post": { "x-mojo-name": "global", "parameters": [ { "in": "body", "name": "body", "schema": { "type": "object" } } ], "responses": { "200": {"description": "Echo response", "schema": { "type": "object" }} } } }, "/simple": { "post": { "x-mojo-name": "simple", "security": [{"pass2": []}], "parameters": [ { "in": "body", "name": "body", "schema": { "type": "object" } } ], "responses": { "200": {"description": "Echo response", "schema": { "type": "object" }} } } }, "/options": { "options": { "x-mojo-name": "options", "security": [{"pass1": []}], "parameters": [ { "in": "body", "name": "body", "schema": { "type": "object" } } ], "responses": { "200": {"description": "Echo response", "schema": { "type": "object" }} } } }, "/fail_or_pass": { "post": { "x-mojo-name": "fail_or_pass", "security": [ {"fail1": []}, {"pass1": []} ], "parameters": [ { "in": "body", "name": "body", "schema": { "type": "object" } } ], "responses": { "200": {"description": "Echo response", "schema": { "type": "object" }} } } }, "/fail_and_pass": { "post": { "x-mojo-name": "fail_and_pass", "security": [ { "fail1": [], "pass1": [] } ], "parameters": [ { "in": "body", "name": "body", "schema": { "type": "object" } } ], "responses": { "200": {"description": "Echo response", "schema": { "type": "object" }} } } }, "/multiple_fail": { "post": { "x-mojo-name": "multiple_fail", "security": [ { "fail1": [] }, { "fail2": [] } ], "parameters": [ { "in": "body", "name": "body", "schema": { "type": "object" } } ], "responses": { "200": {"description": "Echo response", "schema": { "type": "object" }} } } }, "/multiple_and_fail": { "post": { "x-mojo-name": "multiple_and_fail", "security": [ { "fail1": [], "fail2": [] } ], "parameters": [ { "in": "body", "name": "body", "schema": { "type": "object" } } ], "responses": { "200": {"description": "Echo response", "schema": { "type": "object" }} } } }, "/fail_escape": { "post": { "x-mojo-name": "fail_escape", "security": [{"~fail/escape": []}], "parameters": [ { "in": "body", "name": "body", "schema": { "type": "object" } } ], "responses": { "200": {"description": "Echo response", "schema": { "type": "object" }} } } }, "/cache": { "post": { "x-mojo-name": "cache", "security": [ { "fail1": [], "pass1": [] }, { "pass1": [], "pass2": [] } ], "parameters": [ { "in": "body", "name": "body", "schema": { "type": "object" } } ], "responses": { "200": {"description": "Echo response", "schema": { "type": "object" }} } } }, "/die": { "post": { "x-mojo-name": "die", "security": [ {"die": []}, {"pass1": []} ], "parameters": [ { "in": "body", "name": "body", "schema": { "type": "object" } } ], "responses": { "200": {"description": "Echo response", "schema": { "type": "object" }} } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/data000755 000765 000024 00000000210 14374621224 022650 xustar00jhthorsenstaff000000 000000 30 mtime=1676878484.566655849 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/data/000755 000765 000024 00000000000 14374621224 020760 5ustar00jhthorsenstaff000000 000000 Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v3-invalid_file_refs_no_path.t000644 000765 000024 00000000210 14125731256 027701 xustar00jhthorsenstaff000000 000000 30 mtime=1633137326.288933185 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v3-invalid_file_refs_no_path.t000644 000765 000024 00000001317 14125731256 025741 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; get '/test' => sub { my $c = shift->openapi->valid_input or return; $c->render(status => 200, openapi => $c->param('pcversion')); }, 'File'; plugin OpenAPI => {url => app->home->rel_file('spec/v3-invalid_file_refs_no_path.yaml')}; my $t = Test::Mojo->new; $t->get_ok('/api')->status_is(200)->json_hasnt('/PCVersion/name')->json_has('/components/schemas') ->content_like(qr!v3-valid_include_yaml!); eval { die JSON::Validator::Schema::OpenAPIv3->new($t->get_ok('/api')->tx->res->json)->errors->[0] }; like $@, qr/Properties not allowed: components/, 'load_and_validate_schema fails, wrong placement of data'; done_testing; Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v3-defaults.t000644 000765 000024 00000000210 14013056427 024330 xustar00jhthorsenstaff000000 000000 30 mtime=1613520151.420706537 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v3-defaults.t000644 000765 000024 00000002007 14013056427 022365 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; get '/test' => sub { my $c = shift->openapi->valid_input or return; $c->render(status => 200, openapi => $c->param('pcversion')); }, 'File'; plugin OpenAPI => {url => 'data://main/file.yaml'}; my $t = Test::Mojo->new; $t->get_ok('/api/test')->status_is(200)->content_is('"10.1.0"'); done_testing; package main; __DATA__ @@ file.yaml openapi: 3.0.0 info: title: Test defaults version: "1" servers: - url: /api paths: /test: get: operationId: File parameters: - $ref: "#/components/parameters/PCVersion" responses: "200": description: thing content: "*/*": schema: type: string components: parameters: PCVersion: name: pcversion in: query description: version of commands which will run on backend schema: type: string enum: - 9.6.1 - 10.1.0 default: 10.1.0 Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/basic-404-501.t000644 000765 000024 00000000210 14013060620 024052 xustar00jhthorsenstaff000000 000000 30 mtime=1613521296.075613229 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/basic-404-501.t000644 000765 000024 00000004373 14013060620 022117 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; package MyApp; use Mojo::Base 'Mojolicious'; sub startup { my $app = shift; $app->plugin(OpenAPI => {url => 'data://main/v2.yaml'}); $app->plugin(OpenAPI => {url => 'data://main/v3.yaml'}); } package MyApp::Controller::User; use Mojo::Base 'Mojolicious::Controller'; sub find { my $c = shift->openapi->valid_input or return; $c->render(openapi => {age => 42}, status => $c->param('code') || 200); } package main; my $t = Test::Mojo->new(MyApp->new); for my $base_url (qw(/v2 /v3)) { subtest $base_url => sub { $t->get_ok("$base_url/user")->status_is(200)->json_is('/age', 42); $t->get_ok("$base_url/user?code=201")->status_is(501) ->json_is('/errors/0/message', 'No response rule for "201".'); $t->get_ok("$base_url/user/foo")->status_is(404)->json_is('/errors/0/message', 'Not Found.'); }; } done_testing; __DATA__ @@ v2.yaml swagger: "2.0" info: {version: "0.8", title: v2} basePath: /v2 paths: /user: delete: x-mojo-to: 'user#delete' responses: 200: description: 'TODO' schema: {type: object} get: x-mojo-to: 'user#find' responses: 200: description: User schema: type: object properties: age: {type: integer} post: x-mojo-to: 'user#create' parameters: - {in: formData, name: age, type: integer} responses: 400: description: Error schema: {type: object} @@ v3.yaml openapi: 3.0.0 info: {version: '0.8', title: v2} servers: - url: http://petstore.swagger.io/v3 paths: /user: delete: x-mojo-to: 'user#delete' responses: 200: description: 'TODO' content: application/json: schema: {type: object} get: x-mojo-to: 'user#find' responses: 200: description: User content: application/json: schema: type: object properties: age: {type: integer} post: x-mojo-to: 'user#create' responses: 400: description: Error content: application/json: schema: {type: object} Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v2-file.t000644 000765 000024 00000000152 13742714631 023452 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v2-file.t000644 000765 000024 00000002126 13742714631 021504 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; post '/user' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {}); }, 'createUser'; plugin OpenAPI => {url => 'data://main/readonly.json'}; my $t = Test::Mojo->new; $t->post_ok('/api/user')->status_is(400) ->json_is('/errors/0', {message => 'Missing property.', path => '/image'}); my $image = Mojo::Asset::Memory->new->add_chunk('smileyface'); $t->post_ok('/api/user', form => {id => 1, image => {file => $image}})->status_is(200); done_testing; __DATA__ @@ readonly.json { "swagger": "2.0", "info": { "version": "0.8", "title": "Test readonly" }, "schemes": [ "http" ], "basePath": "/api", "paths": { "/user": { "post": { "operationId": "createUser", "parameters": [ {"name": "image", "in": "formData", "type": "file", "required": true}, {"name": "id", "in": "formData", "type": "string"} ], "responses": { "200": { "description": "ok", "schema": { "type": "object" } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v3-style-array.t000644 000765 000024 00000000210 14146067564 025010 xustar00jhthorsenstaff000000 000000 30 mtime=1637379956.749707835 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v3-style-array.t000644 000765 000024 00000020607 14146067564 023053 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; get '/pets' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'getPets'; get '/pets/:id' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'getPetsById'; get '/petsByLabelId#id' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'getPetsByLabelId'; get '/petsByExplodedLabelId#id' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'getPetsByExplodedLabelId'; get '/petsByMatrixId#id' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'getPetsByMatrixId'; get '/petsByExplodedMatrixId#id' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'getPetsByExplodedMatrixId'; plugin OpenAPI => {url => 'data:///parameters.json'}; my $t = Test::Mojo->new; subtest 'Expected array - got null' => sub { $t->get_ok('/api/pets')->status_is(400)->json_is('/errors/0/path', '/ri'); }; subtest 'Expected integer - got number.' => sub { $t->get_ok('/api/pets?ri=1.3')->status_is(400)->json_is('/errors/0/path', '/ri/0'); }; subtest 'Not enough items: 1\/2' => sub { $t->get_ok('/api/pets?ri=3&ml=5')->status_is(400)->json_is('/errors/0/path', '/ml'); }; subtest 'Valid, in path' => sub { $t->get_ok('/api/pets/10,11,12')->status_is(200)->json_is('/id', [qw(10 11 12)]); $t->get_ok('/api/pets/10')->status_is(200)->content_like(qr{"id":\[10\]}); $t->get_ok('/api/petsByLabelId.3,4,5')->status_is(200)->json_is('/id', [qw(3 4 5)]); $t->get_ok('/api/petsByLabelId.5')->status_is(200)->json_is('/id', [5]); $t->get_ok('/api/petsByExplodedLabelId.3.4.5')->status_is(200)->json_is('/id', [qw(3 4 5)]); $t->get_ok('/api/petsByExplodedLabelId.5')->status_is(200)->json_is('/id', [5]); $t->get_ok('/api/petsByMatrixId;id=3,4,5')->status_is(200)->json_is('/id', [qw(3 4 5)]); $t->get_ok('/api/petsByMatrixId;id=5')->status_is(200)->json_is('/id', [5]); $t->get_ok('/api/petsByExplodedMatrixId;id=3;id=4;id=5')->status_is(200) ->json_is('/id', [qw(3 4 5)]); $t->get_ok('/api/petsByExplodedMatrixId;id=5')->status_is(200)->json_is('/id', [5]); }; subtest 'Valid, in query' => sub { $t->get_ok('/api/pets?ri=3&ml=4&ml=2&no=5')->status_is(200)->json_is('/ri', [3]) ->content_like(qr{"ml":\["4","2"\]})->content_like(qr{"no":\[5\]}); $t->get_ok('/api/pets?ri=3&no=5,6&sp=7 8 9&pi=10|11')->status_is(200)->json_is('/no', [5, 6]) ->json_is('/sp', [7, 8, 9])->json_is('/pi', [10, 11]); }; done_testing; __DATA__ @@ parameters.json { "openapi": "3.0.0", "info": { "license": { "name": "MIT" }, "title": "Swagger Petstore", "version": "1.0.0" }, "servers": [ { "url": "/api" } ], "paths": { "/pets/{id}": { "get": { "operationId": "getPetsById", "parameters": [ { "name": "id", "in": "path", "required": true, "style": "simple", "schema": { "type": "array", "items": { "type": "integer" }, "minItems": 1 } } ], "responses": { "200": { "description": "pet response", "content": { "*/*": { "schema": { "type": "object" } } } } } } }, "/petsByLabelId{id}": { "get": { "operationId": "getPetsByLabelId", "parameters": [ { "name": "id", "in": "path", "required": true, "style": "label", "explode": false, "schema": { "type": "array", "items": { "type": "integer" }, "minItems": 1 } } ], "responses": { "200": { "description": "pet response", "content": { "*/*": { "schema": { "type": "object" } } } } } } }, "/petsByExplodedLabelId{id}": { "get": { "operationId": "getPetsByExplodedLabelId", "parameters": [ { "name": "id", "in": "path", "required": true, "style": "label", "explode": true, "schema": { "type": "array", "items": { "type": "integer" }, "minItems": 1 } } ], "responses": { "200": { "description": "pet response", "content": { "*/*": { "schema": { "type": "object" } } } } } } }, "/petsByMatrixId{id}": { "get": { "operationId": "getPetsByMatrixId", "parameters": [ { "name": "id", "in": "path", "required": true, "style": "matrix", "explode": false, "schema": { "type": "array", "items": { "type": "integer" }, "minItems": 1 } } ], "responses": { "200": { "description": "pet response", "content": { "*/*": { "schema": { "type": "object" } } } } } } }, "/petsByExplodedMatrixId{id}": { "get": { "operationId": "getPetsByExplodedMatrixId", "parameters": [ { "name": "id", "in": "path", "required": true, "style": "matrix", "explode": true, "schema": { "type": "array", "items": { "type": "integer" }, "minItems": 1 } } ], "responses": { "200": { "description": "pet response", "content": { "*/*": { "schema": { "type": "object" } } } } } } }, "/pets": { "get": { "operationId": "getPets", "parameters": [ { "name": "no", "in": "query", "style": "form", "explode": false, "schema": { "type": "array", "items": { "type": "integer" }, "minItems": 1 } }, { "name": "ml", "in": "query", "style": "form", "explode": true, "schema": { "type": "array", "items": { "type": "string" }, "minItems": 2 } }, { "name": "ri", "in": "query", "required": true, "style": "form", "explode": true, "schema": { "type": "array", "items": { "type": "integer" }, "minItems": 1 } }, { "name": "sp", "in": "query", "style": "spaceDelimited", "schema": { "type": "array", "items": { "type": "integer" } } }, { "name": "pi", "in": "query", "style": "pipeDelimited", "schema": { "type": "array", "items": { "type": "integer" } } } ], "responses": { "200": { "description": "pet response", "content": { "*/*": { "schema": { "type": "object" } } } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/v2-body.t000644 000765 000024 00000000152 14344504022 023456 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/v2-body.t000644 000765 000024 00000003056 14344504022 021513 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Test::Mojo; use Test::More; use Mojolicious::Lite; for ('string', 'array') { my ($path, $name) = ("/body-$_", 'body' . ucfirst); post $path => sub { warn $_[0]->req->body; my $c = shift->openapi->valid_input or return; $c->render(text => $c->req->body, status => 200); }, $name; } plugin OpenAPI => {url => 'data:///api.yml'}; my $t = Test::Mojo->new; $t->post_ok('/api/body-string', {'Content-Type' => 'text/plain'} => 'invalid_json')->status_is(200) ->content_is('invalid_json'); $t->post_ok('/api/body-array', json => [{cool => 'beans'}])->status_is(200) ->json_is('/0/cool', 'beans'); $t->post_ok('/api/body-array', json => ['str'])->status_is(400) ->json_is('/errors/0', {path => '/body/0', message => 'Expected object - got string.'}); done_testing; __DATA__ @@ api.yml { "swagger": "2.0", "info": {"version": "0.8", "title": "Raw data"}, "basePath": "/api", "paths": { "/body-string": { "post": { "x-mojo-name": "bodyString", "parameters": [ {"name": "echo", "in": "body", "schema": {"type": "string"}} ], "responses": { "200": {"description": "response", "schema": {"type": "string"}} } } }, "/body-array": { "post": { "x-mojo-name": "bodyArray", "parameters": [ {"name": "body", "in": "body", "schema": {"type": "array", "items": {"type": "object"}}} ], "responses": { "200": {"description": "response", "schema": {"type": "array"}} } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/PaxHeader/basic-coerce.t000644 000765 000024 00000000152 13754077517 024535 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/basic-coerce.t000644 000765 000024 00000002344 13754077517 022571 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Mojolicious; use Test::Mojo; use Test::More; my $coerced = t(); $coerced->post_ok('/api/user')->status_is(200)->json_is('/age', 34); $coerced->post_ok('/api/user', json => [{}])->status_is(400) ->json_is('/errors/0/message', 'Expected object - got array.'); my $strict = t(coerce => {}); $strict->post_ok('/api/user')->status_is(500)->json_has('/errors'); sub t { my $t = Test::Mojo->new(Mojolicious->new); $t->app->routes->post( '/user' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => {age => '34'}); # '34' is not an integer } )->name('user'); $t->app->plugin(OpenAPI => {url => 'data://main/user.json', @_}); $t; } done_testing; __DATA__ @@ user.json { "swagger" : "2.0", "info" : { "version": "0.8", "title" : "Pets" }, "schemes" : [ "http" ], "basePath" : "/api", "paths" : { "/user" : { "post" : { "x-mojo-name" : "user", "parameters": [ {"in": "body", "name": "body", "schema": {"type": "object"}} ], "responses" : { "200": { "description": "User", "schema": { "properties": { "age": { "type": "integer"} } } } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/data/PaxHeader/image.jpeg000644 000765 000024 00000000152 13736770604 024670 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/data/image.jpeg000644 000765 000024 00000000021 13736770604 022712 0ustar00jhthorsenstaff000000 000000 some binary data Mojolicious-Plugin-OpenAPI-5.09/t/spec/PaxHeader/bundlecheck.json000644 000765 000024 00000000152 13736770604 026122 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/bundlecheck.json000644 000765 000024 00000002005 13736770604 024150 0ustar00jhthorsenstaff000000 000000 { "swagger": "2.0", "info": { "title": "t-app", "version": "0.1.0", "license": { "name": "Apache License, Version 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" } }, "basePath": "/api", "host": "localhost:3000", "consumes": [ "application/json" ], "produces": [ "application/json" ], "paths": { "/t": { "get": { "operationId": "listT", "x-mojo-to": "Controller::OpenAPI::T#list", "tags": [ "t" ], "responses": { "200": { "description": "Self sufficient", "schema": { "items": { "type": "string" }, "type": "array" } }, "default": { "$ref": "#/responses/error" } } } } }, "responses": { "error": { "description": "Self sufficient", "schema": { "type": "object", "required": [ "error" ], "additionalProperties": false, "properties": { "error": { "type": "string" } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/spec/PaxHeader/v3-petstore.json000644 000765 000024 00000000152 13742714631 026041 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/v3-petstore.json000644 000765 000024 00000011346 13742714631 024077 0ustar00jhthorsenstaff000000 000000 { "openapi": "3.0.0", "info": { "license": { "name": "MIT" }, "title": "Swagger Petstore", "version": "1.0.0" }, "servers": [ { "url": "http://petstore.swagger.io/v1" } ], "paths": { "/pets/{petId}": { "get": { "operationId": "showPetById", "tags": [ "pets" ], "summary": "Info for a specific pet", "parameters": [ { "description": "The id of the pet to retrieve", "in": "path", "name": "petId", "required": true, "schema": { "type": "string" } }, { "description": "Indicates if the age is wanted in the response object", "in": "query", "name": "wantAge", "schema": { "type": "boolean" } } ], "responses": { "default": { "description": "unexpected error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }, "application/xml": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "200": { "description": "Expected response to a valid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } }, "application/xml": { "schema": { "$ref": "#/components/schemas/Pet" } } } } } } }, "/pets": { "get": { "operationId": "listPets", "summary": "List all pets", "tags": [ "pets" ], "parameters": [ { "description": "How many items to return at one time (max 100)", "in": "query", "name": "limit", "required": false, "schema": { "type": "integer", "format": "int32" } } ], "responses": { "200": { "description": "An paged array of pets", "headers": { "x-next": { "schema": { "type": "string" }, "description": "A link to the next page of responses" } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pets" } }, "application/xml": { "schema": { "$ref": "#/components/schemas/Pets" } } } }, "default": { "description": "unexpected error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } }, "application/xml": { "schema": { "$ref": "#/components/schemas/Error" } } } } } }, "post": { "operationId": "createPets", "summary": "Create a pet", "tags": [ "pets" ], "parameters": [ { "description": "Turn on/off debug", "in": "cookie", "name": "debug", "schema": { "type": "integer", "enum": [0, 1] } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Pet" } }, "application/x-www-form-urlencoded": { "schema": { "$ref": "#/components/schemas/Pet" } } } }, "responses": { "201": { "description": "Null response", "content": { "*/*": { "schema": { "type": "string" } } } }, "default": { "description": "unexpected error", "content": { "*/*": { "schema": { "$ref": "#/components/schemas/Error" } } } } } } } }, "components": { "schemas": { "Pets": { "type": "array", "items": { "$ref": "#/components/schemas/Pet" } }, "Pet": { "required": [ "id", "name" ], "properties": { "tag": { "type": "string" }, "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" }, "age": { "type": "integer" } } }, "Error": { "required": [ "code", "message" ], "properties": { "code": { "format": "int32", "type": "integer" }, "message": { "type": "string" } } } } } } Mojolicious-Plugin-OpenAPI-5.09/t/spec/PaxHeader/swagger000755 000765 000024 00000000210 14374621224 024330 xustar00jhthorsenstaff000000 000000 30 mtime=1676878484.573651144 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/swagger/000755 000765 000024 00000000000 14374621224 022440 5ustar00jhthorsenstaff000000 000000 Mojolicious-Plugin-OpenAPI-5.09/t/spec/PaxHeader/v3-invalid_include.yaml000644 000765 000024 00000000210 14124010463 027274 xustar00jhthorsenstaff000000 000000 30 mtime=1632637235.628152587 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/v3-invalid_include.yaml000644 000765 000024 00000000274 14124010463 025335 0ustar00jhthorsenstaff000000 000000 PCVersion: name: pcversion in: query description: version of commands which will run on backend schema: type: string enum: - 9.6.1 - 10.1.0 default: 10.1.0 Mojolicious-Plugin-OpenAPI-5.09/t/spec/PaxHeader/v3-invalid_file_refs_no_path.yaml000644 000765 000024 00000000152 13736770604 031346 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/v3-invalid_file_refs_no_path.yaml000644 000765 000024 00000000524 13736770604 027400 0ustar00jhthorsenstaff000000 000000 openapi: 3.0.0 info: title: Test file refs version: "1" servers: - url: /api paths: /test: get: operationId: File parameters: - $ref: "v3-valid_include.yaml#" responses: "200": description: thing content: "*/*": schema: type: stringMojolicious-Plugin-OpenAPI-5.09/t/spec/PaxHeader/v3-valid_file_refs.yaml000644 000765 000024 00000000210 14124001216 027254 xustar00jhthorsenstaff000000 000000 30 mtime=1632633486.665684239 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/v3-valid_file_refs.yaml000644 000765 000024 00000000564 14124001216 025317 0ustar00jhthorsenstaff000000 000000 openapi: 3.0.0 info: title: Test file refs version: "1" servers: - url: /api paths: /test: get: operationId: File parameters: - $ref: "v3-valid_include.yaml#/components/parameters/PCVersion" responses: "200": description: thing content: "*/*": schema: type: stringMojolicious-Plugin-OpenAPI-5.09/t/spec/PaxHeader/v3-invalid_file_refs.yaml000644 000765 000024 00000000210 14124010474 027611 xustar00jhthorsenstaff000000 000000 30 mtime=1632637244.496974241 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/v3-invalid_file_refs.yaml000644 000765 000024 00000000540 14124010474 025646 0ustar00jhthorsenstaff000000 000000 openapi: 3.0.0 info: title: Test file refs version: "1" servers: - url: /api paths: /test: get: operationId: File parameters: - $ref: "v3-invalid_include.yaml#/PCVersion" responses: "200": description: thing content: "*/*": schema: type: stringMojolicious-Plugin-OpenAPI-5.09/t/spec/PaxHeader/v3-valid_include.yaml000644 000765 000024 00000000152 13736770604 026774 xustar00jhthorsenstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/v3-valid_include.yaml000644 000765 000024 00000000376 13736770604 025033 0ustar00jhthorsenstaff000000 000000 components: parameters: PCVersion: name: pcversion in: query description: version of commands which will run on backend schema: type: string enum: - 9.6.1 - 10.1.0 default: 10.1.0 Mojolicious-Plugin-OpenAPI-5.09/t/spec/PaxHeader/v2-petstore.json000644 000765 000024 00000000210 14062524777 026041 xustar00jhthorsenstaff000000 000000 30 mtime=1623894527.952339666 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/v2-petstore.json000644 000765 000024 00000005622 14062524777 024104 0ustar00jhthorsenstaff000000 000000 { "swagger": "2.0", "info": { "version": "1.0.0", "title": "Swagger Petstore", "contact": { "name": "OAI", "url": "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/petstore.json" }, "license": {"name": "MIT"} }, "host": "petstore.swagger.io", "basePath": "/v1", "schemes": ["http"], "consumes": ["application/json"], "produces": ["application/json"], "paths": { "/pets": { "get": { "summary": "List all pets", "operationId": "listPets", "tags": ["pets"], "parameters": [ { "name": "limit", "in": "query", "description": "How many items to return at one time (max 100)", "required": false, "type": "integer", "format": "int32" } ], "responses": { "200": { "description": "An paged array of pets", "headers": { "x-next": { "type": "string", "description": "A link to the next page of responses" } }, "schema": {"$ref": "#/definitions/Pets"} }, "default": { "description": "unexpected error", "schema": {"$ref": "#/definitions/Error"} } } }, "post": { "summary": "Create a pet", "operationId": "createPets", "tags": ["pets"], "responses": { "201": { "description": "Null response" }, "default": { "description": "unexpected error", "schema": {"$ref": "#/definitions/Error"} } } } }, "/pets/{petId}": { "get": { "summary": "Info for a specific pet", "operationId": "showPetById", "tags": ["pets"], "parameters": [ { "name": "petId", "in": "path", "required": true, "description": "The id of the pet to retrieve", "type": "string" } ], "responses": { "200": { "description": "Expected response to a valid request", "schema": {"$ref": "#/definitions/Pets"} }, "default": { "description": "unexpected error", "schema": {"$ref": "#/definitions/Error"} } } } } }, "definitions": { "Pet": { "required": ["id", "name"], "properties": { "id": {"type": "integer", "format": "int64"}, "name": {"type": "string"}, "tag": {"type": "string"} } }, "Pets": { "type": "array", "items": {"$ref": "#/definitions/Pet"} }, "Error": { "required": ["code", "message"], "properties": { "code": {"type": "integer", "format": "int32"}, "message": {"type": "string"} } } } } Mojolicious-Plugin-OpenAPI-5.09/t/spec/swagger/PaxHeader/swagger.yaml000644 000765 000024 00000000210 14277266256 026741 xustar00jhthorsenstaff000000 000000 30 mtime=1660775598.463781584 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/swagger/swagger.yaml000644 000765 000024 00000000607 14277266256 025002 0ustar00jhthorsenstaff000000 000000 --- swagger: "2.0" info: title: "Test spec" version: "1.2.3" host: localhost basePath: "/swagger" schemes: ["https"] paths: /external/ref: $ref: "./paths/ref.yaml" /external/schema: post: parameters: - in: body name: body schema: $ref: "./parameters/body.yaml" responses: 200: $ref: "./responses/ok.yaml" Mojolicious-Plugin-OpenAPI-5.09/t/spec/swagger/PaxHeader/responses000755 000765 000024 00000000210 14374621224 026351 xustar00jhthorsenstaff000000 000000 30 mtime=1676878484.570210225 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/swagger/responses/000755 000765 000024 00000000000 14374621224 024461 5ustar00jhthorsenstaff000000 000000 Mojolicious-Plugin-OpenAPI-5.09/t/spec/swagger/PaxHeader/paths000755 000765 000024 00000000210 14374621224 025447 xustar00jhthorsenstaff000000 000000 30 mtime=1676878484.561770363 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/swagger/paths/000755 000765 000024 00000000000 14374621224 023557 5ustar00jhthorsenstaff000000 000000 Mojolicious-Plugin-OpenAPI-5.09/t/spec/swagger/PaxHeader/parameters000755 000765 000024 00000000210 14374621224 026473 xustar00jhthorsenstaff000000 000000 30 mtime=1676878484.573865225 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/swagger/parameters/000755 000765 000024 00000000000 14374621224 024603 5ustar00jhthorsenstaff000000 000000 Mojolicious-Plugin-OpenAPI-5.09/t/spec/swagger/parameters/PaxHeader/body.yaml000644 000765 000024 00000000210 14277266256 030402 xustar00jhthorsenstaff000000 000000 30 mtime=1660775598.462477466 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/swagger/parameters/body.yaml000644 000765 000024 00000000021 14277266256 026431 0ustar00jhthorsenstaff000000 000000 --- age: integer Mojolicious-Plugin-OpenAPI-5.09/t/spec/swagger/paths/PaxHeader/ref.yaml000644 000765 000024 00000000210 14277266256 027175 xustar00jhthorsenstaff000000 000000 30 mtime=1660775598.463087212 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/swagger/paths/ref.yaml000644 000765 000024 00000000142 14277266256 025230 0ustar00jhthorsenstaff000000 000000 get: responses: 200: description: "Ref response" schema: "../responses/ok.yaml" Mojolicious-Plugin-OpenAPI-5.09/t/spec/swagger/responses/PaxHeader/ok.yaml000644 000765 000024 00000000210 14277266256 027734 xustar00jhthorsenstaff000000 000000 30 mtime=1660775598.463530502 57 LIBARCHIVE.xattr.com.apple.provenance=AQAAAKEBnVNCP/4 49 SCHILY.xattr.com.apple.provenance=SB? Mojolicious-Plugin-OpenAPI-5.09/t/spec/swagger/responses/ok.yaml000644 000765 000024 00000000027 14277266256 025771 0ustar00jhthorsenstaff000000 000000 --- timestamp: integer