Incomplete Swagger documentation for SpringBoot endpoint with MultipartFile parameter - java

I am using Swagger with SprintBoot to generate the endpoints documentation, it is working great with one exception: I have a POST endpoint with a MultipartFile parameter. In this case Swagger generates the documentation but not the Example Value (it is empty).
public String create(#ApiParam(value = "Record to be created", required = true, type = "json", format = "json")
#RequestPart(name = "candidate") MyDto record,
#ApiParam(value = "File associated to the record", required = false)
#RequestPart(value = "file", required = false) MultipartFile file) throws Exception
Without the MultipartFile the Example Value shows the JSON example which can be used. I would like to have the same when an additional (optional) MultipartFile parameter is included.
Can this be addressed somehow?

as i know swagger docs can be placed when adding annotations something like this:
#ApiModel(value="MyFile")
public class MyFile{
#ApiModelProperty(value = "originalFileName", example="The original filename")
private String getOriginalFilename;
[...]
}
I would suggest to extend the MultipartFile Object and add this Annotations. So you can add documentation to the params and your optional params too.
P.S. MultipartFile is an Interface so you have to extend one of the Implementations e.g. CommonsMultipartFile. Than you have to include the org.apache.commons.fileupload dependency to your project (for FileItem).

Related

Is it possible to use #Parameter in a REST controller?

I'm trying to use #Parameter for a path parameter in a #RestController, but it ignores the parameter.
Note: All works well if I use #PathVariable instead (commented). However, #PathVariable does not produce the OpenAPI yaml file with all properties like "description", "references", etc, that the springdoc-openapi-maven-plugin produces automatically.
Is it possible to use #Parameter in a REST controller?
Here's my code:
#RequestMapping("/channels")
#RestController
public class ChannelRESTController {
#PostMapping("{channelId}/connect")
#Operation(summary = "Initiates a session to a channel", tags = { "session" })
#ResponseBody
ResponseEntity<?> connect( //
#Parameter(name = "channelId", in = ParameterIn.PATH,
required = true, description = "The channel id") String channelId,
// #PathVariable(required = true) String channelId,
#Parameter(description = "Credentials' username") String username,
#Parameter(description = "Credentials' password") String password
) {
System.out.println(
"Starting channel #" + channelId); // displays null :(
return ...
}
In order to automate the OpenAPI YAML file generation it's possible to add both annotations to the parameters.
In short, the parameter channelId above can be annotated as:
#PathVariable(required = true)
#Parameter(name = "channelId", in = ParameterIn.PATH,
required = true, description = "The channel id")
String channelId,
In this case:
the first annotation #PathVariable allows Spring to retrieve the parameter from the URL.
the second annnotation #Parameter produces the correct description in OpenAPI (YAML) file generated by the springdoc-openapi-maven-plugin plugin.
This solution is not ideal, but does the trick. I wish in the future the plugin will recognize the second one by itself, to avoid typing them both.

Mapping multiple values (multipart data + json) in Post method with #RequestParam arguments

