Mojolicious-Plugin-OpenAPI

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN


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

Changes  view on Meta::CPAN

 - 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

Changes  view on Meta::CPAN


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

Changes  view on Meta::CPAN

 - 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

MANIFEST  view on Meta::CPAN

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

lib/Mojolicious/Plugin/OpenAPI.pm  view on Meta::CPAN

  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));

lib/Mojolicious/Plugin/OpenAPI.pm  view on Meta::CPAN

    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};

lib/Mojolicious/Plugin/OpenAPI.pm  view on Meta::CPAN

  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 {

lib/Mojolicious/Plugin/OpenAPI.pm  view on Meta::CPAN

  {
    "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<JSON::Validator::Error> objects or empty list on valid input.

Note that this helper is only for customization. You probably want
L</openapi.valid_input> in most cases.

=head2 openapi.valid_input

  $c = $c->openapi->valid_input;

Returns the L<Mojolicious::Controller> object if the input is valid or

lib/Mojolicious/Plugin/OpenAPI.pm  view on Meta::CPAN

    for my $route (@$routes) {
      ...
    }
  });

This hook is EXPERIMENTAL and subject for change.

=head1 RENDERER

This plugin register a new handler called C<openapi>. 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</renderer> to format the output data. The code
below shows the default L</renderer> which generates JSON data:

  $app->plugin(
    OpenAPI => {

lib/Mojolicious/Plugin/OpenAPI.pm  view on Meta::CPAN

Holds either a L<JSON::Validator::Schema::OpenAPIv2> or a
L<JSON::Validator::Schema::OpenAPIv3> 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</HELPERS> and adds a
L<before_render|Mojolicious/before_render> hook for auto-rendering of error
documents. The return value is the object instance, which allow you to access
the L</ATTRIBUTES> after you load the plugin.

C<%config> can have:

=head3 coerce

See L<JSON::Validator/coerce> for possible values that C<coerce> can take.

lib/Mojolicious/Plugin/OpenAPI/Cors.pm  view on Meta::CPAN

      "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<x-mojo-to>
key set to "#openapi_plugin_cors_exchange". This will enable
L<Mojolicious::Plugin::OpenAPI::Cors> 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</openapi.cors_exchange> if the input is valid. The only extra part that needs
to be done in the C<add_post()> 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</openapi.cors_exchange>:

  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...

lib/Mojolicious/Plugin/OpenAPI/Cors.pm  view on Meta::CPAN

the "Origin" header in case the default
L</openapi_cors_default_exchange_callback> 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</openapi.cors_exchange>, unless you pass on a C<$callback>. The default
provided by this plugin will simply validate the C<Origin> header against
L</openapi_cors_allowed_origins>.

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;
  });

lib/Mojolicious/Plugin/OpenAPI/Cors.pm  view on Meta::CPAN

=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<Mojolicious::Controller>
object and must return an error or C<undef()> on success:

  my $error = $callback->($c);

The C<$error> must be in one of the following formats:

=over 2

lib/Mojolicious/Plugin/OpenAPI/Guides/OpenAPIv2.pod  view on Meta::CPAN


    # 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<Mojolicious::Plugin::OpenAPI/openapi.valid_input> while the output is
validated through then L<openapi|Mojolicious::Plugin::OpenAPI/RENDERER>
handler.

=head2 Route names

Routes will get its name from either L</x-mojo-name> or from L</operationId> 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<Mojolicious::Lite> apps.

lib/Mojolicious/Plugin/OpenAPI/Guides/OpenAPIv3.pod  view on Meta::CPAN


    # 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<Mojolicious::Plugin::OpenAPI/openapi.valid_input> while the output is
validated through then L<openapi|Mojolicious::Plugin::OpenAPI/RENDERER>
handler.

=head2 Route names

Routes will get its name from either L</x-mojo-name> or from L</operationId> 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<Mojolicious::Lite> apps.

lib/Mojolicious/Plugin/OpenAPI/Parameters.pm  view on Meta::CPAN

application, required by L<Mojolicious::Plugin::OpenAPI>. 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<JSON::Validator::Schema::OpenAPIv2/validate_request>.

=head2 openapi.build_schema_response

  $hash_ref = $c->openapi->build_schema_response;

Builds input data for L<JSON::Validator::Schema::OpenAPIv2/validate_response>.

=head2 openapi.coerce_request_parameters

  $c->openapi->coerce_request_parameters(\@evaluated_parameters);

Used by L<Mojolicious::Plugin::OpenAPI> to write the validated data back to
L<Mojolicious::Controller/req> and
L<Mojolicious::Validator::Validation/output>.

=head2 openapi.coerce_response_parameters

  $c->openapi->coerce_response_parameters(\@evaluated_parameters);

Used by L<Mojolicious::Plugin::OpenAPI> to write the validated data to
L<Mojolicious::Controller/res>.

=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};

t/basic-bundle.t  view on Meta::CPAN

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;

t/basic-custom-validation.t  view on Meta::CPAN

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');

t/jv-recursion.t  view on Meta::CPAN

    '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;

t/plugin-spec-renderer-options.t  view on Meta::CPAN

  ->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#')

t/v2-formats.t  view on Meta::CPAN

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;

t/v2-tutorial.t  view on Meta::CPAN

    # 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

t/v3-invalid_file_refs_no_path.t  view on Meta::CPAN


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;

t/v3-tutorial.t  view on Meta::CPAN

    # 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



( run in 2.100 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )