Document a multipart/form-data endpoint with Springfox - java

I have a POST endpoint which receives a #ModelAttribute parameter. Everything is working ok, but the swagger documentation fails to have the descriptions, examples, etc.
I am using java 11, springboot 2.5.4 and springfox-boot-starter 3.0.0
Here is my code:
#Api
#RestController
#RequestMapping("/foo")
#Validated
public class MyRest {
#PostMapping(value = "/{id}/bar", consumes = { MediaType.MULTIPART_FORM_DATA_VALUE })
#ApiOperation(value = "Do nothing", notes = "This endpoint does nothing")
public ResponseEntity<String> search(
#ModelAttribute MyModelRequest request,
#ApiParam(value = "Folder ID", required = true)
#PathVariable String id) {
// some business code
return new ResponseEntity<>("lorem ipsum", HttpStatus.OK);
}
}
MyModelRequest
#ApiModel
#Data
public class MyModelRequest {
#ApiParam(name = "fileName", value = "The name of the image to be stored in database")
#ApiModelProperty(value = "name model description", example = "summer picture", required = true)
private String name;
#DecimalMin("0.00")
#DecimalMax("100.00")
#ApiParam(name = "accuracy", value = "The required accuracy")
#ApiModelProperty(value = "Minimum required accuracy", example = "95.15", required = false)
private BigDecimal accuracy;
#ApiParam(name = "marginTop", value = "Top margin of the image")
#ApiModelProperty(value = "Separation between top item and the image", example = "300", required = false)
private Integer marginTop;
#ApiParam(name = "image")
#ApiModelProperty(value = "The image to be stored", example = "vacations.png", required = true)
private MultipartFile image;
}
And this is the generated swagger doc
UPDATE: I noticed that if I change the consumes = { MediaType.MULTIPART_FORM_DATA_VALUE } for consumes = { MediaType.APPLICATION_JSON_VALUE } or remove the whole "consumes" parameter from the endpoint, the documentation shows up correctly, however, doing this will make the fileupload fail.

Related

Values are not being validated against openani-generated enum inside spring-boot project

I am using openapi 3.0.3 spec with openapi-generator-maven-plugin for Java code generation to build interfaces that are implemented in a spring-boot project.
/user/search:
get:
parameters:
- in: query
name: sortBy
description: Field to sort by
required: true
schema:
$ref: "#/components/schemas/UserSearchSortBy"
# omitting some code to shorted the copy-pasted spec
schemas:
UserSearchSortBy:
type: string
enum: [first_name, last_name, email, phone_number]
The above results in an interface that I can implement. The enum for UserSearchSortBy is created fine.
#RequestMapping(
method = RequestMethod.GET,
value = "/user/search",
produces = { "application/json" }
)
default ResponseEntity<UsersResponsePageableModel> _searchUsers(
#NotNull #Min(1) #Parameter(name = "currentPage", description = "Page number", required = true) #Valid #RequestParam(value = "currentPage", required = true) Integer currentPage,
#NotNull #Min(1) #Max(100) #Parameter(name = "pageSize", description = "Number of records to show per page", required = true) #Valid #RequestParam(value = "pageSize", required = true) Integer pageSize,
#NotNull #Parameter(name = "sortOrder", description = "Sort order", required = true) #Valid #RequestParam(value = "sortOrder", required = true) SortOrderEnumModel sortOrder,
#NotNull #Parameter(name = "sortBy", description = "Field to sort by", required = true) #Valid #RequestParam(value = "sortBy", required = true) UserSearchSortByModel sortBy,
#NotNull #Size(max = 128) #Parameter(name = "searchQuery", description = "Search field", required = true) #Valid #RequestParam(value = "searchQuery", required = true) String searchQuery
) {
return searchUsers(currentPage, pageSize, sortOrder, sortBy, searchQuery);
}
I expect the values that are being submitted to an API to be validated against the UserSearchSortBy enum. The issue is that there is no validation present. It looks like the generator is not generating a piece that is responsible for validating values against the enum. Any help is appreciated.
The following post helped me to create a converter factory.
#Component
public class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
private static class StringToEnumConverter<T extends Enum> implements Converter<String, T> {
private Class<T> enumType;
public StringToEnumConverter(Class<T> enumType) {
this.enumType = enumType;
}
public T convert(String source) {
return (T) Enum.valueOf(this.enumType, source.trim().toUpperCase());
}
}
#Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnumConverter(targetType);
}
}
And the following exception handler helps me craft a nice-looking error response.
#ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<?> handleMismatchException(MethodArgumentTypeMismatchException e) {
String message = e.getMessage();
Class<?> parameterType = e.getParameter().getParameterType();
if (parameterType.isEnum()) {
Object[] enumConstants = parameterType.getEnumConstants();
if (enumConstants != null && enumConstants.length > 0) {
if (e.getName() != null && !e.getName().isEmpty() && e.getValue() != null) {
message = String.format("Invalid value '%s' for field '%s'.", e.getValue(), e.getName()) + " Valid values are " + Arrays.asList(enumConstants);
}
}
}
Map<String, String> errors = new HashMap<>();
errors.put("message", message);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).header("Content-Type", MediaType.APPLICATION_JSON_VALUE).body(errors);
}

