From 9b2afbc8f0e164b843c20be7d088e337704e7ca8 Mon Sep 17 00:00:00 2001 From: Jules Ivanic Date: Mon, 9 Mar 2026 17:14:30 +1100 Subject: [PATCH 1/4] [typescript-fetch] Add withRequestOptsInInterface option to hide RequestOpts from API interfaces The *RequestOpts methods are implementation details that build request options before sending the fetch call. Most consumers using the API interfaces don't need these methods exposed in the contract. Add a new `withRequestOptsInInterface` boolean option (default: true for backward compatibility) that controls whether *RequestOpts methods appear in the generated API interface declarations. When set to false, the methods remain as public methods on the class but are excluded from the interface. --- .../TypeScriptFetchClientCodegen.java | 16 +++++++ .../resources/typescript-fetch/apis.mustache | 2 + .../TypeScriptFetchClientCodegenTest.java | 44 +++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java index 482c286927c7..e66f40d55342 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java @@ -61,6 +61,7 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege public static final String PASCAL_CASE = "PascalCase"; public static final String USE_SQUARE_BRACKETS_IN_ARRAY_NAMES = "useSquareBracketsInArrayNames"; public static final String VALIDATION_ATTRIBUTES = "validationAttributes"; + public static final String WITH_REQUEST_OPTS_IN_INTERFACE = "withRequestOptsInInterface"; @Getter @Setter protected String npmRepository = null; @@ -68,6 +69,7 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege protected String importFileExtension = ""; private boolean useSingleRequestParameter = true; private boolean prefixParameterInterfaces = false; + private boolean withRequestOptsInInterface = true; protected boolean addedApiIndex = false; protected boolean addedModelIndex = false; protected boolean withoutRuntimeChecks = false; @@ -130,6 +132,7 @@ public TypeScriptFetchClientCodegen() { 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())); + this.cliOptions.add(new CliOption(WITH_REQUEST_OPTS_IN_INTERFACE, "Setting this property to true will include *RequestOpts methods in the API interface declarations. Set to false to keep them only on the class.", SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.TRUE.toString())); } @Override @@ -273,6 +276,11 @@ public void processOpts() { } writePropertyBack(PREFIX_PARAMETER_INTERFACES, getPrefixParameterInterfaces()); + if (additionalProperties.containsKey(WITH_REQUEST_OPTS_IN_INTERFACE)) { + this.setWithRequestOptsInInterface(convertPropertyToBoolean(WITH_REQUEST_OPTS_IN_INTERFACE)); + } + writePropertyBack(WITH_REQUEST_OPTS_IN_INTERFACE, getWithRequestOptsInInterface()); + if (additionalProperties.containsKey(NPM_NAME)) { addNpmPackageGeneration(); } @@ -1084,6 +1092,14 @@ private void setPrefixParameterInterfaces(boolean prefixParameterInterfaces) { this.prefixParameterInterfaces = prefixParameterInterfaces; } + private boolean getWithRequestOptsInInterface() { + return withRequestOptsInInterface; + } + + private void setWithRequestOptsInInterface(boolean withRequestOptsInInterface) { + this.withRequestOptsInInterface = withRequestOptsInInterface; + } + private static boolean itemsAreUniqueId(CodegenProperty items) { if (items != null && items.items != null) { return itemsAreUniqueId(items.items); diff --git a/modules/openapi-generator/src/main/resources/typescript-fetch/apis.mustache b/modules/openapi-generator/src/main/resources/typescript-fetch/apis.mustache index 1e8db2b3c4c3..7815a1bac7b7 100644 --- a/modules/openapi-generator/src/main/resources/typescript-fetch/apis.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-fetch/apis.mustache @@ -42,6 +42,7 @@ export interface {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterIn */ export interface {{classname}}Interface { {{#operation}} + {{#withRequestOptsInInterface}} /** * Creates request options for {{nickname}} without sending the request {{#allParams}} @@ -55,6 +56,7 @@ export interface {{classname}}Interface { */ {{nickname}}RequestOpts({{#allParams.0}}requestParameters: {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}Request{{/allParams.0}}): Promise; + {{/withRequestOptsInInterface}} /** * {{¬es}} {{#summary}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java index da8324317640..2ae791d0f195 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java @@ -458,6 +458,50 @@ public void testValidationAttributesWithWithoutRuntimeChecks() throws IOExceptio TestUtils.assertFileContains(modelsIndex, "[property: string]:"); } + @Test(description = "Verify withRequestOptsInInterface=true (default) includes RequestOpts in interface") + public void testRequestOptsInInterfaceByDefault() throws IOException { + Map properties = new HashMap<>(); + properties.put(TypeScriptFetchClientCodegen.WITH_INTERFACES, true); + + File output = generate(properties); + + Path apiFile = Paths.get(output + "/apis/PetControllerApi.ts"); + TestUtils.assertFileExists(apiFile); + + // Interface should contain RequestOpts methods + TestUtils.assertFileContains(apiFile, "export interface PetControllerApiInterface"); + TestUtils.assertFileContains(apiFile, "addPetRequestOpts("); + + // Class should also contain RequestOpts methods + TestUtils.assertFileContains(apiFile, "async addPetRequestOpts("); + } + + @Test(description = "Verify withRequestOptsInInterface=false excludes RequestOpts from interface but keeps them on class") + public void testRequestOptsNotInInterfaceWhenDisabled() throws IOException { + Map properties = new HashMap<>(); + properties.put(TypeScriptFetchClientCodegen.WITH_INTERFACES, true); + properties.put(TypeScriptFetchClientCodegen.WITH_REQUEST_OPTS_IN_INTERFACE, false); + + File output = generate(properties); + + Path apiFile = Paths.get(output + "/apis/PetControllerApi.ts"); + TestUtils.assertFileExists(apiFile); + + // Read file content and split into interface and class sections + String content = new String(Files.readAllBytes(apiFile)); + int interfaceStart = content.indexOf("export interface PetControllerApiInterface"); + int interfaceEnd = content.indexOf("}", interfaceStart); + String interfaceSection = content.substring(interfaceStart, interfaceEnd); + + // Interface should NOT contain RequestOpts methods + assertThat(interfaceSection).doesNotContain("RequestOpts"); + + // Class should still contain RequestOpts methods + int classStart = content.indexOf("export class PetControllerApi"); + String classSection = content.substring(classStart); + assertThat(classSection).contains("async addPetRequestOpts("); + } + private static File generate( Map properties ) throws IOException { From 911fe67619d3bfaf0ab769d99d1f0f49c93f16fe Mon Sep 17 00:00:00 2001 From: Jules Ivanic Date: Mon, 9 Mar 2026 17:25:45 +1100 Subject: [PATCH 2/4] Fix test: scope RequestOpts assertion to interface section The previous test used indexOf("}") to find the interface end, which matched the } inside @throws {RequiredError} JSDoc instead of the actual closing brace. Use the class declaration position as the boundary instead. --- .../fetch/TypeScriptFetchClientCodegenTest.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java index 2ae791d0f195..78c837f201f2 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java @@ -468,12 +468,18 @@ public void testRequestOptsInInterfaceByDefault() throws IOException { Path apiFile = Paths.get(output + "/apis/PetControllerApi.ts"); TestUtils.assertFileExists(apiFile); + // Read file content and split into interface and class sections + String content = new String(Files.readAllBytes(apiFile)); + int interfaceStart = content.indexOf("export interface PetControllerApiInterface"); + int classStart = content.indexOf("export class PetControllerApi"); + String interfaceSection = content.substring(interfaceStart, classStart); + // Interface should contain RequestOpts methods - TestUtils.assertFileContains(apiFile, "export interface PetControllerApiInterface"); - TestUtils.assertFileContains(apiFile, "addPetRequestOpts("); + assertThat(interfaceSection).contains("addPetRequestOpts("); // Class should also contain RequestOpts methods - TestUtils.assertFileContains(apiFile, "async addPetRequestOpts("); + String classSection = content.substring(classStart); + assertThat(classSection).contains("async addPetRequestOpts("); } @Test(description = "Verify withRequestOptsInInterface=false excludes RequestOpts from interface but keeps them on class") @@ -490,14 +496,13 @@ public void testRequestOptsNotInInterfaceWhenDisabled() throws IOException { // Read file content and split into interface and class sections String content = new String(Files.readAllBytes(apiFile)); int interfaceStart = content.indexOf("export interface PetControllerApiInterface"); - int interfaceEnd = content.indexOf("}", interfaceStart); - String interfaceSection = content.substring(interfaceStart, interfaceEnd); + int classStart = content.indexOf("export class PetControllerApi"); + String interfaceSection = content.substring(interfaceStart, classStart); // Interface should NOT contain RequestOpts methods assertThat(interfaceSection).doesNotContain("RequestOpts"); // Class should still contain RequestOpts methods - int classStart = content.indexOf("export class PetControllerApi"); String classSection = content.substring(classStart); assertThat(classSection).contains("async addPetRequestOpts("); } From 58342bd5550d32abc449281b02a09c8e02c28709 Mon Sep 17 00:00:00 2001 From: Jules Ivanic Date: Mon, 9 Mar 2026 17:31:44 +1100 Subject: [PATCH 3/4] Fix forbiddenapis violation: use explicit UTF-8 charset Replace new String(byte[]) with new String(byte[], StandardCharsets.UTF_8) to satisfy the forbiddenapis Maven plugin check. --- .../typescript/fetch/TypeScriptFetchClientCodegenTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java index 78c837f201f2..bb63f62225ea 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -469,7 +470,7 @@ public void testRequestOptsInInterfaceByDefault() throws IOException { TestUtils.assertFileExists(apiFile); // Read file content and split into interface and class sections - String content = new String(Files.readAllBytes(apiFile)); + String content = new String(Files.readAllBytes(apiFile), StandardCharsets.UTF_8); int interfaceStart = content.indexOf("export interface PetControllerApiInterface"); int classStart = content.indexOf("export class PetControllerApi"); String interfaceSection = content.substring(interfaceStart, classStart); @@ -494,7 +495,7 @@ public void testRequestOptsNotInInterfaceWhenDisabled() throws IOException { TestUtils.assertFileExists(apiFile); // Read file content and split into interface and class sections - String content = new String(Files.readAllBytes(apiFile)); + String content = new String(Files.readAllBytes(apiFile), StandardCharsets.UTF_8); int interfaceStart = content.indexOf("export interface PetControllerApiInterface"); int classStart = content.indexOf("export class PetControllerApi"); String interfaceSection = content.substring(interfaceStart, classStart); From f4afe8e945a3d5c0bf3b8dd0d61a3a5056f7130d Mon Sep 17 00:00:00 2001 From: Jules Ivanic Date: Mon, 9 Mar 2026 18:01:33 +1100 Subject: [PATCH 4/4] Add withRequestOptsInInterface to options test and regenerate docs --- docs/generators/typescript-fetch.md | 1 + .../codegen/options/TypeScriptFetchClientOptionsProvider.java | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/generators/typescript-fetch.md b/docs/generators/typescript-fetch.md index 3019201de917..9856b1d021ba 100644 --- a/docs/generators/typescript-fetch.md +++ b/docs/generators/typescript-fetch.md @@ -47,6 +47,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |useSquareBracketsInArrayNames|Setting this property to true will add brackets to array attribute names, e.g. my_values[].| |false| |validationAttributes|Setting this property to true will generate the validation attributes of model properties.| |false| |withInterfaces|Setting this property to true will generate interfaces next to the default class implementations.| |false| +|withRequestOptsInInterface|Setting this property to true will include *RequestOpts methods in the API interface declarations. Set to false to keep them only on the class.| |true| |withoutRuntimeChecks|Setting this property to true will remove any runtime checks on the request and response payloads. Payloads will be casted to their expected types.| |false| ## IMPORT MAPPING diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/TypeScriptFetchClientOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/TypeScriptFetchClientOptionsProvider.java index 146b5a69293c..a6bccce568cb 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/TypeScriptFetchClientOptionsProvider.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/TypeScriptFetchClientOptionsProvider.java @@ -53,6 +53,7 @@ public Map createOptions() { .put(TypeScriptFetchClientCodegen.STRING_ENUMS, STRING_ENUMS) .put(TypeScriptFetchClientCodegen.USE_SQUARE_BRACKETS_IN_ARRAY_NAMES, Boolean.FALSE.toString()) .put(TypeScriptFetchClientCodegen.VALIDATION_ATTRIBUTES, Boolean.FALSE.toString()) + .put(TypeScriptFetchClientCodegen.WITH_REQUEST_OPTS_IN_INTERFACE, Boolean.TRUE.toString()) .build(); } }