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.