reduxjs / redux-toolkit

The official, opinionated, batteries-included toolset for efficient Redux development
https://redux-toolkit.js.org
MIT License
10.55k stars 1.13k forks source link

Codegen - Enum integer values #1788

Open grumpyTofu opened 2 years ago

grumpyTofu commented 2 years ago

Running into an issue when I have an enum in my openapi schema.

"SpecialTypes": {
        "enum": [
          0,
          1,
          2
        ],
        "type": "integer",
        "format": "int32"
      },

Here is the stack trace

% yarn openapi
yarn run v1.22.10
$ esr openapi.config.ts && yarn gen-api && yarn gen-schema
Successfully updated openapi config
$ npx @rtk-incubator/rtk-query-codegen-openapi@0.5.1 --hooks ./openapi.config.json --baseQuery ./src/app/apiBaseQuery.ts:apiBaseQueryWithDeauth --file ./src/app/finsense-api.generated.ts
npx: installed 105 in 5.81s
TypeError: s.replace is not a function
    at escapeString (/Users/austinfelix/.npm/_npx/61405/lib/node_modules/@rtk-incubator/rtk-query-codegen-openapi/node_modules/typescript/lib/typescript.js:17825:18)
    at escapeNonAsciiString (/Users/austinfelix/.npm/_npx/61405/lib/node_modules/@rtk-incubator/rtk-query-codegen-openapi/node_modules/typescript/lib/typescript.js:17830:13)
    at Object.getLiteralText (/Users/austinfelix/.npm/_npx/61405/lib/node_modules/@rtk-incubator/rtk-query-codegen-openapi/node_modules/typescript/lib/typescript.js:14695:34)
    at getLiteralTextOfNode (/Users/austinfelix/.npm/_npx/61405/lib/node_modules/@rtk-incubator/rtk-query-codegen-openapi/node_modules/typescript/lib/typescript.js:111632:23)
    at emitLiteral (/Users/austinfelix/.npm/_npx/61405/lib/node_modules/@rtk-incubator/rtk-query-codegen-openapi/node_modules/typescript/lib/typescript.js:109021:24)
    at pipelineEmitWithHintWorker (/Users/austinfelix/.npm/_npx/61405/lib/node_modules/@rtk-incubator/rtk-query-codegen-openapi/node_modules/typescript/lib/typescript.js:108822:32)
    at pipelineEmitWithHint (/Users/austinfelix/.npm/_npx/61405/lib/node_modules/@rtk-incubator/rtk-query-codegen-openapi/node_modules/typescript/lib/typescript.js:108448:17)
    at pipelineEmitWithComments (/Users/austinfelix/.npm/_npx/61405/lib/node_modules/@rtk-incubator/rtk-query-codegen-openapi/node_modules/typescript/lib/typescript.js:112002:13)
    at pipelineEmit (/Users/austinfelix/.npm/_npx/61405/lib/node_modules/@rtk-incubator/rtk-query-codegen-openapi/node_modules/typescript/lib/typescript.js:108388:13)
    at emitExpression (/Users/austinfelix/.npm/_npx/61405/lib/node_modules/@rtk-incubator/rtk-query-codegen-openapi/node_modules/typescript/lib/typescript.js:108372:13)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

problem is resolved by manually changing the enum integers into strings like so:

"SpecialTypes": {
        "enum": [
          "0",
          "1",
          "2"
        ],
        "type": "integer",
        "format": "int32"
      },
floatrx commented 2 years ago
$ npx @rtk-query/codegen-openapi openapi-config.json

