Accessing custom attributes in Swagger Codegen

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.

3 Replies to “Accessing custom attributes in Swagger Codegen”

  1. Nimble Fungus says:

Leave a Reply

Your email address will not be published. Required fields are marked *