Skip to content

feature: [typescript-fetch] add model suffix support - #24873

Closed
WojciechZankowski wants to merge 3 commits into
OpenAPITools:masterfrom
WojciechZankowski:feature/add-model-suffix-support-in-typescript-fetch
Closed

feature: [typescript-fetch] add model suffix support#24873
WojciechZankowski wants to merge 3 commits into
OpenAPITools:masterfrom
WojciechZankowski:feature/add-model-suffix-support-in-typescript-fetch

Conversation

@WojciechZankowski

@WojciechZankowski WojciechZankowski commented Sep 4, 2026

Copy link
Copy Markdown

PR checklist

  • Read the contribution guidelines.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

Summary by cubic

Adds a modelSuffix config option to the typescript-fetch generator so generated model names and filenames can be appended with a custom suffix.

  • The suffix is applied to all model names, filenames, and references in the generated SDK.
  • The option only accepts alphanumeric characters and fails fast otherwise.
  • Adds a new sample generation config and regenerated model-suffixed output under samples/client/petstore/typescript-fetch/builds/model-suffix.

Written for commit 0785758. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 47 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java">

<violation number="1" location="modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java:567">
P3: The new suffix test only asserts the suffixed model files and their `export interface`/`export type` declarations, but never checks that references to these models were renamed consistently elsewhere. If the suffix renaming updated only the model filenames/declarations and not the imports in `apis/*.ts`, the `models/index.ts` barrel, or cross-model `import type { TestBResource } from './TestBResource'` statements, the output would not compile and this test would still pass. The sibling tests in this file (e.g. `testGeneratedFilenamesInKebabCaseWithAdditionalModelPrefix`) assert exactly that, e.g. `} from '../models/some-prefix-pet';`. Add an assertion on an API-side or a referencing model import (e.g. that `TestResponseResource.ts` imports `TestBResource` from `./TestBResource`) so the suffix feature is verified end-to-end.</violation>
</file>

<file name="samples/client/petstore/typescript-fetch/builds/model-suffix/README.md">

<violation number="1" location="samples/client/petstore/typescript-fetch/builds/model-suffix/README.md:21">
P2: The README example imports `import type { TestRequest } from '@openapitools/typescript-fetch-model-suffix';`, but no `TestRequest` symbol is exported from this package. The build was generated with `modelSuffix: Resource`, so every model (and any generated request/parameter interface) carries the `Resource` suffix. `src/models/index.ts` exports only `*Resource` types, and a repository-wide search finds no `TestRequest`. Following the README would therefore fail to compile. The root cause is `api_example.mustache`, which renders `import type { {{operationIdCamelCase}}Request }` without applying the model suffix (and does not gate on request-interface generation, which is disabled for this config).</violation>
</file>

<file name="samples/client/petstore/typescript-fetch/builds/model-suffix/src/runtime.ts">

<violation number="1" location="samples/client/petstore/typescript-fetch/builds/model-suffix/src/runtime.ts:179">
P2: When an `initOverrides` callback changes `headers`, this check still uses the original request headers, so JSON bodies are serialized according to stale content-type metadata. Inspect the effective `overriddenInit.headers` (including `Headers` and case-insensitive names) before deciding whether to `JSON.stringify` the body.</violation>
</file>

<file name="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java">

<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java:61">
P3: `CLASS_NAME_SUFFIX_PATTERN` is an immutable constant but is neither `static final` nor `final`, unlike the other compile-time constants in this class. Mark it `private static final String`.</violation>

