Skip to content

[BUG][java]Additional properties in multipart requests generates invalid code #22572

@sermler

Description

@sermler

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
  • [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

Using a multipart request body which has additional-properties as part generates invalid code for the java-resttemplate client. It was introduced in version 7.5.0 before this version it worked correctly

Spec below creates following for example:

package org.openapitools.client.api;

import org.openapitools.client.ApiClient;
import org.openapitools.client.BaseApi;

import java.io.File;
import org.openapitools.client.model.StructuredType;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.19.0-SNAPSHOT")
public class FileApi extends BaseApi {

    public FileApi() {
        super(new ApiClient());
    }

    public FileApi(ApiClient apiClient) {
        super(apiClient);
    }

    /**
     * 
     * 
     * <p><b>201</b> - File created successfully
     * @param documentBytes  (required)
     * @param documentType  (required)
     * @param properties  (required)
     * @param structured  (optional)
     * @throws RestClientException if an error occurs while attempting to invoke the API
     */
    public void createFile(File documentBytes, String documentType, Map<String, String> properties, StructuredType structured) throws RestClientException {
        createFileWithHttpInfo(documentBytes, documentType, properties, structured);
    }

    /**
     * 
     * 
     * <p><b>201</b> - File created successfully
     * @param documentBytes  (required)
     * @param documentType  (required)
     * @param properties  (required)
     * @param structured  (optional)
     * @return ResponseEntity&lt;Void&gt;
     * @throws RestClientException if an error occurs while attempting to invoke the API
     */
    public ResponseEntity<Void> createFileWithHttpInfo(File documentBytes, String documentType, Map<String, String> properties, StructuredType structured) throws RestClientException {
        Object localVarPostBody = null;
        
        // verify the required parameter 'documentBytes' is set
        if (documentBytes == null) {
            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Missing the required parameter 'documentBytes' when calling createFile");
        }
        
        // verify the required parameter 'documentType' is set
        if (documentType == null) {
            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Missing the required parameter 'documentType' when calling createFile");
        }
        
        // verify the required parameter 'properties' is set
        if (properties == null) {
            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Missing the required parameter 'properties' when calling createFile");
        }
        

        final MultiValueMap<String, String> localVarQueryParams = new LinkedMultiValueMap<String, String>();
        final HttpHeaders localVarHeaderParams = new HttpHeaders();
        final MultiValueMap<String, String> localVarCookieParams = new LinkedMultiValueMap<String, String>();
        final MultiValueMap<String, Object> localVarFormParams = new LinkedMultiValueMap<String, Object>();

        if (documentBytes != null)
            localVarFormParams.add("documentBytes", new FileSystemResource(documentBytes));
        if (documentType != null)
            localVarFormParams.add("documentType", documentType);
        if (structured != null)
            localVarFormParams.add("structured", structured);
        if (properties != null)
            localVarFormParams.addAll("properties", properties);

        final String[] localVarAccepts = {  };
        final List<MediaType> localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
        final String[] localVarContentTypes = { 
            "multipart/form-data"
         };
        final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);

        String[] localVarAuthNames = new String[] {  };

        ParameterizedTypeReference<Void> localReturnType = new ParameterizedTypeReference<Void>() {};
        return apiClient.invokeAPI("/api/v1/file", HttpMethod.POST, Collections.<String, Object>emptyMap(), localVarQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType, localVarAuthNames, localReturnType);
    }

    @Override
    public <T> ResponseEntity<T> invokeAPI(String url, HttpMethod method, Object request, ParameterizedTypeReference<T> returnType) throws RestClientException {
        String localVarPath = url.replace(apiClient.getBasePath(), "");
        Object localVarPostBody = request;

        final Map<String, Object> uriVariables = new HashMap<String, Object>();
        final MultiValueMap<String, String> localVarQueryParams = new LinkedMultiValueMap<String, String>();
        final HttpHeaders localVarHeaderParams = new HttpHeaders();
        final MultiValueMap<String, String> localVarCookieParams = new LinkedMultiValueMap<String, String>();
        final MultiValueMap<String, Object> localVarFormParams = new LinkedMultiValueMap<String, Object>();

        final String[] localVarAccepts = {  };
        final List<MediaType> localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
        final String[] localVarContentTypes = { 
            "multipart/form-data"
         };
        final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);

        String[] localVarAuthNames = new String[] {  };

        return apiClient.invokeAPI(localVarPath, method, uriVariables, localVarQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType, localVarAuthNames, returnType);
    }
}

but should look like

package org.openapitools.client.api;

import org.openapitools.client.ApiClient;
import org.openapitools.client.BaseApi;

import java.io.File;
import org.openapitools.client.model.StructuredType;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.19.0-SNAPSHOT")
public class FileApi extends BaseApi {

    public FileApi() {
        super(new ApiClient());
    }

    public FileApi(ApiClient apiClient) {
        super(apiClient);
    }

    /**
     * 
     * 
     * <p><b>201</b> - File created successfully
     * @param documentBytes  (required)
     * @param documentType  (required)
     * @param properties  (required)
     * @param structured  (optional)
     * @throws RestClientException if an error occurs while attempting to invoke the API
     */
    public void createFile(File documentBytes, String documentType, Map<String, String> properties, StructuredType structured) throws RestClientException {
        createFileWithHttpInfo(documentBytes, documentType, properties, structured);
    }

