diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAxiosClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAxiosClientCodegen.java index 6898d3d7ccfd..4c38da4c10c2 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAxiosClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAxiosClientCodegen.java @@ -22,6 +22,8 @@ import lombok.Getter; import lombok.Setter; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.openapitools.codegen.*; import org.openapitools.codegen.meta.features.DocumentationFeature; import org.openapitools.codegen.meta.features.SecurityFeature; @@ -39,6 +41,8 @@ public class TypeScriptAxiosClientCodegen extends AbstractTypeScriptClientCodegen { + private final Logger LOGGER = LoggerFactory.getLogger(TypeScriptAxiosClientCodegen.class); + public static final String NPM_REPOSITORY = "npmRepository"; public static final String WITH_INTERFACES = "withInterfaces"; public static final String SEPARATE_MODELS_AND_API = "withSeparateModelsAndApi"; @@ -53,10 +57,13 @@ public class TypeScriptAxiosClientCodegen extends AbstractTypeScriptClientCodege public static final String AXIOS_VERSION = "axiosVersion"; public static final String DEFAULT_AXIOS_VERSION = "^1.13.5"; public static final String WITH_AWSV4_SIGNATURE = "withAWSV4Signature"; + public static final String USE_ERASABLE_SYNTAX = "useErasableSyntax"; + public static final String USE_ERASABLE_SYNTAX_DESC = "Use erasable syntax for the generated code, compatible with TypeScript's erasableSyntaxOnly option."; @Getter @Setter protected String npmRepository = null; protected Boolean stringEnums = false; + protected Boolean useErasableSyntax = false; protected String importFileExtension = ""; @Getter @Setter @@ -97,6 +104,7 @@ public TypeScriptAxiosClientCodegen() { 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(AXIOS_VERSION, "Use this property to override the axios version in package.json").defaultValue(DEFAULT_AXIOS_VERSION)); this.cliOptions.add(new CliOption(WITH_AWSV4_SIGNATURE, "whether to include AWS v4 signature support", SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString())); + this.cliOptions.add(new CliOption(USE_ERASABLE_SYNTAX, USE_ERASABLE_SYNTAX_DESC, SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString())); // Templates have no mapping between formatted property names and original base names so use only "original" and remove this option removeOption(CodegenConstants.MODEL_PROPERTY_NAMING); } @@ -177,6 +185,18 @@ public void processOpts() { additionalProperties.put("importFileExtension", this.importFileExtension); } + if (additionalProperties.containsKey(USE_ERASABLE_SYNTAX)) { + this.useErasableSyntax = Boolean.parseBoolean(additionalProperties.get(USE_ERASABLE_SYNTAX).toString()); + additionalProperties.put(USE_ERASABLE_SYNTAX, this.useErasableSyntax); + } + + if (this.useErasableSyntax && this.stringEnums) { + LOGGER.warn("useErasableSyntax and stringEnums are both enabled. " + + "TypeScript 'enum' declarations are not erasable syntax and will fail with " + + "erasableSyntaxOnly. Consider disabling stringEnums (the default generates " + + "erasable-compatible const objects instead)."); + } + if (additionalProperties.containsKey(NPM_NAME)) { addNpmPackageGeneration(); } diff --git a/modules/openapi-generator/src/main/resources/typescript-axios/baseApi.mustache b/modules/openapi-generator/src/main/resources/typescript-axios/baseApi.mustache index 2bfb779f8194..7655a33f0c12 100644 --- a/modules/openapi-generator/src/main/resources/typescript-axios/baseApi.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-axios/baseApi.mustache @@ -25,6 +25,7 @@ export interface RequestArgs { export class BaseAPI { protected configuration: Configuration | undefined; +{{^useErasableSyntax}} constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) { if (configuration) { @@ -32,13 +33,37 @@ export class BaseAPI { this.basePath = configuration.basePath ?? basePath; } } +{{/useErasableSyntax}} +{{#useErasableSyntax}} + protected basePath: string; + protected axios: AxiosInstance; + + constructor(configuration?: Configuration, basePath: string = BASE_PATH, axios: AxiosInstance = globalAxios) { + this.basePath = basePath; + this.axios = axios; + if (configuration) { + this.configuration = configuration; + this.basePath = configuration.basePath ?? basePath; + } + } +{{/useErasableSyntax}} }; export class RequiredError extends Error { +{{^useErasableSyntax}} constructor(public field: string, msg?: string) { super(msg); this.name = "RequiredError" } +{{/useErasableSyntax}} +{{#useErasableSyntax}} + public field: string; + constructor(field: string, msg?: string) { + super(msg); + this.name = "RequiredError" + this.field = field; + } +{{/useErasableSyntax}} } interface ServerMap { diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/axios/TypeScriptAxiosClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/axios/TypeScriptAxiosClientCodegenTest.java index 1713e2ca4a4d..b4fad35d5bd9 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/axios/TypeScriptAxiosClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/axios/TypeScriptAxiosClientCodegenTest.java @@ -11,6 +11,7 @@ import org.testng.annotations.Test; import java.io.File; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -178,4 +179,45 @@ public void testDeprecatedArrayAttribute() throws Exception { // Verify the non-deprecated array property 'nicknames' is also present TestUtils.assertFileContains(file, "'nicknames'?: Array"); } + + @Test(description = "Verify useErasableSyntax generates erasable code in base.ts") + public void testUseErasableSyntaxConfig() throws IOException { + boolean[] options = {true, false}; + for (boolean useErasableSyntax : options) { + final File output = Files.createTempDirectory("typescript_axios_erasable_").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("typescript-axios") + .setInputSpec("src/test/resources/3_0/petstore.yaml") + .addAdditionalProperty("useErasableSyntax", useErasableSyntax) + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + final DefaultGenerator generator = new DefaultGenerator(); + final List files = generator.opts(clientOptInput).generate(); + files.forEach(File::deleteOnExit); + + Path baseTsPath = Paths.get(output + "/base.ts"); + TestUtils.assertFileExists(baseTsPath); + if (useErasableSyntax) { + // Erasable syntax: no parameter properties, explicit field declarations and assignments + TestUtils.assertFileContains(baseTsPath, "protected basePath: string;"); + TestUtils.assertFileContains(baseTsPath, "protected axios: AxiosInstance;"); + TestUtils.assertFileContains(baseTsPath, "this.basePath = basePath;"); + TestUtils.assertFileContains(baseTsPath, "this.axios = axios;"); + TestUtils.assertFileContains(baseTsPath, "public field: string;"); + TestUtils.assertFileContains(baseTsPath, "this.field = field;"); + // Should NOT contain parameter properties + TestUtils.assertFileNotContains(baseTsPath, "protected basePath: string = BASE_PATH,"); + TestUtils.assertFileNotContains(baseTsPath, "protected axios: AxiosInstance = globalAxios"); + TestUtils.assertFileNotContains(baseTsPath, "public field: string,"); + } else { + // Non-erasable syntax: uses parameter properties + TestUtils.assertFileContains(baseTsPath, "protected basePath: string = BASE_PATH,"); + TestUtils.assertFileContains(baseTsPath, "protected axios: AxiosInstance = globalAxios"); + TestUtils.assertFileContains(baseTsPath, "constructor(public field: string,"); + } + } + } }