== OpenAPI

This module helps automating the generation of API documentation using Jooby projects. jooby-openapi works by examining an application at *build* time to infer API semantics based on byte code and optional annotations.

Automatically generates documentation in JSON/YAML format APIs. This documentation can be completed by javadoc comments or using swagger-api annotations.

This library supports:

- https://swagger.io/specification[OpenAPI 3] (json and yaml)
- https://swagger.io/tools/swagger-ui[Swagger UI] (Optional)
- https://github.com/Redocly/redoc[Redoc] (Optional)
- AsciiDoc Output (Optional)

Checkout the https://github.com/jooby-project/library-demo[demo project]

=== Configuration

.pom.xml
[source, xml, role = "primary", subs="verbatim,attributes"]
----
<properties>
  <application.class>myapp.App</application.class>
</properties>
...
<plugins>
  ...
  <plugin>
    <groupId>io.jooby</groupId>
    <artifactId>jooby-maven-plugin</artifactId>
    <version>{joobyVersion}</version>
    <executions>
      <execution>
        <goals>
          <goal>openapi</goal>
        </goals>
        <configuration>
          ...
        </configuration>
      </execution>
    </executions>
  </plugin>
</plugins>
----

.build.gradle
[source, groovy, role = "secondary", subs="verbatim,attributes"]
----
buildscript {

  ext {
    joobyVersion = "{joobyVersion}"
  }

}

plugins {
    id "application"
    id "io.jooby.openAPI" version "$\{joobyVersion\}"
    ...
}

...
mainClassName = "myapp.App"
...

// Configuration

openAPI {
  ....
}

// Run openAPI task on joobyRun
joobyRun.dependsOn openAPI

// Run openAPI task on build
jar.dependsOn openAPI
----

[NOTE]
====
The default phase of the plugin execution is `process-classes` which means it will be executed on `mvn jooby:run` command and on hot reload.
It may slow down `hot reload` process in case of large projects with a lot of code to process.
To avoid this behaviour you can specify maven build phase which suits your needs better (e.g. `prepare-package`).
====

==== Options

[cols="1,1,4a"]
|===
| Option | Default Value | Description

|`adoc`
| 
|List of asciidoc files used as template to generate documentation:

.Maven:

    <adoc>
      <file>guide.adoc</file>
     </adoc>

.Gradle:
    {
      adoc = ["guide.adoc"]
    }

|`basedir`
| `${project.dir}`
|Set base directory used it for loading openAPI template file name.

|`excludes`
|
|Regular expression used to excludes route. Example: `/web`.

|`includes`
| 
|Regular expression used to includes/keep route. Example: `/api/.*`.

|`javadoc`
|`on/true`
|Turn on/off javadoc generation from Java source files. Set to `off` or `false` to turn it off.

|`specVersion`
|`3.0`
|Set the desired spec output. Possible values: `3.0` or `3.1`

|`templateName`
|`openapi.yaml`
|Set openAPI template file path.

|===

=== Usage

To learn how it works, let's write a simple Pet API:

.Java
[source, java, role="primary"]
----
{
  install(new OpenAPIModule());        <1>

  path("/pets", () {                   <2>

    get("/", ctx -> {
      PetRepository repo = ...;
      return repo.list();
    });

  });
}
----

.Kotlin
[source, kt, role="secondary"]
----
{
  install(OpenAPIModule())             <1>

  path("/pets") {                      <2>

    get("/") {
       val repo = ...;
       repo.list()
    }

  }
}
----

<1> Install `OpenAPIModule`
<2> Write your API

The javadoc:OpenAPIModule[] read from classpath the generated `json` and `yaml` files. To generate
them you need to build your project.

.Maven
    mvn clean package

.Gradle
    ./gradlew build

You will find the files in the output build directory. If your application is `bar.Foo`, then:

.Maven
    target/classes/bar/Foo.json
    target/classes/bar/Foo.yaml

.Gradle
    build/classes/java/main/bar/Foo.json
    build/classes/java/main/bar/Foo.yaml

[NOTE]
====
This is the main difference with previous version. We moved from runtime to build time generation. This way we:

- Are able to get our OpenAPI files at build time (of course)
- At runtime, we don't waste resources (CPU, memory) while analyze and build the OpenAPI model
- We keep bootstrap as fast as possible
====

