swagger-core icon indicating copy to clipboard operation
swagger-core copied to clipboard

Trying to declare an ApiResponse of type Map<String, List<Object>> does not work

Open thousandeyes-fwieland opened this issue 3 years ago • 0 comments

I'm using swagger-annotations-2.2.0. I have an API like this (VERSION A/B: one of these lines is commented out):

@RestController
@RequestMapping("/v1/examples")
@Tag(
    name = "Examples",
    description = "CRUD for Examples"
)
class AgentLabelsApi {
    @Operation(
        description = "Example",
        responses = [
            ApiResponse(responseCode = "200"),
            ApiResponse(
                responseCode = "400",
                content = [Content(additionalPropertiesSchema = Schema(implementation = Example::class), schema = Schema(additionalProperties = Schema.AdditionalPropertiesValue.TRUE))] // VERSION A
                content = [Content(schema = Schema(implementation = MapOfStringToListOfExamples::class))] // VERSION B
            )
        ]
    )
    @PostMapping
    fun addExamples(
        @SpringRequestBody
        @RequestBody
        examples: Map<String, List<Example>>
    ) = mapOf("someId" to listOf(Example("example")))
}

data class Example(
    val name: String
)

abstract class MapOfStringToListOfExamples : Map<String, List<Example>>

I want to be able to express that for the 400 response, it returns a Map<String, List<Example>> using swagger/openapi annotations. Swagger knows what this should look like because it correctly formats it when I use this map type for the request body and on the function's return type. However, I can't seem to be able to express the same type when using the @Content and @Schema annotations. The above, with the one commented out line in place, is semantically wrong - I could not find a way to get @Schema itself to look like an array because the array prop is on @Content. But it also generates an even more confusing OpenAPI spec (abbreviated):

        "400":
          description: Bad Request
          content:
            '*/*':
              schema:
                type: string
                additionalProperties: true

Huh? Where's my name prop?

This was Version A (where the line for Version B is commented out). Version B instead yields this OpenAPI spec (abbreviated):

      responses:
        "400":
          description: Bad Request
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/MapOfStringToListOfExamples'
components:
  schemas:
    MapOfStringToListOfExamples:
      type: object
      properties:
        size:
          type: integer
          format: int32
        entries:
          uniqueItems: true
          type: array
          items:
            type: object
        keys:
          uniqueItems: true
          type: array
          items:
            type: object
        values:
          type: array
          items:
            type: object
        empty:
          type: boolean

Huh? That's the accessor methods of Map<K, V>!

Is it possible to express, using Swagger annotations, that in the event of a 400 response, the response body will be Map<String, List<Example>>?

The real-life use case is that we have an API that needs to return a Map<String, List<ValidationError>> response if the user makes a mistake in filling out a form, where the map key is the field name, and each field can have more than one validation error. The actual request body and return value are different types.

thousandeyes-fwieland avatar Jun 08 '22 14:06 thousandeyes-fwieland