Skip to content

Add Content-Type to multipart/form-data request #964

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
shpi0 opened this issue Dec 2, 2020 · 4 comments
Closed

Add Content-Type to multipart/form-data request #964

shpi0 opened this issue Dec 2, 2020 · 4 comments

Comments

@shpi0
Copy link

shpi0 commented Dec 2, 2020

Description
I'm making a controller that consumes multipart/form-data request with file and some extra information in json format.
I've made an project under spring-boot 2.3.3 and spriongdoc-openapi 1.5.0
Example of my project is here: https://github.com/shpi0/swagger-ui-test

Example of POST method with multipart data

@RouterOperation(
    path = PATH_MULTIPART,
    method = RequestMethod.POST,
    beanClass = TestController.class,
    beanMethod = "testMultipart",
    operation =
    @Operation(
            operationId = "testMultipart",
            parameters = {@Parameter(
                    name = "id",
                    required = true,
                    description = "ID of something",
                    in = ParameterIn.PATH)},
            requestBody = @RequestBody(
                    description = "Create file",
                    required = true,
                    content = @Content(
                            mediaType = "multipart/form-data",
                            encoding = {
                                    @Encoding(name = "document", contentType = "application/json")
                            },
                            schema = @Schema(type = "object", implementation = MultipartRequestDto.class))),
            security = {@SecurityRequirement(name = "bearer-key")},
            responses = {
                    @ApiResponse(
                            responseCode = "200",
                            description = "Successful Operation",
                            content = @Content(
                                    mediaType = "application/json",
                                    schema = @Schema(implementation = ResponseDto.class)))
            }))

Current behaviour
Now, when I'm trying to execute query from Swagger-ui, it produces the following query:

------WebKitFormBoundary29u9fdl6VqOiM8pP
Content-Disposition: form-data; name="document"

{
  "name": "Some name",
  "folder_id": "28fae5d4-f4db-4fc5-9dc6-8571ab30731a"
}
------WebKitFormBoundary29u9fdl6VqOiM8pP
Content-Disposition: form-data; name="file"; filename="q.txt"
Content-Type: text/plain


------WebKitFormBoundary29u9fdl6VqOiM8pP--

Problem
Is it possible to add Content-Type for document part in Swagger-ui, as in example below?

------WebKitFormBoundary29u9fdl6VqOiM8pP
Content-Disposition: form-data; name="document"
Content-Type: application/json
{
  "name": "Some name",
  "folder_id": "28fae5d4-f4db-4fc5-9dc6-8571ab30731a"
}
------WebKitFormBoundary29u9fdl6VqOiM8pP
Content-Disposition: form-data; name="file"; filename="q.txt"
Content-Type: text/plain


------WebKitFormBoundary29u9fdl6VqOiM8pP--

Screenshots
Here is an example how I make the request in Postman:
Screenshot_1

@bnasslahsen
Copy link
Collaborator

Instead of using encoding, use encodings.

Note that Since version v1.5.0, a functional DSL has been introduced.
This can make your code less verbose.
You have samples here:

@shpi0
Copy link
Author

shpi0 commented Dec 2, 2020

@bnasslahsen Thanks for your reply. Could you explain, what do you mean by telling "use encodings"?
There is no "encodings" parameter for any annotations in the io.swagger.v3.oas.annotations package. For example:

public @interface Content {
    String mediaType() default "";
    ExampleObject[] examples() default {};
    Schema schema() default @Schema;
    ArraySchema array() default @ArraySchema;
    Encoding[] encoding() default {};
    Extension[] extensions() default {};
}

BTW, I did the same documentation using functional DSL. Result is same - no Content-Type for json added to request when you execute it from swagger-ui.

    return SpringdocRouteBuilder.route()
        .POST(DOCS_PATH,
            accept(MediaType.MULTIPART_FORM_DATA),
            documentController::saveDocument,
            ops -> ops
                .operationId("saveDocument")
                .parameter(parameterBuilder().in(ParameterIn.PATH).name("mdmId").required(true).description("test"))
                .requestBody(requestBodyBuilder()
                    .content(contentBuilder()
                        .mediaType("multipart/form-data")
                        .encoding(encodingBuilder().name("document").contentType("application/json"))
                        .schema(schemaBuilder().type("object").implementation(MultipartRequest.class)))))
        .build();

Screenshot_2

@bnasslahsen
Copy link
Collaborator

@shpi0,

You can generate multiple encodings using the encodings attribute withing the @Content annotation.
You should get a correct OpenAPI spec independently from the swagger-ui, which is done by springdoc-openapi.

Testing the encoding from the swagger-ui is not yet supported in the swagger-ui and already answred here: #820

@jearton
Copy link

jearton commented Jun 14, 2024

I resolved it for any @RequestPart parameter.

    @Bean
    public OperationCustomizer operationCustomizer(ConversionService conversionService, ObjectProvider<GroupedOpenApi> groupedOpenApis) {
        OperationCustomizer customizer = (operation, handlerMethod) -> {
            Optional.ofNullable(operation.getRequestBody())
                    .map(RequestBody::getContent)
                    .filter(content -> content.containsKey(MediaType.MULTIPART_FORM_DATA_VALUE))
                    .map(content -> content.get(MediaType.MULTIPART_FORM_DATA_VALUE))
                    .ifPresent(multipartFormData -> {
                        for (MethodParameter methodParameter : handlerMethod.getMethodParameters()) {
                            if (MultipartResolutionDelegate.isMultipartArgument(methodParameter)) {
                                // ignore MultipartFile parameters
                                continue;
                            }
                            RequestPart requestPart = methodParameter.getParameterAnnotation(RequestPart.class);
                            if (requestPart == null) {
                                // ignore parameters without @RequestPart annotation
                                continue;
                            }
                            if (conversionService.canConvert(TypeDescriptor.valueOf(String.class), new TypeDescriptor(methodParameter))) {
                                // ignore parameters that can be converted from String to a basic type by ObjectToStringHttpMessageConverter
                                continue;
                            }
                            String parameterName = requestPart.name();
                            if (!StringUtils.hasText(parameterName)) {
                                parameterName = methodParameter.getParameterName();
                            }
                            if (!StringUtils.hasText(parameterName)) {
                                parameterName = methodParameter.getParameter().getName();
                            }
                            if (StringUtils.hasText(parameterName)) {
                                multipartFormData.addEncoding(parameterName, new Encoding().contentType(MediaType.APPLICATION_JSON_VALUE));
                            }
                        }
                    });
            return operation;
        };
        groupedOpenApis.forEach(groupedOpenApi -> groupedOpenApi.getOperationCustomizers().add(customizer));
        return customizer;
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants