OpenAPITools / OpenAPITools/openapi-generator

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

Open
#3,145 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Client: Java Issue: Bug
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the issue with the provided OpenAPI document and Python generation command, then inspect the generated default_api.py method root_put_with_http_info. Trace where the supplied content_type header is replaced by select_header_content_type(); done means the generated client preserves the caller-provided Content-Type for the request.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, python
Domain
api, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.