Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions bin/configs/typescript-fetch-model-suffix.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
generatorName: typescript-fetch
outputDir: samples/client/petstore/typescript-fetch/builds/model-suffix
inputSpec: modules/openapi-generator/src/test/resources/3_0/typescript-fetch/oneOf.yaml
templateDir: modules/openapi-generator/src/main/resources/typescript-fetch
additionalProperties:
modelSuffix: Resource
npmName: '@openapitools/typescript-fetch-model-suffix'
npmVersion: 1.0.0
snapshot: false
1 change: 1 addition & 0 deletions docs/generators/typescript-fetch.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|legacyDiscriminatorBehavior|Set to false for generators with better support for discriminators. (Python, Java, Go, PowerShell, C# have this enabled by default).|<dl><dt>**true**</dt><dd>The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.</dd><dt>**false**</dt><dd>The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.</dd></dl>|true|
|licenseName|The name of the license| |null|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |camelCase|
|modelSuffix|The suffix of the generated model.| |null|
|npmName|The name under which you want to publish generated npm package. Required to generate a full package| |null|
|npmRepository|Use this property to set an url your private npmRepo in the package.json| |null|
|npmVersion|The version of your npm package. If not provided, using the version from the OpenAPI specification file.| |1.0.0|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]*$";

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>