I need to upload from Post request MultipartFile and map a class from json.
My controller code looks like this:
#PostMapping(value = API_FILE_DIRECT_ENDPOINT,
consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE }, produces = MediaType.APPLICATION_JSON_VALUE)
FileDto create( #RequestParam("file") #NotNull MultipartFile multipartFile,
#PathVariable( IbsAttachmentServiceApi.FILE_TYPE_KEY) String type,
#RequestParam("dto") #NotNull ApplyingFileDto applyingFileDto){
FileDto result= process(applyingFileDto, multipartFile);
return result;
}
when I'm trying to query it from Postman, like this
but it gives me an error
"Failed to convert value of type 'java.lang.String' to required type 'com.domain.ibs.attachment.model.ApplyingFileDto'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.domain.ibs.attachment.model.ApplyingFileDto': no matching editors or conversion strategy found"
So it doesn't map ApplyingFileDto class, only if I change it to String, and convert by ObjectMapper explicitly, then it works
#RequestParam("dto") #NotNull String applyingFileDto) ...
ApplyingFileDto mappedDto = objectMapper.readValue(applyingFileDto, ApplyingFileDto.class);
Is there is any way to configure editor to map it after #RequestParam ?
like the error says. You're trying to convert a String value to an ApplyingFileDto Object. The following method to pass objects to a service is to use the #RequestBody annotation.
I think that should solve your problem. Give it a try.
It can be because you are unnecessarily including #RequestParam in the controller method. You should use #RequestBody
But before that, Make sure the attributes received as request parameters have perfect getters and setters (eg for attribute bot - setBot) on the object to be mapped.
Add the object as a parameter in the controller method, but do not annotate with #RequestParam. The setter method of the target object will be called for each matching request parameter.
Example -
#PostMapping()
FileDto create(#RequestBody DTO dto) {
}
You have #RequestParam but the dto was not in your params it was in the body tab
I've found the solution for the answer. It is needed to add #RequestPart annotation instead #RequestParam like this
#PostMapping(value = API_FILE_DIRECT_ENDPOINT,
consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE }, produces = MediaType.APPLICATION_JSON_VALUE)
FileDto create( #RequestParam("file") #NotNull MultipartFile multipartFile,
#PathVariable( IbsAttachmentServiceApi.FILE_TYPE_KEY) String type,
#RequestPart("dto") #NotNull ApplyingFileDto applyingFileDto){
FileDto result= process(applyingFileDto, multipartFile);
return result;
}
then reason for is in different type of converters.
It is mentioned here
Note that #RequestParam annotation can also be used to associate the
part of a "multipart/form-data" request with a method argument
supporting the same method argument types. The main difference is that
when the method argument is not a String or raw MultipartFile / Part,
#RequestParam relies on type conversion via a registered Converter or
PropertyEditor while RequestPart relies on HttpMessageConverters
taking into consideration the 'Content-Type' header of the request
part. RequestParam is likely to be used with name-value form fields
while RequestPart is likely to be used with parts containing more
complex content e.g. JSON, XML).
also this example is very useful

form-data file upload + array of object

I want to push data inside a form to my Spring REST api. The handler method looks as follows:
#PostMapping
public RepresentationModel<?> handleFileUpload(
#RequestParam(value = "file") MultipartFile file,
#RequestParam(value = "description", required = false) String description,
#RequestParam(value = "public") Boolean publicAccessible,
#RequestParam(value = "access") #Valid List<SomeObject> access,
Authentication authentication) {
....
}
this gave me:
But I need a way to wrap a complex Object into my array.
This is the only thing I came up with so far.
How do I have to shape my request in Postman to satisfy my controller? The problem is the access attribute. Spring complains all the time access parameter is not present or access parameter is not of type List.
In Json my request should look like this:
"public": "true",
"description": "Bachelor Dokument",
"access": [
{
"someAttribute": "something",
"someAttribute2": "something"
},
{
"someAttribute": "something2",
"someAttribute2": "something2"
}
]
...
/*Content of uarttest.py*/
I need the form-data for uploading files I figured. Or do I do something wrong here?
Thanks in advance.
You need to just use one access variable in your Postman request with the desired JSON as value:
Of course, you need to specify the Content-Type as application/json of the access field.

Spring #RequestParam - Mixing named params and Map<String,String> params

