Swagger 3

Introduction

  • https://handlebarsjs.com/
  • http://mustache.github.io/mustache.5.html

Creating a custom code generator for Slim

Download current stable 3.x.x branch (OpenAPI version 3)

wget 
  https://repo1.maven.org/maven2/io/swagger/codegen/v3/swagger-codegen-cli/3.0.20/swagger-codegen-cli-3.0.20.jar 
    -O swagger-codegen-cli.jar

or using curl:

curl 
  https://repo1.maven.org/maven2/io/swagger/codegen/v3/swagger-codegen-cli/3.0.20/swagger-codegen-cli-3.0.20.jar  
    --output swagger-codegen-cli.jar

Show help using

java -jar swagger-codegen-cli.jar -help

Show all the supported languages

java -jar swagger-codegen-cli.jar langs

To start our new generator; we’ll need to create some boilerplate code. We can easily do this with the meta command:

java -jar swagger-codegen-cli.jar meta -o output/slim -n slim -p net.alanboy.swagger

In the previous command

  • meta is the subcommand to construct all the codegen boilerplate.
  • -o where to write the generated files
  • -n the human-readable name of the generator
  • -p the package to put the main class into

Running that command will leave us with the following files:

output
output\slim
output\slim\README.md
output\slim\pom.xml
output\slim\src
output\slim\src\main
output\slim\src\main\java
output\slim\src\main\java\net
output\slim\src\main\java\net\alanboy
output\slim\src\main\java\net\alanboy\swagger
output\slim\src\main\java\net\alanboy\swagger\SlimGenerator.java
output\slim\src\main\resources
output\slim\src\main\resources\META-INF
output\slim\src\main\resources\META-INF\services
output\slim\src\main\resources\META-INF\services\io.swagger.codegen.v3.CodegenConfig
output\slim\src\main\resources\slim
output\slim\src\main\resources\slim\api.mustache
output\slim\src\main\resources\slim\model.mustache
output\slim\src\main\resources\slim\myFile.mustache

We can now go ahead and build them:

output\slim>mvn package

With this, we have created a new jar file:

output\slim\target\slim-swagger-codegen-1.0.0.jar

Ok, so we have our client, which allowed us to build a basic (empty) code generator, which we have just built.

In order to try it, we need an OpenAPI yaml file. This pet store example seems to be the canonical example. So grab that and put it in the root and call it petstore.yaml:

curl https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v3.0/petstore.yaml  --output pets tore.yaml

Ok, let’s actually build some generated code using our new slim code generator.

java -classpath swagger-codegen-cli.jar;output\slim\target\slim-swagger-codegen-1.0.0.jar  ^
   io.swagger.codegen.v3.cli.SwaggerCodegen ^
   generate ^
  -i petstore.yaml ^
  -l slim ^
  -o generated-code/slim

If this worked, we now have a generated structure that looks like this:

generated-code\slim
generated-code\slim\.swagger-codegen
generated-code\slim\.swagger-codegen-ignore
generated-code\slim\.swagger-codegen\VERSION
generated-code\slim\myFile.sample
generated-code\slim\src
generated-code\slim\src\io
generated-code\slim\src\io\swagger
generated-code\slim\src\io\swagger\client
generated-code\slim\src\io\swagger\client\api
generated-code\slim\src\io\swagger\client\api\PetsApi.sample
generated-code\slim\src\io\swagger\client\model
generated-code\slim\src\io\swagger\client\model\Error.sample
generated-code\slim\src\io\swagger\client\model\Pet.sample
generated-code\slim\src\io\swagger\client\model\Pets.sample

