Spring REST Controller Swagger's default response template error - spring

For JSON requests, the response template on Swagger is showing up properly. However, if I have MultiPartFile as request, the template seems to disappear.
This is the code for controller for MultiPartFile request.
...
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
...
#ApiOperation(value = "Image Update")
#ApiResponses(value = {
#ApiResponse(responseCode = "200", description = "Patch complete",
content = #Content(schema = #Schema(implementation = ProfileDefaultResponseDto.class))),
...
#ApiResponse(responseCode = "500", description = "Server Error")
})
#PatchMapping(value = "/img", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<DefaultResponseDto<Object>> updateImage(HttpServletRequest servletRequest,
#RequestPart(required = false) MultipartFile image) {
...
}
This is what the response template should look like. But it is not showing up like this.

Related

How can I hide #ApiResponse form #ControllerAdvice for an endpoint?

I'm trying to migrate our manually writen OpenAPI (swagger) to a generated OpenAPI using springdoc-openapi for our Spring-Boot application. We got some issues, because the controller responses (mostly ErrorCodes) didn't match to the documentatation.
We already used a #ControllerAdvice annotated handler configuration. Here a snippet:
#ControllerAdvice
public class ExceptionHandler {
#ResponseStatus(code = HttpStatus.NOT_FOUND)
#ApiResponse(responseCode = "404", description = "(NOT FOUND) Resource does not exist!", content = #Content)
#ExceptionHandler(NotFoundException.class)
public void handleException(NotFoundException e) {
log.warn("Returning {} due to a NotFoundException: {}", HttpStatus.NOT_FOUND, e.toString());
}
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ApiResponse(responseCode = "400", description = "(BAD REQUEST) Given resource is invalid!", content = #Content)
#ExceptionHandler(InvalidResourceException.class)
public void handleException(InvalidResourceExceptione) {
log.error("Invalid resource: {}", e.toString());
}
The generated API now showed all defined ApiResponses as responses for all controllers and endpoints. So I splittet the handler config using #ControllerAdvice(basePackageClasses = MyController.class) to group the possible exceptions. But there are still responses that are not fitting to all endpoints of a controller. Like:
#RestController
public class MyController {
#ResponseStatus(HttpStatus.CREATED)
#Operation(summary = "Create", description = "Create myResource!")
#PostMapping(value = "/myResources/", produces = {"application/json"})
#ResponseBody
public Integer create(#RequestBody MyResource newResource) throws InvalidResourceException {
return creationService.createResource(newResource).getId();
}
#ResponseStatus(HttpStatus.OK)
#Operation(summary = "Update", description = "Update myResource!")
#PutMapping(value = "/myResources/{id}", produces = {"application/json"})
public void update(#PathVariable("id") Integer id, #RequestBody MyResource newResource)
throws ResourceNotFoundException, InvalidResourceException {
return updateService.updateResource(id, newResource);
}
#ResponseStatus(HttpStatus.OK)
#Operation(summary = "Get", description = "Get myResource!")
#GetMapping(value = "/myResources/{id}", produces = {"application/json"})
#ResponseBody
public MyResource get(#PathVariable("id") Integer id) throws ResourceNotFoundException {
return loadingService.getResource(id);
}
}
POST will never respond with my 'business' 404 and GET will never respond with my 'business' 400. Is it possible to annotate an endpoint, so that not possible response codes are hidden in the API?
I tried to override the responses, but didn't work as intended:
#ResponseStatus(HttpStatus.OK)
#Operation(summary = "Get", description = "Get myResource!")
#ApiResponses({#ApiResponse(responseCode = "200", description = "(OK) Returning myResource"),
#ApiResponse(responseCode = "404", description = "(NOT FOUND) Resource does not exist!")})
#GetMapping(value = "/myResources/{id}", produces = {"application/json"})
#ResponseBody
public MyResource get(#PathVariable("id") Integer id) throws ResourceNotFoundException {
return loadingService.getResource(id);
}
400 still shows up...
You need to remove the #ApiResponse from your #ControllerAdvice class and need to add the respective response in your controller class, as mentioned by you.
#ResponseStatus(HttpStatus.OK)
#Operation(summary = "Get", description = "Get myResource!")
#ApiResponses({#ApiResponse(responseCode = "200", description = "(OK) Returning myResource"),
#ApiResponse(responseCode = "404", description = "(NOT FOUND) Resource does not exist!")})
#GetMapping(value = "/myResources/{id}", produces = {"application/json"})
#ResponseBody
public MyResource get(#PathVariable("id") Integer id) throws ResourceNotFoundException {
return loadingService.getResource(id);
}

Spring Boot - how to allow CORS on REST Controller HTTP PUT

I'm my Spring Boot REST controller, I'm able to allow CORS for HTTP POST, but for some reason HTTP PUT is still being blocked.
I have placed my CORS decorator at the Controller level - HTTP PUT handler still being blocked.
Here is my controller:
package com.khoubyari.example.api.rest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import com.khoubyari.example.domain.Hotel;
import com.khoubyari.example.exception.DataFormatException;
import com.khoubyari.example.service.HotelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
#RestController
#RequestMapping(value = "/example/v1/hotels")
#Api(tags = {"hotels"})
//#CrossOrigin(origins = "http://localhost:4200")
#CrossOrigin( origins = "*" , allowedHeaders = "*")
public class HotelController extends AbstractRestHandler {
#Autowired
private HotelService hotelService;
#RequestMapping(value = "",
method = RequestMethod.POST,
consumes = {"application/json", "application/xml"},
produces = {"application/json", "application/xml"})
#ResponseStatus(HttpStatus.CREATED)
#ApiOperation(value = "Create a hotel resource.", notes = "Returns the URL of the new resource in the Location header.")
public void createHotel(#RequestBody Hotel hotel,
HttpServletRequest request, HttpServletResponse response) {
Hotel createdHotel = this.hotelService.createHotel(hotel);
response.setHeader("Location", request.getRequestURL().append("/").append(createdHotel.getId()).toString());
}
#RequestMapping(value = "",
method = RequestMethod.GET,
produces = {"application/json", "application/xml"})
#ResponseStatus(HttpStatus.OK)
#ApiOperation(value = "Get a paginated list of all hotels.", notes = "The list is paginated. You can provide a page number (default 0) and a page size (default 100)")
public
#ResponseBody
Page<Hotel> getAllHotel(#ApiParam(value = "The page number (zero-based)", required = true)
#RequestParam(value = "page", required = true, defaultValue = DEFAULT_PAGE_NUM) Integer page,
#ApiParam(value = "Tha page size", required = true)
#RequestParam(value = "size", required = true, defaultValue = DEFAULT_PAGE_SIZE) Integer size,
HttpServletRequest request, HttpServletResponse response) {
return this.hotelService.getAllHotels(page, size);
}
#RequestMapping(value = "/{id}",
method = RequestMethod.GET,
produces = {"application/json", "application/xml"})
#ResponseStatus(HttpStatus.OK)
#ApiOperation(value = "Get a single hotel.", notes = "You have to provide a valid hotel ID.")
public
#ResponseBody
Hotel getHotel(#ApiParam(value = "The ID of the hotel.", required = true)
#PathVariable("id") Long id,
HttpServletRequest request, HttpServletResponse response) throws Exception {
Hotel hotel = this.hotelService.getHotel(id);
checkResourceFound(hotel);
return hotel;
}
#RequestMapping(value = "/{id}",
method = RequestMethod.PUT,
consumes = {"application/json", "application/xml"},
produces = {"application/json", "application/xml"})
#ResponseStatus(HttpStatus.NO_CONTENT)
#ApiOperation(value = "Update a hotel resource.", notes = "You have to provide a valid hotel ID in the URL and in the payload. The ID attribute can not be updated.")
public void updateHotel(#ApiParam(value = "The ID of the existing hotel resource.", required = true)
#PathVariable("id") Long id, #RequestBody Hotel hotel,
HttpServletRequest request, HttpServletResponse response) {
checkResourceFound(this.hotelService.getHotel(id));
if (id != hotel.getId()) throw new DataFormatException("ID doesn't match!");
this.hotelService.updateHotel(hotel);
}
//todo: #ApiImplicitParams, #ApiResponses
#RequestMapping(value = "/{id}",
method = RequestMethod.DELETE,
produces = {"application/json", "application/xml"})
#ResponseStatus(HttpStatus.NO_CONTENT)
#ApiOperation(value = "Delete a hotel resource.", notes = "You have to provide a valid hotel ID in the URL. Once deleted the resource can not be recovered.")
public void deleteHotel(#ApiParam(value = "The ID of the existing hotel resource.", required = true)
#PathVariable("id") Long id, HttpServletRequest request,
HttpServletResponse response) {
checkResourceFound(this.hotelService.getHotel(id));
this.hotelService.deleteHotel(id);
}
}
What should I change in order for the HTTP PUT handler to allow updates?
You may need to specify allowed methods explicitely like this in your CORS config (I'm using Kotlin and implementing WebMvcConfigurer):
override fun addCorsMappings(registry: CorsRegistry) {
log.info("CORS: {}", origins)
registry.addMapping("/**").allowedOrigins(*origins)
.allowedMethods("GET", "POST", "PUT", "OPTIONS")
}
PUT is not allowed by default, as can be seen in CorsConfiguration#DEFAULT_PERMIT_METHODS:
private static final List<String> DEFAULT_PERMIT_METHODS = Collections.unmodifiableList(
Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name()));

