stoicflame / enunciate-openapi

OpenAPI3 module for Enunciate
Apache License 2.0
10 stars 10 forks source link

duplicated mapping key - support for different content types on the same path & method #28

Open Robbilie opened 4 years ago

Robbilie commented 4 years ago

currently when writing a controller with different methods for the same path but different content-types the resulting openapi.yml is invalid since it will generate two entries of the same method instead of reusing the same method and just adding the second content type.

example: expected result:

openapi: 3.0.0
info:
  title: "openapi-v3-test"
  version: "0.0.1-SNAPSHOT"
  description: "<h1>openapi-v3-test</h1><p>Test for th use of openapi v3</p>"
  license:
    name: "Apache License, Version 2.0"
    url: "https://www.apache.org/licenses/LICENSE-2.0"
servers: []
paths:
  "/hello/world":
    get:
      description: "Gets a list of vehicle functions by calling the operation list for the vehicle and merging it with the service\nconfiguration data from the AEM config service."
      tags:
        - "Hello"
      summary: "Gets a list of vehicle functions by calling the operation list for the vehicle and merging it with the service\nconfiguration data from the AEM config service."
      deprecated: false
      operationId: sayHelloV1
      parameters:
      - name: "Accept-Language"
        in: header
        description: "Standard HTTP Accept-Language header"
        required: false
        schema:
          type: string
        style: simple
      responses:
        "200":
          description: "Success"
          content:
            "application/json+v1":
              schema:
                description: "Success"
                type: string
            "application/json+v2":
              schema:
                description: "Success"
                type: string
components: {}

actual result:

openapi: 3.0.0
info:
  title: "openapi-v3-test"
  version: "0.0.1-SNAPSHOT"
  description: "<h1>openapi-v3-test</h1><p>Test for th use of openapi v3</p>"
  license:
    name: "Apache License, Version 2.0"
    url: "https://www.apache.org/licenses/LICENSE-2.0"
servers: []
paths:
  "/hello/world":
    get:
      description: "Gets a list of vehicle functions by calling the operation list for the vehicle and merging it with the service\nconfiguration data from the AEM config service."
      tags:
        - "Hello"
      summary: "Gets a list of vehicle functions by calling the operation list for the vehicle and merging it with the service\nconfiguration data from the AEM config service."
      deprecated: false
      operationId: sayHelloV1
      parameters:
      - name: "Accept-Language"
        in: header
        description: "Standard HTTP Accept-Language header"
        required: false
        schema:
          type: string
        style: simple
      responses:
        "200":
          description: "Success"
          content:
            "application/json+v1":
              schema:
                description: "Success"
                type: string
            "application/json+v2":
              schema:
                description: "Success"
                type: string

    get:
      description: "Gets a list of vehicle functions by calling the operation list for the vehicle and merging it with the service\nconfiguration data from the AEM config service."
      tags:
        - "Hello"
      summary: "Gets a list of vehicle functions by calling the operation list for the vehicle and merging it with the service\nconfiguration data from the AEM config service."
      deprecated: false
      operationId: sayHelloV2
      parameters:
      - name: "Accept-Language"
        in: header
        description: "Standard HTTP Accept-Language header"
        required: false
        schema:
          type: string
        style: simple
      responses:
        "200":
          description: "Success"
          content:
            "application/json+v2":
              schema:
                description: "Success"
                type: string

components: {}
jskov-jyskebank-dk commented 4 years ago

That should be easy to fix. But I do not have the time to do it myself right now. If you can fix it yourself, I will be happy to accept the PR.

jskov-jyskebank-dk commented 4 years ago

@Robbilie This may be fixed by @bkratz 's changes in 1.1.5 If so, please close this issue. Ta!

m4h3 commented 3 years ago

hitting this problem with 1.1.5. AFAIK @bkratz changes were related to expanding @Consumes/@Produces for 1 method. Here there is multiple methods, one for each content type.

bkratz commented 3 years ago

@Robbilie Could you provide some sample code. Since it is a difference, whether you have "different methods for the same path but different content-types" (as stated in your description) or one method for two different content-types (as also stated in your description). For the first case, the actual result is absolutely correct, since you have one path and 2 'get' definitions with different 'operationId' representing the different methods.

Robbilie commented 3 years ago

"absolutely correct" in what sense? Certainly not in the sense of "valid open API yaml" :)

Errors
Hide

Parser error duplicated mapping key
Jump to line 40
bkratz commented 3 years ago

OK, I take back my sentence stating that the generated yaml is correct.

But the to be able to help you, could you provide some sample code of your controller. You seem to have 2 controller methods for the path "/hello/world", one named "sayHelloV1" and one named "sayHelloV2". Maybe this is helpful, especially the comment from Damon Sutherland. Also have a look here.