Ok, let’s open these two side by side:


  1                                                                          |  1
  2 # This is a sample api mustache template.  It is representing a ficticous|  2 # This is a sample api mustache template.  It is representing a ficticous
  3 # language and won't be usable or compile to anything without lots of cha|  3 # language and won't be usable or compile to anything without lots of cha
  4 # Use it as an example.  You can access the variables in the generator ob|  4 # Use it as an example.  You can access the variables in the generator ob
  5 # like such:                                                             |  5 # like such:
  6                                                                          |  6
  7 # use the package from the `apiPackage` variable                         |  7 # use the package from the `apiPackage` variable
  8 package: {{package}}                                                     |  8 package: io.swagger.client.api
  9                                                                          |  9
 10 # operations block                                                       | 10 # operations block
 11 {{#operations}}                                                          | 11 classname: PetsApi
 12 classname: {{classname}}                                                 | 12
 13                                                                          | 13 # loop over each operation in the API:
 14 # loop over each operation in the API:                                   | 14
 15 {{#operation}}                                                           | 15 # each operation has an `operationId`:
 16                                                                          | 16 operationId: createPets
 17 # each operation has an `operationId`:                                   | 17
 18 operationId: {{operationId}}                                             | 18 # and parameters:
 19                                                                          | 19
 20 # and parameters:                                                        | 20
 21 {{#allParams}}                                                           | 21 # each operation has an `operationId`:
 22 {{paramName}}: {{dataType}}                                              | 22 operationId: listPets
 23 {{/allParams}}                                                           | 23
 24                                                                          | 24 # and parameters:
 25 {{/operation}}                                                           | 25 limit: Integer
 26                                                                          | 26
 27 # end of operations block                                                | 27
 28 {{/operations}}                                                          | 28 # each operation has an `operationId`:
~                                                                            | 29 operationId: showPetById
~                                                                            | 30
~                                                                            | 31 # and parameters:
~                                                                            | 32 petId: String
~                                                                            | 33
~                                                                            | 34
~                                                                            | 35 # end of operations block

According to the Slim Framework website, this is the minimal we need for a working API:

<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

$app->get('/hello/{name}', function (Request $request, Response $response, array $args) {
    $name = $args['name'];
    $response->getBody()->write("Hello, $name");
    return $response;
});

$app->run();

So let’s start making the mustache look like this our desired output. We are going to be iterating this a buch of times, lets create a run.cmd script:

rem Build the generator
call mvn -Dmaven.test.skip=true --file output\slim\pom.xml package

rem Generate code
java -classpath swagger-codegen-cli.jar;output\slim\target\slim-swagger-codegen-1.0.0.jar  ^
   io.swagger.codegen.v3.cli.SwaggerCodegen ^
   generate ^
  -i petstore.yaml ^
  -l slim ^
  -o generated-code/slim

So we can now, change the mustache files and run run.cmd and see the output in the generated code.


  1 <?php                                                                    |  1 <?php
  2                                                                          |  2
  3 use Psr\Http\Message\ResponseInterface as Response;                      |  3 use Psr\Http\Message\ResponseInterface as Response;
  4 use Psr\Http\Message\ServerRequestInterface as Request;                  |  4 use Psr\Http\Message\ServerRequestInterface as Request;
  5 use Slim\Factory\AppFactory;                                             |  5 use Slim\Factory\AppFactory;
  6                                                                          |  6
  7 require __DIR__ . '/../vendor/autoload.php';                             |  7 require __DIR__ . '/../vendor/autoload.php';
  8                                                                          |  8
  9 $app = AppFactory::create();                                             |  9 $app = AppFactory::create();
 10                                                                          | 10
 11 {{#operations}}                                                          | 11
 12                                                                          | 12 $app->POST('/pets', function (Request $request, Response $response, array
 13 {{#operation}}                                                           | 13     $response->getBody()->write("Hello, $name");
 14 $app->{{httpMethod}}('{{path}}', function (Request $request, Response $re| 14     return $response;
 15     {{#allParams}}                                                       | 15 });
 16     ${{paramName}} = $args['{{paramName}}'];                             | 16
 17     {{/allParams}}                                                       | 17 $app->GET('/pets', function (Request $request, Response $response, array
 18     $response->getBody()->write("Hello, $name");                         | 18     $limit = $args['limit'];
 19     return $response;                                                    | 19     $response->getBody()->write("Hello, $name");
 20 });                                                                      | 20     return $response;
 21                                                                          | 21 });
 22 {{/operation}}                                                           | 22
 23 {{/operations}}                                                          | 23 $app->GET('/pets/{petId}', function (Request $request, Response $response
 24                                                                          | 24     $petId = $args['petId'];
 25 $app->run();                                                             | 25     $response->getBody()->write("Hello, $name");
~                                                                            | 26     return $response;
~                                                                            | 27 });
~                                                                            | 28
~                                                                            | 29
~                                                                            | 30 $app->run();

I took some of the template variables from here.

Ok, so we’ve proved that we can actually start building the output code using our new generator.

First thing to note is that we are developing for codegen v3, which uses handlebars. Check this for more information on mustache and handlebars. Also this.

We are writing a code gen for swagger 3, there is already support for slim in v2, so i took a lookt at one of the mustache files and just copy paster

this did not work out of the box, so i want to know what are the variables that i have at my disposal, i can do this by adding -DdebugOperations to my generate command in run.cmd:

java -DdebugOperations -classpath swagger-codegen-cli.jar;output\slim\target\slim-swagger-codegen-1.0.0.jar  ^
   io.swagger.codegen.v3.cli.SwaggerCodegen ^
   generate ^
  -i petstore.yaml ^
  -l slim ^
  -o generated-code/slim

Other debug configurations are:

-DdebugSwagger prints the swagger specification as interpreted by the codegen -DdebugModels prints models passed to the template engine -DdebugOperations prints operations passed to the template engine -DdebugSupportingFiles prints additional data passed to the template engine

This will put a 00:06:47.361 [main] INFO io.swagger.codegen.v3.DefaultGenerator - ############ Model info ############ section with useful information.

This is what helped me realize that {apiInfo} is no longer supported? So I took out pieces that were not supported and got to a point where stuff seems to be working:


  1 <?php                                                                    |  1 <?php
  2 /**                                                                      |  2 /**
  3  * {{appName}}                                                           |  3  * Swagger Petstore
  4  * @version {{appVersion}}                                               |  4  * @version 1.0.0
  5  */                                                                      |  5  */
  6                                                                          |  6
  7 require_once __DIR__ . '/vendor/autoload.php';                           |  7 require_once __DIR__ . '/vendor/autoload.php';
  8                                                                          |  8
  9 $app = new Slim\App();                                                   |  9 $app = new Slim\App();
 10                                                                          | 10
 11 {{#operations}}{{#operation}}                                            | 11 /**
 12 /**                                                                      | 12  * POST createPets
 13  * {{httpMethod}} {{nickname}}                                           | 13  * Summary: Create a pet
 14  * Summary: {{summary}}                                                  | 14  * Notes:
 15  * Notes: {{notes}}                                                      | 15  * Output-Formats: [application/json]
 16 {{#hasProduces}} * Output-Formats: [{{#produces}}{{{mediaType}}}{{#hasMor| 16  */
 17  */                                                                      | 17 $app->POST('/v1/pets', function($request, $response, $args) {
 18 $app->{{httpMethod}}('{{{basePathWithoutHost}}}{{{path}}}', function($req| 18
 19             {{#hasHeaderParams}}$headers = $request->getHeaders();{{/hasH| 19
 20             {{#hasQueryParams}}$queryParams = $request->getQueryParams();| 20
 21             {{#queryParams}}${{paramName}} = $queryParams['{{paramName}}'| 21
 22             {{#hasFormParams}}{{#formParams}}${{paramName}} = $args['{{pa| 22             $response->write('How about implementing createPets as a POST
 23             {{#hasBodyParam}}$body = $request->getParsedBody();{{/hasBody| 23             return $response;
 24             $response->write('How about implementing {{nickname}} as a {{| 24             });
 25             return $response;                                            | 25
 26             });                                                          | 26 /**
 27                                                                          | 27  * GET listPets
 28 {{/operation}}{{/operations}}                                            | 28  * Summary: List all pets
 29                                                                          | 29  * Notes:
 30                                                                          | 30  * Output-Formats: [application/json]
 31 $app->run();                                                             | 31  */
~                                                                            | 32 $app->GET('/v1/pets', function($request, $response, $args) {
~                                                                            | 33
~                                                                            | 34             $queryParams = $request->getQueryParams();
~                                                                            | 35             $limit = $queryParams['limit'];
~                                                                            | 36
~                                                                            | 37
~                                                                            | 38             $response->write('How about implementing listPets as a GET me
~                                                                            | 39             return $response;
~                                                                            | 40             });
~                                                                            | 41
~                                                                            | 42 /**
~                                                                            | 43  * GET showPetById
~                                                                            | 44  * Summary: Info for a specific pet
~                                                                            | 45  * Notes:
~                                                                            | 46  * Output-Formats: [application/json]
~                                                                            | 47  */
~                                                                            | 48 $app->GET('/v1/pets/{petId}', function($request, $response, $args) {
~                                                                            | 49
~                                                                            | 50
~                                                                            | 51
~                                                                            | 52
~                                                                            | 53             $response->write('How about implementing showPetById as a GET
~                                                                            | 54             return $response;
~                                                                            | 55             });
~                                                                            | 56
~                                                                            | 57
~                                                                            | 58
~                                                                            | 59 $app->run();

Running that slim didnt actually work for me, so i acommodated the example from the slim website:


  1 <?php                                                                    |  1 <?php
  2                                                                          |  2
  3 /**                                                                      |  3 /**
  4  * {{appName}}                                                           |  4  * Swagger Petstore
  5  * @version {{appVersion}}                                               |  5  * @version 1.0.0
  6  */                                                                      |  6  */
  7                                                                          |  7
  8 use Psr\Http\Message\ResponseInterface as Response;                      |  8 use Psr\Http\Message\ResponseInterface as Response;
  9 use Psr\Http\Message\ServerRequestInterface as Request;                  |  9 use Psr\Http\Message\ServerRequestInterface as Request;
 10 use Slim\Factory\AppFactory;                                             | 10 use Slim\Factory\AppFactory;
 11                                                                          | 11
 12 require __DIR__ . '/../../vendor/autoload.php';                          | 12 require __DIR__ . '/../../vendor/autoload.php';
 13                                                                          | 13
 14 $app = AppFactory::create();                                             | 14 $app = AppFactory::create();
 15                                                                          | 15
 16 // Add error middleware                                                  | 16 // Add error middleware
 17 $app->addErrorMiddleware(true, true, true);                              | 17 $app->addErrorMiddleware(true, true, true);
 18 $app->setBasePath("/api/index.php");                                     | 18 $app->setBasePath("/api/index.php");
 19                                                                          | 19
 20 {{#operations}}{{#operation}}                                            | 20 /**
 21 /**                                                                      | 21  * POST createPets
 22  * {{httpMethod}} {{nickname}}                                           | 22  * Summary: Create a pet
 23  * Summary: {{summary}}                                                  | 23  * Notes:
 24  * Notes: {{notes}}                                                      | 24  * Output-Formats: [application/json]
 25 {{#hasProduces}} * Output-Formats: [{{#produces}}{{{mediaType}}}{{#hasMor| 25  */
 26  */                                                                      | 26 $app->POST('/v1/pets', function($request, $response, $args) {
 27 $app->{{httpMethod}}('{{{basePathWithoutHost}}}{{{path}}}', function($req| 27
 28             {{#hasHeaderParams}}$headers = $request->getHeaders();{{/hasH| 28
 29             {{#hasQueryParams}}$queryParams = $request->getQueryParams();| 29
 30             {{#queryParams}}${{paramName}} = $queryParams['{{paramName}}'| 30
 31             {{#hasFormParams}}{{#formParams}}${{paramName}} = $args['{{pa| 31             $response->getBody()->write("Hello world!");
 32             {{#hasBodyParam}}$body = $request->getParsedBody();{{/hasBody| 32             return $response;
 33             $response->getBody()->write("Hello world!");                 | 33             });
 34             return $response;                                            | 34
 35             });                                                          | 35 /**
 36                                                                          | 36  * GET listPets
 37 {{/operation}}{{/operations}}                                            | 37  * Summary: List all pets
 38                                                                          | 38  * Notes:
 39 $app->run();                                                             | 39  * Output-Formats: [application/json]
~                                                                            | 40  */
~                                                                            | 41 $app->GET('/v1/pets', function($request, $response, $args) {
~                                                                            | 42
~                                                                            | 43             $queryParams = $request->getQueryParams();
~                                                                            | 44             $limit = $queryParams['limit'];
~                                                                            | 45
~                                                                            | 46
~                                                                            | 47             $response->getBody()->write("Hello world!");
~                                                                            | 48             return $response;
~                                                                            | 49             });

Ok, we are geeting somewhere. At this point I started tweaking the mustache and actually running the slim code in my webserver to make sure it worked. Turns out this example json, the pet store does not take any

Now ill start creating a more complex API using https://studio.apicur.io/ so that i can visually add/remove apis, arguments, reponses, etc….

I then drag that and insert it and run…

Ok, we have proved that the mustache works. now for the real custom generator that i need, i need actually 3 files:

  • The API surface. This will be the Slim Framework code that we saw
  • The controllers interfaces. These interfaces are what the controllers must implement.
  • The controllers themselves. These have the actual code logic, busines rules and everything behind the API.

For this, we’ll need to modify the

\Code\swagger-codegen-test2\output\slim\src\main\java\net\alanboy\swagger\SlimGenerator.java

This file drives the generation process. The comments are the documentation i suppose.

package net.alanboy.swagger;                                                                                                                                                                                                                                                                                             2

import io.swagger.codegen.v3.*;
import io.swagger.codegen.v3.generators.DefaultCodegenConfig;

import java.util.*;
import java.io.File;

public class SlimGenerator extends DefaultCodegenConfig {

  // source folder where to write the files
  protected String sourceFolder = "src";
  protected String apiVersion = "1.0.0";

  /**
   * Configures the type of generator.
   *
   * @return  the CodegenType for this generator
   * @see     io.swagger.codegen.CodegenType
   */
  public CodegenType getTag() {
    return CodegenType.CLIENT;
  }

  /**
   * Configures a friendly name for the generator.  This will be used by the generator
   * to select the library with the -l flag.
   *
   * @return the friendly name for the generator
   */
  public String getName() {
    return "slim";
  }

I changed of that to this:

package net.alanboy.swagger;

import io.swagger.codegen.v3.*;
import io.swagger.codegen.v3.generators.DefaultCodegenConfig;

import java.util.*;
import java.io.File;

public class SlimGenerator extends DefaultCodegenConfig {

  // source folder where to write the files
  protected String sourceFolder = "src";
  protected String apiVersion = "1.0.0";

  public CodegenType getTag() {
    return CodegenType.SERVER;
  }

  public String getName() {
    // the friendly name for the generator
    return "slim";
  }

  public String getHelp() {
    // Returns human-friendly help for the generator
    return "Generates a slim server stub.";
  }

  public SlimGenerator() {
    super();

    // set the output folder here
    outputFolder = "generated-code/slim";

    /**
     * Api classes.  You can write classes for each Api file with the apiTemplateFiles map.
     * as with models, add multiple entries with different extensions for multiple files per
     * class
     */
    apiTemplateFiles.put( "api.mustache", ".api.php");
    apiTemplateFiles.put( "interfaces.mustache", ".interfaces.php");
    apiTemplateFiles.put( "controllers.mustache", ".controller.php");

    /**
     * Supporting Files.  You can write single files for the generator with the
     * entire object tree available.  If the input file has a suffix of `.mustache
     * it will be processed by the template engine.  Otherwise, it will be copied
     * //  the destination folder, relative `outputFolder`
     */
    //supportingFiles.add(new SupportingFile("interfaces.mustache", "", "interfaces.php"));
    //supportingFiles.add(new SupportingFile("controllers.mustache", "", "controllers.php"));

    /**
     * Template Location.  This is the location which templates will be read from.  The generator
     * will use the resource stream to attempt to read the templates.
     */
    templateDir = "slim";

    /**
     * Models.  You can write model files using the modelTemplateFiles map.
     * if you want to create one template for file, you can do so here.
     * for multiple files for model, just put another entry in the `modelTemplateFiles` with
     * a different extension
     */
    modelTemplateFiles.put( "model.mustache", ".sample");

    /**
     * Reserved words.  Override this with reserved words specific to your language
     */
    reservedWords = new HashSet<String> (
      Arrays.asList( "sample1", "sample2")
    );

    /**
     * Additional Properties.  These values can be passed to the templates and
     * are available in models, apis, and supporting files
     */
    additionalProperties.put("apiVersion", apiVersion);


    /**
     * Language Specific Primitives.  These types will not trigger imports by
     * the client generator
     */
    languageSpecificPrimitives = new HashSet<String>(
      Arrays.asList( "Type1", "Type2")
    );
  }

And create the two new api files, controllers and interfaces:

C:\Users\alan\Code\swagger-codegen-test2\output\slim\src\main\resources\slim>dir /b
api.mustache
controllers.mustache
interfaces.mustache

When I run run.cmd again, I get my new generated files:

generated-code\slim\src\DefaultApi.api.php
generated-code\slim\src\DefaultApi.controller.php
generated-code\slim\src\DefaultApi.interfaces.php
generated-code\slim\src\Instance.sample
generated-code\slim\src\io
generated-code\slim\src\io\swagger
generated-code\slim\src\io\swagger\client
generated-code\slim\src\io\swagger\client\api
generated-code\slim\src\io\swagger\client\model

Useful stuff to know about mustache and handlebar syntax:

Dealing with commas

https://stackoverflow.com/questions/6114435/in-mustache-templating-is-there-an-elegant-way-of-expressing-a-comma-separated-l

static function {{operationIdCamelCase}}( 
    {{#hasPathParams}}{{#pathParams}}{{#comma}},{{/comma}} ${{paramName}}{{/pathParams}}{{/hasPathParams}}) 

That does not seem to work with Swagger codegen, for that use:

 {{#@last}},{{/@last}}

or

 {{^@last}},{{/@last}}

Here is an example: https://github.com/swagger-api/swagger-codegen-generators/blob/master/src/main/resources/handlebars/go/api_doc.mustache

Adding to a pre-existing codegen project

When adding a new subclass of DefaultCodegenConfig to have more than one available command in the single jar, don’t forget to add the new entry in the META-INF file:

net.enterpos.swagger.SlimGenerator
net.enterpos.swagger.SimpleGenerator
net.enterpos.swagger.TypeScriptAngularClientCodegen

Handlebars helpers

Override addHandlebarHelpers if you want to add custom handlebar helpers which extende the logic that you can run.

    @Override
    public void addHandlebarHelpers(Handlebars handlebars) {
        handlebars.registerHelper(IsHelper.NAME, new IsHelper());
        handlebars.registerHelper(HasHelper.NAME, new HasHelper());
        handlebars.registerHelper(IsNotHelper.NAME, new IsNotHelper());
        handlebars.registerHelper(HasNotHelper.NAME, new HasNotHelper());
        handlebars.registerHelper(BracesHelper.NAME, new BracesHelper());
        handlebars.registerHelper(BaseItemsHelper.NAME, new BaseItemsHelper());
        handlebars.registerHelper(NotEmptyHelper.NAME, new NotEmptyHelper());
        handlebars.registerHelpers(new StringUtilHelper());
    }

Reference

  • https://support.smartbear.com/swaggerhub/docs/enterprise/config/codegen-templates.html