How to wrap Path Not Found Exception in Spring Boot Rest using ExceptionHandler?

I am using Spring Boot and Spring Rest Example. In this example, I am passing custom header, if that value is valid, endpoint gets called successfully, if custom header value is not correct then I get below response, which I want to wrap into show it to the enduser using #ControllerAdvice ExceptionHandler.
Note: I went through Spring mvc - How to map all wrong request mapping to a single method, but here in my case I am taking decision based on CustomHeader.
{
"timestamp": "2020-01-28T13:47:16.201+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/employee-data/employee-codes"
}
Controller
#Operation(summary = "Find Employee")
#ApiResponses(value = { #ApiResponse(code = 200, message = "SUCCESS"),
#ApiResponse(code = 500, message = "Internal Server Error") })
#Parameter(in = ParameterIn.HEADER, description = "X-Accept-Version", name = "X-Accept-Version",
content = #Content(schema = #Schema(type = "string", defaultValue = "v1",
allowableValues = {HeaderConst.V1}, implementation = Country.class)))
#GetMapping(value = "/employees/employee-codes", headers = "X-Accept-Version=v1")
public ResponseEntity<Employees> findEmployees(
#RequestParam(required = false) String employeeCd,
#RequestParam(required = false) String firstName,
#RequestParam(required = false) Integer lastName) {
Employees response = employeeService.getEmployees(employeeCd, firstName, lastName);
return new ResponseEntity<>(response, HttpStatus.OK);
}
I've implemented HttpMessageNotReadableException and HttpMediaTypeNotSupportedException and NoHandlerFoundException, but still not able to wrap this error.
Any suggestions?
I was able to find the solution for it.
# Whether a "NoHandlerFoundException" should be thrown if no Handler was found to process a request.
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
Error Handling Code:
#Override
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
// custom logic here
return handleExceptionInternal(ex, error, getHeaders(), HttpStatus.BAD_REQUEST, request);
}
If you're using #ControllerAdvice,
do this:
#ControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
#ExceptionHandler(value
= { IllegalArgumentException.class, IllegalStateException.class })
protected ResponseEntity<Object> handleConflict(
RuntimeException ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
return handleExceptionInternal(ex, bodyOfResponse,
new HttpHeaders(), HttpStatus.CONFLICT, request);
}
}