The OpenAPI generator works exactly the same for MVC routes (a.k.a Controller):

.Java
[source, java, role="primary"]
----
{
  install(new OpenAPIModule());

  mvc(new Pets_());
}

/**
* Pet Library.
*/
@Path("/pets")
public class Pets {
  
  /**
   * List pets.
   * 
   * @return All pets.
   */
  @GET
  public List<Pet> list() {
    ...
  }
  
}
----

.Kotlin
[source, kt, role="secondary"]
----
{
  install(OpenAPIModule())

  mvc(Pets_())
}

@Path("/pets")
class Pets {
  
  @GET
  fun list(): List<Pet> {
    ...
  }
  
}
----

The Maven plugin and Gradle task provide two filter properties `includes` and `excludes`. These
properties filter routes by their path pattern. The filter is a regular expression.

=== Documenting your API

Full/complete example available https://github.com/jooby-project/library-demo[here]

==== JavaDoc comments

JavaDoc comments are supported for script and MVC routes on Java code base. They are enable by default, to turn them off set `javadoc: off` or `javadoc: false` in your maven/gradle plugin.

.Script routes
[source,java, role="primary"]
----

/**
* My Library.
* @version 5.4.1
* @tag Books. All about books.
* @server.url https://books.api.com
* @server.description Production
* @x-logo https://my.api.com/logo.png
*/
public class App extends Jooby {
  {
    install(new OpenAPIModule());
   
    /*
     * Books operations.
     * @tag Books  
    */
    path("/api/books", () -> {
        /*
         * Query and filter books.
         * 
         * @param query Book filter.
         * @return List of matching books.
         * @operationId query
         */
        get("/", ctx -> {
         var query = ctx.query(BookQuery.class);  
        });
        /*
         * Find a book by ISBN.
         * @param isbn ISBN code.
         * @return A book.
         * @throws BookNotFoundException When a book doesn't exist. <code>404</code> 
         * @operationId bookByIsbn
         */
        get("/{isbn}", ctx -> {
         var query = ctx.query(BookQuery.class);  
        });
    });
    
  }
}

/**
 * Book query.
 * 
 * @type Book type.
 * @aga Book age.
 */
public record BookQuery(BookType type, int age) {}

/**
 * Books can be broadly categorized into fiction and non-fiction
 */
public enum BookType {
  /**
  * Fiction includes genres like fantasy, science fiction, romance, and mystery.
  */
  Fiction,
  /**
  * Non-fiction encompasses genres like history, biography, and self-help.
  */
  NonFiction
}

----

.MVC routes
[source,java, role="secondary"]
----

/**
* My Library.
* @version 5.4.1
* @tag Books. All about books.
* @server.url https://books.api.com
* @server.description Production
* @x-logo https://my.api.com/logo.png
*/
public class App extends Jooby {
  {
    mvc(new Books_());
    install(new OpenAPIModule());
  }
}
/**
 * Books operations.
 * @tag Books  
 */
@Path("/api/books")
public class Books {
  /**
   * Query and filter books.
    * 
   * @param query Book filter.
   * @return List of matching books.
   */
  public List<Book> query(@QueryParam BookQuery query) {
     ....
  }
  /**
   * Find a book by ISBN.
   * 
   * @param isbn ISBN code.
   * @return A book.
   * @throws BookNotFoundException When a book doesn't exist. <code>404</code> 
   */
  public Book bookByIsbn(@PathParam String isbn) {
    
  }
}

/**
 * Book query.
 * 
 * @type Book type.
 * @aga Book age.
 */
public record BookQuery(BookType type, int age) {}

/**
 * Books can be broadly categorized into fiction and non-fiction
 */
public enum BookType {
  /**
  * Fiction includes genres like fantasy, science fiction, romance, and mystery.
  */
  Fiction,
  /**
  * Non-fiction encompasses genres like history, biography, and self-help.
  */
  NonFiction
}
----

A JavaDoc comment is split into:

- summary: Everything before the first `.` or `<p>` paragraph
- description: Everything after the first `.` or `<p>` paragraph

Whitespaces (including new lines) are ignored. To introduce a new line, you must use a `<p>`.

==== Supported OpenAPI tags

[cols="1,1,1,1,4"]
|===
| Tag | Main | Controller | Method | Description

|@version 4.0.4
| [x]
|
|
|