I'm writing a Spring Boot application which receives parameters via REST endpoint and forwards them to another system. The received params contain some known fields but may also contain multiple variable fields starting with filter followed by an undefined name:
example.com?id=1&name=foo&filter1=2&filterA=B&[...]&filterWhatever=something
As you can see there are params id and name, and multiple params starting with filter. When calling the target system, I need to remove filter from the param keys and use everything after that as key:
targetsystem.com?id=1&name=foo&1=2&A=B&[...]&whatever=something (no more filter in keys)
This itself is not a problem, I can just just #RequestParam Map<String, String> params, stream/loop the params and modify as I want. But using Swagger as API documentation tool, I want to list all known parameters, so clients can see what is actually supported.
I tried mixing named params and catch-all params, but it doesn't recognize a handler:
myEndpoint(final #RequestParam String id, final #RequestParam String name, final #RequestParam Map<String, String> remainingParams)
Is it possible to map specific params and catch everything else in Map<String,String>? Or are there other possiblities, like mapping all params starting with filter using regex pattern?
Unfortunately I can't change source and target systems.
If your only concern with using a generic map is simply Swagger being accurate, why not just add the #ApiImplicitParams annotation to your endpoint? This would let you specify which params are required in your Swagger output:
#ApiImplicitParams(value = {
#ApiImplicitParam(name = "name", type = "String", required = true, paramType = "query"),
#ApiImplicitParam(name = "id", type = "String", required = true, paramType = "query")
})
Try
#RequestMapping
public String books(#RequestParam Map<String, String> requestParams, Other params)
{
//Your code here
}
You could make a class, e.g.
#Data
public class Paramss {
#NotNull
private String a;
private String b;
}
and then
#GetMapping
public Object params( #Valid #ModelAttribute Paramss params ) {
return params;
}
See also: Spring #RequestParam with class

Content type 'multipart/form-data;boundary=----...;charset=UTF-8' not supported

I want to send an object to the controller that has several lists with files and several fields with plain text.
public class ContributionNew<T extends MovieInfoDTO> {
private List<T> elementsToAdd;
private Map<Long, T> elementsToUpdate;
private Set<Long> idsToDelete;
private Set<String> sources;
private String comment;
}
public class Photo extends MovieInfoDTO {
private MultipartFile photo;
}
#PostMapping(value = "/{id}/contributions/photos")
#ResponseStatus(HttpStatus.CREATED)
public
ResponseEntity<Void> createPhotoContribution(
#ApiParam(value = "The movie ID", required = true)
#PathVariable("id") final Long id,
#ApiParam(value = "The contribution", required = true)
#RequestBody #Valid final ContributionNew<Photo> contribution
) {
I am sending data using postman. However, he throws me away
org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'multipart/form-data;boundary=----WebKitFormBoundarywY7ByvgonAjDoaCT;charset=UTF-8' not supported
What should I set the Content-type for this controller so that I can send an object that has fields of plain text and lists with files?
If I set the header in the header
Content-type: multipart/form-data; charset=utf-8
it throws me in the console
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
As said dknight #RequestBody means use of JSON or XML data with maps your DTO bean.
In case of MultipartFile you can't use JSON data so you can't use #RequestBody.
Try with #ModelAttribute annotation.
Working sample :
#PostMapping("/promoters")
#Timed
public ResponseEntity<PromoterDTO> createPromoter(#ModelAttribute PromoterDTO promoterDTO) throws URISyntaxException { ... }
With PromoterDTO like this :
public class PromoterDTO implements Serializable {
private Long id;
private String name;
private String address;
private MultipartFile logo;
}
In Postman, you need to set the body to be of type raw, and from the drop down you can select JSON, I had a similar issue, this fixed my issue.
Instead of #RequestBody, use #RequestParam!
use #ModelAttribute instead of #ResponseBody as this takes up data in key value pairs and the later is used for an object like, json.
While hitting the api simply pass the multipart type and json key value pairs of the object. It works fine!
stack overflow question on this
Instead of #RequestBody, use #ModelAttribute like,
#PostMapping(value = "/{id}/contributions/photos")
#ResponseStatus(HttpStatus.CREATED)
public
ResponseEntity<Void> createPhotoContribution(
#ApiParam(value = "The movie ID", required = true)
#PathVariable("id") final Long id,
#ApiParam(value = "The contribution", required = true)
#ModelAttribute #Valid final ContributionNew<Photo> contribution
) {
import org.springframework.web.bind.annotation.ModelAttribute;
Use #ModelAttribute instead of #RequestBody. It worked for me.
produces = { "application/json" } has to written in the controller instead of consumes = { "application/json" }
Hi Folks simply change : #RequestBody to #ModelAttribute
public ResponseEntity<DTO> exemple(#ModelAttribute Dto dto) throws TechnicalException
Happy coding.
Here's a full code sample written in Kotlin using Spring Boot 2.1.7
Example uses a ProfileRepository that would be something you implement of course.
Kotlin is nice, because the data class implements serializable already.
Take note, that you have to use var not val for the model objects properties otherwise the field values will be null in the log message.
import org.springframework.web.bind.annotation.ModelAttribute
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile
#RestController
class ExampleController(private val myProfileRepository: ProfileRepository) {
#PostMapping("/api/uploadFile")
fun createProvider(#ModelAttribute request: CreateProfileRequest): Provider {
println("received create request with photo: ${request.photo} for the following person ${request.name}")
return myProfileRepository.save(Provider(name = request.name!!))
}
}
data class CreateProfileRequest(
var name: String? = null,
var photo: MultipartFile? = null
)

Categories

Resources