Spring Boot Swagger File Upload not working, file is null

I am documenting an REST API using swagger but file upload isn't working through the swagger interface. File is coming as null.
#PostMapping(value = "upload")
#ApiOperation(value = "Upload a Document",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
#ApiImplicitParams({
#ApiImplicitParam(name = "tenantId", required = true, paramType = "header"),
#ApiImplicitParam(name = "docImageOne", required = true, dataType = "file", paramType = "body"),
#ApiImplicitParam(name = "docImageTwo", dataType = "file", paramType = "body"),
})
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Success", response = UploadResponse.class),
#ApiResponse(code = 400, message = "Bad Request"),
#ApiResponse(code = 404, message = "Not Found"),
#ApiResponse(code = 500, message = "Failure")})
public ResponseEntity<?> identify(final #RequestHeader HttpHeaders headers,
final #RequestBody MultipartFile docImageOne,
final #RequestBody Optional<MultipartFile> docImageTwo)
{}
Not sure what I am missing, any pointers will be much appreciated.

useDefaultResponseMessages(false) not working

I'm using the swagger-codegen to create a spring-server.
I also used the .useDefaultResponseMessages(false)-attribute
as described in Swagger - Springfox always generates some response messages (401,403...) by default. How can I remove them?
SwaggerConfig.java:
public Docket customImplementation() {
return new Docket(DocumentationType.SWAGGER_2)
.useDefaultResponseMessages(false)
.select()
.apis(RequestHandlerSelectors.basePackage("myrest.api"))
.build()
.directModelSubstitute(org.joda.time.LocalDate.class, java.sql.Date.class)
.directModelSubstitute(org.joda.time.DateTime.class, java.util.Date.class)
.apiInfo(apiInfo());}
related apipart: Api.java:
#ApiOperation(value = "", notes = "Returns all clouds from the system that the user has access to ", response = Cloud.class, responseContainer = "List", tags = {
"cloud",})
#ApiResponses(value = {
#ApiResponse(code = 200, message = "All clouds ", response = Cloud.class),
/*#ApiResponse(code = 401, message = "Authorization for this action is missing", response = Error.class),
#ApiResponse(code = 403, message = "Forbidden action", response = Error.class),
#ApiResponse(code = 500, message = "An unexpected Error occured", response = Error.class),*/
#ApiResponse(code = 504, message = "Server temporary not available", response = Error.class)})
#RequestMapping(value = "/clouds",
produces = {"application/json"},
method = RequestMethod.GET)
ResponseEntity<List<Cloud>> findClouds();
But the swagger-ui still looks like:
swagger-ui: ResponseMessageTable
So it seems .useDefaultResponseMessages(false) is not working.
How do I disable these default error responses?
#John Duskin
I changes the Docketinitialization,changed the #Controller to #Restcontroller but I still get the 404 Message by Get
different looking 404-Message
The generated Serverstubs from Swagger-Codegen looks like:
Api.java:
#Api(value = "clouds", description = "the clouds API")
public interface CloudsApi {
#ApiOperation(value = "", notes = "Returns all clouds from the system that the user has access to ", response = Cloud.class, responseContainer = "List", tags={ "cloud", })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "All clouds ", response = Cloud.class),
#ApiResponse(code = 401, message = "Authorization for this action is missing", response = Cloud.class),
#ApiResponse(code = 403, message = "Forbidden action", response = Cloud.class),
#ApiResponse(code = 500, message = "An unexpected Error occured", response = Cloud.class),
#ApiResponse(code = 504, message = "Server temporary not available", response = Cloud.class) })
#RequestMapping(value = "/clouds",
produces = { "application/json" },
method = RequestMethod.GET)
ResponseEntity<List<Cloud>> findClouds();
and the separated Controller:
#RestController
public class CloudsApiController implements CloudsApi {
#Autowired
private UserService userService;
#Autowired
private CloudService cloudService;
public ResponseEntity<List<Cloud>> findClouds() {
//do some magic
return new ResponseEntity<List<Cloud>>(cloudList, HttpStatus.OK);
}
[...]
}
Try to put the call to useDefaultResponseMessages after the build method in the Docket. I've updated your code to show what I mean.
Hope that helps.
public Docket customImplementation() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("myrest.api"))
.build()
.useDefaultResponseMessages(false)
.directModelSubstitute(org.joda.time.LocalDate.class, java.sql.Date.class)
.directModelSubstitute(org.joda.time.DateTime.class, java.util.Date.class)
.apiInfo(apiInfo());
}
To get the calls working on my machine I've put the ApiReponses with the Controller
#RestController
#RequestMapping("/my_model")
#ApiResponses(value = { #ApiResponse(code = 200, message = "OK"),
#ApiResponse(code = 500, message = "Rocks fell, everyone died.") })
Update
Are you putting the description on the method?
Here is what I have working on my project. Note the API Responses are attached to the controller.
#RestController
#RequestMapping("/my_model/gogo")
#ApiResponses(value = { #ApiResponse(code = 200, message = "OK"),
#ApiResponse(code = 500, message = "Rocks Fall") })
public class GoGoClass {
#RequestMapping(method = RequestMethod.POST)
#ApiOperation(value = "Description")
public void run(
#ApiParam(value = "Param1 description") #RequestParam(required = true) final String param1,
#ApiParam(value = "Param 2 description") final String param2)
throws ModelException {
// Do stuff
}
}
fixed the problem beside working ...
this was a Spring Annotation Problem.
In my SwaggerDocumentationConfig.java I added #EnableSwagger2 and everything works as wanted
#Configuration
#EnableSwagger2
public class SwaggerDocumentationConfig {
#Bean
public Docket customImplementation(){
return new Docket(DocumentationType.SWAGGER_2)
.useDefaultResponseMessages(false)
.select()
.apis(RequestHandlerSelectors.basePackage("myrest.api"))
.build()
.directModelSubstitute(org.joda.time.LocalDate.class, java.sql.Date.class)
.directModelSubstitute(org.joda.time.DateTime.class, java.util.Date.class)
.apiInfo(apiInfo());
}
}

Resources