|@contact.name
|[x]
|
|
|

|@contact.url
|[x]
|
|
|

|@contact.email
|[x]
|
|
|

|@license.name
|[x]
|
|
|

|@license.url
|[x]
|
|
|

|@securityScheme.name
|[x]
|
|
|

|@securityScheme.in
|[x]
|
|
|

|@securityScheme.paramName
|[x]
|
|
|

|@securityScheme.flows.implicit.authorizationUrl
|[x]
|
|
|

|@securityScheme.flows.implicit.scopes.name
|[x]
|
|
|

|@securityScheme.flows.implicit.scopes.description
|[x]
|
|
|

|@securityRequirement
|
|[x]
|[x]
|Name of the `securityScheme` and optionally scopes. Example: `myOauth2Security read:pets`

 
|@server.description
|[x]
|
|
|

|@x-
|[x]
|[x]
|[x]
|Tag starting with `x-` is considered an extension

|@tag.name
|[x]
|[x]
|[x]
|Tag name

|@tag.description
|[x]
|[x]
|[x]
|Tag Description

|@tag
|[x]
|[x]
|[x]
|Shortcut for previous `@tag.name` and `@tag.description`. The tag name is everything before `.`

|@operationId <name>
|
|
|[x]
|Defined the operationId, useful for script routes.

|@param <name>
|
|
|[x]
|Operation parameter

|@return
|
|
|[x]
|Operation default response

|@throws
|
|
|[x]
|Additional non-success responses types. The HTTP response code is taken from `<code>{number}</code>`, or set to `500` error.

|===

This feature is only available for Java routes. Kotlin source code is not supported.


==== Documentation Template

The OpenAPI output generates some default values for `info` and `server` section. It generates
the necessary to follow the specification and produces a valid output. These sections can be override
with better information/metadata.

To do so just write an `openapi.yaml` file inside the `conf` directory to use it as template.

.conf/openapi.yaml
[source, yaml]
----
openapi: 3.0.1
info:
  title: My Super API
  description: |
    Nunc commodo ipsum vitae dignissim congue. Quisque convallis malesuada tortor, non
    lacinia quam malesuada id. Curabitur nisi mi, lobortis non tempus vel, vestibulum et neque.

    ...
  version: "1.0"
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  paths:
    /api/pets:
      get:
        operationId: listPets
        description: List and sort pets.
        parameters:
          name: page
            descripton: Page number.
          
----

All sections from template file are merged into the final output.

The extension property: `x-merge-policy` controls how merge must be done:

- ignore: Silently ignore a path or operation present in template but not found in generated output. This is the default value.
- keep: Add a path or operation to final output. Must be valid path or operation.
- fail: Throw an error when path or operation is present in template but not found in generated output.

The extension property can be added at root/global level, paths, pathItem, operation or parameter level.

[NOTE]
====
Keep in mind that any section found here in the template overrides existing metadata.
====

=== Swagger Annotations

Optionally, this plugin depends on some OpenAPI annotations. To use them, you need to add a dependency to your project:

[dependency, artifactId="swagger-annotations"]
.

Once you added to your project, you can annotate your routes:

.Script
[source,java,role="primary"]
----
import io.swagger.v3.oas.annotations.Operation;
...

public class App extends Jooby {
  {
    path("/pets", () -> {
      
      get("/{id}", this::findPetById)

    });
  }

  @Operation(
      summary = "Find a pet by ID",
      description = "Find a pet by ID or throws a 404"
  )
  public Pet findPetById(Context ctx) {
    PetRepo repo = require(PetRepo.class);
    long id = ctx.path("id").longValue();
    return repo.find(id);
  }
}
----

.Kotlin
[source,kotlin,role="secondary"]
----
import io.swagger.v3.oas.annotations.Operation

class App :Kooby({
  path("/pets") {
  
    get("/{id}", ::findPetById)

  }
})

@Operation(
  summary = "Find a pet by ID",
  description = "Find a pet by ID or throws a 404"
)
fun findPetById(ctx: Context) : Pet {
  val repo = ...
  val id = ctx.path("id").longValue()
  return repo.find(id)
}
----

The OpenAPI annotations complement the openAPI byte code parser by adding documentation
or being more specific about an operation, parameter, response type, response status, etc.

