OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
21.68k stars 6.55k forks source link

[BUG] Missing imports from generated API client #5231

Open mtraynham opened 4 years ago

mtraynham commented 4 years ago

Bug Report Checklist

Description

Generator failing to include import when an Operation's response type is Map<List<Type>>.

For instance a function that returns Map<List<Option>> fails to include Option as part of the imports and the class becomes unable to compile.

See the yaml below for the input.

The following code is generated. There is a reference to Option in the code, but the import is not included.

/*
 * OpenAPI Test
 * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
 *
 * The version of the OpenAPI document: 1.0.0
 * 
 *
 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
 * https://openapi-generator.tech
 * Do not edit the class manually.
 */

package com.test.resources;

import com.test.ApiCallback;
import com.test.ApiClient;
import com.test.ApiException;
import com.test.ApiResponse;
import com.test.Configuration;
import com.test.Pair;
import com.test.ProgressRequestBody;
import com.test.ProgressResponseBody;

import com.google.gson.reflect.TypeToken;

import java.io.IOException;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class OptionsApi {
    private ApiClient localVarApiClient;

    public OptionsApi() {
        this(Configuration.getDefaultApiClient());
    }

    public OptionsApi(ApiClient apiClient) {
        this.localVarApiClient = apiClient;
    }

    public ApiClient getApiClient() {
        return localVarApiClient;
    }

    public void setApiClient(ApiClient apiClient) {
        this.localVarApiClient = apiClient;
    }

    /**
     * Build call for getOptions
     * @param type  (optional)
     * @param _callback Callback for upload/download progress
     * @return Call to execute
     * @throws ApiException If fail to serialize the request body object
     * @http.response.details
     <table summary="Response Details" border="1">
        <tr><td> Status Code </td><td> Description </td><td> Response Headers </td></tr>
        <tr><td> 0 </td><td> default response </td><td>  -  </td></tr>
     </table>
     */
    public okhttp3.Call getOptionsCall(List<String> type, final ApiCallback _callback) throws ApiException {
        Object localVarPostBody = null;

        // create path and map variables
        String localVarPath = "/options";

        List<Pair> localVarQueryParams = new ArrayList<Pair>();
        List<Pair> localVarCollectionQueryParams = new ArrayList<Pair>();
        if (type != null) {
            localVarCollectionQueryParams.addAll(localVarApiClient.parameterToPairs("multi", "type", type));
        }

        Map<String, String> localVarHeaderParams = new HashMap<String, String>();
        Map<String, String> localVarCookieParams = new HashMap<String, String>();
        Map<String, Object> localVarFormParams = new HashMap<String, Object>();
        final String[] localVarAccepts = {
            "application/json"
        };
        final String localVarAccept = localVarApiClient.selectHeaderAccept(localVarAccepts);
        if (localVarAccept != null) {
            localVarHeaderParams.put("Accept", localVarAccept);
        }

        final String[] localVarContentTypes = {

        };
        final String localVarContentType = localVarApiClient.selectHeaderContentType(localVarContentTypes);
        localVarHeaderParams.put("Content-Type", localVarContentType);

        String[] localVarAuthNames = new String[] {  };
        return localVarApiClient.buildCall(localVarPath, "GET", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAuthNames, _callback);
    }

    @SuppressWarnings("rawtypes")
    private okhttp3.Call getOptionsValidateBeforeCall(List<String> type, final ApiCallback _callback) throws ApiException {

        okhttp3.Call localVarCall = getOptionsCall(type, _callback);
        return localVarCall;

    }

    /**
     * Get available options
     * 
     * @param type  (optional)
     * @return Map&lt;String, List&lt;Option&gt;&gt;
     * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body
     * @http.response.details
     <table summary="Response Details" border="1">
        <tr><td> Status Code </td><td> Description </td><td> Response Headers </td></tr>
        <tr><td> 0 </td><td> default response </td><td>  -  </td></tr>
     </table>
     */
    public Map<String, List<Option>> getOptions(List<String> type) throws ApiException {
        ApiResponse<Map<String, List<Option>>> localVarResp = getOptionsWithHttpInfo(type);
        return localVarResp.getData();
    }

    /**
     * Get available options
     * 
     * @param type  (optional)
     * @return ApiResponse&lt;Map&lt;String, List&lt;Option&gt;&gt;&gt;
     * @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body
     * @http.response.details
     <table summary="Response Details" border="1">
        <tr><td> Status Code </td><td> Description </td><td> Response Headers </td></tr>
        <tr><td> 0 </td><td> default response </td><td>  -  </td></tr>
     </table>
     */
    public ApiResponse<Map<String, List<Option>>> getOptionsWithHttpInfo(List<String> type) throws ApiException {
        okhttp3.Call localVarCall = getOptionsValidateBeforeCall(type, null);
        Type localVarReturnType = new TypeToken<Map<String, List<Option>>>(){}.getType();
        return localVarApiClient.execute(localVarCall, localVarReturnType);
    }

    /**
     * Get available options (asynchronously)
     * 
     * @param type  (optional)
     * @param _callback The callback to be executed when the API call finishes
     * @return The request call
     * @throws ApiException If fail to process the API call, e.g. serializing the request body object
     * @http.response.details
     <table summary="Response Details" border="1">
        <tr><td> Status Code </td><td> Description </td><td> Response Headers </td></tr>
        <tr><td> 0 </td><td> default response </td><td>  -  </td></tr>
     </table>
     */
    public okhttp3.Call getOptionsAsync(List<String> type, final ApiCallback<Map<String, List<Option>>> _callback) throws ApiException {

        okhttp3.Call localVarCall = getOptionsValidateBeforeCall(type, _callback);
        Type localVarReturnType = new TypeToken<Map<String, List<Option>>>(){}.getType();
        localVarApiClient.executeAsync(localVarCall, localVarReturnType, _callback);
        return localVarCall;
    }
}
openapi-generator version