Robbilie commented 3 years ago
@RestController
public interface VehicleFunctionsController {

    @GetMapping(value = {"/vehicles/{vin}/services"}, produces = {"application/vnd.example.functionlist.v3+json",
            "application/vnd.example.error.v1+json"})
    @Operation(description = "Gets a list of vehicle functions by calling the operation list for the vehicle and merging it with the service configuration data from the AEM config service.")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "Success", content = @Content(
                    mediaType = "application/vnd.example.functionlist.v3+json",
                    schema = @Schema(implementation = VehicleFunctionsListV3.class))),
            @ApiResponse(responseCode = "403", description = "user is non paired guest user / anonymous and is not owner", content = @Content(
                    mediaType = "application/vnd.example.error.v1+json",
                    schema = @Schema(implementation = ExceptionResponseMessage.class))),
            @ApiResponse(responseCode = "500", description = "Internal error during request execution", content = @Content(
                    mediaType = "application/vnd.example.error.v1+json",
                    schema = @Schema(implementation = ExceptionResponseMessage.class)))
    })
    ResponseEntity<?> listFunctions(
            @Parameter(description = "Standard HTTP Accept-Language header")
            @NotNull @RequestHeader(HttpHeaders.ACCEPT_LANGUAGE) String acceptLanguage,
            @Parameter(description = "Bearer access token")
            @NotNull @RequestHeader(HttpHeaders.AUTHORIZATION) String authorization,
            @Parameter(description = "The VIN of the vehicle")
            @NotNull @PathVariable(Headers.VIN) String vin);

    @GetMapping(value = {"/vehicles/{vin}/services"}, produces = {"application/vnd.example.functionlist.v2+json",
            "application/vnd.example.error.v1+json"})
    @Operation(description = "Gets a list of vehicle functions by calling the operation list for the vehicle and merging it with the service configuration data from the AEM config service.")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "Success", content = @Content(
                    mediaType = "application/vnd.example.functionlist.v2+json",
                    schema = @Schema(implementation = VehicleFunctionsListV2.class))),
            @ApiResponse(responseCode = "403", description = "user is non paired guest user / anonymous and is not owner", content = @Content(
                    mediaType = "application/vnd.example.error.v1+json",
                    schema = @Schema(implementation = ExceptionResponseMessage.class))),
            @ApiResponse(responseCode = "500", description = "Internal error during request execution", content = @Content(
                    mediaType = "application/vnd.example.error.v1+json",
                    schema = @Schema(implementation = ExceptionResponseMessage.class)))
    })
    ResponseEntity<?> listFunctionsV2(
            @Parameter(description = "Standard HTTP Accept-Language header")
            @NotNull @RequestHeader(HttpHeaders.ACCEPT_LANGUAGE) String acceptLanguage,
            @Parameter(description = "Bearer access token")
            @NotNull @RequestHeader(HttpHeaders.AUTHORIZATION) String authorization,
            @Parameter(description = "The VIN of the vehicle")
            @NotNull @PathVariable(Headers.VIN) String vin);

    @GetMapping(value = {"/vehicles/{vin}/services"}, produces = {"application/json",
            "application/vnd.example.functionlist.v1+json", "application/vnd.example.error.v1+json"})
    @Operation(description = "Gets a list of vehicle functions by calling the operation list for the vehicle and merging it with the service configuration data from the AEM config service.")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "Success", content = @Content(
                    mediaType = "application/vnd.example.functionlist.v1+json",
                    schema = @Schema(implementation = VehicleFunctionsListV1.class))),
            @ApiResponse(responseCode = "200", description = "Success", content = @Content(
                    mediaType = "application/json",
                    schema = @Schema(implementation = VehicleFunctionsListV1.class))),
            @ApiResponse(responseCode = "403", description = "user is non paired guest user / anonymous and is not owner", content = @Content(
                    mediaType = "application/vnd.example.error.v1+json",
                    schema = @Schema(implementation = ExceptionResponseMessage.class))),
            @ApiResponse(responseCode = "500", description = "Internal error during request execution", content = @Content(
                    mediaType = "application/vnd.example.error.v1+json",
                    schema = @Schema(implementation = ExceptionResponseMessage.class)))
    })
    ResponseEntity<?> listFunctionsV1(
            @Parameter(description = "Standard HTTP Accept-Language header")
            @NotNull @RequestHeader(HttpHeaders.ACCEPT_LANGUAGE) String acceptLanguage,
            @Parameter(description = "Bearer access token")
            @NotNull @RequestHeader(HttpHeaders.AUTHORIZATION) String authorization,
            @Parameter(description = "The VIN of the vehicle")
            @NotNull @PathVariable(Headers.VIN) String vin);

}
m4h3 commented 3 years ago

any update on this ?