ETOOBUSY 🚀 minimal blogging for the impatient
OpenAPI with Mojolicious - using name for default_response
TL;DR
Using
namefordefault_responsein Mojolicious::Plugin::OpenAPI.
I know this sounds criptic… let’s go in order. As you might remember, I’m trying some Perl OpenAPI with Mojolicious.
I was trying to use something along the lines of the following specification:
openapi: 3.0.0
info:
title: Example Web API
version: 0.0.1
description: PoC definition
components:
schemas:
base:
type: object
properties:
status:
type: integer
exception:
type: object
allOf:
- $ref: '#/components/schemas/base'
- type: object
required: [ reason ]
properties:
reason:
type: string
paths:
/item/{id}:
get:
x-mojo-name: item
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/base'
The goal of defining #/components/schemas/exception is to use it as
the schema for… exceptions, using the handy default_response
capability conveniently provided by Mojolicious::Plugin::OpenAPI.
The way I found to do this was a bit hacky: load the specification as a
Perl data structure and then use schema to refer to it, like this:
...
my $spec = YAML::LoadFile('api-spec.yaml');
plugin OpenAPI => {
url => 'api-spec.yaml',
default_response => {
schema => $spec->{components}{schemas}{response_error},
...
It’s hacky because the specification is loaded twice… but no problem for me!
Fact is that this arrangement does not work because in this way the
code expects that the provided schema is self-contained, and in my
example the schema for exception isn’t (it references
#/components/schemas/base in the allOf section). Ouch.
But fear not, module author to the rescue! With this suggestion that is slightly redacted to match the example above:
Another solution […] is to simply load the plugin with
nameinstead ofschema:plugin OpenAPI => {default_response => {name => 'exception'}, ...};
Well… let’s try it:
#!/usr/bin/env perl
use v5.24;
use warnings;
use Mojolicious::Lite -signatures;
use Mojo::JSON 'encode_json';
get "/item/:id" => sub ($c) {
$c->openapi->valid_input or return;
my $id = $c->param('id');
if ($id == 404) {
$c->render(
status => 404,
openapi => {status => 404, reason => 'Not Found'},
);
} ## end if ($c->param('id') > ...)
elsif ($id == 500) {
$c->render(
status => 500,
openapi => {status => 500, reason => 'Internal Server Error'},
);
} ## end if ($c->param('id') > ...)
else {
$c->render(
status => 200,
openapi => {status => 200},
);
} ## end else [ if ($c->param('id') > ...)]
},
'item';
plugin OpenAPI => {
url => "data:///api.yaml",
default_response => { name => 'exception', },
renderer => sub ($c, $data) {
$c->res->headers->content_type('application/json');
return encode_json($data);
},
};
app->start;
__DATA__
@@ api.yaml
openapi: 3.0.0
info:
title: Example Web API
version: 0.0.1
description: PoC definition
components:
schemas:
base:
type: object
properties:
status:
type: integer
exception:
type: object
allOf:
- $ref: '#/components/schemas/base'
- type: object
required: [ reason ]
properties:
reason:
type: string
paths:
/item/{id}:
get:
x-mojo-name: item
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/base'
It works, yay!
I thought that name as a way to give a name to some section that would
be added to the definition (and it is, when schema is provided), but I
didn’t think to short-circuit the whole thing and use something that is
already in the specification!
Thanks Jan Henning Thorsen!