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.48k stars 6.5k forks source link

[BUG][Java] Content-Type input header not honored #3145

Open dougheitkamp opened 5 years ago

dougheitkamp commented 5 years ago
Description

We have a REST endpoint where users can upload files of various content types. The user supplies the content type in the header. This supplied header value is not being passed correctly through the generated code (opting to set it to application/json instead).

openapi-generator version

java -jar openapi-generator-cli.jar version 4.0.1

We are creating Python clients with an advanced form of the following spec (which demonstrates the issue):

OpenAPI declaration file content or url

Open API spec (json):

{
  "openapi": "3.0.1",
  "info": {
    "title": "Object",
    "version": "2019-06-11T02:09:00.000Z"
  },
  "servers": [
    {
      "url": "https://me.com/objects/v1"
    }
  ],
  "paths": {
    "/": {
      "put": {
        "description": "CreateObject: Creates an object within the collection specified; object-id is returned in response",
        "parameters": [
          {
            "name": "Content-Type",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            },
            "application/xml": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            },
            "image/png": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            },
            "application/octet-stream": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            },
            "application/pdf": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            },
            "image/jpeg": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            },
            "application/atom+xml": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            },
            "image/bmp": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            },
            "image/gif": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            },
            "application/xhtml+xml": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            },
            "application/rtf": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            },
            "text/html": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            },
            "image/tiff": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            },
            "text/csv": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            },
            "application/zip": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            },
            "application/vnd.ms-excel": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            },
            "text/plain": {
              "schema": {
                "$ref": "#/components/schemas/ObjectRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "202 response",
            "headers": {
              "Access-Control-Allow-Origin": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ObjectResponse"
                }
              }
            }
          },
          "400": {
            "description": "400 response",
            "headers": {
              "Access-Control-Allow-Origin": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "403 response",
            "headers": {
              "Access-Control-Allow-Origin": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "404 response",
            "headers": {
              "Access-Control-Allow-Origin": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "405 response",
            "headers": {
              "Access-Control-Allow-Origin": {
                "schema": {
                  "type": "string"
                }
              },
              "Allow": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "409 response",
            "headers": {
              "Access-Control-Allow-Origin": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "422 response",
            "headers": {
              "Access-Control-Allow-Origin": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "500 response",
            "headers": {
              "Access-Control-Allow-Origin": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "api_key": []
          }
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "title": "Error Response schema",
        "required": [
          "context",
          "error"
        ],
        "type": "object",
        "properties": {
          "context": {
            "$ref": "#/components/schemas/ContextProperties"
          },
          "error": {
            "$ref": "#/components/schemas/ErrorProperties"
          }
        }
      },
      "ErrorProperties": {
        "title": "Error properties schema",
        "required": [
          "message",
          "type"
        ],
        "type": "object",
        "properties": {
          "stack-trace": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "corrective-action": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        }
      },
      "ObjectProperties": {
        "title": "Object properties schema",
        "required": [
          "asset-id",
          "collection-id",
          "collection-url",
          "object-id",
          "object-state",
          "object-url",
          "owner-id"
        ],
        "type": "object",
        "properties": {
          "object-expiration-date": {
            "type": "string"
          },
          "object-state": {
            "type": "string",
            "enum": [
              "Created",
              "Pending",
              "Failed",
              "Removed"
            ]
          },
          "object-url": {
            "type": "string"
          },
          "owner-id": {
            "type": "integer"
          },
          "collection-id": {
            "type": "string"
          },
          "collection-url": {
            "type": "string"
          },
          "asset-id": {
            "type": "integer"
          },
          "object-id": {
            "type": "string"
          }
        }
      },
      "ObjectRequest": {
        "title": "Object body schema",
        "type": "string",
        "format": "binary"
      },
      "ObjectResponse": {
        "title": "Object response schema",
        "required": [
          "context",
          "object"
        ],
        "type": "object",
        "properties": {
          "object": {
            "$ref": "#/components/schemas/ObjectProperties"
          }
        }
      }
    },
    "securitySchemes": {
      "api_key": {
        "type": "apiKey",
        "name": "x-api-key",
        "in": "header"
      }
    }
  }
}
Command line used for generation

java -jar openapi-generator-cli.jar generate -i content-type-bug.json -g python -o client\object --package-name content_type_bug

Steps to reproduce

The generated code creates the following (snippit) in default_api.py, which shows that the Content-Type header is set correctly but then (almost immediately) overwritten by the result of self.api_client.select_header_content_type().

def root_put_with_http_info(self, content_type, body, **kwargs):  # noqa: E501
    """root_put  # noqa: E501

    CreateObject: Creates an object within the collection specified; object-id is returned in response  # noqa: E501
    This method makes a synchronous HTTP request by default. To make an
    asynchronous HTTP request, please pass async_req=True
    >>> thread = api.root_put_with_http_info(content_type, body, async_req=True)
    >>> result = thread.get()

    :param async_req bool: execute request asynchronously
    :param str content_type: (required)
    :param file body: (required)
    :param _return_http_data_only: response data without head status code
                                   and headers
    :param _preload_content: if False, the urllib3.HTTPResponse object will
                             be returned without reading/decoding response
                             data. Default is True.
    :param _request_timeout: timeout setting for this request. If one
                             number provided, it will be total request
                             timeout. It can also be a pair (tuple) of
                             (connection, read) timeouts.
    :return: tuple(ObjectResponse, status_code(int), headers(HTTPHeaderDict))
             If the method is called asynchronously,
             returns the request thread.
    """

    local_var_params = locals()

    all_params = ['content_type', 'body']  # noqa: E501
    all_params.append('async_req')
    all_params.append('_return_http_data_only')
    all_params.append('_preload_content')
    all_params.append('_request_timeout')

    for key, val in six.iteritems(local_var_params['kwargs']):
        if key not in all_params:
            raise ApiTypeError(
                "Got an unexpected keyword argument '%s'"
                " to method root_put" % key
            )
        local_var_params[key] = val
    del local_var_params['kwargs']
    # verify the required parameter 'content_type' is set
    if ('content_type' not in local_var_params or
            local_var_params['content_type'] is None):
        raise ApiValueError("Missing the required parameter `content_type` when calling `root_put`")  # noqa: E501
    # verify the required parameter 'body' is set
    if ('body' not in local_var_params or
            local_var_params['body'] is None):
        raise ApiValueError("Missing the required parameter `body` when calling `root_put`")  # noqa: E501

    collection_formats = {}

    path_params = {}

    query_params = []

    header_params = {}
    if 'content_type' in local_var_params:
        header_params['Content-Type'] = local_var_params['content_type']  # noqa: E501

    form_params = []
    local_var_files = {}

    body_params = None
    if 'body' in local_var_params:
        body_params = local_var_params['body']
    # HTTP header `Accept`
    header_params['Accept'] = self.api_client.select_header_accept(
        ['application/json'])  # noqa: E501

    # HTTP header `Content-Type`
    header_params['Content-Type'] = self.api_client.select_header_content_type(  # noqa: E501
        ['application/json', 'application/xml', 'image/png', 'application/octet-stream', 'application/pdf', 'image/jpeg', 'application/atom+xml', 'image/bmp', 'image/gif', 'application/xhtml+xml', 'application/rtf', 'text/html', 'image/tiff', 'text/csv', 'application/zip', 'application/vnd.ms-excel', 'text/plain'])  # noqa: E501

    # Authentication setting
    auth_settings = ['api_key']  # noqa: E501

    return self.api_client.call_api(
        '/', 'PUT',
        path_params,
        query_params,
        header_params,
        body=body_params,
        post_params=form_params,
        files=local_var_files,
        response_type='ObjectResponse',  # noqa: E501
        auth_settings=auth_settings,
        async_req=local_var_params.get('async_req'),
        _return_http_data_only=local_var_params.get('_return_http_data_only'),  # noqa: E501
        _preload_content=local_var_params.get('_preload_content', True),
        _request_timeout=local_var_params.get('_request_timeout'),
        collection_formats=collection_formats)
Suggest a fix

The call to self.api_client.select_header_content_type() should only be made if the Content-Type header hasn't already been set.

auto-labeler[bot] commented 5 years ago

👍 Thanks for opening this issue! 🏷 I have applied any labels matching special text in your issue.

The team will review the labels and make any necessary changes.

waixwong commented 5 years ago

Hello, just wanted to follow up on this issue - We experienced the same problem with our python client. it sets Application/json as the default header for all requests, including ones that doesn't have a body (GET and DELETE etc) which causes our server to reject the requests due to validation errors.

dougheitkamp commented 5 years ago

Update: This problem exists in the Java generated code as well. This isn't a Python-only issue.

ghost commented 3 years ago

Is this problem going on? It's a problem that Application/json set in the header

spacether commented 2 years ago

This has been fixed in python-experimental. One can see two content-types defined for the request body here: https://github.com/OpenAPITools/openapi-generator/blob/master/samples/openapi3/client/petstore/python-experimental/petstore_api/api/fake_api_endpoints/inline_composition.py#L331 And then the developer must input their used content_type and it will be used when serializing the data in the request body here: https://github.com/OpenAPITools/openapi-generator/blob/master/samples/openapi3/client/petstore/python-experimental/petstore_api/api/fake_api_endpoints/inline_composition.py#L529

spacether commented 2 years ago

I am removing the [Python] tag from this issue because we have the above python-experimental generator which support this use case