Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
Last revision Both sides next revision
devel:documentation:architecture:dev:swagger [2018/03/22 15:25]
stloukalp created
devel:documentation:architecture:dev:swagger [2023/10/19 10:43]
chalupat [#3330] Replace Springfox with springdoc
Line 1: Line 1:
 +====== 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. 
 +
 +===== 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