<violation number="2" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java:358">
P2: The new `modelSuffix` option is functionally identical to the existing global `modelNameSuffix` option (CodegenConstants.MODEL_NAME_SUFFIX), which typescript-fetch already supports end to end: DefaultCodegen.processOpts reads `modelNameSuffix` into the same `modelNameSuffix` field (DefaultCodegen.java:437), and AbstractTypeScriptClientCodegen.toModelName appends it via addSuffix (AbstractTypeScriptClientCodegen.java:623). Both paths produce the same output, so `modelSuffix` adds a second, inconsistently-named (`modelSuffix` vs `modelNameSuffix`) way to do the same thing and silently overrides `modelNameSuffix` when both are provided. Consider reusing the existing `modelNameSuffix` option and only adding the missing validation there, rather than introducing a duplicate option.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Configuration,
DefaultApi,
} from '@openapitools/typescript-fetch-model-suffix';
import type { TestRequest } from '@openapitools/typescript-fetch-model-suffix';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The README example imports import type { TestRequest } from '@openapitools/typescript-fetch-model-suffix';, but no TestRequest symbol is exported from this package. The build was generated with modelSuffix: Resource, so every model (and any generated request/parameter interface) carries the Resource suffix. src/models/index.ts exports only *Resource types, and a repository-wide search finds no TestRequest. Following the README would therefore fail to compile. The root cause is api_example.mustache, which renders import type { {{operationIdCamelCase}}Request } without applying the model suffix (and does not gate on request-interface generation, which is disabled for this config).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/typescript-fetch/builds/model-suffix/README.md, line 21:

<comment>The README example imports `import type { TestRequest } from '@openapitools/typescript-fetch-model-suffix';`, but no `TestRequest` symbol is exported from this package. The build was generated with `modelSuffix: Resource`, so every model (and any generated request/parameter interface) carries the `Resource` suffix. `src/models/index.ts` exports only `*Resource` types, and a repository-wide search finds no `TestRequest`. Following the README would therefore fail to compile. The root cause is `api_example.mustache`, which renders `import type { {{operationIdCamelCase}}Request }` without applying the model suffix (and does not gate on request-interface generation, which is disabled for this config).</comment>

<file context>
@@ -0,0 +1,124 @@
+  Configuration,
+  DefaultApi,
+} from '@openapitools/typescript-fetch-model-suffix';
+import type { TestRequest } from '@openapitools/typescript-fetch-model-suffix';
+
+async function example() {
</file context>

|| (overriddenInit.body instanceof URLSearchParams)
|| isBlob(overriddenInit.body)) {
body = overriddenInit.body;
} else if (this.isJsonMime(headers['Content-Type'])) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When an initOverrides callback changes headers, this check still uses the original request headers, so JSON bodies are serialized according to stale content-type metadata. Inspect the effective overriddenInit.headers (including Headers and case-insensitive names) before deciding whether to JSON.stringify the body.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/typescript-fetch/builds/model-suffix/src/runtime.ts, line 179:

<comment>When an `initOverrides` callback changes `headers`, this check still uses the original request headers, so JSON bodies are serialized according to stale content-type metadata. Inspect the effective `overriddenInit.headers` (including `Headers` and case-insensitive names) before deciding whether to `JSON.stringify` the body.</comment>

<file context>
@@ -0,0 +1,505 @@
+            || (overriddenInit.body instanceof URLSearchParams)
+            || isBlob(overriddenInit.body)) {
+          body = overriddenInit.body;
+        } else if (this.isJsonMime(headers['Content-Type'])) {
+          body = JSON.stringify(overriddenInit.body);
+        } else {
</file context>

}

