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;