use strict;
use warnings;
package Test::Mojo::Role::OpenAPI::Modern; # git description: v0.002-5-g34097db
# vim: set ts=8 sts=2 sw=2 tw=100 et :
# ABSTRACT: Test::Mojo role providing access to an OpenAPI document and parser
# KEYWORDS: validation evaluation JSON Schema OpenAPI Swagger HTTP request response

our $VERSION = '0.003';

use 5.020;  # for fc, unicode_strings features
use strictures 2;
use if "$]" >= 5.022, experimental => 're_strict';
no if "$]" >= 5.031009, feature => 'indirect';
no if "$]" >= 5.033001, feature => 'multidimensional';
no if "$]" >= 5.033006, feature => 'bareword_filehandles';
use namespace::clean;

use Mojo::Base -role, -signatures;

has 'openapi' => sub ($self) {
  # use the plugin's object, if the plugin is being used
  # FIXME: we should be calling $self->app->$_call_if_can, but can() isn't behaving
  my $openapi = eval { $self->app->openapi };
  return $openapi if $openapi;

  # otherwise try to construct our own using provided configs
  if (my $config = $self->app->config->{openapi}) {
    return $config->{openapi_obj} if $config->{openapi_obj};

    if (my $args = eval {
        +require Mojolicious::Plugin::OpenAPI::Modern;
        Mojolicious::Plugin::OpenAPI::Modern->VERSION('0.007');
        Mojolicious::Plugin::OpenAPI::Modern::_process_configs($config);
      }) {
      return OpenAPI::Modern->new($args);
    }
  }

  die 'openapi object or configs required';
};

sub _openapi_stash ($self, @data) { Mojo::Util::_stash(_openapi_stash => $self, @data) }

after _request_ok => sub ($self, @args) {
  $self->{_openapi_stash} = {};
};

sub request_valid ($self, $desc = 'request is valid') {
  return $self->test('ok', $self->request_validation_result, $desc);
}

sub response_valid ($self, $desc = 'response is valid') {
  return $self->test('ok', $self->response_validation_result, $desc);
}

sub request_validation_result ($self) {
  my $result = $self->_openapi_stash('request_result');
  return $result if $result;

  $result = $self->openapi->validate_request($self->tx->req);
  $self->_openapi_stash(request_result => $result);
  return $result;
}

sub response_validation_result ($self) {
  my $result = $self->_openapi_stash('response_result');
  return $result if $result;

  my $options = { request => $self->tx->req };
  $result = $self->openapi->validate_response($self->tx->res, $options);
  $self->_openapi_stash(response_result => $result);
  return $result;
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Test::Mojo::Role::OpenAPI::Modern - Test::Mojo role providing access to an OpenAPI document and parser

=head1 VERSION

version 0.003

=head1 SYNOPSIS

  my $openapi = OpenAPI::Modern->new(
    openapi_uri => '/api',
    openapi_schema => YAML::PP->new(boolean => 'JSON::PP')->load_string(<<'YAML'));
---
  openapi: 3.1.0
  info:
    title: Test API
    version: 1.2.3
  paths:
    /foo/{foo_id}:
      parameters:
      - name: foo_id
        in: path
        required: true
        schema:
          pattern: ^[a-z]+$
      post:
        operationId: my_foo_request
        requestBody:
          required: true
          content:
            application/json:
              schema: {}
        responses:
          200:
            description: success
            content:
              application/json:
                schema:
                  type: object
                  required: [ status ]
                  properties:
                    status:
                      const: ok
  YAML

  my $t = Test::Mojo
    ->with_roles('+OpenAPI::Modern')
    ->new('MyApp', { ... })
    ->openapi($openapi);

  $t->post_ok('/foo/hello')
    ->status_is(200)
    ->json_is('/status', 'ok')
    ->request_valid
    ->response_valid;

=head1 DESCRIPTION

Provides methods on a L<Test::Mojo> object suitable for using L<OpenAPI::Modern> to validate the
request and response.

=for stopwords OpenAPI openapi

=head1 ACCESSORS/METHODS

=head2 openapi

The L<OpenAPI::Modern> object to use for validation. This object stores the OpenAPI schema used to
describe requests and responses in your application, as well as the underlying
L<JSON::Schema::Modern> object used for the validation itself. See that documentation for
information on how to customize your validation and provide the specification document.

If not provided, the object is constructed using configuration values passed to the application
under the C<openapi> key (see L<Test::Mojo/new>), as for L<Mojolicious::Plugin::OpenAPI::Modern>,
or re-uses the object from the application itself if that plugin is applied.

=head2 request_valid

Passes C<< $t->tx->req >> to L<OpenAPI::Modern/validate_request>, as in
L<Mojolicious::Plugin::OpenAPI::Modern/validate_request>, producing a boolean test result.

=head2 response_valid

Passes C<< $t->tx->res >> to L<OpenAPI::Modern/validate_response> as in
L<Mojolicious::Plugin::OpenAPI::Modern/validate_response>, producing a boolean test result.

=head2 request_validation_result

Returns the L<JSON::Schema::Modern::Result> object for the validation result for the last request,
or calculates it if not already available.

Does not emit a test result.

=head2 response_validation_result

Returns the L<JSON::Schema::Modern::Result> object for the validation result for the last response,
or calculates it if not already available.

Does not emit a test result.

=head1 FUTURE FEATURES

Lots of features are still to come, including:

=over 4

=item *

C<request_invalid>, C<response_invalid> test methods, including a mechanism for providing the expected validation error(s)

=item *

stashing the validation results on the test object for reuse or diagnostic printing

=item *

integration with L<Mojolicious::Plugin::OpenAPI::Modern>, including sharing the openapi object and customization options that are set in the application

=back

=head1 SEE ALSO

=over 4

=item *

L<Mojolicious::Plugin::OpenAPI::Modern>

=item *

L<Test::Mojo>

=item *

L<Test::Mojo::WithRoles>

=item *

L<OpenAPI::Modern>

=item *

L<JSON::Schema::Modern::Document::OpenAPI>

=item *

L<JSON::Schema::Modern>

=item *

L<https://json-schema.org>

=item *

L<https://www.openapis.org/>

=item *

L<https://learn.openapis.org/>

=item *

L<https://spec.openapis.org/oas/v3.1.0>

=back

=head1 SUPPORT

Bugs may be submitted through L<https://github.com/karenetheridge/Test-Mojo-Role-OpenAPI-Modern/issues>.

There is also an irc channel available for users of this distribution, at
L<C<#mojo> on C<irc.libera.chat>|irc://irc.libera.chat/#mojo>.

I am also usually active on irc, as 'ether' at C<irc.perl.org> and C<irc.libera.chat>.

=head1 AUTHOR

Karen Etheridge <ether@cpan.org>

=head1 COPYRIGHT AND LICENCE

This software is copyright (c) 2023 by Karen Etheridge.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
