Skip to content

parseKey is not a function #1146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
filipw01 opened this issue Mar 27, 2025 · 2 comments
Open

parseKey is not a function #1146

filipw01 opened this issue Mar 27, 2025 · 2 comments
Labels
bug Something isn't working

Comments

@filipw01
Copy link

After updating from 13.0.3 to 13.0.28 I'm getting this error when running generateApi from swagger-typescript-api

undefined:29
    const discriminatorKey = parseKey(discriminatorValue)
                             ^

TypeError: parseKey is not a function
    at interfaceTemplate (eval at compile (file:///Users/filip/projects/chilipiper/frontend/node_modules/eta/dist/eta.module.mjs:520:12), <anonymous>:29:30)
    at eval (eval at compile (file:///Users/filip/projects/chilipiper/frontend/node_modules/eta/dist/eta.module.mjs:520:12), <anonymous>:73:83)
    at Array.forEach (<anonymous>)
    at eval (eval at compile (file:///Users/filip/projects/chilipiper/frontend/node_modules/eta/dist/eta.module.mjs:520:12), <anonymous>:69:12)
    at Module.render (file:///Users/filip/projects/chilipiper/frontend/node_modules/eta/dist/eta.module.mjs:874:42)
    at Ie.renderTemplate (file:///Users/filip/projects/chilipiper/frontend/node_modules/swagger-typescript-api/dist/chunk-5FV4QLUU.js:24:10223)
    at je.createMultipleFileInfos (file:///Users/filip/projects/chilipiper/frontend/node_modules/swagger-typescript-api/dist/chunk-5FV4QLUU.js:40:2286)
    at async je.generateOutputFiles (file:///Users/filip/projects/chilipiper/frontend/node_modules/swagger-typescript-api/dist/chunk-5FV4QLUU.js:40:1096)
    at async je.start (file:///Users/filip/projects/chilipiper/frontend/node_modules/swagger-typescript-api/dist/chunk-5FV4QLUU.js:39:3903)
    at async vi (file:///Users/filip/projects/chilipiper/frontend/node_modules/swagger-typescript-api/dist/chunk-5FV4QLUU.js:41:4125)
@smorimoto smorimoto added the bug Something isn't working label Mar 27, 2025
@smorimoto
Copy link
Collaborator

Are you using a custom template?

@filipw01
Copy link
Author

filipw01 commented Mar 28, 2025

Yes, that's how I call it

generateApi({
    name: file.name,
    input: path.resolve(file.path),
    output: path.resolve(process.cwd(), getPath(file.name)),
    modular: true,
    silent: true,
    templates: path.resolve(process.cwd(), './templates'),
    unwrapResponseData: true,
    cleanOutput: true,
    codeGenConstructs: constructs => {
      return {
        ...constructs,
        Keyword: {
          ...constructs.Keyword,
          File: 'Record<string, any>',
          csv: 'string',
        },
      }
    },
    hooks: {
      onCreateRoute: routeData => {
        if (
          routeData.request.method === 'get' &&
          'payload' in routeData.request &&
          routeData.request.payload
        ) {
          console.warn(
            `Removing endpoint: ${routeData.raw.route} | Reason: GET endpoint with body is not supported by fetch`
          )
          return false
        }
        return routeData
      },
      onFormatTypeName: typeName => {
        if (typeName === 'Record') {
          return 'RecordType'
        }
      },
    },
  })

The templates are

api.ejs

<%
const { utils, route, config, modelTypes } = it;
const { _, pascalCase, require } = utils;
const apiClassName = pascalCase(route.moduleName);
const routes = route.routes;
const dataContracts = _.map(modelTypes, "name");
%>

import { HttpClient, FullRequestParams, RequestParams, ContentType, HttpResponse, QueryParamsType } from "./<%~ config.fileNames.httpClient %>";
<% if (dataContracts.length) { %>
import { <%~ dataContracts.join(", ") %> } from "./<%~ config.fileNames.dataContracts %>"
<% } %>

export class <%= apiClassName %><SecurityDataType = unknown> {
  // See templates/api.ejs we need this in order to send as parameter to request call
  serviceName = "<%~ config.fileName %>"
  /**
   * @deprecated Internal use only.
   */
  httpClient: HttpClient<SecurityDataType>

  constructor(config?: ConstructorParameters<typeof HttpClient<SecurityDataType>>[0]) {
    this.httpClient = new HttpClient(config)
  }

  /**
   * @deprecated Internal use only.
   * See src/http-client we need this so we can send service name to http-client so it knows
   * which service is calling it
   */
  request = <T = any, E = any>(params: FullRequestParams): Promise<T> => {
    return this.httpClient.clientRequest<T, E>({ ...params, serviceName: this.serviceName})
  }

  /**
   * @deprecated Internal use only.
   * See src/http-client we need this so we can send service name to http-client so it knows
   * which service is calling it
   */
   getServicePath = <Q extends QueryParamsType>(path: string, query?: Q): string => {
    const servicePath = this.httpClient.clientGetPath({ path, serviceName: this.serviceName });
  
    if (!query) return servicePath;
  
    const queryParams = this.httpClient.encodeQueryParams(query);
    return `${servicePath}?${queryParams}`;
  };

  <% routes.forEach((route) => { %>
      <%~ includeFile('./procedure-call.ejs', { ...it, route }) %>
      <%~ includeFile('./procedure-call-path.ejs', { ...it, route }) %>
  <% }) %>
}

data-contracts.ejs

<%
const { modelTypes, utils, config } = it;
const { formatDescription, require, _, Ts } = utils;
const camelCase = require('camelcase')
const parseKey = require('../utils/parseEnumKeys.cjs')
// Replaces enums with typescript map + union type
const enumTemplate = (contract) => {
  return `
  export const ${contract.name} = {
    ${contract.$content.map(content => `
      /**
        * @deprecated Discriminator consts are deprecated. Please use raw string instead
      */
      ${content.key}: ${content.value}`).join(',\n    ')}
  } as const
  export type ${contract.name} = ${contract.$content.map(content => content.value).join(' | ')};\n\n`;
}
const interfaceTemplate = (contract) => {
  // x-discriminator values comes from injectDiscriminatorConstant
  // defined under plugins/plugins.js
  if (contract['x-discriminator-parent']) {
    const discriminatedInterface = contract['x-discriminator-parent']
    const discriminatorProperty = contract['x-discriminator-property']
    const discriminatorValue = contract['x-discriminator-value']
    const discriminatorName = `${discriminatedInterface}Discriminator`
    const discriminatorKey = parseKey(discriminatorValue)
    const discriminatorAccess = `${camelCase(discriminatorName, {pascalCase: true, preserveConsecutiveUppercase: true})}.${discriminatorKey}`
    const oldDiscriminatorPiece = `${discriminatorProperty}: "${discriminatorValue}"`
    const newDiscriminatorPiece = `/** @see {@link ${discriminatorAccess}} */
      ${discriminatorProperty}: typeof ${discriminatorAccess}\n\n`
    
    // Replaces string with discriminator enum usage and add @see tsdoc
    const newContent = contract.content.replace(
      oldDiscriminatorPiece,
      newDiscriminatorPiece
    )
    return `/** @see {@link ${discriminatorAccess}} */
    export interface ${contract.name} {\r\n${newContent}};\n\n`;
  }
  return `export interface ${contract.name} {\r\n${contract.content}};\n\n`;
}
const typeTemplate = (contract) => {
  if (contract.discriminator) {
    const discriminatorName = `${contract.name}Discriminator`
    const content = Object.keys(contract.discriminator.mapping)
      .map(mapping => ({ key: parseKey(mapping), value: `'${mapping}'` }))
    // For each union schema containing a discriminator, creates the discriminator enum
    return `${dataContractTemplates.enum({ name: discriminatorName, $content: content })}
    /** @see {@link ${discriminatorName}} */
    export type ${contract.name} = ${contract.content};\n\n`;
  }
  return `export type ${contract.name} = ${contract.content};\n\n`;
}
const dataContractTemplates = {
  enum: enumTemplate,
  interface: interfaceTemplate,
  type: typeTemplate,
}
%>

<% if (config.internalTemplateOptions.addUtilRequiredKeysType) { %>
type <%~ config.Ts.CodeGenKeyword.UtilRequiredKeys %><T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>
<% } %>

<% modelTypes.forEach((contract) => { %>
  <%~ includeFile('@base/data-contract-jsdoc.ejs', { ...it, data: { ...contract, ...contract.typeData } }) %>
  <%~ (dataContractTemplates[contract.typeIdentifier] || dataContractTemplates.type)(contract) %>
<% }) %>

procedure-call-path.ejs

<%
const { utils, route, config } = it;
const { specificArgNameResolver } = route;
const { _, getInlineParseContent } = utils;
const { parameters, path, query, requestParams } = route.request;
const { type } = route.response;
const { RESERVED_REQ_PARAMS_ARG_NAMES } = config.constants;
const queryName = (query && query.name) || "query";
const pathParams = _.values(parameters);
const pathParamsNames = _.map(pathParams, "name");

const requestConfigParam = {
    name: specificArgNameResolver.resolve(RESERVED_REQ_PARAMS_ARG_NAMES),
    optional: true,
    type: "RequestParams",
    defaultValue: "{}",
}

const argToTmpl = ({ name, optional, type, defaultValue }) => `${name}${!defaultValue && optional ? '?' : ''}: ${type}${defaultValue ? ` = ${defaultValue}` : ''}`;

const rawWrapperArgs = _
    .compact([
        ...pathParams,
        query,
    ])

const wrapperArgs = _
    // Sort by optionality
    .sortBy(rawWrapperArgs, [o => o.optional])
    .map(argToTmpl)
    .join(', ')

let getServicePathArgs = `\`${path}\``

if (query) {
    getServicePathArgs = `${getServicePathArgs}, query`
}
%>

/**
 * @description Returns string containing request path for <%~ route.routeName.usage %>
 */
<%~ route.routeName.usage %>PathGetter = (<%~ wrapperArgs %>) => this.getServicePath(<%~ getServicePathArgs %>)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants