0

In my Spring project I'm using Springdoc to generate a OpenApiSpecification doc. I created my Api with these annotations. I want to have the same endpoint url with different mediatype to handle the POST of different objects.

@Validated
@Tag(name = "Calendar", description = "Api for Calendar resource")
public interface CalendarApi {


    @Operation(summary = "Add an appointment to the calendar", description = "Add an appointment to the calendar", tags = {"appointment"})
    @ApiResponses(value = {
            @ApiResponse(responseCode = "201", description = "Successful operation", content = @Content(mediaType = "application/json+widget", schema = @Schema(implementation = AppointmentWidgetDto.class))),
            @ApiResponse(responseCode = "400", description = "Invalid input")
    })
    @PostMapping(value = "/appointments", consumes = "application/json+widget")
    ResponseEntity<Appointment> saveFromWidget(@Parameter(description = "The new appointment to save", required = true) @Valid @RequestBody AppointmentWidgetDto appointmentDto);

    @Operation(summary = "Add an appointment to the calendar", description = "Add an appointment to the calendar", tags = {"appointment"})
    @ApiResponses(value = {
            @ApiResponse(responseCode = "201", description = "Successful operation", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Appointment.class))),
            @ApiResponse(responseCode = "400", description = "Invalid input")
    })
    @PostMapping(value = "/appointments", consumes = MediaType.APPLICATION_JSON_VALUE)
    ResponseEntity<Appointment> save(@Parameter(description = "The new appointment to save", required = true) @Valid @RequestBody Appointment appointmentDto);

}

The generated Open Api Spec document is:

  /api/v1/appointments:
    post:
      tags:
        - Calendar
      summary: Add an appointment to the calendar
      description: Add an appointment to the calendar
      operationId: save_1
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Appointment'
          application/json+widget:
            schema:
              $ref: '#/components/schemas/AppointmentWidgetDto'
        required: true
      responses:
        '201':
          description: Successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Appointment'
        '400':
          description: Invalid input
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/Appointment'

I've a couple of porblems:

  • the endpoint name is not meaningful (save_1)

  • when I use Open Api generator to generate the Angular client from this specification, I've some warnings that prevent the generation of both methods.

    [WARNING] Multiple schemas found in the OAS 'content' section, returning only the first one (application/json) [WARNING] Multiple MediaTypes found, using only the first one

I know there is this issue opened (https://github.com/OpenAPITools/openapi-generator/issues/3990). Is there any way to permit to POST two different bodies in the same endpoint url and using OpenApi generator to create client for different languages/platforms?

===== UPDATE =======

This is AppointmentWidgetDTO:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
public class AppointmentWidgetDto implements Serializable {
    @NotNull(message = "{appointment.store.missing}")
    @JsonDeserialize(using = StoreUriDeserializer.class)
    private Store store;

    @NotNull(message = "{appointment.title.missing}")
    @Size(max = 255)
    private String title;

    @Lob
    @Size(max = 1024)
    private String description;

    @Size(max = 50)
    private String type;

    @Size(max = 50)
    private String icon;

    @NotNull(message = "{appointment.startdate.missing}")
    private Instant startDate;

    @NotNull(message = "{appointment.enddate.missing}")
    private Instant endDate;

    @JsonDeserialize(using = ContactUriDeserializer.class)
    private Contact contact;

    @NotBlank(message = "{appointment.contactname.missing}")
    private String contactName;

    @NotBlank(message = "{appointment.email.missing}")
    @Email
    private String contactEmail;

    @NotBlank(message = "{appointment.phone.missing}")
    @PhoneNumber
    private String contactPhone;

}

and this is Appointment:

@ScriptAssert(lang = "javascript", script = "_.startDate.isBefore(_.endDate)", alias = "_", reportOn = "endDate", message = "{appointment.invalid.end.date}")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
public class Appointment extends AbstractEntity {


    @NotNull(message = "{appointment.store.missing}")
    @JsonDeserialize(using = StoreUriDeserializer.class)
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "store_id", updatable = false)
    private Store store;

    @NotNull
    @Size(max = 255)
    @Column(nullable = false, length = 255)
    private String title;

    @Lob
    @Size(max = 1024)
    @Column(length = 1024)
    private String description;

    @Size(max = 30)
    @Column(length = 30)
    private String color;

    @Size(max = 50)
    @Column(length = 50)
    private String type;

    @Size(max = 50)
    @Column(length = 50)
    private String icon;

    @Size(max = 255)
    @Column(length = 255)
    private String location;

    @NotNull
    @Column(nullable = false)
    private Instant startDate;

    @NotNull
    @Column(nullable = false)
    private Instant endDate;

    @Builder.Default
    @NotNull
    @Column(nullable = false, columnDefinition = "BIT DEFAULT 0")
    private boolean allDay = false;


    @JoinColumn(name = "contact_id")
    @JsonDeserialize(using = ContactUriDeserializer.class)
    @ManyToOne(fetch = FetchType.LAZY)
    private Contact contact;

    private String contactName;

    @Email
    private String contactEmail;

    @PhoneNumber
    private String contactPhone;

   
    @JoinColumn(name = "agent_id")
    @JsonDeserialize(using = AgentUriDeserializer.class)
    @ManyToOne(fetch = FetchType.LAZY)
    private Agent agent;

   
    private String agentName;

    @Builder.Default
    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    @NotNull
    @Column(nullable = false)
    @Enumerated(EnumType.STRING)
    private AppointmentStatus status = AppointmentStatus.VALID;
drenda
  • 5,846
  • 11
  • 68
  • 141

1 Answers1

3

With OpenAPI 3, you can not have many operations for the same path.

You will have only one endpoint and only one OpenAPI description.

What you can do is to define the @Operation annotation on the top of one of the methods, where you add the OpenAPI documentation of the merged OpenAPI description of all your other methods as well and add the @Hidden annotation on the others.

Or you can define two different groups: For each one you filter using header matching, option headersToMatch of GroupedOpenApi Bean.

brianbro
  • 4,141
  • 2
  • 24
  • 37