A lot of API are documented using Swagger, it’s a good thing that API are documented four us dev for understanding how they work and how to call them.
But a lot of these API are documented using Swagger 2, now that OpenApi is released (since 2017, the actual version is the 3.1 and is available since 15/02/2021) some projects didn’t update their documentation tools, I will try in this article to help you do so.
What you will need
In order to follow this tutorial, you will need a REST API, so you can :
Follow the tutorial to built your API and the one to documente it
Clone the swagger branch of this repository
Having your own API documented using Swagger 2 ready
Step 1 : getting ride of SpringFox dependencies
When we first implemented our Swagger, we add these dependencies to have
The json generated at http://localhost:8080/v2/api-docs
The UI at http://localhost:8080/swagger-ui.html
-
The Bean Validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-bean-validators</artifactId> <version>2.9.2</version> </dependency> This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-ui</artifactId> <version>1.5.8</version> </dependency> The json generated http://localhost:8080/v3/api-docs/
The UI page http://localhost:8080/swagger-ui.html
The bean validation
At this point, we should have some compilation problems because of some annotations due to the missing dependencies that we have replaced.
We will corrige that now.
Step 2: changing the annotations of the endpoints
In the previous tutorial, we documented our API using a configuration class
package com.erwan.human.config; | |
import com.google.common.base.Predicate; | |
import com.google.common.base.Predicates; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import springfox.documentation.builders.PathSelectors; | |
import springfox.documentation.builders.RequestHandlerSelectors; | |
import springfox.documentation.service.ApiInfo; | |
import springfox.documentation.service.Contact; | |
import springfox.documentation.spi.DocumentationType; | |
import springfox.documentation.spring.web.plugins.Docket; | |
import springfox.documentation.swagger2.annotations.EnableSwagger2; | |
import java.util.Collections; | |
@Configuration | |
@EnableSwagger2 | |
public class SwaggerConfig { | |
@Bean | |
public Docket api() { | |
return new Docket(DocumentationType.SWAGGER_2) | |
.select() | |
.apis(RequestHandlerSelectors.any()) | |
.paths(path()) | |
.build() | |
.apiInfo(apiInfo()) | |
.useDefaultResponseMessages(false); | |
} | |
private Predicate<String> path() { | |
return Predicates.not(PathSelectors.regex("/error")); | |
} | |
private ApiInfo apiInfo() { | |
return new ApiInfo( | |
"Human Cloning API", | |
"API for creating clone to fight in the clones war", | |
"1.0.O", | |
"", | |
new Contact("Erwan Le Tutour", "https://github.com/ErwanLT", "erwanletutour.elt@gmail.com"), | |
"MIT License", | |
"https://opensource.org/licenses/mit-license.php", | |
Collections.emptyList()); | |
} | |
} |
Here we have 2 choices
declaring a new configuration class
Using annotations in one of our controllers
I will give you 2 equivalent example of the previous code in OpenApi way
Configuration class
@Configuration | |
public class OpenApiConfiguration { | |
@Bean | |
public OpenAPI customOpenAPI() { | |
return new OpenAPI().info(apiInfo()); | |
} | |
private Info apiInfo() { | |
return new Info() | |
.title("Human cloning API") | |
.description("API for creating clone who will fight in the clones wars") | |
.version("2.0") | |
.contact(apiContact()) | |
.license(apiLicence()); | |
} | |
private License apiLicence() { | |
return new License() | |
.name("MIT Licence") | |
.url("https://opensource.org/licenses/mit-license.php"); | |
} | |
private Contact apiContact() { | |
return new Contact() | |
.name("Erwan LE TUTOUR") | |
.email("erwanletutour.elt@gmail.com") | |
.url("https://github.com/ErwanLT"); | |
} | |
} |
With annotations
@OpenAPIDefinition(info = @Info(title = "Human cloning API", | |
description = "API for creating clone who will fight in the clones wars", | |
version = "2.0", | |
contact = @Contact( | |
name = "LE TUTOUR Erwan", | |
email = "erwanletutour.elt@gmail.com", | |
url = "https://github.com/ErwanLT" | |
), | |
license = @License( | |
name = "MIT Licence", | |
url = "https://opensource.org/licenses/mit-license.php" | |
) | |
)) | |
public class HumanCloningController { | |
} |
What it will look like in the UI
The previous example will look the same in the UI page, so it’s up to you to choose what method you want to use
Step 3: changing the annotations of the endpoints
Swagger 2.0 annotation
Let’s take an example
@PostMapping() | |
@PreAuthorize("hasAnyAuthority('ROLE_KAMINOAIN', 'ROLE_EMPEROR')") | |
@ApiOperation(value = "Create a clone to fight in the clones war", | |
produces = "application/json") | |
@ApiResponses(value = { | |
@ApiResponse(code = 200, message = "Clone created", response = Clone.class), | |
@ApiResponse(code = 500, message = "An error occured") | |
}) | |
public Clone createClone(@RequestBody Clone clone){ | |
return repository.save(clone); | |
} |
Here is a POST method documented with classique Swagger 2 annotations
@ApiOperation : Describes an operation or typically a HTTP method against a specific path.
@ApiResponses : A wrapper to allow a list of multiple ApiResponse objects. Here I have 2 @ApiResponse to describe my 200 and 500 HTTP status return code.
I can also describe what my status will return, the 200 will respond with an objet, so I added the object class to response field.
That will render like the following picture in the UI page
Here a second example with this time a GET method
@GetMapping("/{id}") | |
@PreAuthorize("hasAnyAuthority('ROLE_KAMINOAIN', 'ROLE_EMPEROR')") | |
@ApiOperation(value = "Find a clone by it's ID") | |
@ApiResponses(value = { | |
@ApiResponse(code = 200, message = "Clone found",response = Clone.class), | |
@ApiResponse(code = 404, message = "Clone not found") | |
}) | |
public Clone findById(@ApiParam(name = "The clone ID", example = "12",required = true) @PathVariable("id") Long id) throws BeanNotFound { | |
return getOne(id); | |
} |
Here in addition of the previous annotations, I have added the documentation of the method parameter using @ApiParam.
With the new dependency, the annotation described are no longer the same.
So let’s see what has changed.
OpenAPI annotations
@Operation(summary = "Create a clone", description = "Create a clone with the information of the body.") | |
@ApiResponses(value = { | |
@ApiResponse(responseCode = "200", description = "Clone created", content = { | |
@Content(mediaType = "application/json", schema = @Schema(implementation = Clone.class)) }), | |
@ApiResponse(responseCode = "500", description = "An error occured.", content = @Content) }) | |
@PostMapping | |
@PreAuthorize("hasAnyAuthority('ROLE_KAMINOAIN', 'ROLE_EMPEROR')") | |
public Clone createClone(@RequestBody Clone clone){ | |
return repository.save(clone); | |
} |
@GetMapping("/{id}") | |
@PreAuthorize("hasAnyAuthority('ROLE_KAMINOAIN', 'ROLE_EMPEROR')") | |
@Operation(description = "Find a clone by it's ID") | |
@ApiResponses(value = { | |
@ApiResponse(responseCode = "200", description = "Clone found",content = { | |
@Content(mediaType = "application/json", schema = @Schema(implementation = Clone.class)) }), | |
@ApiResponse(responseCode = "404", description = "No clones found", content = @Content) | |
}) | |
public Clone findById(@Parameter(name = "The clone ID", example = "12",required = true) @PathVariable("id") Long id) throws BeanNotFound { | |
return getOne(id); | |
} |
With the 2 above example who is the same method that i used previously we can now see some of the changes that was operated.
@ApiOperation *-> *@Operation, the value field become summary and notes *become *description.
@ApiResponse : the field code become responseCode and is no longer an integer but a string, also the field message become description.
The biggest change is that the response field is now an annotation. @content.@ApiParam become @Parameter
They are other change, but since they are not used here, i recommend you to use the openAPI documentation.
Step 4: changing the annotations of the models
Swagger 2.0 annotation
So in Swagger 2 when i wanted to document an object, my class looked somewhere like this
package com.erwan.human.domaine; | |
import com.erwan.human.reference.CloneType; | |
import com.sun.istack.NotNull; | |
import com.sun.istack.Nullable; | |
import io.swagger.annotations.ApiModel; | |
import io.swagger.annotations.ApiModelProperty; | |
import lombok.Getter; | |
import lombok.Setter; | |
import javax.persistence.*; | |
@Entity | |
@Getter | |
@Setter | |
@ApiModel | |
public class Clone { | |
@Id | |
@GeneratedValue(strategy = GenerationType.AUTO) | |
@Column(name = "id") | |
@ApiModelProperty(value = "The generated ID when saved in database", | |
name = "ID", | |
dataType = "Long", | |
required = false, | |
position = 0) | |
private Long id; | |
@ApiModelProperty( value = "The birthplace of my clone", | |
name = "birthPlace", | |
dataType = "String", | |
required = false, | |
position = 1 | |
) | |
private final String birthPlace = "Kamino"; | |
@NotNull | |
@ApiModelProperty( value = "The code of my clone", | |
name = "codeName", | |
dataType = "String", | |
required = true, | |
position = 2 | |
) | |
private String codeName; | |
@NotNull | |
@Enumerated | |
@ApiModelProperty( value = "The type of my clone", | |
name = "type", | |
dataType = "String", | |
required = true, | |
position = 3, | |
allowableValues = "flametrooper, medic, gunner, scoot, jetpack" | |
) | |
private CloneType type; | |
@Nullable | |
@ApiModelProperty( value = "The platoon of my clone", | |
name = "platoon", | |
dataType = "Integer", | |
required = false, | |
position = 4 | |
) | |
private int platoon; | |
@ApiModelProperty( value = "The platoon of my clone", | |
name = "platoon", | |
dataType = "Integer", | |
required = false, | |
position = 5 | |
) | |
private String affiliation = "Galactic Republic"; | |
} |
As you can see, my classe is annoted with the @ApiModel and it’s properties with @ApiModelProperty.
*The *@ApiModelProperty allow us to add definitions such as description (value), name, data type, example values, and allowed values.
OpenAPI annotations
If you look closely at my ApiResponse with status code 200, you will see that the response is now @content, and that we gave the schema field the class that will be returned like this @Schema(implementation = MyClass.class).
With that annotation, OpenApi know which class to load, so i don’t have to annotate my class with an @ApiModel like annotation, but I still can document my property.
@Entity | |
@Getter | |
@Setter | |
public class Clone { | |
@Id | |
@GeneratedValue(strategy = GenerationType.AUTO) | |
@Column(name = "id") | |
@Schema(description = "The generated ID when saved in database", | |
name = "id", | |
required = false) | |
private Long id; | |
@Schema(description = "The clone code name", | |
name = "brithPlace", | |
required = false, | |
example = "Kamino") | |
@Size(min = 3, max = 40) | |
private final String birthPlace = "Kamino"; | |
@Schema(description = "The clone code name", | |
name = "codeName", | |
required = true) | |
@NotNull | |
private String codeName; | |
@Schema(description = "The clone specialisation", | |
name = "type", | |
required = true) | |
@NotNull | |
@Enumerated | |
private CloneType type; | |
@Schema(description = "The clone's platoon", | |
name = "platoon", | |
required = false, | |
example = "501") | |
@Nullable | |
private int platoon; | |
@Schema(description = "The clone affilation", | |
name = "affilation", | |
required = false, | |
minLength = 3, | |
maxLength = 40, | |
example = "Galactic Republic") | |
private String affiliation = "Galactic Republic"; | |
} |
So, what I have done here ?
Like previously, my fields are documented, but I have used 2 differentes method for some validation (size).
I can use the bean validation annotation, or I can use the property of the Schema annotation, the result will be the same.
The position property don’t exist anymore, the fields are in the same order as the class.
Thanks for your reading time, as previously, the code used in this tutorial is findable in this Github repository, branch toOpenApi.
Top comments (1)
Thanks for the help. This is the first place I found which showed me how to convert the ApiInfo() from v2 to v3.