    /**
     * 
     * 
     * <p><b>201</b> - File created successfully
     * @param documentBytes  (required)
     * @param documentType  (required)
     * @param properties  (required)
     * @param structured  (optional)
     * @return ResponseEntity&lt;Void&gt;
     * @throws RestClientException if an error occurs while attempting to invoke the API
     */
    public ResponseEntity<Void> createFileWithHttpInfo(File documentBytes, String documentType, Map<String, String> properties, StructuredType structured) throws RestClientException {
        Object localVarPostBody = null;
        
        // verify the required parameter 'documentBytes' is set
        if (documentBytes == null) {
            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Missing the required parameter 'documentBytes' when calling createFile");
        }
        
        // verify the required parameter 'documentType' is set
        if (documentType == null) {
            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Missing the required parameter 'documentType' when calling createFile");
        }
        
        // verify the required parameter 'properties' is set
        if (properties == null) {
            throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Missing the required parameter 'properties' when calling createFile");
        }
        

        final MultiValueMap<String, String> localVarQueryParams = new LinkedMultiValueMap<String, String>();
        final HttpHeaders localVarHeaderParams = new HttpHeaders();
        final MultiValueMap<String, String> localVarCookieParams = new LinkedMultiValueMap<String, String>();
        final MultiValueMap<String, Object> localVarFormParams = new LinkedMultiValueMap<String, Object>();

        if (documentBytes != null)
            localVarFormParams.add("documentBytes", new FileSystemResource(documentBytes));
        if (documentType != null)
            localVarFormParams.add("documentType", documentType);
        if (structured != null)
            localVarFormParams.add("structured", structured);
        if (properties != null)
            localVarFormParams.add("properties", properties);

        final String[] localVarAccepts = {  };
        final List<MediaType> localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
        final String[] localVarContentTypes = { 
            "multipart/form-data"
         };
        final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);

        String[] localVarAuthNames = new String[] {  };

        ParameterizedTypeReference<Void> localReturnType = new ParameterizedTypeReference<Void>() {};
        return apiClient.invokeAPI("/api/v1/file", HttpMethod.POST, Collections.<String, Object>emptyMap(), localVarQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType, localVarAuthNames, localReturnType);
    }

    @Override
    public <T> ResponseEntity<T> invokeAPI(String url, HttpMethod method, Object request, ParameterizedTypeReference<T> returnType) throws RestClientException {
        String localVarPath = url.replace(apiClient.getBasePath(), "");
        Object localVarPostBody = request;

        final Map<String, Object> uriVariables = new HashMap<String, Object>();
        final MultiValueMap<String, String> localVarQueryParams = new LinkedMultiValueMap<String, String>();
        final HttpHeaders localVarHeaderParams = new HttpHeaders();
        final MultiValueMap<String, String> localVarCookieParams = new LinkedMultiValueMap<String, String>();
        final MultiValueMap<String, Object> localVarFormParams = new LinkedMultiValueMap<String, Object>();

        final String[] localVarAccepts = {  };
        final List<MediaType> localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
        final String[] localVarContentTypes = { 
            "multipart/form-data"
         };
        final MediaType localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);

        String[] localVarAuthNames = new String[] {  };

        return apiClient.invokeAPI(localVarPath, method, uriVariables, localVarQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAccept, localVarContentType, localVarAuthNames, returnType);
    }
}
openapi-generator version

7.17.0

OpenAPI declaration file content or url
openapi: 3.0.3
info:
  title: Minimal
  description: Api to reproduce bug
  version: 0.5.1-SNAPSHOT.0
tags:
  - name: test
servers:
  - url: http://localhost:8080
paths:
  "/api/v1/file":
    post:
      tags:
        - file
      operationId: createFile
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/FileUploadRequest"
            encoding:
              documentBytes:
                contentType: "*/*"
              properties:
                contentType: application/json
      responses:
        "201":
          description: File created successfully
components:
  schemas:
    FileUploadRequest:
      type: object
      properties:
        documentBytes:
          type: string
          format: binary
        documentType:
          type: string
        properties:
          $ref: '#/components/schemas/TypeMap'
      required:
        - documentBytes
        - documentType
        - properties
    TypeMap:
        type: object
        additionalProperties:
          type: string

<--
(for YAML code) or

(here your code)

(for JSON code), so it becomes more readable. If it is longer than about ten lines,
please create a Gist (https://gist.github.com) or upload it somewhere else and
link it here.
-->

Generation Details
{
  "$schema": "https://raw.githubusercontent.com/OpenAPITools/openapi-generator-cli/refs/heads/master/apps/generator-cli/src/config.schema.json",
  "spaces": 2,
  "generator-cli": {
    "version": "7.18.0",
    "generators": {
      "java-client-sdk": {
        "generatorName": "java",
        "library": "resttemplate",
        "inputSpec": "api-specification.yml",
        "output": "#{cwd}/sdks/java-client-sdk",
        "globalProperty": {
          "apiTests": false,
          "apiDocs": false,
          "modelTests": false,
          "modelDocs": false
        },
        "additionalProperties": {
          "serializationLibrary": "jackson",
          "useJakartaEe": true,
          "hideGenerationTimestamp": true,
          "useAbstractionForFiles": true,
          "sourceFolder": "src/gen/java",
          "booleanGetterPrefix": "is",
          "enumUnknownDefaultCase": true
        }
      }
    }
  }
}

npx @openapitools/openapi-generator-cli generate

Steps to reproduce

See config and spec above.

Related issues/PRs
Suggest a fix

I am going to prepare a PR.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions