Search code examples
spring-bootopenapiopenapi-generator

OpenAPI: Specifying an optional request object but with mandatory fields


I'm using an OpenApi generator setup on Maven + Spring Boot (2.7.7) to generate API interfaces to implement in my software. This includes Validation.

I'm trying to understand if there is a way to specify validation in an OpenApi (3.0.1 atm but I'm flexible) yaml in such a way that I can have an object not be mandatory, but if ANY fields are included, then all of it must be included.

For example:

paths:
 /api/complexobject:
    get:
      tags:
        - complexobject
      summary: generic search
      operationId: findAll
      parameters:
        - in: query
          name: complexobject
          schema: 
            $ref: '#/components/schemas/ComplexObject'
          explode: true
        - in: query
          name: sort
          schema:
            $ref: '#/components/schemas/Sorting'
        - in: query
          name: page
          schema:
            $ref: '#/components/schemas/Pagination'
      responses:
          "200":
            description: successful operation
            content:
              application/json:
                schema:
                  type: array
                  items:
                    $ref: '#/components/schemas/ComplexObject'
components:
  schemas:
    ComplexObject:
      type: object
      <cut>

    Pagination:
      type: object
      properties:
        page:
          type: integer
          minimum: 0
          description: 0-indexed page number for pagination
        size:
          type: integer
          minimum: 1
          description: Number of returned records (page size). Suggested default is 20
      required: 
            - page
            - size
    Sorting:
      type: object
      properties:
        dir:
          $ref: '#/components/schemas/SortDir'
        sorted:
          type: array
          items:
            type: string
      required:
          - dir
          - sorted
    
    SortDir:
      type: string
      enum: ["asc", "desc"]

The intention here is that the Sorting and the Pagination objects can be omitted from the query, however IF they're included, they must be wholly included (and with valid inputs too).

However it seems that at runtime the Pagination and Sorting Java Objects are instanced even without any parameters being sent to the Controller, which makes them fail the validation.

Is there some different validation setup I can use in the yaml, or do I need to change something in Java code?

Or, just remove the requirements altogether from the OpenAPI field declation and do programmatic validation directly instead?


Solution

  • As far as I can tell the specification I wrote is correct, however the generator (the most recent one available at the time of writing) doesn't support this kind of behaviour.

    However what it does support is the use of defaults:

        Pagination:
          type: object
          nullable: true
          properties:
            page:
              type: integer
              minimum: 0
              default: 0 # Added default
              description: 0-indexed page number for pagination
            size:
              type: integer
              minimum: 1
              default: 20 # Added default
              description: Number of returned records (page size).
          required: 
                - page
                - size
        Sorting:
          type: object
          nullable: true
          properties:
            dir:
              $ref: '#/components/schemas/SortDir'
            sorted:
              type: array # No default needed, see below!
              items:
                type: string
          required:
              - dir
              - sorted
        
        SortDir:
          type: string
          default: "asc" # Added default
          enum: ["asc", "desc"]
    

    In this case you'll actually be bypassing the problem a bit, in that you'll never end up in a situation when any of the required fields will ever be null, see the generated code for reference:

    @Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-01-30T16:25:58.375+01:00[Europe/Berlin]")
    public class Pagination {
    
      @JsonProperty("page")
      private Integer page = 0;
    
      @JsonProperty("size")
      private Integer size = 20;
    

    As a side note, the validation for the "sorted" property actually works as expected, even though the way the codegen attains that is weird. It'll work the same way whether you specify it as required or not AND without specifying defaults.

    However we have the two following codegens:

    Without required:

    @JsonProperty("sorted")
      @Valid
      private List<String> sorted = null;
    
    // Cut for brevity
    
      /**
       * Get sorted
       * @return sorted
      */
      @NotNull 
      @Schema(name = "sorted", required = false)
      public List<String> getSorted() {
        return sorted;
      }
    

    WITH required:

    @JsonProperty("sorted")
      @Valid
      private List<String> sorted = new ArrayList<>();
    
    // Cut for brevity
    
      /**
       * Get sorted
       * @return sorted
      */
      @NotNull 
      @Schema(name = "sorted", required = true)
      public List<String> getSorted() {
        return sorted;
      }
    

    This also means that the validation in the second case WILL be active... but never fail.