ETOOBUSY 🚀 minimal blogging for the impatient
OpenAPI with Mojolicious - using name for default_response
TL;DR
Using
name
fordefault_response
in 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
name
instead 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!