The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

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

OVERVIEW

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

You can also have a look at 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 Swagger.

TUTORIAL

Specification

This plugin reads an OpenAPI specification and generate routes and input/output rules from it. See JSON::Validator for supported schema file 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 GET /api/pets The first part of the path ("/api") comes from basePath, the second part comes from the key under paths, and the HTTP method comes from the key under /pets.

parameters and responses will be used to define rules for input and output. Continue reading for explanation about "x-mojo-to".

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 "to" in Mojolicious::Routes::Route, 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});

x-mojo-name

"x-mojo-name" is also a non-standard key, which will either find an existing route (useful for Mojolicious::Lite apps) or name the route which is generated. The default value used is "operationId" (see the specification), unless "x-mojo-name" is specified.

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 "Specification". See "register" in Mojolicious::Plugin::OpenAPI for information about what the plugin config can be.

See also "SYNOPSIS" in Mojolicious::Plugin::OpenAPI for example Mojolicious::Lite application.

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 "openapi.invalid_input" in Mojolicious::Plugin::OpenAPI while the output is validated through "reply.openapi" in Mojolicious::Plugin::OpenAPI.

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

Rendering binary data

Rendering binary data such as images can be accomplished by creating a Mojo::Asset object and pass it on to "reply.openapi" in Mojolicious::Plugin::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 "path" in Mojo::Asset::File 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);
  }

SEE ALSO

Mojolicious::Plugin::OpenAPI, https://openapis.org/specification.