Creating a new generator which will become a part of the officially supported generators in OpenAPI Generator is pretty simple. We've created a helper script to bootstrap the operation. Let's look at the files necessary to create a new generator, then an example of bootstrapping a generator using the new.sh script in the root of the repository.

Required Files

The minimum set of files required to create a new generator are:

  • A "Codegen" file
    • exists under modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/
    • defines language options
    • defines framework options
    • determines OpenAPI feature set
    • extends the generation workflow
  • SPI registration
    • Above class must be referenced in modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
    • Tells the generator that this class exists
    • Allows for classpath extension (addition) of generators
  • A minimal template
    • Should include a README explaining usage
    • Must include an api.mustache
    • Exists under modules/openapi-generator/src/main/resources/ (plus embeddedTemplate dir value, see below)
  • Sample scripts under ./bin and ./bin/windows
    • Gives users a "real life" example of generated output
    • Samples are used by CI to verify generators and test for regressions in some casesNow, let's generate an example generator and then walk through the pieces. At the end, we'll touch on some known sticking points for new generator authors and provide some suggestions.

new.sh

The new.sh script in the root of the project is meant to simplify this process. Run ./new.sh —help.

  1. Stubs out files for new generators
  2. Usage:
  3. ./new.sh [options]
  4. Options:
  5. -n Required. Specify generator name, should be kebab-cased.
  6. -c Create a client generator
  7. -s Create a server generator
  8. -d Create a documentation generator
  9. -t When specified, creates test file(s) for the generator.
  10. -h Display help.
  11. Examples:
  12. Create a server generator for ktor:
  13. ./new.sh -n kotlin -s
  14. Creates:
  15. modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinServerCodegen.java
  16. modules/openapi-generator/src/main/resources/kotlin-server/README.mustache
  17. modules/openapi-generator/src/main/resources/kotlin-server/model.mustache
  18. modules/openapi-generator/src/main/resources/kotlin-server/api.mustache
  19. bin/windows/kotlin-server-petstore.bat
  20. bin/kotlin-server-petstore.sh
  21. Create a generic C# server generator:
  22. ./new.sh -n csharp -s -t
  23. Creates:
  24. modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CsharpServerCodegen.java
  25. modules/openapi-generator/src/main/resources/csharp-server/README.mustache
  26. modules/openapi-generator/src/main/resources/csharp-server/model.mustache
  27. modules/openapi-generator/src/main/resources/csharp-server/api.mustache
  28. bin/windows/csharp-server-petstore.bat
  29. bin/csharp-server-petstore.sh
  30. modules/openapi-generator/src/test/java/org/openapitools/codegen/csharp/CsharpServerCodegenTest.java
  31. modules/openapi-generator/src/test/java/org/openapitools/codegen/csharp/CsharpServerCodegenModelTest.java
  32. modules/openapi-generator/src/test/java/org/openapitools/codegen/csharp/CsharpServerCodegenOptionsTest.java
  33. modules/openapi-generator/src/test/java/org/openapitools/codegen/options/CsharpServerCodegenOptionsProvider.java

This script allows us to define a client, server, schema, or documentation generator. We'll focus on the simplest generator (documentation). The other generator types may require heavy extension of the "Config" base class, and these docs could very quickly become outdated. When creating a new generator, please review existing generators as a guideline for implementation.

Create a new Markdown generator:

  1. ./new.sh -n markdown -d

You should see output similar to the following:

  1. Creating modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/MarkdownDocumentationCodegen.java
  2. Creating modules/openapi-generator/src/main/resources/markdown-documentation/README.mustache
  3. Creating modules/openapi-generator/src/main/resources/markdown-documentation/model.mustache
  4. Creating modules/openapi-generator/src/main/resources/markdown-documentation/api.mustache
  5. Creating bin/windows/markdown-documentation-petstore.bat
  6. Creating bin/markdown-documentation-petstore.sh
  7. Finished.

Review Generated Config

Beginning with the "Codegen" file (MarkdownDocumentationCodegen.java), the constructor was created:

  1. public MarkdownDocumentationCodegen() {
  2. super();
  3. outputFolder = "generated-code" + File.separator + "markdown";
  4. modelTemplateFiles.put("model.mustache", ".zz");
  5. apiTemplateFiles.put("api.mustache", ".zz");
  6. embeddedTemplateDir = templateDir = "markdown-documentation";
  7. apiPackage = File.separator + "Apis";
  8. modelPackage = File.separator + "Models";
  9. // TODO: Fill this out.
  10. }

These options are some defaults which may require updating. Let's look line-by-line at the config.

  1. outputFolder = "generated-code" + File.separator + "markdown";

This is the default output location. This will be generated-code/markdown on non-Windows machines and generated-code\markdown on Windows. You may change this to any value you'd like, but a user will almost always provide an output directory.

When joining paths, always use File.seperator

  1. modelTemplateFiles.put("model.mustache", ".zz");

The model.mustache file is registered as the template for model generation. The new.sh script doesn't have a way to know your intended file extension, so we default to a .zz extension. This must be changed (unless your generator's target extension is .zz). For this example, you'd change .zz to .md or .markdown, depending on your preference.

