This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. CKG Edit ====== 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 ''<server>/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. <note tip> 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.''<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). * **Static documentation** is generated from raw swagger specification (e.g. ''<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''. </note> ===== 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]]. <code java> /** * 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; } } </code> === Swagger endpoint === <code java> /** * 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"); } } </code> springdoc version (13.1.0+) <code java> /** * 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"); } } </code> === Rest controller === Add [[https://github.com/swagger-api/swagger-core/wiki/Annotations-1.5.X|swagger annotations]] to controllers. <code java> /** * 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); } } </code> For springdoc version (13.1.0+) add [[https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations|new swagger annotations]] to controllers. <code java> /** * 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); } } </code> === Model === Add [[https://github.com/swagger-api/swagger-core/wiki/Annotations-1.5.X|swagger annotations]] to dtos. <code java> /** * 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 } </code> For springdoc version (13.1.0+) add [[https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations|new swagger annotations]] to dtos. <code java> /** * 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 } </code> === Test === Create integration test: <code java> /** * 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); } } </code> ==== 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: * ''<module>/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 ''<module>/src/docs/asciidoc/extensions/security/document-begin-text.adoc'' with content: <code asciidoc> == Demo credentials admin / admin </code> 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'': <code 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> </code> All maven properties are preconfigured in parent ''pom.xml'': <code 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 </code> ===== 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-<module>.properties with ''PropertyModuleDescriptor''. * Use ''produces = BaseController.APPLICATION\_HAL\_JSON\_VALUE'' in controller mapping * Use ''@ApiOperation(nickname = "<operatonId>")'' 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-<module>.properties with ''PropertyModuleDescriptor''. * ''produces = BaseController.APPLICATION\_HAL\_JSON\_VALUE'' is set by default but you can override it * Use ''@Operation(operationId= "<operatonId>")'' 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/<version>-doc.zip'' (tar.gz, tar.bz2). <code shell> cd aggregator/ </code> <code shell> mvn package -Prelease -DdocumentationOnly=true </code> <note info> 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. </note> ===== Tips ===== Use IdmIdentityController as inspiration. Export swagger.json by running single test: <code shell> mvn clean package -DskipTests mvn surefire:test -Dtest=Swagger2MarkupTest -Prelease </code> 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'': <code shell> mvn package -DskipTests -Prelease </code> Static html documentation will be available from url (application's war has to be build under ''release'' profile): <code url> <server>/webjars/<module>/<version>/doc/index.html // e.g. http://localhost:8080/idm/webjars/core/7.3.0-rc.4-SNAPSHOT/doc/index.html </code> ===== 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) by chalupat Log In