openapi-generator icon indicating copy to clipboard operation
openapi-generator copied to clipboard

[BUG] typescript-fetch casts enum scalar values to Blob

Open fernard opened this issue 3 years ago • 4 comments

Description

Currently, when the OpenAPI declaration file is being generated, the open-api-generator treats enum values (which are scalars, such as string and numbers) as complex, object-like structures in insists of casting them to Blob as a part of the multipart/form-data request payload.

openapi-generator version

5.3.1

OpenAPI declaration file content or url

This is the output from the FASTApi application, an endpoint expecting a multipart/form-data request with both file and some "metadata" describing this file.

(...)

  "components": {
    "schemas": {
      "Body_create_upload_form_data_request": {
        "title": "Body_create_upload_form_data_request",
        "required": [
          "file_type",
          "some_prop_1",
        ],
        "type": "object",
        "properties": {
          "source_file": {
            "title": "Source File",
            "type": "string",
            "format": "binary"
          },
          "file_type": {
            "$ref": "#/components/schemas/FileType"
          },
          "some_prop_1": {
            "title": "Some Prop",
            "type": "string"
          },
        }
      },

(...)
      "FileType": {
        "title": "FileType",
        "enum": [
          "MY_FILE_TYPE_1",
          "MY_FILE_TYPE_2",
          "MY_FILE_TYPE_3",

        ],
        "type": "string",
        "description": "An enumeration."
      },
(...)
Generation Details

The command-line script to run the docker container with open-api generator

docker run --add-host=host.docker.internal:host-gateway --rm \
    --user $(id -u):$(id -g) \
    -v "${PWD}:/local" openapitools/openapi-generator-cli:v5.3.1 generate \
    -i http://host.docker.internal/api/v1/openapi.json \ # all containers are run in the same docker-compose network, hence the address 
    -g typescript-fetch \
    --additional-properties=typescriptThreePlus=true \
    -o /som/dir 

The output of the generated code:


export enum FileType {
    MyFileType1 = 'MY_FILE_TYPE_1',
    MyFileType2 = 'MY_FILE_TYPE_1',
    MyFileType3 = 'MY_FILE_TYPE_1',
}

if (requestParameters.fileType !== undefined) {
   formParams.append(
       'file_type', new Blob(
       [JSON.stringify(FileTypeToJSON(requestParameters.fileType))],
       { type: "application/json" }
      )
   );
}
Steps to reproduce

Create an appropriate data structure in your code so that it leads to the OpenAPI declaration output as shown above

Suggest a fix

Treat enum value as a primitive and allow it to be directly taken as an argument for formData in multipart/form-data requests.


 if (requestParameters.fileType !== undefined) {
    formParams.append('file_type', requestParameters.fileType as any);
}

fernard avatar Feb 15 '22 09:02 fernard

The same problem happens when I want to send an object as JSON to the backend. Without the SDK I would just formData.append('files'. JSON.stringify(data)). I don't see the point of having new Blob() there

adrianwix avatar Apr 07 '22 17:04 adrianwix

new Blob() makes "multerjs" fails also. Because it thinks the the json is a file and I didn't specified the field with the json to be a file inside the multer configuration

adrianwix avatar Apr 07 '22 18:04 adrianwix

Im having issues with this also. I was able to get it working for my needs with a workaround, but I would much rather this handle it properly and generate the client code cleanly. If anyone is curious, here is the workaround I used. It essentially unravels the Blob wrapper around the enum to get the string value in a middleware.

async function ResolveAssetTypeEnumMiddleware(context: RequestContext) {
  const { body } = context.init;

  if (body && body instanceof FormData) {
    const type = body.get("type");
    if (type instanceof File) {
      const jsonType = await type.text();
      const resolvedType = JSON.parse(jsonType);
      body.set("type", resolvedType);
    }
  }

  return context;
}

const assetsApi = new AssetsApi(tokenApiConfig).withPreMiddleware(
  ResolveAssetTypeEnumMiddleware
);

zmrl010 avatar Apr 12 '22 22:04 zmrl010

@zmrl010 Thanks!

BTW, if you have multiple types to handle:

async function resolveEnumTypes(context: RequestContext) {
  const { body } = context.init;

  if (body && body instanceof FormData) {
    for (const key of ["amazing_type1", "amazing_type2"]) {
      const type = body.get(key);

      if (type instanceof File) {
        const jsonType = await type.text();
        const resolvedType = JSON.parse(jsonType);
        body.set(key, resolvedType);
      }
    }
  }

  return context;
}

kigawas avatar Sep 20 '22 07:09 kigawas

Confirmed this with 6.2.1 have the same issue

Request Interface

export interface EmployeesPatchRequestParams {
  /** A unique integer value identifying this User. */
  id: number;
  /** Company ID for which employees should be filtered. */
  company_id?: number;
  position?: string | null;
  lang?: PatchedUserPatchRequestLang | null;
}

implementation

if (lang !== undefined) {
      localVarFormParams =
        (localVarFormParams.append(
          'lang',
          localVarUseForm ? new Blob([JSON.stringify(lang)], { type: 'application/json' }) : <any>lang
        ) as any) || localVarFormParams;
    }

swagger

"PatchedUserRequest": {
                "type": "object",
                "properties": {
                    "position": {
                        "type": "string",
                        "nullable": true,
                        "maxLength": 256
                    },
                    "lang": {
                        "nullable": true,
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/LangEnum"
                            },
                            {
                                "$ref": "#/components/schemas/BlankEnum"
                            },
                            {
                                "$ref": "#/components/schemas/NullEnum"
                            }
                        ]
                    },

fincha avatar Dec 01 '22 10:12 fincha

Same problem with multipart/form-data When i try to send files and json string as form-data key-value openapi-generator-cli add new Blob

openapi-generator-cli version 6.5.0

swagger.json

"requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "request": {
                    "$ref": "#/components/schemas/MySchema"
                  },
                  "files": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    }
                  }
                }
              },

generated code:

if (request !== undefined) { 
                localVarFormParams.append('request', new Blob([JSON.stringify(request)], { type: "application/json", }));
            }
                if (files) {
                files.forEach((element) => {
                    localVarFormParams.append('files', element as any);
                })
            }

Fix it please

Davidos533 avatar Apr 30 '23 13:04 Davidos533

Also hoping in. How it is currently done, requests fail on mobile devices using React-Native with fetch. Treating enum values as scalar values fixes it.

 if (requestParameters.type !== undefined) {
    formParams.append('type', requestParameters.type as any);
}

R3DST0RM avatar Oct 05 '23 08:10 R3DST0RM