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

Type and Enum duplicated when schema property enum as $ref

Open Ginxo opened this issue 11 months ago • 9 comments

openapi-typescript version

7.5.2

Node.js version

20.11.0

OS + version

Fedora Linux 41

Description

after executing the tool with --root-types --root-types-no-schema-prefix --enum flags (since types are required for not refactoring the whole application and Schema prefix avoided due to the same reason...) some of the enums are duplicated also as type aliases, which is producing an error at TS validation time.

I'm clicking My OpenAPI schema is valid and passes the [Redocly validator](https://redocly.com/docs/cli/commands/lint/) (npx @redocly/cli@latest lint) * because it is required, I have few issues but none of them related with the enums

It is mandatory for us to use --root-types-no-schema-prefix not to affect the whole application

Reproduction

npx openapi-typescript https://api.stage.openshift.com/api/clusters_mgmt/v1/openapi -o src/types/clusters_mgmt.v1/schema.ts --root-types --root-types-no-schema-prefix --enum

Expected result

Maybe different type alias name, see case for AWSInfrastructureAccessRoleState where enum is AWSInfrastructureAccessRoleState (AWS uppercase) and type AwsInfrastructureAccessRoleGrantState (Aws upper first)

Required

  • [X] My OpenAPI schema is valid and passes the Redocly validator (npx @redocly/cli@latest lint)

Extra

Ginxo avatar Jan 13 '25 11:01 Ginxo

@drwpow @htunnicliff @kerwanp any idea about how to workaround it?

Ginxo avatar Jan 15 '25 11:01 Ginxo

for now as workaround, I'm moving enums to enums.ts file and pointing every reference to it

export enum AWSInfrastructureAccessRoleGrantState {
  deleting = 'deleting',
  failed = 'failed',
  pending = 'pending',
  ready = 'ready',
  removed = 'removed',
}
...
...
export enum WildcardPolicy {
  WildcardsAllowed = 'WildcardsAllowed',
  WildcardsDisallowed = 'WildcardsDisallowed',
}

and generating the rest of the model without --enum flag

npx openapi-typescript https://api.stage.openshift.com/api/clusters_mgmt/v1/openapi -o src/types/clusters_mgmt.v1/schema.ts --root-types --root-types-no-schema-prefix

this way, whenever the issue is solved or a workaround exists the refactoring should be pretty straightforward

Ginxo avatar Jan 15 '25 14:01 Ginxo

so I think we came to a better conclusion by generating the model twice

  • one for Schema without enum and without prefix
  • one for enums with Schema prefix

like:

npx openapi-typescript ./openapi/clusters_mgmt.v1.json -o src/types/clusters_mgmt.v1/index.ts --root-types --root-types-no-schema-prefix
npx openapi-typescript ./openapi/clusters_mgmt.v1.json -o src/types/clusters_mgmt.v1/enums.ts --root-types --enum

Ginxo avatar Jan 16 '25 08:01 Ginxo

any news @drwpow @htunnicliff @kerwanp ? thanks

Ginxo avatar Jan 22 '25 08:01 Ginxo

@Ginxo your issue is likely valid! But we haven’t had time to review it yet. We review things when we are able to. And your issue does not have a schema we can reproduce with. Please refrain from pinging us directly, especially multiple times like you have.

drwpow avatar Jan 22 '25 15:01 drwpow

@Ginxo your issue is likely valid! But we haven’t had time to review it yete. We review things when we are able to. And your issue does not have a schema we can reproduce with. Please refrain from pinging us directly, especially multiple times like you have.

Thanks for replying... just to clarify this, the schema is public and you can reproduce the issue by executing

npx openapi-typescript https://api.stage.openshift.com/api/clusters_mgmt/v1/openapi -o src/types/clusters_mgmt.v1/schema.ts --root-types --root-types-no-schema-prefix --enum

I look forward to your input folks, thanks again!

Ginxo avatar Jan 22 '25 15:01 Ginxo

I'm running into this issue as well. A possible solution is to not export types for the enums. Is there a reason one would ever need the enum exported as both an enum and a type?

aaronmorgulis-slalom avatar Jan 28 '25 22:01 aaronmorgulis-slalom

