— my toolbox is filled with perl, javascript, css and html

thorsen.pm

@swaggerapi and #Mojolicious - How to validate your input/output with a schema

Sunday, July 05, 2015

Introduction

Mojolicious is an awesome web framework which allow you concentrate on the business logic, while the rest Just Works (tm).

Swagger is “The World’s Most Popular Framework for APIs”. Swagger is a language for specifying the input and output to your HTTP API. (REST- or RPC API, if you like). The API rules are based on top of the JSON schema rules, but extends beyond basic data validation and allows you to define a complete API spec.

Mojolicious::Plugin::Swagger2 is a plugin for Mojolicious which ties the web framework together with the API specification and automatically builds input/output validation rules.

This tutorial show how to build a working blog app, based on kraih’s Mojo::Pg blog example.

Why you want to use swagger

This question comes up quite often after telling people about Swagger: “but…why??” The people asking this often come from the same background as myself, where you both write the producer (backend web server) and consumer (javascript, …) code. When you’re in complete control of both sides you don’t really need to write any formal specification or document your API, since you already know how it works. This can be very true - at least if you make sure you have tests of all your endpoints.

Personally I’m a huge fan of documenting as well as testing. I often say that if you can’t document (describe) the API/method/whatever, something is wrong and you should reconsider what you’re coding. Documenting an API on the other hand is awful IMO, especially during rapid prototyping/developing where it’s hard to keep the code and documentation up to date.

So how does swagger fix this? Since the input/ouput Perl code is generated from the swagger document, you know that the backend is always running code accordingly to the specification. Also, since the documentation you generate is not hand written, but generated from the same swagger document you can know that the code the server is running is in sync with the documentation.

When “generated code” is mentioned, it’s not just the routing, but also input and output validation. This means that when any data has made it through to your controller action, you know that the data is valid. On the other side, the consumer (let’s say a javascript that cares about the difference between an integer and string) will know that it has received the correct data, since invalid output will result in a 500.

So… If you don’t care about documenation or collaberation with others, then I’m not sure if I would care much about swagger either.

Note that the swagger spec is not just for the server, but can also be used to generate javascript and perl client side code.

Swagger specification

This first thing you need to do is to design you API. I’m not going into the test-driven-, design-driven- or whatever-driven-development discussion, but for this to work you need to design you API. The reason for this is that the design/documentation process will define the rules which again generates in-memory Perl code used to validate the input/output of your Mojolicious application.

This specification is written in JSON, but you can use YAML instead if you have a YAML parser installed.

If you’re new to the whole Swagger concept, I suggest start looking at the resources under “See also”.

There’s a lot of details in the specification, which is better exaplained in the official documentation but there are some important parts worth mentioning:

The application

The example application is a fully working blog, with a PostgreSQL backend.

If you have a PostgreSQL server running and Mojolicious installed, you can start the application with these simple steps:

$ git clone https://github.com/jhthorsen/swagger2.git
$ cd swagger2/t/blog/
$ BLOG_PG_URL=postgresql://postgres@/test perl script/blog daemon

To see all the routes generated, you can run:

$ BLOG_PG_URL=postgresql://postgres@/test perl script/blog routes
/api
  +/posts        GET     "index"
  +/posts        POST    "store"
  +/posts/(:id)  PUT     "update"
  +/posts/(:id)  GET     "show"
  +/posts/(:id)  DELETE  "remove"
...

In addition to the manually added routes, Mojolicious::Plugin::Swagger2 has added five more routes, which are automatically generated from the API spec.

The generated routes differ from the standard routes, since they point to a generated callback where the input and output validation is done. This means that when a request hit http://example.com/api/posts/42, it will go through these steps:

  1. Check input against the swagger spec. Render a 400 error document if input validation fail.
  2. Call the method (operationId) in the specified x-mojo-controller, but with two extra parameters: $args and $cb. $args is the validated input, and $cb is a callback used to pass the response back to the user agent.
  3. Verify the response passed to $cb against the swagger spec. Render a 500 error document if the output validation fail.

These steps make sure that the input/output of your application will always follow the specification.

Below is an example action which will be called when a “GET” request with the path part “/api/posts/42” hit your application:

package Blog::Controller::Posts; # "x-mojo-controller"

sub show { # "operationId"

  # This method is called with $args and $cb,
  # in addition to the controller object ($self)
  my ($self, $args, $cb) = @_;

  # Find post with id 42 (from the request URL)
  my $entry = $self->posts->find($args->{id});

  # Serialize the blog post as JSON if found
  return $self->$cb($entry, 200) if $entry;

  # Render a 404 error document if blog post was not found
  return $self->$cb({errors => [{message => 'Blog post not found.', path => '/id'}]}, 404);
}

$args above will only contain “id”, since that is the only parameter specified for this resource. The response passed on to $cb need to match either the “200” or “default” response in the API spec.

Note that the “default” response defined in the specification match the default error generated by Mojolicious::Plugin::Swagger2.

Authentication

It’s possible to specify a custom route which does authentication:

$app->plugin(swagger2 => {
  url   => "...",
  route => $app->routes->under->to(cb => sub {
    my $c = shift;

    # Authenticated
    return 1 if $c->param('secret');

    # Not authenticated
    $c->render(
      status => 401,
      json   => {
        errors => [{message => 'Not authenticated', path => '/'}]
      }
    );
    return;
  });
});

Mojolicious commands

The Swagger2 distribution comes with a Mojolicious command extension which gives you these command line tools:

$ mojo swagger2 client path/to/spec.json <method> [args]
$ mojo swagger2 edit
$ mojo swagger2 edit path/to/spec.json --listen http://*:5000
$ mojo swagger2 pod path/to/spec.json
$ mojo swagger2 perldoc path/to/spec.json
$ mojo swagger2 validate path/to/spec.json

mojo swagger2 client

The “client” command generates a swagger client and calls the swagger ready server. The input specification file need to have host, basePath and schemes defined for the client to just work.

Example usage:

$ mojo swagger2 client \
  https://raw.githubusercontent.com/jhthorsen/swagger2/master/t/blog/api.json \
  show id=42

mojo swagger2 edit

The “edit” command starts a Mojolicious server where you can edit and read the swagger specification in your browser. The browser component uses localStorage, which automatically saves your changes locally even if your browser crash.

Example usage:

$ mojo swagger2 edit \
  https://raw.githubusercontent.com/jhthorsen/swagger2/master/t/blog/api.json

And then visit http://localhost:3000/ in your browser.

mojo swagger2 pod

You can generate pod and read the specification as perldoc. This again enables such thing as swagger-to-pdf:

$ sudo apt-get install pod2pdf
$ mojo swagger2 pod t/blog/api.json > blog.pod
$ pod2pdf blog.pod > blog.pdf

Just to read the documentation:

$ mojo swagger2 perldoc t/blog/api.json

mojo swagger2 validate

The last command can be used to validate that the API spec against the swagger specification:

$ mojo swagger2 validate t/blog/api.json

The end

This tutorial should give you a simple understanding on how to add input and output validation to your application, based on a Swagger spec.

Got questions or feedback? Contact me on twitter, join the IRC channel #swagger on irc.perl.org. or drop me an email.

See also

blog comments powered by Disqus