unable to reach request mapping endpoint so im getting 404 error

I created a new controller with request mapping "/route" but no matter whatever i do I am unable to reach the endpoint localhost:8080/route. I always get 404.
How can I access the endpoint route without getting 404?
#RestController
public interface RouteController {
#ApiOperation(value = "Add a route details to adapter", nickname = "addroute", notes = "Add route details to the table and Send it to ATLAS", tags = {
"route", })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Successful Insertion into DB and Proceded for Transformation"),
#ApiResponse(code = 405, message = "Invalid Input") })
#RequestMapping(value = "/route", produces = { "application/json" }, consumes = {
"application/json" }, method = RequestMethod.POST)
public ResponseEntity<Void> createData(
#ApiParam(value = "Route Array", required = true) #Valid #RequestBody RouteArray routeArray) ;
}
Implementation class
public class RouteControllerImpl implements RouteController {
#Autowired
private RouteService routeService;
#Override
public ResponseEntity<Void> createData(#ApiParam(value = "Route Details", required = true) #Valid #RequestBody RouteArray routeArray) {
return new ResponseEntity<Void>(routeService.transformRoute(routeArray));
}
}
You need to change a few things in your implementation. You should not use #RestController with your OpenAPI api, but rather use #Api annotation. You should use #RestController on the class implementing this API.
#Validated
#Api(
value = "route",
description = The Controller API"
)
public interface RouteController {
#ApiOperation(value = "Add a route details to adapter", nickname = "addroute", notes = "Add route details to the table and Send it to ATLAS", tags = {
"route", })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Successful Insertion into DB and Proceded for Transformation"),
#ApiResponse(code = 405, message = "Invalid Input") })
#RequestMapping(value = "/route", produces = { "application/json" }, consumes = {
"application/json" }, method = RequestMethod.POST)
public ResponseEntity<Void> createData(
#ApiParam(value = "Route Array", required = true) #Valid #RequestBody RouteArray routeArray) ;
}
Implementation:
#RestController
public class RouteControllerImpl implements RouteController {
#Autowired
private RouteService routeService;
#Override
public ResponseEntity<Void> createData(#Valid RouteArray routeArray) {
return new ResponseEntity<Void>(routeService.transformRoute(routeArray));
}

springdoc-openapi different examples

I use springdoc-openapi to document my REST API. An error is returned by an error object, that has an errorCode and message. I use #Schema annotation to document an example. However I need different examples per different errors. Is there any way, how to do that?
Example from my code:
#PostMapping(consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
#Operation(summary = "Get new license or retrieve previously issued one for this userId.", tags = "License Endpoint", description = "Licensing operations.",
responses = {
#ApiResponse(
responseCode = "200",
description = "New license or previously issued license for this user, if request was called multiple times.",
content = {#Content(schema = #Schema(implementation = LicenseResponse.class))}
),
#ApiResponse(responseCode = "400",
description = "License can not be retrieved because of either expired bundle or requested bundleId does not exist.",
//I need different example for this error
content = {#Content(schema = #Schema(implementation = LicenseErrorResponse.class))
}
),
#ApiResponse(responseCode = "500",
description = "Internal Error",
//And different example for this error
content = {#Content(schema = #Schema(implementation = LicenseErrorResponse.class))
}
)
}
)
#LoggedIO(input = INFO, result = INFO)
public ResponseEntity<Object> newLicense(#Valid #RequestBody LicenseRequest licenseRequest) {
//content not interesting
}
import javax.validation.constraints.NotBlank;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
#Data
public class LicenseErrorResponse {
// I need different examples for different error in controller.
#Schema(example = "UNKNOWN_BUNDLE_ID", required = true)
private final LicenseErrorCode licenseErrorCode;
#Schema(example = "Bundle doesn't exist, bundleId=com.unknown.id")
private final String message;
#JsonCreator
public LicenseErrorResponse(
#NotBlank #JsonProperty(value = "errorCode") final LicenseErrorCode licenseErrorCode,
#NotBlank #JsonProperty(value = "message") final String message) {
this.licenseErrorCode = licenseErrorCode;
this.message = message;
}
public enum LicenseErrorCode {
EXPIRED_BUNDLE, UNKNOWN_BUNDLE_ID, OTHER
}
}
One way to do that is you can define a string as an example
public static final String exampleInternalError = "{\r\n"
+ " \"licenseErrorCode\": 500,\r\n"
+ " \"message\": \"Internal Error\"\r\n" + "}";
same is used to show the example as
#ApiResponse(responseCode = "500",
description = "Internal Error",
//And different example for this error
content = #Content(schema = #Schema(implementation = LicenseErrorResponse.class),
examples = #ExampleObject(description = "Internal Error", value = exampleInternalError)))