This model template registration will use model.mustache to generate a new file for every model defined in your API's specification document.

The path is considered relative to embeddedTemplateDir, templateDir, or a library subdirectory (refer to the Java client generator implementation for a prime example).

  1. apiTemplateFiles.put("api.mustache", ".zz");

This is the template used for generating API related files. Similar to the above model template, you'll want to change .zz to .md or .markdown.

The path is considered relative to embeddedTemplateDir, templateDir, or a library subdirectory (refer to the Java client generator implementation for a prime example).

  1. embeddedTemplateDir = templateDir = "markdown-documentation";

This line sets the embedded and template directories to markdown-documentation. The embeddedTemplateDir refers to the directory which will exist under modules/openapi-generator/src/main/resources and will be published with every release in which your new generator is present.

The templateDir variable refers to the "current" template directory setting, as defined by the user. That is, the user may invoke with -t or —template-directory (or plugin option variants), and override this directory.

Both of these variables exist because the generator will fallback to files under embeddedTemplateDir if they are not defined in the user's custom template directory.

  1. apiPackage = File.separator + "Apis";

This sets the "package" location for anything considered an API document. You might want to change this setting if, for instance, your language doesn't support uppercase letters in the path. We don't need to worry about that here.

Every templated output from api.mustache (registered via apiTemplateFiles above) will end up in the directory defined by apiPackage here.

  1. modelPackage = File.separator + "Models";

Similarly, this sets the packasge for Models.

Every templated output from model.mustache (registered via modelTemplateFiles above) will end up in the directory defined by modelPackage here.

  1. supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));

A "supporting file" is an extra file which isn't created once for every operation or model defined in your specification document. It is a single file which may or may not be templated (determined by the extension of the filename).

A supporting file only passes through the Markdown template processor if the filename ends in .mustache.

The path is considered relative to embeddedTemplateDir, templateDir, or a library subdirectory (refer to the Java client generator implementation for a prime example).

If you want your readme to be generic (not templated), just rename the file to README.md and change README.mustache to README.md above.

Create templates

The new.sh created our three required files. Let's start filling out each of these files.