@Ginxo I think your workaround to generate the types and enums in 2 separate files is very clever! However, I ran into an issue with my use case which is that the models in the types file internally reference the types rather than the enums (because the enums are in a separate file). So my consuming code tries to set an enum value on the model type which causes a type error. If that doesn't make sense, I can add an example that illustrates the issue.

aaronmorgulis-slalom avatar Jan 30 '25 19:01 aaronmorgulis-slalom

@Ginxo I think your workaround to generate the types and enums in 2 separate files is very clever! However, I ran into an issue with my use case which is that the models in the types file internally reference the types rather than the enums (because the enums are in a separate file). So my consuming code tries to set an enum value on the model type which causes a type error. If that doesn't make sense, I can add an example that illustrates the issue.

Hi @aaronmorgulis-slalom, thanks for your comment. We are facing the same issue by applying this workaround, so for now and since the cases are just a few for us (like two or three of them) we are using type assertion for those cases. It is not ideal but I can't find a better approach for this.

Like cred.status as BreakGlassCredentialStatus

Ginxo avatar Jan 31 '25 06:01 Ginxo

The problem is that an enum and a type is defined in the generated file with exact same name. Since the enums and types have a clear defined format we can remove the type definition with a script, which will ensure that the enum is used.

Script to remove the unnecessary types:

const fs = require('fs');
const path = require('path');

/**
 * Script to clean up .d.ts files by removing type definitions that have corresponding enum exports
 * Usage: node script.js <path-to-dts-file>
 */

function cleanDtsFile(filePath) {
  try {
    // Read the file
    const content = fs.readFileSync(filePath, 'utf8');
    const lines = content.split('\n');
    
    // Find all exported enum names
    const enumNames = new Set();
    const enumRegex = /^export\s+enum\s+(\w+)/;
    
    for (const line of lines) {
      const match = line.match(enumRegex);
      if (match) {
        enumNames.add(match[1]);
        console.log(`Found enum: ${match[1]}`);
      }
    }
    
    if (enumNames.size === 0) {
      console.log('No exported enums found in the file.');
      return;
    }
    
    // Filter out lines that are type definitions for the found enums
    const filteredLines = [];
    let removedCount = 0;
    
    for (const line of lines) {
      let shouldRemove = false;
      
      // Check if this line is a type definition for any of our enums
      for (const enumName of enumNames) {
        const typeRegex = new RegExp(`^export\\s+type\\s+${enumName}\\b`);
        if (typeRegex.test(line)) {
          console.log(`Removing type definition: ${line.trim()}`);
          shouldRemove = true;
          removedCount++;
          break;
        }
      }
      
      if (!shouldRemove) {
        filteredLines.push(line);
      }
    }
    
    // Write the cleaned content back to file
    const cleanedContent = filteredLines.join('\n');
    fs.writeFileSync(filePath, cleanedContent, 'utf8');
    
    console.log(`\nCleaning complete!`);
    console.log(`- Found ${enumNames.size} exported enum(s)`);
    console.log(`- Removed ${removedCount} corresponding type definition(s)`);
    console.log(`- File updated: ${filePath}`);
    
  } catch (error) {
    console.error('Error processing file:', error.message);
    process.exit(1);
  }
}

// Get file path from command line arguments
const filePath = process.argv[2];

if (!filePath) {
  console.error('Usage: node script.js <path-to-dts-file>');
  process.exit(1);
}

if (!fs.existsSync(filePath)) {
  console.error(`File not found: ${filePath}`);
  process.exit(1);
}

if (!filePath.endsWith('.d.ts')) {
  console.error('File must be a .d.ts file');
  process.exit(1);
}

console.log(`Processing file: ${filePath}\n`);
cleanDtsFile(filePath);

After that just create an npm script similar to mine to automate the process:

openapi-typescript http://localhost:3000/docs-json -o ./path/to/type-definition/openapi.d.ts --root-types --root-types-no-schema-prefix --enum & node path/to/cleanup-script.js /path/to/type-definition/openapi.d.ts

This method works with v7.8.0. Hopefully this is just a temporary solution.

EDIT: use .ts file extension instead of .d.ts if you want to use the enums as values

nobergg avatar Jun 17 '25 17:06 nobergg