NAME

Dancer2::Plugin::OpenAPIRoutes - automatic routes creation from Swagger specification file.

SYNOPSIS

 use Dancer2;
 use Dancer2::Plugin::OpenAPIRoutes;
 OpenAPIRoutes(0);

DESCRIPTION

Automatically creates Dancer's routes from Swagger specification file. Extracts request parameters according to given spec. Uploaded files are Dancer2::Core::Request::Upload objects.

Automatically decodes JSON parameters if "Content-Type" is application/json. Automatically encodes answers to application/json if "Accept" header asks for it and returned value is reference.

IFF JSV::Compilator module was loaded before, then it will be used for input data validation. Otherwise it checks whether parameter is required or not but doesn't do real validation.

Catches thrown exceptions and makes JSON error messages if "Accept" is application/json.

Makes very smart mapping from route to Module::handler_function. For example:

/order:
  post:
  ...
/order/{id}
  delete:
  ...
  patch:
  ...

will be mapped to Order::create(), Order::remove() and Order::update() accordingly.

CONFIGURATION

Schema details will be taken from your Dancer2 application config file, and should be specified as, for example:

plugins:
  OpenAPIRoutes:
    schema: public/swagger.yaml
    namespace: MyApp
    default_module: MyApp
schema

Location of the Swagger spec file relative to the project root.

namespace

Starting namespace for generated module name.

default_module

Module name to put root's routes.

You have to call OpenAPIRoutes([$debug_flag, $custom_map]) in your main application module. Optionally you can pass true value as first argument to see how it maps routes to Modules and functions.

SMART MAPPING

This is probably the most crucial feature of this plugin. It automatically makes your application structured according to given spec file. It also makes your application less dependent on Dancer2 framework - you have to think more about application logic and less about framework details. Mapping is complicated but intuitive.

MAPPING RULES

Both the route and its HTTP method are used to compose the mapping.

HTTP METHOD MAPPING

This is starting point of the mapping algorithm. If route has only one method, then route's last part can be used as function name in module which name made of previous route parts.

POST

In RESful terms POST means creation of some resource. That's why usually it maps to create() function with one exception: if route ends with /{someId} then it means update().

GET

This methis is mapped to function fetch().

DELETE

This method is mapped to remove(). Perl language already has delete() function and it's better not to reuse its name.

PUT

In RESful terms PUT means full replacement of some resource. This method is mapped to replace()

PATCH

In RESful terms PATCH means partial update of some resource. This method is mapped to update()

OPTIONS

This method is mapped to choices()

This method is mapped to check()

You don't usually have to define HEAD method because it's done automatically from GET throwing away real answer.

ROUTES MAPPING

Basic idea is very simple: /resource/subresource is mapped to Resource::Subresource module and function name is mapped according HTTP method. Then there're special cases (from OpenAPI example spec):

POST /pet/{petId}/uploadImage
GET /pet/findByTags
GET /pet/findByStatus

It would be silly to put these three routes with single method in separate modules Pet::UploadImage, Pet::FindByTags and Pet::FindByStatus. That's why routes with only one method are mapped to theirs "parents" with function name from last route part.

NOTICE: It's important to describe path parameters twice: in route and in parameters method's section. Because they are extracted as regexp captures and routes with integer parameters should be dispatched first to avoid collision between /pet/{petId} and /pet/findByTags type of routes.

INTERFACE

ENVIRONMENT VARIABLES

When you need some variable from PSGI's environment, like REMOTE_USER, then it's really inconvenient to get directly from Dancer2 framework. There's a support to get it automatic using OpenAPI extension keyword x-env-{environment-variable} like x-env-remote-user: user. This keyword should be put in HTTP method section. Directive x-env-remote-user: user will put value of PSGI's environment variable REMOTE_USER into input hash parameter key user.

FUNCTION INTERFACE

Mapped route's function is called like this:

($result, $status, $callback) = ${module_name}::$module_func( \%input, $dsl );

Function receives hash reference with extracted parameters according to Swagger spec and Dancer2 DSL object. This object is rarely needed but sometimes you need to have access to application's object, for example:

$dsl->app->send_file(...);

Most of the time function can return only one result like this:

sub fetch {
  my $input   = $_[0];
  my $pet = schema->get_pet( $input->{petId} );
  return $pet;
}

Sometimes you want to change response status:

sub remove {
  my $input = $_[0];
  my $error = schema->delete_pet( $input->{petId} );
  if ($error) {
      return ( { error => $error }, 'bad_request' );
  }
  return ( '', 'no_content' );
}

In some odd cases when you use old Dancer2, then you have to call specific functions directly from route handler using callback:

sub downloadFile {
  my $dsl      = $_[1];
  # ... 
  return (
    undef, undef,
    sub {
      $dsl->app->send_file(
        $filename,
        filename     => $filename,
        content_type => 
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
      );
    }
  );
}

CUSTOM MAPPING

When you need some customization to your routes mapping, you can do it passing hash reference as second parameter to OpenAPIRoutes([$debug, $castom_map]). You can change mapping for HTTP method for all paths or only for specific ones like this:

OpenAPIRoutes(1, {"get:/store/order/{orderId}" => "remove"});

(Very naughty joke): Instead of calling "fetch" for this specific path it will call "remove". The whole schema:

OpenAPIRoutes(1, {"$method[:$path]" => "[$function]:[$full::module::name]"});

like this:

OpenAPIRoutes(1, {
    "put"               => "update",
    "post:/store/order" => "create_order",
    "post:/store/image" => "upload_image",
    # and so on ...
});

AUTHOR

This module was written and is maintained by:

  • Anton Petrusevich