README.mustache

  1. # Documentation for {{appName}}
  2. {{#generateApiDocs}}
  3. <a name="documentation-for-api-endpoints"></a>
  4. ## Documentation for API Endpoints
  5. All URIs are relative to *{{{basePath}}}*
  6. Class | Method | HTTP request | Description
  7. ------------ | ------------- | ------------- | -------------
  8. {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**](Apis/{{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{{summary}}}{{/summary}}
  9. {{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
  10. {{/generateApiDocs}}
  11. {{#generateModelDocs}}
  12. <a name="documentation-for-models"></a>
  13. ## Documentation for Models
  14. {{#modelPackage}}
  15. {{#models}}{{#model}} - [{{{modelPackage}}}.{{{classname}}}](Models/{{modelDocPath}}{{{classname}}}.md)
  16. {{/model}}{{/models}}
  17. {{/modelPackage}}
  18. {{^modelPackage}}
  19. No model defined in this package
  20. {{/modelPackage}}
  21. {{/generateModelDocs}}
  22. <a name="documentation-for-authorization"></a>{{! TODO: optional documentation for authorization? }}
  23. ## Documentation for Authorization
  24. {{^authMethods}}
  25. All endpoints do not require authorization.
  26. {{/authMethods}}
  27. {{#authMethods}}
  28. {{#last}}
  29. Authentication schemes defined for the API:
  30. {{/last}}
  31. {{/authMethods}}
  32. {{#authMethods}}
  33. <a name="{{name}}"></a>
  34. ### {{name}}
  35. {{#isApiKey}}- **Type**: API key
  36. - **API key parameter name**: {{keyParamName}}
  37. - **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}}
  38. {{/isApiKey}}
  39. {{#isBasic}}- **Type**: HTTP basic authentication
  40. {{/isBasic}}
  41. {{#isOAuth}}- **Type**: OAuth
  42. - **Flow**: {{flow}}
  43. - **Authorization URL**: {{authorizationUrl}}
  44. - **Scopes**: {{^scopes}}N/A{{/scopes}}
  45. {{#scopes}} - {{scope}}: {{description}}
  46. {{/scopes}}
  47. {{/isOAuth}}
  48. {{/authMethods}}

Let's not focus too much on the contents of this file. You may refer to templating for more details on the variables bound to these files and to debugging how to debug the structures. Of note here is that we're generating structures in markdown as defined by the objects constructed by our new "Config" class.

api.mustache

The API documentation might look like this:

  1. # {{classname}}{{#description}}
  2. {{description}}{{/description}}
  3. All URIs are relative to *{{basePath}}*
  4. Method | HTTP request | Description
  5. ------------- | ------------- | -------------
  6. {{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}}
  7. {{/operation}}{{/operations}}
  8. {{#operations}}
  9. {{#operation}}
  10. <a name="{{operationId}}"></a>
  11. # **{{operationId}}**
  12. > {{#returnType}}{{returnType}} {{/returnType}}{{operationId}}({{#allParams}}{{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/allParams}})
  13. {{summary}}{{#notes}}
  14. {{notes}}{{/notes}}
  15. ### Parameters
  16. {{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}}
  17. Name | Type | Description | Notes
  18. ------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}}
  19. {{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}{{#generateModelDocs}}[**{{dataType}}**]({{baseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{dataType}}**{{/generateModelDocs}}{{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{#defaultValue}} [default to {{defaultValue}}]{{/defaultValue}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}}
  20. {{/allParams}}
  21. ### Return type
  22. {{#returnType}}{{#returnTypeIsPrimitive}}**{{returnType}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}{{#generateModelDocs}}[**{{returnType}}**]({{returnBaseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{returnType}}**{{/generateModelDocs}}{{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}null (empty response body){{/returnType}}
  23. ### Authorization
  24. {{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{name}}](../README.md#{{name}}){{^-last}}, {{/-last}}{{/authMethods}}
  25. ### HTTP request headers
  26. - **Content-Type**: {{#consumes}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/consumes}}{{^consumes}}Not defined{{/consumes}}
  27. - **Accept**: {{#produces}}{{{mediaType}}}{{#hasMore}}, {{/hasMore}}{{/produces}}{{^produces}}Not defined{{/produces}}
  28. {{/operation}}
  29. {{/operations}}

model.mustache

The models file could resemble the following.

  1. {{#models}}
  2. {{#model}}
  3. # {{{packageName}}}.{{modelPackage}}.{{{classname}}}
  4. ## Properties
  5. Name | Type | Description | Notes
  6. ------------ | ------------- | ------------- | -------------
  7. {{#parent}}
  8. {{#parentVars}}
  9. **{{name}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{complexType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#readOnly}}[readonly] {{/readOnly}}{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}}
  10. {{/parentVars}}
  11. {{/parent}}
  12. {{#vars}}**{{name}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{complexType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#readOnly}}[readonly] {{/readOnly}}{{#defaultValue}}[default to {{{.}}}]{{/defaultValue}}
  13. {{/vars}}
  14. [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
  15. {{/model}}
  16. {{/models}}

Build it

To compile quickly to test this out, you can run mvn clean package -DskipTests.

When implementing a more robust generator, you'll want to run all tests as well: mvn clean package

Compile Sample

The new.sh script created bin/markdown-documentation-petstore.sh:

  1. #!/bin/sh
  2. SCRIPT="$0"
  3. while [ -h "$SCRIPT" ] ; do
  4. ls=$(ls -ld "$SCRIPT")
  5. link=$(expr "$ls" : '.*-> \(.*\)$')
  6. if expr "$link" : '/.*' > /dev/null; then
  7. SCRIPT="$link"
  8. else
  9. SCRIPT=$(dirname "$SCRIPT")/"$link"
  10. fi
  11. done
  12. if [ ! -d "${APP_DIR}" ]; then
  13. APP_DIR=$(dirname "$SCRIPT")/..
  14. APP_DIR=$(cd "${APP_DIR}"; pwd)
  15. fi
  16. executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"
  17. if [ ! -f "$executable" ]
  18. then
  19. mvn clean package
  20. fi
  21. # if you've executed sbt assembly previously it will use that instead.
  22. export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
  23. ags="$@ generate -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g markdown -o samples/documentation/petstore/markdown"
  24. java ${JAVA_OPTS} -jar ${executable} ${ags}

This script is often used to apply default options for generation. A common option in most of these script is to define the template directory as the generator's directory under resources. This allows template maintainers to modify and test out template changes which don't require recompilation of the entire project. You'd still need to recompile the project in full if you add or modify behaviors to the generator (such as adding a CliOption).

Add -t modules/openapi-generator/src/main/resources/markdown-documentation to ags line to simplify the evaluation of template-only modifications:

  1. diff --git a/bin/markdown-documentation-petstore.sh b/bin/markdown-documentation-petstore.sh
  2. index d816771478..94b4ce6d12 100644
  3. --- a/bin/markdown-documentation-petstore.sh
  4. +++ b/bin/markdown-documentation-petstore.sh
  5. @@ -26,6 +26,6 @@ fi
  6. # if you've executed sbt assembly previously it will use that instead.
  7. export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
  8. -ags="$@ generate -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g markdown -o samples/documentation/petstore/markdown"
  9. +ags="$@ generate -t modules/openapi-generator/src/main/resources/markdown-documentation -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g markdown -o samples/documentation/petstore/markdown"
  10. java ${JAVA_OPTS} -jar ${executable} ${ags}

Verify output

Creating a new generator will be an iterative task. Once you've generated the sample, you'll want to try it out. For compiled client/server outputs, this would mean running the code or creating a small sample project to consume your artifact just to make sure it works.

For markdown, you can open in Visual Studio Code or any other editor with a markdown preview. Not all editors support relative links to other markdown documents. To test the output in this guide, install markserv:

  1. npm install --global markserv

Now, you can serve the output directory directly and test your links:

  1. markserv samples/documentation/petstore/markdown

That's it! You've created your first generator!