Updates to build.gradle
The following packages should be added to the module's build.gradle:
implementation 'org.springdoc:springdoc-openapi-ui:1.6.5'
Updates to application.yml
Additionally a springdoc field should be added to the module's application.yml. Here is an example for mod-agreements. The “packages-to-scan” would need to be updated to be correct for the module being worked on:
springdoc: api-docs: path: /api packages-to-scan: org.olf.agreements.controllers swagger-ui: path: /docsui tryItOutEnabled: true operationsSorter: method tagsSorter: alpha filter: true
New configuration class
A configuration class for OpenAPI should be added within the config folder. This will be used to setup the top level fields of the OpenAPI json - including the Info object and the server objects. Note: this should be within the src/main/java
file directory within the module.
An example is shown below:
package org.olf.agreements.config; import java.util.List; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; import io.swagger.v3.oas.models.servers.Server; @Configuration public class OpenAPIConfig { @Value("${someurl}") private String devUrl; @Value("${prod-url}") private String prodUrl; @Bean public OpenAPI myOpenAPI() { Server devServer = new Server(); devServer.setUrl(devUrl); devServer.setDescription("Server URL in Development environment"); Server prodServer = new Server(); prodServer.setUrl(prodUrl); prodServer.setDescription("Server URL in Production environment"); Contact contact = new Contact(); contact.setEmail("some-contact-address@gmail.com"); contact.setName("some-contact-name"); contact.setUrl("https://some-contact-url.com"); License mitLicense = new License().name("MIT License").url("https://some-license-url/"); Info info = new Info() .title("Tutorial Management API") .version("1.0") .contact(contact) .description("This API exposes endpoints to manage tutorials.").termsOfService("https://www.some-terms-url.com/terms") .license(mitLicense); return new OpenAPI().info(info).servers(List.of(devServer, prodServer)); } }
Changes to module controller classes
Existing controller classes will need to be refactored into java removing any grails/groovy specific methods that are currently used. It may also be necessary to expose hidden methods which are currently created automatically by Grails.
Tag annotation is used to create the Tag Object in the resulting OpenAPI specification
RestController annotations are required at the top level of each controller class defining the name and description of that controller
An example set of annoationts for a class is as follows:
@Tag(name = "Tutorial", description = "Tutorial management APIs") @RestController public class TestController {
Each method in controller will need an expected response body implemented directly into the controller. For example:
@Data @AllArgsConstructor @Builder @ToString public static class DummyResponse { String code; }
Below is an example of how the methods within said controller should be annotated, this includes the following annotations:
Operation annotation is used to define the OpenAPI Operation object which describes a single API operation on a path.
ApiResponses and ApiResponse annotations are used to define the OpenAPI Responses and Response objects respectively. These define the responses which can be returned from the method, including the HTTP response code and content, note that with a 200 response we also have the previously defined DummyResponse as the content for that ApiResponse.
Finally for this example we use the GetMapping annotation to define the endpoint for this method
@Operation( summary = "Retrieve a Tutorial by Id", description = "Get a Tutorial object by specifying its id. The response is Tutorial object with id, title, description and published status.", tags = { "one", "two" } ) @ApiResponses({ @ApiResponse(responseCode = "200", content = { @Content(schema = @Schema(implementation = DummyResponse.class), mediaType = "application/json") }), @ApiResponse(responseCode = "404", content = { @Content(schema = @Schema()) }), @ApiResponse(responseCode = "500", content = { @Content(schema = @Schema()) }) }) @GetMapping(value="/tutorials/{id}") public @ResponseBody ResponseEntity<DummyResponse> getTutorialById(@PathVariable("id") long id) { DummyResponse dr = new DummyResponse("Wibble"); return new ResponseEntity(dr, HttpStatus.OK); }
A full example of this controller implementation is below, along with required package:
package org.olf.agreements.controllers; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.*; import io.swagger.v3.oas.annotations.responses.*; import java.util.ArrayList; import java.util.List; import org.springframework.http.MediaType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.http.ResponseEntity; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; import io.swagger.v3.oas.models.servers.Server; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import com.fasterxml.jackson.annotation.JsonView; import java.util.Optional; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; import java.util.Map; // See https://github.com/bezkoder/spring-boot-3-rest-api-example @Tag(name = "Tutorial", description = "Tutorial management APIs") @RestController public class TestController { @Data @AllArgsConstructor @Builder @ToString public static class DummyResponse { String code; } @Operation( summary = "Retrieve a Tutorial by Id", description = "Get a Tutorial object by specifying its id. The response is Tutorial object with id, title, description and published status.", tags = { "one", "two" } ) @ApiResponses({ @ApiResponse(responseCode = "200", content = { @Content(schema = @Schema(implementation = DummyResponse.class), mediaType = "application/json") }), @ApiResponse(responseCode = "404", content = { @Content(schema = @Schema()) }), @ApiResponse(responseCode = "500", content = { @Content(schema = @Schema()) }) }) @GetMapping(value="/tutorials/{id}") public @ResponseBody ResponseEntity<DummyResponse> getTutorialById(@PathVariable("id") long id) { DummyResponse dr = new DummyResponse("Wibble"); return new ResponseEntity(dr, HttpStatus.OK); } }