Generating ./src/store/testApi.ts
TypeError: s.replace is not a function
    at escapeString (/Users/floatrx/Projects/CORE/support-app/node_modules/@rtk-query/codegen-openapi/node_modules/typescript/lib/typescript.js:17825:18)
    at escapeNonAsciiString (/Users/floatrx/Projects/CORE/support-app/node_modules/@rtk-query/codegen-openapi/node_modules/typescript/lib/typescript.js:17830:13)
    at Object.getLiteralText (/Users/floatrx/Projects/CORE/support-app/node_modules/@rtk-query/codegen-openapi/node_modules/typescript/lib/typescript.js:14695:34)
    at getLiteralTextOfNode (/Users/floatrx/Projects/CORE/support-app/node_modules/@rtk-query/codegen-openapi/node_modules/typescript/lib/typescript.js:111651:23)
    at emitLiteral (/Users/floatrx/Projects/CORE/support-app/node_modules/@rtk-query/codegen-openapi/node_modules/typescript/lib/typescript.js:109040:24)
    at pipelineEmitWithHintWorker (/Users/floatrx/Projects/CORE/support-app/node_modules/@rtk-query/codegen-openapi/node_modules/typescript/lib/typescript.js:108841:32)
    at pipelineEmitWithHint (/Users/floatrx/Projects/CORE/support-app/node_modules/@rtk-query/codegen-openapi/node_modules/typescript/lib/typescript.js:108467:17)
    at pipelineEmitWithComments (/Users/floatrx/Projects/CORE/support-app/node_modules/@rtk-query/codegen-openapi/node_modules/typescript/lib/typescript.js:112021:13)
    at pipelineEmit (/Users/floatrx/Projects/CORE/support-app/node_modules/@rtk-query/codegen-openapi/node_modules/typescript/lib/typescript.js:108407:13)
    at emitExpression (/Users/floatrx/Projects/CORE/support-app/node_modules/@rtk-query/codegen-openapi/node_modules/typescript/lib/typescript.js:108391:13)

same issue -> same fix! πŸ˜€

floatrx commented 2 years ago

My temporary solution -> "openapi-download"

Simple util script (nodejs)

β”œβ”€β”€ openapi
β”‚Β Β  β”œβ”€β”€ config.ts
β”‚Β Β  β”œβ”€β”€ index.js
β”‚Β Β  └── schemas.json
β”œβ”€β”€ package.json

package.json


  "scripts": {
    "openapi": "node openapi",
  },

I used @rtk-incubator/rtk-query-codegen-openapi instead of @rtk-query/codegen-openapi -> coz:

npx @rtk-query/codegen-openapi openapi-config.ts
npx: installed 64 in 13.608s
Encountered a TypeScript configfile, but neither esbuild-runner nor ts-node are installed.

Downloader openapi/index.js

// * OpenAPI – Code generator

console.log(`\n* πŸ‘» RTKQ OpenAPI code generator v0.0.1`);
require('dotenv').config(); // load .env

const fs = require('fs');
const path = require('path');
const util = require('util');
const exec = util.promisify(require('child_process').exec);

// Config
const specsUrl = process.env.OPENAPI_URL;
const schemaFile = path.resolve(__dirname, 'schemas.json'); // rewrite mode
const openApiConfig = path.resolve(__dirname, 'config.ts');

console.log('With config', { specsUrl, schemaFile, openApiConfig });

// Step 1
const download = () =>
  new Promise(async (resolve, reject) => {
    console.log('\nStep 1. Downloading JSON from:', specsUrl);

    // Download latest openapi specs
    await exec(`curl ${specsUrl} -o ${schemaFile} --silent`, (error, stdout) => {
      if (error) {
        console.error('Failed. Error: ' + error);
        reject(error);
      }
      console.log('Downloaded');
      resolve(require(schemaFile));
    });
  });

// Step 2
const fixEnums = (openapi) => {
  console.log('Step 2. Fix enums... [1,2,3]->["1","2","3"]');

  if (!fs.existsSync(schemaFile)) {
    return console.error(`File ${schemaFile} not exists`);
  }

  if (!openapi.components.schemas) {
    console.error('Bad file!');
    return;
  }

  for (let scheme in openapi.components.schemas) {
    let _enum = openapi.components.schemas[scheme].enum;
    const isNumbersOnly = !!_enum?.every((v) => typeof v === 'number'); // all array items are numbers!
    if (isNumbersOnly) {
      openapi.components.schemas[scheme].enum = _enum.map((v) => String(v));
    }
  }
  return openapi;
};

// Step 3
const save = async (data) => {
  try {
    await fs.writeFileSync(schemaFile, JSON.stringify(data));
    console.log('Success');
  } catch (error) {
    console.error('File save error', error);
    console.error(error);
  }
};