4.3.0-SNAPSHOT

OpenAPI declaration file content or url
openapi: 3.0.1
info:
  title: OpenAPI Test
  version: 1.0.0
servers:
- url: http://localhost:8080
paths:
  "/options":
    get:
      tags:
      - options
      summary: Get available options
      operationId: getOptions
      parameters:
      - name: type
        in: query
        schema:
          type: array
          items:
            type: string
      responses:
        default:
          description: default response
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: array
                  items:
                    "$ref": "#/components/schemas/Option"
components:
  schemas:
    Option:
      type: object
      properties:
        value:
          type: string
          readOnly: true
        description:
          type: string
          readOnly: true
Command line used for generation
openapi-generator-cli generate -g kotlin-client -i test.yaml
Steps to reproduce
Related issues/PRs
Suggest a fix

Maybe pass in the Operations imports Set reference to the fromResponse method and add any arbitrary imports as needed.

u4842 commented 4 years ago

I also have this issue, but with the java spring generator. Could these issues be related?

mtraynham commented 4 years ago

@u4842 I believe the issue you linked is related and not necessarily typescript, nor java-spring or kotlin-client specific. I believe the issue is actually buried deep in the DefaultCodegen parsing of Operations and this affects all languages.

As mentioned in the Suggest a fix section, I believe the additionalProperties on a map type don't get added to the imports Set and thus aren't included in the generated import list of the API. I suggested maybe passing that imports set down into the fromResponse function so they could be added as a one-off instead of adding a new property to the CodegenOperation.

Unfortunately, I'm not entirely familiar with this code, so was hoping a maintainer could delegate best course of action.

black-snow commented 4 years ago

I'm also seeing some imports that are not reflected in the project's dependencies:

com.google.gson.reflect.TypeToken;
com.squareup.okhttp.Call
okio.BufferedSink
okio.Okio
org.threeten.bp.LocalDate

and many more. 5.0.0-beta2

/edit: I had to add:

compile group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
compile group: 'com.squareup.okhttp', name: 'okhttp', version: '2.7.5'
compile group: 'com.squareup.okhttp', name: 'logging-interceptor', version: '2.7.5'
compile group: 'com.squareup.retrofit', name: 'retrofit', version: '1.9.0'
compile group: 'org.threeten', name: 'threetenbp', version: '1.4.0'
compile group: 'io.swagger.core.v3', name: 'swagger-annotations', version: '2.1.4'
compile group: 'io.gsonfire', name: 'gson-fire', version: '1.8.4'

to get it to compile. But many of those imports are unused.

wanderrful commented 3 years ago

Using the latest release for a Maven project (JDK11.0.10 and Kotlin 1.4.21), here are the equivalent dependencies I needed to add explicitly in order to get the classpath to work properly:

        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.6</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.14.7</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>logging-interceptor</artifactId>
            <version>3.14.7</version>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.5.24</version>
        </dependency>
        <dependency>
            <groupId>io.gsonfire</groupId>
            <artifactId>gson-fire</artifactId>
            <version>1.8.4</version>
        </dependency>
        <dependency>
            <groupId>org.threeten</groupId>
            <artifactId>threetenbp</artifactId>
            <version>1.4.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.10</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.findbugs</groupId>
            <artifactId>jsr305</artifactId>
            <version>3.0.2</version>
        </dependency>

To find out which versions you need for your project, just go into your target/generated-sources/openapi/... folder and check the build.gradle file.

The underlying problem seems to be that some of the dependencies of the plugin are apparently designed to be dependencies of the project that uses the plugin. This information should be made explicit in the README instructions if that's supposed to be intended behavior. Or, if there's an OpenAPI dependency we can add that will cover all the above, it should say to add that in the README as a project dependency alongside the plugin.

Maven plugins are supposed to be designed (if my understanding is right) to have their own independent dependency tree, so it's not designed to include those things in the project's classpath itself (i.e. via mvn dependency:tree).