=head1 NAME

Mojolicious::Plugin::OpenAPI::Guides::Tutorial - Mojolicious <3 Open API (Swagger)

=head1 OVERVIEW

This guide will give you an introduction to how to use
L<Mojolicious::Plugin::OpenAPI>.

You can also have a look at
L<http://thorsen.pm/perl/programming/2015/07/05/mojolicious-swagger2.html>,
which includes reasons for why you want to use Open API - also known as
L<Swagger|Swagger2>.

=head1 TUTORIAL

=head2 Specification

This plugin reads an L<OpenAPI specification|https://openapis.org/specification>
and generate routes and input/output rules from it. See L<JSON::Validator> for
L<supported schema file formats|JSON::Validator/Supported schema formats>.

  {
    "basePath": "/api",
    "paths": {
      "/pets": {
        "get": {
          "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": "array", "items": { "type": "object" } }
            },
            "default": {
              "description": "Unexpected error",
              "schema": { "$ref": "http://git.io/vcKD4#" }
            }
          }
        }
      }
    }
  }

The complete HTTP request for getting the "pet list" will be C<GET /api/pets>
The first part of the path ("/api") comes from C<basePath>, the second part
comes from the key under C<paths>, and the HTTP method comes from the key under
C</pets>.

The C<basePath> will also be used to add a route that renders back the
specification either as JSON or HTML, dependent on the extension used in the
request.

C<parameters> and C<responses> will be used to define rules for
L<input|Mojolicious::Plugin::OpenAPI/openapi.input> and
L<output|Mojolicious::Plugin::OpenAPI/reply.openapi>. Continue reading for
explanation about L</x-mojo-to>.

=head3 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<Mojolicious::Routes::Route/to>, 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}]
  $route->to("pet#list", {foo => 123});

=head3 x-mojo-name

See L</Route names>.

=head2 Application

  package Myapp;
  use Mojolicious;

  sub startup {
    my $app = shift;
    $app->plugin("OpenAPI" => {url => $app->home->rel_file("myapi.json")});
  }

The first thing in your code that you need to do is to load this plugin and the
L</Specification>. See L<Mojolicious::Plugin::OpenAPI/register> for information
about what the plugin config can be.

See also L<Mojolicious::Plugin::OpenAPI/SYNOPSIS> for example
L<Mojolicious::Lite> application.

=head2 Controller

  package Myapp::Controller::Pet;

  sub list {
    my $c = shift;

    # Do not continue on invalid input and render a default 400
    # error document.
    return if $c->openapi->invalid_input;

    # 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->reply->openapi([], 498);
    }

    # $c->openapi->invalid_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->reply->openapi(200 => $output);
  }

The input will be validated using
L<Mojolicious::Plugin::OpenAPI/openapi.invalid_input> while the output is
validated through L<Mojolicious::Plugin::OpenAPI/reply.openapi>.

=head2 Route names

Routes will get its name from either L</x-mojo-name> or from L</operationId>.

"x-mojo-name" can also be used the other way around, to find already defined
routes. This is especially useful for L<Mojolicious::Lite> apps.

Note that if L<spec_route_name|Mojolicious::Plugin::OpenAPI/spec_route_name> is
specified then all the route names will have that value as prefix:

  spec_route_name = "my_cool_api"
  operationId     = "Foo"
  Route name      = "my_cool_api.Foo";

=head2 Default error document

The default error document rendered on invalid input and output looks like
this:

  {
    "errors": [
      {"path": "/some/json/path", "message": "Some error message"},
      {"path": "/age", "message": "Expected integer - got 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.

The HTTP status code on invalid input is 400, and 500 for invalid output

=head2 Rendering binary data

Rendering binary data such as images can be accomplished by creating a
L<Mojo::Asset> object and pass it on to
L<Mojolicious::Plugin::OpenAPI/reply.openapi>, like this:

  sub get_image {
    my $c = shift->openapi->valid_input or return;
    my $asset = Mojo::Asset::File->new(path => "image.jpeg");
    $c->reply->openapi(200 => $asset);
  }

The example above will try to guess the "Content-Type" by looking at the
extension of L<Mojo::Asset::File/path> and default to
"application/octet-stream" if the extension is unknown. If you want to specify
another content type, then simple define it up front:

  sub get_text {
    my $c = shift->openapi->valid_input or return;
    my $asset = Mojo::Asset::Memory->new;
    $asset->add_chunk("some data");
    $c->res->headers->content_type("text/plain");
    $c->reply->openapi(200 => $asset);
  }

=head1 SEE ALSO

L<Mojolicious::Plugin::OpenAPI>,
L<https://openapis.org/specification>.

=cut