// Code generation
// https://redux-toolkit.js.org/rtk-query/usage/code-generation
const generate = async () => {
  console.log(
    'Step 3. Generate endpoints\nCheck docs: https://redux-toolkit.js.org/rtk-query/usage/code-generation\nConfig:',
    openApiConfig,
  );
  await exec(`npx @rtk-incubator/rtk-query-codegen-openapi ${openApiConfig}`, (error, stdout) => {
    if (error) {
      console.error('Failed with error ' + error);
    }
    // console.log(stdout); <-- uncomment for debug
  });
};
// Actualize
download().then(fixEnums).then(save).then(generate);

config.ts

import * as fs from 'fs';
import { camelCase } from 'lodash'; // for camelCase
import { ConfigFile } from '@rtk-query/codegen-openapi';

// Paths
const path = require('path');
const basePath = path.resolve(__dirname, '../src/api'); // <-- destination path
const schemaFile = path.resolve(__dirname, 'schemas.json'); // <-- source openapi.json

!fs.existsSync(basePath) && fs.mkdirSync(basePath); // Fix: create folder if not exist

// Imports & relations
const apiFile = '@/store/emptyApi.ts';
const apiImport = 'emptyApi';

// Entities list (sync it manually)
const entities: string[] = [
  'Auth',
  'Events',
  'Profile',
  'Customers',
  'Orders',
  'Files',
  'Discounts',
];

// Generate files by endpoint pattern
// Returns object according to docs
// https://redux-toolkit.js.org/rtk-query/usage/code-generation#multiple-output-files
const outputFiles = entities.reduce((acc, name) => {
  const entityName = camelCase(name);
  const regExFilter = new RegExp(entityName, 'i');

  const fileName = `${basePath}/${entityName}.ts`; // name file

  return {
    ...acc,
    [fileName]: { filterEndpoints: [regExFilter] },
  };
}, {});

// console.log(outputFiles); <-- uncomment for debug

const config: ConfigFile = {
  schemaFile,
  apiFile,
  apiImport,
  hooks: true,
  outputFiles,
};

export default config;

ps; still waiting for an official fix... 🍻

gormonn commented 2 years ago

@floatrx To use it with @rtk-query/codegen-openapi : await exec(`node ./node_modules/@rtk-query/codegen-openapi/lib/bin/cli.js ${openApiConfig}`, (error, stdout) => {

lindapaiste commented 2 years ago

This is the code which is causing the issue. It's actually in the oazapfts package, which is a dependency of the RTK codegen, rather than in the RTK codegen itself.

 if (schema.enum) {
      // enum -> union of literal types
      const types = schema.enum.map((s) => {
        if (s === null) return cg.keywordType.null;
        if (typeof s === "boolean")
          return s
            ? factory.createLiteralTypeNode(
                ts.factory.createToken(ts.SyntaxKind.TrueKeyword)
              )
            : factory.createLiteralTypeNode(
                ts.factory.createToken(ts.SyntaxKind.FalseKeyword)
              );
        return factory.createLiteralTypeNode(factory.createStringLiteral(s));
      });
      return types.length > 1 ? factory.createUnionTypeNode(types) : types[0];
    }

There is a check for null and boolean avlues but not for number. So it calls factory.createStringLiteral(s) with a number and fails as this function requires a string. It seems pretty straight-forward to fix -- I'll see what I can do 🀞.

lindapaiste commented 2 years ago

It turns out that this has already been fixed in the oazapfts repo. That PR was approved and merged but they haven't put out a new release since then 😞

henkkasoft commented 2 years ago

Would be great to have a new release soon, because of this

GeorchW commented 8 months ago

I think this should already be fixed with the new version

iliapnmrv commented 3 months ago

@GeorchW no its not :( issue persists in v2.2.3

Getterac7 commented 3 months ago

@iliapnmrv

issue persists in v2.2.3

I'm depending on "@reduxjs/toolkit": "^1.9.3" and it works great for me; probably works in v2.2.3 as well but I haven't tried.

Using https://github.com/reduxjs/redux-toolkit/issues/1788#issuecomment-999086924 as a starting point...

  1. Remove the fixEnums function call on the last line: download().then(save).then(generate);
  2. Modify your config.ts/config.js file to use the enum type flag:
    const config = {
    ...
    useEnumType: true,
    }

And that's it! Re-run the script to generate your type definitions and it should have proper Typescript enums.