public static final String NPM_REPOSITORY = "npmRepository";
public static final String WITH_INTERFACES = "withInterfaces";
public static final String USE_SINGLE_REQUEST_PARAMETER = "useSingleRequestParameter";
Expand All @@ -71,6 +73,7 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege
public static final String STRING_ENUMS_DESC = "Generate string enums instead of objects for enum values.";
public static final String IMPORT_FILE_EXTENSION_SWITCH = "importFileExtension";
public static final String IMPORT_FILE_EXTENSION_SWITCH_DESC = "File extension to use with relative imports. Set it to '.js' or '.mjs' when using [ESM](https://nodejs.org/api/esm.html).";
public static final String MODEL_SUFFIX = "modelSuffix";
public static final String FILE_NAMING = "fileNaming";
public static final String KEBAB_CASE = "kebab-case";
public static final String CAMEL_CASE = "camelCase";
Expand Down Expand Up @@ -159,6 +162,7 @@ public TypeScriptFetchClientCodegen() {
this.cliOptions.add(new CliOption(SAGAS_AND_RECORDS, "Setting this property to true will generate additional files for use with redux-saga and immutablejs.", SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
this.cliOptions.add(new CliOption(STRING_ENUMS, STRING_ENUMS_DESC, SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
this.cliOptions.add(new CliOption(IMPORT_FILE_EXTENSION_SWITCH, IMPORT_FILE_EXTENSION_SWITCH_DESC).defaultValue(""));
this.cliOptions.add(new CliOption(MODEL_SUFFIX, "The suffix of the generated model."));
this.cliOptions.add(new CliOption(FILE_NAMING, "Naming convention for the output files: 'PascalCase', 'camelCase', 'kebab-case'.").defaultValue(this.fileNaming));
this.cliOptions.add(new CliOption(USE_SQUARE_BRACKETS_IN_ARRAY_NAMES, "Setting this property to true will add brackets to array attribute names, e.g. my_values[].", SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
this.cliOptions.add(new CliOption(VALIDATION_ATTRIBUTES, "Setting this property to true will generate the validation attributes of model properties.", SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
Expand Down Expand Up @@ -230,6 +234,21 @@ public void setStringEnums(Boolean stringEnums) {
this.stringEnums = stringEnums;
}

/**
* Validates that the given string value only contains alpha numeric characters.
* Throws an IllegalArgumentException, if the string contains any other characters.
*
* @param argument The name of the argument being validated. This is only used for displaying an error message.
* @param value The value that is being validated.
*/
private void validateClassSuffixArgument(String argument, String value) {
if (!value.matches(CLASS_NAME_SUFFIX_PATTERN)) {
throw new IllegalArgumentException(
String.format(Locale.ROOT, "%s class suffix only allows alphanumeric characters.", argument)
);
}
}

/**
* Set the file naming type.
*
Expand Down Expand Up @@ -335,6 +354,11 @@ public void processOpts() {
additionalProperties.put("stringEnums", this.stringEnums);
}

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>

validateClassSuffixArgument("Model", modelNameSuffix);
}

if (additionalProperties.containsKey(FILE_NAMING)) {
this.setFileNaming(additionalProperties.get(FILE_NAMING).toString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
import io.swagger.v3.oas.models.media.MapSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import java.util.Collections;
import java.util.Locale;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.config.CodegenConfigurator;
Expand All @@ -25,9 +22,8 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;

Expand Down Expand Up @@ -561,6 +557,22 @@ public void containsESMTSConfigFileInCaseOfES6AndNPM() {
assertThat(codegen.supportingFiles()).contains(new SupportingFile("tsconfig.esm.mustache", "", "tsconfig.esm.json"));
}

@Test(description = "Verify model suffix is added to model name and model filename")
public void testModelSuffixGeneration() throws IOException {
Map<String, Object> properties = new HashMap<>();
properties.put(TypeScriptFetchClientCodegen.MODEL_SUFFIX, "Resource");

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>

TestUtils.assertFileExists(modelWithSuffix);
TestUtils.assertFileContains(modelWithSuffix, "export interface TestBResource");

Path discriminatorModelWithSuffix = Paths.get(output + "/models/TestDiscriminatorResponseResource.ts");
TestUtils.assertFileExists(discriminatorModelWithSuffix);
TestUtils.assertFileContains(discriminatorModelWithSuffix, "export type TestDiscriminatorResponseResource");
}

@Test(description = "Verify file name formatting from model name in PascalCase")
public void testModelFileNameInPascalCase() {
final TypeScriptFetchClientCodegen codegen = new TypeScriptFetchClientCodegen();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
wwwroot/*.js
node_modules
typings
dist
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator

# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.

# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs

# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux

# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux

# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.gitignore
.npmignore
.openapi-generator-ignore
README.md
docs/DashedOptionOneResource.md
docs/DashedOptionTwoResource.md
docs/DefaultApi.md
docs/NumericSingletonEnumModelResource.md
docs/OptionOneResource.md
docs/OptionTwoResource.md
docs/SnakeOptionOneResource.md
docs/SnakeOptionTwoResource.md
docs/TestAResource.md
docs/TestArrayResponseResource.md
docs/TestBResource.md
docs/TestDashedDiscriminatorResponseResource.md
docs/TestDiscriminatorResponseResource.md
docs/TestResponseResource.md
docs/TestSnakeCaseDiscriminatorResponseResource.md
package.json
src/apis/DefaultApi.ts
src/apis/index.ts
src/index.ts
src/models/DashedOptionOneResource.ts
src/models/DashedOptionTwoResource.ts
src/models/NumericSingletonEnumModelResource.ts
src/models/OptionOneResource.ts
src/models/OptionTwoResource.ts
src/models/SnakeOptionOneResource.ts
src/models/SnakeOptionTwoResource.ts
src/models/TestAResource.ts
src/models/TestArrayResponseResource.ts
src/models/TestBResource.ts
src/models/TestDashedDiscriminatorResponseResource.ts
src/models/TestDiscriminatorResponseResource.ts
src/models/TestResponseResource.ts
src/models/TestSnakeCaseDiscriminatorResponseResource.ts
src/models/index.ts
src/runtime.ts
tsconfig.esm.json
tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7.26.0-SNAPSHOT
124 changes: 124 additions & 0 deletions samples/client/petstore/typescript-fetch/builds/model-suffix/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# @openapitools/typescript-fetch-model-suffix@1.0.0

A TypeScript SDK client for the localhost API.

## Usage

First, install the SDK from npm.

```bash
npm install @openapitools/typescript-fetch-model-suffix --save
```

Next, try it out.


```ts
import {
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>


async function example() {
console.log("🚀 Testing @openapitools/typescript-fetch-model-suffix SDK...");
const api = new DefaultApi();

try {
const data = await api.test();
console.log(data);
} catch (error) {
console.error(error);
}
}

// Run the test
example().catch(console.error);
```


## Documentation

### API Endpoints

All URIs are relative to *http://localhost:3000*

| Class | Method | HTTP request | Description
| ----- | ------ | ------------ | -------------
*DefaultApi* | [**test**](docs/DefaultApi.md#test) | **GET** /test |
*DefaultApi* | [**testArray**](docs/DefaultApi.md#testarray) | **GET** /test-array |
*DefaultApi* | [**testDashedDiscriminator**](docs/DefaultApi.md#testdasheddiscriminator) | **GET** /test-dashed-discriminator |
*DefaultApi* | [**testDiscriminator**](docs/DefaultApi.md#testdiscriminator) | **GET** /test-discriminator |
*DefaultApi* | [**testSnakeCaseDiscriminator**](docs/DefaultApi.md#testsnakecasediscriminator) | **GET** /test-snake-case-discriminator |


### Models

- [DashedOptionOneResource](docs/DashedOptionOneResource.md)
- [DashedOptionTwoResource](docs/DashedOptionTwoResource.md)
- [NumericSingletonEnumModelResource](docs/NumericSingletonEnumModelResource.md)
- [OptionOneResource](docs/OptionOneResource.md)
- [OptionTwoResource](docs/OptionTwoResource.md)
- [SnakeOptionOneResource](docs/SnakeOptionOneResource.md)
- [SnakeOptionTwoResource](docs/SnakeOptionTwoResource.md)
- [TestAResource](docs/TestAResource.md)
- [TestArrayResponseResource](docs/TestArrayResponseResource.md)
- [TestBResource](docs/TestBResource.md)
- [TestDashedDiscriminatorResponseResource](docs/TestDashedDiscriminatorResponseResource.md)
- [TestDiscriminatorResponseResource](docs/TestDiscriminatorResponseResource.md)
- [TestResponseResource](docs/TestResponseResource.md)
- [TestSnakeCaseDiscriminatorResponseResource](docs/TestSnakeCaseDiscriminatorResponseResource.md)

### Authorization

Endpoints do not require authorization.


## About

This TypeScript SDK client supports the [Fetch API](https://fetch.spec.whatwg.org/)
and is automatically generated by the
[OpenAPI Generator](https://openapi-generator.tech) project:

- API version: `1.0.0`
- Package version: `1.0.0`
- Generator version: `7.26.0-SNAPSHOT`
- Build package: `org.openapitools.codegen.languages.TypeScriptFetchClientCodegen`

The generated npm module supports the following:

- Environments
* Node.js
* Webpack
* Browserify
- Language levels
* ES5 - you must have a Promises/A+ library installed
* ES6
- Module systems
* CommonJS
* ES6 module system


## Development

### Building

To build the TypeScript source code, you need to have Node.js and npm installed.
After cloning the repository, navigate to the project directory and run:

```bash
npm install
npm run build
```

### Publishing

Once you've built the package, you can publish it to npm:

```bash
npm publish
```

## License

[]()
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

# DashedOptionOneResource


## Properties

Name | Type
------------ | -------------
`discriminatorField` | string
`someProperty` | string

## Example

```typescript
import type { DashedOptionOneResource } from '@openapitools/typescript-fetch-model-suffix'

// TODO: Update the object below with actual values
const example = {
"discriminatorField": null,
"someProperty": null,
} satisfies DashedOptionOneResource

console.log(example)

// Convert the instance to a JSON string
const exampleJSON: string = JSON.stringify(example)
console.log(exampleJSON)

// Parse the JSON string back to an object
const exampleParsed = JSON.parse(exampleJSON) as DashedOptionOneResource
console.log(exampleParsed)
```

[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)


Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

# DashedOptionTwoResource


## Properties

Name | Type
------------ | -------------
`discriminatorField` | string
`someProperty` | string

## Example

```typescript
import type { DashedOptionTwoResource } from '@openapitools/typescript-fetch-model-suffix'

// TODO: Update the object below with actual values
const example = {
"discriminatorField": null,
"someProperty": null,
} satisfies DashedOptionTwoResource

console.log(example)

// Convert the instance to a JSON string
const exampleJSON: string = JSON.stringify(example)
console.log(exampleJSON)

// Parse the JSON string back to an object
const exampleParsed = JSON.parse(exampleJSON) as DashedOptionTwoResource
console.log(exampleParsed)
```

[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)


Loading