if (additionalProperties.containsKey(MODEL_SUFFIX)) {
this.modelNameSuffix = additionalProperties.get(MODEL_SUFFIX).toString();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The new modelSuffix option is functionally identical to the existing global modelNameSuffix option (CodegenConstants.MODEL_NAME_SUFFIX), which typescript-fetch already supports end to end: DefaultCodegen.processOpts reads modelNameSuffix into the same modelNameSuffix field (DefaultCodegen.java:437), and AbstractTypeScriptClientCodegen.toModelName appends it via addSuffix (AbstractTypeScriptClientCodegen.java:623). Both paths produce the same output, so modelSuffix adds a second, inconsistently-named (modelSuffix vs modelNameSuffix) way to do the same thing and silently overrides modelNameSuffix when both are provided. Consider reusing the existing modelNameSuffix option and only adding the missing validation there, rather than introducing a duplicate option.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java, line 358:

<comment>The new `modelSuffix` option is functionally identical to the existing global `modelNameSuffix` option (CodegenConstants.MODEL_NAME_SUFFIX), which typescript-fetch already supports end to end: DefaultCodegen.processOpts reads `modelNameSuffix` into the same `modelNameSuffix` field (DefaultCodegen.java:437), and AbstractTypeScriptClientCodegen.toModelName appends it via addSuffix (AbstractTypeScriptClientCodegen.java:623). Both paths produce the same output, so `modelSuffix` adds a second, inconsistently-named (`modelSuffix` vs `modelNameSuffix`) way to do the same thing and silently overrides `modelNameSuffix` when both are provided. Consider reusing the existing `modelNameSuffix` option and only adding the missing validation there, rather than introducing a duplicate option.</comment>

<file context>
@@ -335,6 +354,11 @@ public void processOpts() {
         }
 
+        if (additionalProperties.containsKey(MODEL_SUFFIX)) {
+            this.modelNameSuffix = additionalProperties.get(MODEL_SUFFIX).toString();
+            validateClassSuffixArgument("Model", modelNameSuffix);
+        }
</file context>


File output = generate(properties, "src/test/resources/3_0/typescript-fetch/oneOf.yaml");

Path modelWithSuffix = Paths.get(output + "/models/TestBResource.ts");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The new suffix test only asserts the suffixed model files and their export interface/export type declarations, but never checks that references to these models were renamed consistently elsewhere. If the suffix renaming updated only the model filenames/declarations and not the imports in apis/*.ts, the models/index.ts barrel, or cross-model import type { TestBResource } from './TestBResource' statements, the output would not compile and this test would still pass. The sibling tests in this file (e.g. testGeneratedFilenamesInKebabCaseWithAdditionalModelPrefix) assert exactly that, e.g. } from '../models/some-prefix-pet';. Add an assertion on an API-side or a referencing model import (e.g. that TestResponseResource.ts imports TestBResource from ./TestBResource) so the suffix feature is verified end-to-end.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java, line 567:

<comment>The new suffix test only asserts the suffixed model files and their `export interface`/`export type` declarations, but never checks that references to these models were renamed consistently elsewhere. If the suffix renaming updated only the model filenames/declarations and not the imports in `apis/*.ts`, the `models/index.ts` barrel, or cross-model `import type { TestBResource } from './TestBResource'` statements, the output would not compile and this test would still pass. The sibling tests in this file (e.g. `testGeneratedFilenamesInKebabCaseWithAdditionalModelPrefix`) assert exactly that, e.g. `} from '../models/some-prefix-pet';`. Add an assertion on an API-side or a referencing model import (e.g. that `TestResponseResource.ts` imports `TestBResource` from `./TestBResource`) so the suffix feature is verified end-to-end.</comment>

<file context>
@@ -561,6 +557,22 @@ public void containsESMTSConfigFileInCaseOfES6AndNPM() {
+
+        File output = generate(properties, "src/test/resources/3_0/typescript-fetch/oneOf.yaml");
+
+        Path modelWithSuffix = Paths.get(output + "/models/TestBResource.ts");
+        TestUtils.assertFileExists(modelWithSuffix);
+        TestUtils.assertFileContains(modelWithSuffix, "export interface TestBResource");
</file context>

public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodegen {
private final Logger LOGGER = LoggerFactory.getLogger(TypeScriptFetchClientCodegen.class);

private static String CLASS_NAME_SUFFIX_PATTERN = "^[a-zA-Z0-9]*$";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: CLASS_NAME_SUFFIX_PATTERN is an immutable constant but is neither static final nor final, unlike the other compile-time constants in this class. Mark it private static final String.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java, line 61:

<comment>`CLASS_NAME_SUFFIX_PATTERN` is an immutable constant but is neither `static final` nor `final`, unlike the other compile-time constants in this class. Mark it `private static final String`.</comment>

<file context>
@@ -58,6 +58,8 @@
 public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodegen {
     private final Logger LOGGER = LoggerFactory.getLogger(TypeScriptFetchClientCodegen.class);
 
+    private static String CLASS_NAME_SUFFIX_PATTERN = "^[a-zA-Z0-9]*$";
+
     public static final String NPM_REPOSITORY = "npmRepository";
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant