====== API documentation (Swagger) ====== {{tag> documentation}} {{indexmenu_n>60}} We use [[http://swagger.io/specification/|swagger specification]] for RESTful API documentation with tools: * [[http://swagger.io/swagger-ui/|Swagger UI]] - visualize and interact with the API’s resources. It is available at IdM backend url ''/idm-backend/api''. * [[http://swagger2markup.github.io/swagger2markup/1.3.1|Swagger2Markup]] - generation of an up-to-date RESTful API documentation by combining documentation that’s been hand-written with auto-generated API documentation produced by Swagger. The result is intended to be an up-to-date, easy-to-read, on- and offline user guide. * [[https://github.com/asciidoctor/asciidoctor-maven-plugin|Asciidoctor maven plugin]] - official way to convert AsciiDoc documentation using Asciidoctor from an Maven build. Difference between dynamic and static documentation: * **Dynamic documentation** is exposed by Swagger UI from rest controllers with ''@Api'' annotation. Documentation is available on root backend server url e.g.''/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. ''/api/doc?group=core''. Documentation always contains authentication endpoint (security information). * **Static documentation** is generated from raw swagger specification (e.g. ''/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 ''/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 ''/webjars///doc/index.html''. ===== Configuration ===== 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. ==== Java ==== === Module properties === Use ''PropertyModuleDescriptor'' generalization for module descriptor definition and prepare [[https://proj.bcvsolutions.eu/ngidm/doku.php?id=en:navrh:konfigurace_aplikace#module_configuration|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; } } === Swagger endpoint === /** * 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"); } } === Rest controller === Add [[https://github.com/swagger-api/swagger-core/wiki/Annotations-1.5.X|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 [[https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations|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 ping( @Parameter(description = "In / out message", example = "hello") @RequestParam(required = false, defaultValue = "hello") String message ) { return new ResponseEntity<>(service.ping(message), HttpStatus.OK); } } === Model === Add [[https://github.com/swagger-api/swagger-core/wiki/Annotations-1.5.X|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 [[https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations|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 } === Test === 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); } } ==== Static documentation folder structure ==== 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: * ''/src/docs/asciidoc/'' - root documentation folder * ''index.adoc'' - main documentation file / entrypoint. Contains documentation structure, includes all other generated and extension files. * ''extensions'' - contains files with extensions * ''definitions'' - api models * ''overview'' - antre section * ''paths'' - controller paths * ''security'' - security section All files have to be written in asciidoc format. Read more about extensions in swagger2markup [[http://swagger2markup.github.io/swagger2markup/1.3.1/#extension_import_files_configuration|documentation]]. === Example extension === Add security information about demo identity credentials. Create file ''/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 [[http://swagger2markup.github.io/swagger2markup/1.3.1/#_pathsdocumentextension|extension points]]. ==== Maven ==== Documentation is generated under ''release'' profile. Add profile to module ''pom.xml'': release io.github.swagger2markup swagger2markup-maven-plugin ${swagger2markup.version} ${swagger.input} ${generated.asciidoc.directory} ASCIIDOC EN TAGS false ${asciidoctor.input.extensions.directory}/overview ${asciidoctor.input.extensions.directory}/definitions ${asciidoctor.input.extensions.directory}/paths ${asciidoctor.input.extensions.directory}/security/ ${swagger.snippetOutput.dir} true test convertSwagger2markup org.asciidoctor asciidoctor-maven-plugin 1.5.3 ${asciidoctor.input.directory} index.adoc book left 2 ${generated.asciidoc.directory} output-html test process-asciidoc html5 ${asciidoctor.html.output.directory.prefix}/core/${project.version}/doc All maven properties are preconfigured in parent ''pom.xml'': 2.7.0 1.3.1 ${project.basedir}/src/docs/asciidoc ${asciidoctor.input.directory}/extensions ${project.build.directory}/swagger swagger.json ${swagger.output.dir}/${swagger.output.filename} ${project.build.directory}/asciidoc/snippets ${project.build.directory}/asciidoc/generated ${project.build.directory}/classes/META-INF/resources/webjars ${asciidoctor.html.output.directory.prefix}/${project.artifactId}/${project.version}/doc ===== Conventions ===== * Add Swagger annotation. What can be written into annotation, will be written to annotation - will be shown in dynamic and static documentation. Static documentation extension is used, when annotation doesn't fit. * Use module-.properties with ''PropertyModuleDescriptor''. * Use ''produces = BaseController.APPLICATION\_HAL\_JSON\_VALUE'' in controller mapping * Use ''@ApiOperation(nickname = "")'' e.g. ''@ApiOperation(nickname = "ping")'' for controller methods - nickname (=> operationId) can be used in permalink. in springdoc version (13.1.0+) * Add Swagger annotation. What can be written into annotation, will be written to annotation - will be shown in dynamic and static documentation. Static documentation extension is used, when annotation doesn't fit. * Use module-.properties with ''PropertyModuleDescriptor''. * ''produces = BaseController.APPLICATION\_HAL\_JSON\_VALUE'' is set by default but you can override it * Use ''@Operation(operationId= "")'' e.g. ''@Operation(operationId= "ping")'' for controller methods can be used in permalink. ===== Aggregator ===== When module **aggregator** is built under ''release'' profile, then static **html** and **javadoc** are packed into archive ''/target/-doc.zip'' (tar.gz, tar.bz2). cd aggregator/ mvn package -Prelease -DdocumentationOnly=true Which documentation are packed into documentation archive is configured in ''/src/assembly/doc.xml'' descriptor. When new CzechIdM product module is created, don't forget to add new module here. ===== Tips ===== 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///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): /webjars///doc/index.html // e.g. http://localhost:8080/idm/webjars/core/7.3.0-rc.4-SNAPSHOT/doc/index.html ===== Implementation details ===== * Static documentation contains all files for now (copy / paste redundancy - see security section - same in all modules) - will be improved soon. * JSR303 for model documentation (comming soon) * Model SPI (comming soon) * UUID, GuardedString types (comming soon)