Spring #RequestMapping with optional parameters

I have problem in my controller with optional params in requestmapping, look on my controller below:
#GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Books>> getBooks() {
return ResponseEntity.ok().body(booksService.getBooks());
}
#GetMapping(
produces = MediaType.APPLICATION_JSON_VALUE,
params = {"from", "to"}
)
public ResponseEntity<List<Books>>getBooksByFromToDate(
#RequestParam(value = "from", required = false) String fromDate,
#RequestParam(value = "to", required = false) String toDate)
{
return ResponseEntity.ok().body(bookService.getBooksByFromToDate(fromDate, toDate));
}
Now, when I send request like:
/getBooks?from=123&to=123
it's ok, request goes to "getBooksByFromToDate" method
but when I use send something like:
/getBooks?from=123
or
/getBooks?to=123
it goes to "getAlerts" method
Is it possible to make optional params = {"from", "to"} in #RequestMapping ? Any hints?
Use the default values. Example:-
#GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Books>> getBooksByFromToDate(#RequestParam(value = "from", required = false, defaultValue="01/03/2018") String fromDate, #RequestParam(value = "to", required = false, defaultValue="21/03/2018") String toDate) {
....
}
Just use defaultValue as explained in the Spring's docs:
defaultValue
The default value to use as a fallback when the request parameter is
not provided or has an empty value.

Update a response object of the end-point

I generated automatically SpringMVC API using swagger. Now I want to update some end-points manually.
I have the folloiwng end-point:
#ApiOperation(value = "Estimation of ...", notes = "...", response = Similarity.class, responseContainer = "List")
#io.swagger.annotations.ApiResponses(value = {
#io.swagger.annotations.ApiResponse(code = 200, message = "Similarity metrics", response = Similarity.class),
#io.swagger.annotations.ApiResponse(code = 200, message = "Unexpected error", response = Similarity.class) })
#RequestMapping(value = "/estimateSimilarity",
produces = { "application/json" },
method = RequestMethod.GET)
public ResponseEntity<HashMap<String,Double>> estimateSimilarity(
#ApiParam(value = "...", required = true)
#RequestParam(value = "term1", required = true) String term,
#ApiParam(value = "...", required = true)
#RequestParam(value = "terms", required = true) List<String> concepts)
throws NotFoundException {
Similarity similarity = new Similarity();
HashMap<String,Double> result = similarity.getEstimates(term1, terms);
return new ResponseEntity<HashMap<String,Double>>(HttpStatus.OK);
}
Instead of response = Similarity.class, I want to return HashMap<String,Double> result. How should I update the above-given code to be able to return this object?
Try modifying the ApiOperations Response container.
#ApiOperation(value = "Estimation of ...", notes = "...", response = Double.class, responseContainer = "Map")

Categories