We use swagger specification for RESTful API documentation with tools:
<server>/idm-backend/api
.@Api
annotation. Documentation is available on root backend server url e.g.<server>/api
- documented endpoints can be tested with demo credentials directly from Swagger UI. Documentation is splitted by modules. Raw Swagger specification in json format is available on url e.g. <server>/api/doc?group=core
. Documentation always contains authentication endpoint (security information).<server>/api/doc?group=core
) and static content (see next chapter with static documentation folder structure). Documentation is splitted by modules. Generated output html can be found in <module>/target/asciidoc/html/index.html
. This documentation can be exposed as api reference and is exposed directly in application, which is built under release
profile on urls with convention <server>/webjars/<module>/<version>/doc/index.html
.
Parent project contains basic settings for module documentation. When a new module is added, some steps have to be done.
Complete configuration can be found in example
module.
Use PropertyModuleDescriptor
generalization for module descriptor definition and prepare module-example.properties.
/** * Example module descriptor */ @Component @PropertySource("classpath:module-" + ExampleModuleDescriptor.MODULE_ID + ".properties") @ConfigurationProperties(prefix = "module." + ExampleModuleDescriptor.MODULE_ID + ".build", ignoreUnknownFields = true, ignoreInvalidFields = true) public class ExampleModuleDescriptor extends PropertyModuleDescriptor { public static final String MODULE_ID = "example"; @Override public String getId() { return MODULE_ID; } /** * Enables links to swagger documentation */ @Override public boolean isDocumentationAvailable() { return true; } }
/** * Example module swagger configuration */ @Configuration @ConditionalOnProperty(prefix = "springfox.documentation.swagger", name = "enabled", matchIfMissing = true) public class ExampleSwaggerConfig extends AbstractSwaggerConfig { @Autowired private ExampleModuleDescriptor moduleDescriptor; @Override protected ModuleDescriptor getModuleDescriptor() { return moduleDescriptor; } @Bean public Docket exampleApi() { return api("eu.bcvsolutions.idm.example"); } }
springdoc version (13.1.0+)
/** * Example module swagger configuration */ @Configuration @ConditionalOnProperty(prefix = "springdoc.swagger-ui", name = "enabled", matchIfMissing = true) public class ExampleSwaggerConfig extends AbstractSwaggerConfig { @Autowired private ExampleModuleDescriptor moduleDescriptor; @Override protected ModuleDescriptor getModuleDescriptor() { return moduleDescriptor; } @Bean public GroupedOpenApi exampleApi() { return api("eu.bcvsolutions.idm.example.rest"); } }
Add swagger annotations to controllers.
/** * Ping pong example controller */ @RestController @RequestMapping(value = BaseController.BASE_PATH + "/examples", produces = BaseController.APPLICATION_HAL_JSON_VALUE) @Api(value = "Examples", description = "Example operations", tags = { "Examples" }) public class ExampleController { @ResponseBody @RequestMapping(method = RequestMethod.GET) @ApiOperation( value = "Ping - Pong operation", notes= "Returns message with additional informations", nickname = "ping", tags={ "Examples" }, response = Pong.class, authorizations = { @Authorization(SwaggerConfig.AUTHENTICATION_BASIC), @Authorization(SwaggerConfig.AUTHENTICATION_CIDMST) }) public ResponseEntity<?> ping( @ApiParam(value = "In / out message", example = "hello", defaultValue = "hello") @RequestParam(required = false, defaultValue = "hello") String message ) { return new ResponseEntity<>(new Pong(message), HttpStatus.OK); } }
For springdoc version (13.1.0+) add new swagger annotations to controllers.
/** * Ping pong example controller */ @RestController @Enabled(ExampleModuleDescriptor.MODULE_ID) @RequestMapping(value = BaseController.BASE_PATH + "/examples") @Tag(name = ExampleController.TAG, description = "Example operations") public class ExampleController { protected static final String TAG = "Examples"; @Autowired private ExampleService service; @ResponseBody @RequestMapping(method = RequestMethod.GET, path = "/ping") @Operation( summary = "Ping - Pong operation", description= "Returns message with additional informations", operationId = "ping", tags={ ExampleController.TAG } responses = @ApiResponse( responseCode = "200", content = { @Content( mediaType = BaseController.APPLICATION_HAL_JSON_VALUE, schema = @Schema( implementation = Pong.class ) ) } )) @SecurityRequirements({ @SecurityRequirement(name = SwaggerConfig.AUTHENTICATION_BASIC), @SecurityRequirement(name = SwaggerConfig.AUTHENTICATION_CIDMST) }) public ResponseEntity<Pong> ping( @Parameter(description = "In / out message", example = "hello") @RequestParam(required = false, defaultValue = "hello") String message ) { return new ResponseEntity<>(service.ping(message), HttpStatus.OK); } }
Add swagger annotations to dtos.
/** * Example ping - pong response dto */ @ApiModel(description = "Ping - Pong response") public class Pong implements BaseDto { private static final long serialVersionUID = 1L; // @ApiModelProperty(required = true, notes = "Unique pong identifier") private UUID id; @ApiModelProperty(notes = "Ping - Pong response message") private String message; @ApiModelProperty(required = true, notes = "Creation time") private DateTime created; // ... getters, setters }
For springdoc version (13.1.0+) add new swagger annotations to dtos.
/** * Example ping - pong response dto */ @Schema(description = "Ping - Pong response") public class Pong implements BaseDto { private static final long serialVersionUID = 1L; // @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Unique pong identifier") private UUID id; @Schema(description = "Ping - Pong response message") private String message; @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Creation time") private ZonedDateTime created; // ... getters, setters }
Create integration test:
/** * Static swagger generation to sources - will be used as input for swagger2Markup build */ public class Swagger2MarkupTest extends AbstractSwaggerTest { @Test public void testConvertSwagger() throws Exception { super.convertSwagger(ExampleModuleDescriptor.MODULE_ID); } }
We are using asciidoctor maven plugin for static documentation (see maven chapter). Maven plugins prepare all generated artifacts, but requires some static artifacts with static / written documentation in structure in module sources folder:
<module>/src/docs/asciidoc/
- root documentation folderindex.adoc
- main documentation file / entrypoint. Contains documentation structure, includes all other generated and extension files.extensions
- contains files with extensionsdefinitions
- api modelsoverview
- antre sectionpaths
- controller pathssecurity
- security sectionAll files have to be written in asciidoc format. Read more about extensions in swagger2markup documentation.
Add security information about demo identity credentials. Create file <module>/src/docs/asciidoc/extensions/security/document-begin-text.adoc
with content:
== Demo credentials admin / admin
This content will be automatically included to static documentation to the security section. Read more about available extension points.
Documentation is generated under release
profile. Add profile to module pom.xml
:
<profile> <id>release</id> <build> <plugins> <!-- First, use the swagger2markup plugin to generate asciidoc --> <plugin> <groupId>io.github.swagger2markup</groupId> <artifactId>swagger2markup-maven-plugin</artifactId> <version>${swagger2markup.version}</version> <configuration> <swaggerInput>${swagger.input}</swaggerInput> <outputDir>${generated.asciidoc.directory}</outputDir> <config> <swagger2markup.markupLanguage>ASCIIDOC</swagger2markup.markupLanguage> <swagger2markup.outputLanguage>EN</swagger2markup.outputLanguage> <swagger2markup.pathsGroupedBy>TAGS</swagger2markup.pathsGroupedBy> <swagger2markup.generatedExamplesEnabled>false</swagger2markup.generatedExamplesEnabled> <swagger2markup.extensions.dynamicOverview.contentPath>${asciidoctor.input.extensions.directory}/overview</swagger2markup.extensions.dynamicOverview.contentPath> <swagger2markup.extensions.dynamicDefinitions.contentPath>${asciidoctor.input.extensions.directory}/definitions</swagger2markup.extensions.dynamicDefinitions.contentPath> <swagger2markup.extensions.dynamicPaths.contentPath>${asciidoctor.input.extensions.directory}/paths</swagger2markup.extensions.dynamicPaths.contentPath> <swagger2markup.extensions.dynamicSecurity.contentPath>${asciidoctor.input.extensions.directory}/security/</swagger2markup.extensions.dynamicSecurity.contentPath> <swagger2markup.extensions.springRestDocs.snippetBaseUri>${swagger.snippetOutput.dir}</swagger2markup.extensions.springRestDocs.snippetBaseUri> <swagger2markup.extensions.springRestDocs.defaultSnippets>true</swagger2markup.extensions.springRestDocs.defaultSnippets> </config> </configuration> <executions> <execution> <phase>test</phase> <goals> <goal>convertSwagger2markup</goal> </goals> </execution> </executions> </plugin> <!-- Run the generated asciidoc through Asciidoctor to generate other documentation types, such as PDFs or HTML5 --> <plugin> <groupId>org.asciidoctor</groupId> <artifactId>asciidoctor-maven-plugin</artifactId> <version>1.5.3</version> <!-- Configure generic document generation settings --> <configuration> <sourceDirectory>${asciidoctor.input.directory}</sourceDirectory> <sourceDocumentName>index.adoc</sourceDocumentName> <attributes> <doctype>book</doctype> <toc>left</toc> <toclevels>2</toclevels> <!-- Resources by tag names in menu only --> <numbered /> <hardbreaks /> <sectlinks /> <sectanchors /> <generated>${generated.asciidoc.directory}</generated> </attributes> </configuration> <!-- Since each execution can only handle one backend, run separate executions for each desired output type --> <executions> <execution> <id>output-html</id> <phase>test</phase> <goals> <goal>process-asciidoc</goal> </goals> <configuration> <backend>html5</backend> <!-- static documentation will be available as webjars --> <!-- e.g. http://localhost:8080/idm/webjars/core/7.3.0/doc/index.html --> <outputDirectory>${asciidoctor.html.output.directory.prefix}/core/${project.version}/doc</outputDirectory> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile>
All maven properties are preconfigured in parent pom.xml
:
<swagger.version>2.7.0</swagger.version> <swagger2markup.version>1.3.1</swagger2markup.version> <!-- properties are used ind test and doc profile, test: swagger.json input is generated doc: swagger2markup and asciidoctor plugins require them --> <asciidoctor.input.directory>${project.basedir}/src/docs/asciidoc</asciidoctor.input.directory> <asciidoctor.input.extensions.directory>${asciidoctor.input.directory}/extensions</asciidoctor.input.extensions.directory> <swagger.output.dir>${project.build.directory}/swagger</swagger.output.dir> <swagger.output.filename>swagger.json</swagger.output.filename> <swagger.input>${swagger.output.dir}/${swagger.output.filename}</swagger.input> <swagger.snippetOutput.dir>${project.build.directory}/asciidoc/snippets</swagger.snippetOutput.dir> <generated.asciidoc.directory>${project.build.directory}/asciidoc/generated</generated.asciidoc.directory> <!-- static documentation will be available as webjars --> <!-- e.g. http://localhost:8080/idm/webjars/core/7.3.0/doc/index.html --> <asciidoctor.html.output.directory.prefix>${project.build.directory}/classes/META-INF/resources/webjars</asciidoctor.html.output.directory.prefix> <asciidoctor.html.output.directory>${asciidoctor.html.output.directory.prefix}/${project.artifactId}/${project.version}/doc</asciidoctor.html.output.director
PropertyModuleDescriptor
.produces = BaseController.APPLICATION\_HAL\_JSON\_VALUE
in controller mapping@ApiOperation(nickname = "<operatonId>")
e.g. @ApiOperation(nickname = "ping")
for controller methods - nickname (⇒ operationId) can be used in permalink. in springdoc version (13.1.0+)
PropertyModuleDescriptor
.produces = BaseController.APPLICATION\_HAL\_JSON\_VALUE
is set by default but you can override it@Operation(operationId= "<operatonId>")
e.g. @Operation(operationId= "ping")
for controller methods can be used in permalink.
When module aggregator is built under release
profile, then static html and javadoc are packed into archive /target/<version>-doc.zip
(tar.gz, tar.bz2).
cd aggregator/
mvn package -Prelease -DdocumentationOnly=true
/src/assembly/doc.xml
descriptor. When new CzechIdM product module is created, don't forget to add new module here.
Use IdmIdentityController as inspiration.
Export swagger.json by running single test:
mvn clean package -DskipTests mvn surefire:test -Dtest=Swagger2MarkupTest -Prelease
Generate static documentation only (swagger.json has to be exported - see previous tip). Generated html will be available in project's folder /target/classes/META-INF/resources/webjars/<module>/<version>/doc/index.html
:
mvn package -DskipTests -Prelease
Static html documentation will be available from url (application's war has to be build under release
profile):
<server>/webjars/<module>/<version>/doc/index.html // e.g. http://localhost:8080/idm/webjars/core/7.3.0-rc.4-SNAPSHOT/doc/index.html