Introduction
Customizing your Swagger code generation is easier than you would expect. Adding new custom attributes into your specification file is easy as the OpenAPI spec knows the term extensions or vendor extensions which are basically custom attributes within your Swagger contract.
Hopefully you’ve read the previous parts of my Swagger Codegen with Gradle series, if you haven’t, make sure you do it before continuing.
Let’s check how the generated server-side stub looks like.
@RestController public class UserApiController implements UserApi { private final UserApiDelegate delegate; @Autowired public UserApiController(UserApiDelegate delegate) { this.delegate = delegate; } public ResponseEntity<Void> createUser(@ApiParam(value = "The user data" ,required=true ) @Valid @RequestBody UserCreateRequest user) { return delegate.createUser(user); } public ResponseEntity<ListUserResponse> getUsers() { return delegate.getUsers(); } }
As you can see, we have ResponseEntity for all the generated operations, but what if we don’t want that. How can we achieve this in the code generation process?
Extending the code generator
First of all, putting extension attributes into the contract file has a rule, they have to start with x- prefix. One could come out with a solution like the following
swagger: "2.0" info: description: "This is a sample specification for user service" version: "1.0.0" title: "User Service contract" tags: - name: "user" paths: /user: get: tags: - "user" summary: "Gets a list of users" description: "" operationId: "getUsers" consumes: - "*/*" produces: - "application/json" responses: 200: schema: $ref: "#/definitions/ListUserResponse" description: "List of all users" post: tags: - "user" summary: "Creates a new user" operationId: "createUser" x-responseEntity: true consumes: - "application/json" produces: - "application/json" parameters: - in: "body" name: "user" description: "The user data" required: true schema: $ref: "#/definitions/UserCreateRequest" responses: 201: description: "User successfully created" 400: description: "Validation error" definitions: ListUserResponse: type: "array" items: $ref: "#/definitions/UserResponse" UserResponse: type: "object" properties: id: type: "integer" name: type: "string" UserCreateRequest: type: "object" properties: name: type: "string"
Note the only difference between the previous Swagger contract is that now we have the x-responseEntity: true attribute for the POST endpoint. That’s it. In this case we defined the endpoint to have the ResponseEntity as a return type, all the others where it’s not there with a value of true , it will not be generated.
Now the remaining part is to adjust the template for the Api , the ApiController and the ApiDelegate . Accessing the custom attribute from the contract within the template is done via the vendorExtensions property.
Open up the api.mustache in the template folder and let’s change the return type part of the template to this
{{#vendorExtensions.x-responseEntity}}ResponseEntity<{{>returnTypes}}>{{/vendorExtensions.x-responseEntity}}{{^vendorExtensions.x-responseEntity}}{{>returnTypes}}{{/vendorExtensions.x-responseEntity}}
What this is doing is that first, it checks whether the value of the x-responseEntity attribute is true , if yes then it generates the ResponseEntity into the return type. However, if that’s false or not provided the second part will be evaluated which purely just generates the return type of the operation.
The full content of the api.mustache for me is this
package {{package}}; {{#imports}}import {{import}}; {{/imports}} import io.swagger.annotations.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import javax.validation.constraints.*; import java.io.IOException; import java.util.List; import java.util.Optional; import java.util.Optional; @Api(value = "{{{baseName}}}", description = "the {{{baseName}}} API") {{#operations}} public interface {{classname}} { {{#operation}} @ApiOperation(value = "{{{summary}}}", nickname = "{{{operationId}}}", notes = "{{{notes}}}"{{#returnBaseType}}, response = {{{returnBaseType}}}.class{{/returnBaseType}}{{#returnContainer}}, responseContainer = "{{{returnContainer}}}"{{/returnContainer}}{{#hasAuthMethods}}, authorizations = { {{#authMethods}}@Authorization(value = "{{name}}"{{#isOAuth}}, scopes = { {{#scopes}}@AuthorizationScope(scope = "{{scope}}", description = "{{description}}"){{#hasMore}}, {{/hasMore}}{{/scopes}} }{{/isOAuth}}){{#hasMore}}, {{/hasMore}}{{/authMethods}} }{{/hasAuthMethods}}, tags={ {{#vendorExtensions.x-tags}}"{{tag}}",{{/vendorExtensions.x-tags}} }) @ApiResponses(value = { {{#responses}} @ApiResponse(code = {{{code}}}, message = "{{{message}}}"{{#baseType}}, response = {{{baseType}}}.class{{/baseType}}{{#containerType}}, responseContainer = "{{{containerType}}}"{{/containerType}}){{#hasMore}},{{/hasMore}}{{/responses}} }) {{#implicitHeaders}} @ApiImplicitParams({ {{#headerParams}} {{>implicitHeader}} {{/headerParams}} }) {{/implicitHeaders}} @RequestMapping(value = "{{{path}}}",{{#singleContentTypes}} produces = "{{{vendorExtensions.x-accepts}}}", consumes = "{{{vendorExtensions.x-contentType}}}",{{/singleContentTypes}}{{^singleContentTypes}}{{#hasProduces}} produces = { {{#produces}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/produces}} }, {{/hasProduces}}{{#hasConsumes}} consumes = { {{#consumes}}"{{{mediaType}}}"{{#hasMore}}, {{/hasMore}}{{/consumes}} },{{/hasConsumes}}{{/singleContentTypes}} method = RequestMethod.{{httpMethod}}) {{#vendorExtensions.x-responseEntity}}ResponseEntity<{{>returnTypes}}>{{/vendorExtensions.x-responseEntity}}{{^vendorExtensions.x-responseEntity}}{{>returnTypes}}{{/vendorExtensions.x-responseEntity}} {{operationId}}({{#allParams}}{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{#hasMore}},{{/hasMore}}{{/allParams}}); {{/operation}} } {{/operations}}
Now we have to put the very same return type generation logic into the apiController.mustache and apiDelegate.mustache .
Executing ./gradlew clean build install in the contract project will result in the following generated code for the controller
@RestController public class UserApiController implements UserApi { private final UserApiDelegate delegate; @Autowired public UserApiController(UserApiDelegate delegate) { this.delegate = delegate; } public ResponseEntity<Void> createUser(@ApiParam(value = "The user data" ,required=true ) @Valid @RequestBody UserCreateRequest user) { return delegate.createUser(user); } public ListUserResponse getUsers() { return delegate.getUsers(); } }
As you can see, the getUsers method doesn’t have ResponseEntity anymore which is what we wanted to achieve.
The last remaining building block is to adjust the code in the payment-service and in the user-service to follow newly generated contract
@Component public class UserApiDelegateImpl implements UserApiDelegate { @Override public ResponseEntity<Void> createUser(UserCreateRequest user) { // TODO: implementation return null; } @Override public ListUserResponse getUsers() { ListUserResponse response = new ListUserResponse(); response.add(new UserResponse().name("Arnold").id(1)); return response; } }
@RestController("/payment") class PaymentController { private UserApiClient userApiClient; public PaymentController(UserApiClient userApiClient) { this.userApiClient = userApiClient; } @GetMapping public ListPayingUserResponse getPayingUsers() { return new ListPayingUserResponse(userApiClient.getUsers().stream().map(UserResponse::getName).collect(Collectors.toList())); } }
Conclusion
We checked how it’s possible to put custom attributes into the Swagger contract in order to introduce some custom behavior into the code generation process. In this small example, we saw how to include or omit the ResponseEntity from the return type on operation basis. It’s also possible to put other logic here, for example custom security checks, localization check and so much more.
The full code can be found on GitHub and if you liked the article or the series, make sure you share it with others. Follow me on Twitter for further interesting topics and let me know what you think about my writings.
Thank you so much for such a great article.
I have tried to use the same but itsnot working for me.
Can you please help. I have put my question
https://stackoverflow.com/questions/64610937/swagger-codegen-vendor-extensions-are-not-accessible
Hi, just answered on stackoverflow.
i am using the sample Pet Store JSON file to generate the spring-boot based project. To add annotation such as @EnableEurekaClient in the generated code I am trying to use vendor extension in swagger file. Extensions name can be seen below as x-enableEureka in swagger defination.
swagger: “2.0”
info:
description: “This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.”
version: “1.0.0”
title: “Swagger Petstore”
termsOfService: “http://swagger.io/terms/”
x-enableEureka: true
I have modified the controller.mustache by adding below items. However, I am unable to access its value in generated Controller Class from either of the below statment.
{{vendorExtensions.x-enableEureka}}
{{x-enableEureka}}
My intent is to use x-enableEureka as a condition.
{{^vendorExtensions.x-enableEureka}}
@EnableEurekaClient
{{/vendorExtensions.x-enableEureka}}
Similar way to access the vendor extension has been defined here. I am not sure why it’s not working.
Can somebody please help me. I am not sure if I am placing the vendor extension at wrong place. I even tried to place that inside the Path block as well.
Swagger Codegen Version 2.4.15
Json FIle Path https://petstore.swagger.io/v2/swagger.json