Annotations works as documentation but also as a way to override what was generated by the byte
code parser.

Annotations are supported at script routes (using the technique described before) and mvc routes.

If you look at the example, there is no documentation for path parameter: `id`, still this parameter
is going to be present in the OpenAPI files (present, but without documentation).

To add documentation just do:

----
@Operation(
  summary = "Find a pet by ID",
  description = "Find a pet by ID or throws a 404",
  parameters = @Parameter(description = "Pet ID")
)
----

If the parameter annotation doesn't specify a name, parameter binding follows a positional assignment.

==== OpenAPIDefinition

This annotation is supported at the application level:

.OpenAPIDefinition
[source, java, role = "primary"]
----
@OpenAPIDefinition(
    info = @Info(
        title = "Title",
        description = "description",
        termsOfService = "Terms",
        contact = @Contact(
            name = "Jooby",
            url = "https://jooby.io",
            email = "support@jooby.io"
        ),
        license = @License(
            name = "Apache",
            url = "https://jooby.io/LICENSE"
        ),
        version = "10"
    ),
    tags = @Tag(name = "mytag")
)
class App extends Jooby {
  {
    // All routes now have the default tag: `Pets` 
  }
}
----

.Kotlin
[source, kt, role = "secondary"]
----
@OpenAPIDefinition(
    info = Info(
        title = "Title",
        description = "description",
        termsOfService = "Terms",
        contact = Contact(
            name = "Jooby",
            url = "https://jooby.io",
            email = "support@jooby.io"
        ),
        license = License(
            name = "Apache",
            url = "https://jooby.io/LICENSE"
        ),
        version = "10"
    ),
    tags = [Tag(name = "mytag")]
)
class App: Kooby({
})
----

==== Tags

Tagging is supported at three different levels:

.Application level
[source, java, role = "primary"]
----
@Tag(name = "Pets", description = "Pet operations")
class App extends Jooby {
  {
    // All routes now have the default tag: `Pets` 
  }
}
----

.Kotlin
[source, kt, role = "secondary"]
----
@Tag(name = "Pets", description = "Pet operations")
class App: Kooby({
    // All routes now have the default tag: `Pets` 
})
----

.Controller level
[source, java, role = "primary"]
----
@Tag(name = "Pets", description = "Pet operations")
@Path("/pets")
class Pets {
  // All web method now have the default tag: `Pets` 
}
----

.Kotlin
[source, kt, role = "secondary"]
----
@Tag(name = "Pets", description = "Pet operations")
@Path("/pets")
class Pets {
  // All web method now have the default tag: `Pets`
}
----

.Method level
[source, java, role = "primary"]
----
@Tag(name = "Pets", description = "Pet operations")
public List<Pet> list(Context ctx) {
  ...
}
----

.Kotlin
[source, kt, role = "secondary"]
----
@Tag(name = "Pets", description = "Pet operations")
fun list(ctx: Context): List<Pet> {
  ...
}
----

For multiple tags use the `@Tags` annotation or the `tags` property of `@OpenAPIDefinition`
and/or `@Operation` annotations.

==== Responses & Status

The default response code is `Success(200)` (or `NO_CONTENT(204)` for DELETE mvc routes). Now, if
you need to:

- document the default response
- use a custom response code
- use multiple response codes

You need the `ApiResponse` annotation:

.Document default response:
----
@ApiResponse(description = "This is the default response")
----

.Use a custom response code:
----
@ApiResponse(responseCode = "201", description = "This is the default response")
----

.Multiple response codes:
----
@ApiResponses({
  @ApiResponse(description = "This is the default response"),
  @ApiResponse(responseCode = "500"),
  @ApiResponse(responseCode = "400"),
  @ApiResponse(responseCode = "404")
})
----

=== Output/Display

include::modules/openapi-ascii.adoc[]

==== Swagger UI

To use swagger-ui just add the dependency to your project:

[dependency, artifactId="jooby-swagger-ui"]
.

The swagger-ui application will be available at `/swagger`. To modify the default path, just call javadoc:OpenAPIModule[swaggerUI, java.lang.String]

==== Redoc

To use redoc just add the dependency to your project:

[dependency, artifactId="jooby-redoc"]
.

The redoc application will be available at `/redoc`. To modify the default path, just call javadoc:OpenAPIModule[redoc, java.lang.String]
