openapistack / openapi-client-axios

JavaScript client library for consuming OpenAPI-enabled APIs with axios
https://openapistack.co
MIT License
535 stars 67 forks source link

Uncaught error for missing operation #150

Closed johnbeech closed 1 year ago

johnbeech commented 1 year ago

Uncaught error for missing operation.

Run command:

typegen tests/post-deployment/downloaded-appStore-openapi.json

Direct error for my input:

/workspace/src/project/node_modules/openapi-client-axios-typegen/typegen.js:80
    key = key.replace(/\/(.)/g, function (_match, p1) {
              ^

TypeError: Cannot read properties of undefined (reading 'replace')
    at convertKeyToTypeName (/workspace/src/project/node_modules/openapi-client-axios-typegen/typegen.js:80:15)
    at generateMethodForOperation (/workspace/src/project/node_modules/openapi-client-axios-typegen/typegen.js:172:33)
    at /workspace/src/project/node_modules/openapi-client-axios-typegen/typegen.js:218:16
    at Array.map (<anonymous>)
    at generateOperationMethodTypings (/workspace/src/project/node_modules/openapi-client-axios-typegen/typegen.js:217:39)
    at /workspace/src/project/node_modules/openapi-client-axios-typegen/typegen.js:152:40
    at step (/workspace/src/project/node_modules/openapi-client-axios-typegen/typegen.js:52:23)
    at Object.next (/workspace/src/project/node_modules/openapi-client-axios-typegen/typegen.js:33:53)
    at fulfilled (/workspace/src/project/node_modules/openapi-client-axios-typegen/typegen.js:24:58)

Context: trying to generate a typescript definition for a generated Amazon API Gateway Open API doc instance (using GetExport).

node_modules/openapi-client-axios-typegen/typegen.js:80
    key = key.replace(/\/(.)/g, function (_match, p1) {
              ^
TypeError: Cannot read properties of undefined (reading 'replace')
function convertKeyToTypeName(key) {
    key = key.replace(/\/(.)/g, function (_match, p1) {
        return p1.toUpperCase();
    });
    return key
        .replace(/}/g, '')
        .replace(/{/g, '$')
        .replace(/^\//, '')
        .replace(/[^0-9A-Za-z_$]+/g, '_');
}

Stepping up, methodName / operation are empty:

function generateMethodForOperation(methodName, operation, exportTypes) {
    var operationId = operation.operationId, summary = operation.summary, description = operation.description;
    // parameters arg
    console.log('Generating for:', { methodName, operationId })
    var normalizedOperationId = convertKeyToTypeName(operationId);
Generating for: { methodName: 'getConfigSchema', operationId: 'getConfigSchema' }
Generating for: { methodName: 'putConfigSchema', operationId: 'putConfigSchema' }
Generating for: { methodName: undefined, operationId: undefined }

Stepping up again, logged out operations:

function generateOperationMethodTypings(api, exportTypes, opts) {
    var operations = api.getOperations();
    console.log('Operations:', { operations })
    var operationTypings = operations.map(function (op) {
        return generateMethodForOperation(opts.transformOperationName(op.operationId), op, exportTypes);
    });
...
Operations: {
  operations: [
    {
      operationId: 'getConfigSchema',
      parameters: [Array],
      responses: [Object],
      security: [Array],
      path: '/apps/{appId}/versions/{appVersion}/config-schema',
      method: 'get'
    },
    {
      operationId: 'putConfigSchema',
      parameters: [Array],
      requestBody: [Object],
      responses: [Object],
      security: [Array],
      path: '/apps/{appId}/versions/{appVersion}/config-schema',
      method: 'put'
    },
    {
      parameters: [Array],
      responses: [Object],
      path: '/apps/{appId}/versions/{appVersion}/config-schema',
      method: 'options',
      security: undefined
    },
    {
      operationId: 'getStatus',
      responses: [Object],
      security: [Array],
      path: '/status',
      method: 'get'
    },
    {
      responses: [Object],
      path: '/status',
      method: 'options',
      security: undefined
    },
    {
      operationId: 'listApps',
      responses: [Object],
      security: [Array],
      path: '/apps',
      method: 'get'
    },
    {
      operationId: 'registerApp',
      responses: [Object],
      security: [Array],
      path: '/apps',
      method: 'post'
    },
    {
      responses: [Object],
      path: '/apps',
      method: 'options',
      security: undefined
    },
    {
      parameters: [Array],
      responses: [Object],
      path: '/apps/{appId}',
      method: 'options',
      security: undefined
    },
    {
      operationId: 'listAppVersions',
      parameters: [Array],
      responses: [Object],
      security: [Array],
      path: '/apps/{appId}/versions',
      method: 'get'
    },
    {
      parameters: [Array],
      responses: [Object],
      path: '/apps/{appId}/versions',
      method: 'options',
      security: undefined
    },
    {
      operationId: 'getAppVersionDetails',
      parameters: [Array],
      responses: [Object],
      security: [Array],
      path: '/apps/{appId}/versions/{appVersion}',
      method: 'get'
    },
    {
      parameters: [Array],
      responses: [Object],
      path: '/apps/{appId}/versions/{appVersion}',
      method: 'options',
      security: undefined
    },
    {
      operationId: 'getOpenAPISpec',
      responses: [Object],
      security: [Array],
      path: '/openapi',
      method: 'get'
    },
    {
      responses: [Object],
      path: '/openapi',
      method: 'options',
      security: undefined
    },
    {
      responses: [Object],
      path: '/',
      method: 'options',
      security: undefined
    }
  ]

It appears the one it fails on is the CORS options request put, and get blocks processed fine:

    {
      parameters: [Array],
      responses: [Object],
      path: '/apps/{appId}/versions/{appVersion}/config-schema',
      method: 'options',
      security: undefined
    }

Partial JSON to help reproduce the issue:

 {
  "paths": {
    "/apps/{appId}/versions/{appVersion}/config-schema": {
      "get": {
        "operationId": "getConfigSchema",
        "parameters": [
          {
            "name": "appId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "appVersion",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "200 response",
            "headers": {
              "Access-Control-Allow-Origin": {
                "schema": {
                  "type": "string"
                }
              },
              "Access-Control-Allow-Credentials": {
                "schema": {
                  "type": "string"
                }
              },
              "Content-Type": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StubModel"
                }
              }
            }
          }
        },
        "security": [
          {
            "abc123": []
          }
        ]
      },
      "put": {
        "operationId": "putConfigSchema",
        "parameters": [
          {
            "name": "appId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "appVersion",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StubModel"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "200 response",
            "headers": {
              "Access-Control-Allow-Origin": {
                "schema": {
                  "type": "string"
                }
              },
              "Access-Control-Allow-Credentials": {
                "schema": {
                  "type": "string"
                }
              },
              "Content-Type": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StubModel"
                }
              }
            }
          }
        },
        "security": [
          {
            "abc123": []
          }
        ]
      },
      "options": {
        "parameters": [
          {
            "name": "appId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "appVersion",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "204 response",
            "headers": {
              "Access-Control-Allow-Origin": {
                "schema": {
                  "type": "string"
                }
              },
              "Access-Control-Allow-Methods": {
                "schema": {
                  "type": "string"
                }
              },
              "Vary": {
                "schema": {
                  "type": "string"
                }
              },
              "Access-Control-Allow-Headers": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {}
          }
        }
      }
    }
  }
}

I think I can fix the error locally by ignoring operations that don't have the necessary fields.

johnbeech commented 1 year ago

Update, I fixed the error locally by modifying typegen.js ignoring any operation that was missing operation.operationId - by filtering those records from the output.

Result samples:

mport type {
  OpenAPIClient,
  Parameters,
  UnknownParamsObject,
  OperationResponse,
  AxiosRequestConfig,
} from 'openapi-client-axios'; 

declare namespace Components {
    namespace Schemas {
        /**
         * App Version
         */
        export interface AppVersionModel {
            /**
             * The display name used by the App Store for descriptive purposes, e.g. "Dummy App"
             */
            name: string; // ^.{4,64}$
            /**
             * The appId used by the App Store to store multiple versions of the same app, e.g. "dummy-app"
             */
            id: string; // ^[a-z][a-z\d-]+$
            /**
             * Type of release version; one of dev, prerelease, or release
             */
            type: "dev" | "prerelease" | "release";
            /**
             * Semver version string specifying used to index and install this version
             */
            version: string; // ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
            /**
             * Relative path to find more information
             */
            uri: string;
            /**
             * Timestamp specifying when the version was published: e.g. 2023-03-01T00:00:00Z
             */
            timestamp: string; // ^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})Z$
        }
...

And:

export interface OperationMethods {
  /**
   * getConfigSchema
   */
  'getConfigSchema'(
    parameters?: Parameters<Paths.GetConfigSchema.PathParameters> | null,
    data?: any,
    config?: AxiosRequestConfig  
  ): OperationResponse<Paths.GetConfigSchema.Responses.$200>
  /**
   * putConfigSchema
   */
  'putConfigSchema'(
    parameters?: Parameters<Paths.PutConfigSchema.PathParameters> | null,
    data?: Paths.PutConfigSchema.RequestBody,
    config?: AxiosRequestConfig  
  ): OperationResponse<Paths.PutConfigSchema.Responses.$200>
  /**
   * getStatus
   */
  'getStatus'(
    parameters?: Parameters<UnknownParamsObject> | null,
    data?: any,
    config?: AxiosRequestConfig  
  ): OperationResponse<Paths.GetStatus.Responses.$200>
...

etc.

anttiviljami commented 1 year ago

Hi @johnbeech! Would you care submitting a PR with your changes? 🙏

johnbeech commented 1 year ago

Created https://github.com/anttiviljami/openapi-client-axios/pull/152 for review! 🌻

anttiviljami commented 1 year ago

Merged and released part of openapi-client-axios-typegen@7.2.0