From 1eaa1d999b4927687adeb58520fd17418f848789 Mon Sep 17 00:00:00 2001 From: Thomas Galicia Date: Mon, 11 May 2026 15:04:09 +0200 Subject: [PATCH 1/4] Fix angular typescript imports from same file or package being overwritten --- .../codegen/DefaultGenerator.java | 27 ++++++++- .../TypeScriptAngularClientCodegen.java | 29 +++++++++- .../TypeScriptAngularClientCodegenTest.java | 55 ++++++++++++++----- 3 files changed, 92 insertions(+), 19 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java index 60b17e8e47d5..f73aadec2f8c 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java @@ -31,6 +31,7 @@ import org.apache.commons.io.IOCase; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.openapitools.codegen.api.*; import org.openapitools.codegen.config.GlobalSettings; import org.openapitools.codegen.ignore.CodegenIgnoreProcessor; @@ -1656,7 +1657,7 @@ private OperationsMap processOperations(CodegenConfig config, String tag, List mappings = getAllImportsMappings(allImports); + Set> mappings = getAllImportMappings(allImports); Set> imports = toImportsObjects(mappings); //Some codegen implementations rely on a list interface for the imports @@ -1734,6 +1735,16 @@ private Map getAllImportsMappings(Set allImports) { return result; } + private Set> getAllImportMappings(Set allImports) { + return allImports.stream().map(nextImport -> { + String mapping = config.importMapping().get(nextImport); + if (mapping != null) { + return Pair.of(mapping, nextImport); + } + return Pair.of(config.toModelImport(nextImport), nextImport); + }).collect(Collectors.toSet()); + } + /** * Using an import map created via {@link #getAllImportsMappings(Set)} to build a list import objects. * The import objects have two keys: import and classname which hold the key and value of the initial map entry. @@ -1755,6 +1766,20 @@ private Set> toImportsObjects(Map mappedImpo return result; } + private Set> toImportsObjects(Set> importPairs) { + Set> result = new TreeSet<>( + Comparator.comparing(o -> o.get("classname")) + ); + + importPairs.forEach((pair) -> { + Map im = new LinkedHashMap<>(); + im.put("import", pair.getLeft()); + im.put("classname", pair.getRight()); + result.add(im); + }); + return result; + } + private ModelsMap processModels(CodegenConfig config, Map definitions) { ModelsMap objs = new ModelsMap(); objs.put("package", config.modelPackage()); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java index a7c1b962a2b7..f540b1b962d8 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java @@ -22,6 +22,7 @@ import lombok.Data; import lombok.Getter; import lombok.Setter; +import org.apache.commons.lang3.tuple.Pair; import org.openapitools.codegen.*; import org.openapitools.codegen.meta.features.DocumentationFeature; import org.openapitools.codegen.meta.features.GlobalFeature; @@ -476,13 +477,13 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap operations, L operations.put("hasSomeEncodableParams", hasSomeEncodableParams); // Add additional filename information for model imports in the services - List> imports = operations.getImports(); - for (Map im : imports) { + List> mergedImports = mergeImports(operations.getImports()); + for (Map im : mergedImports) { // This property is not used in the templates any more, subject for removal im.put("filename", im.get("import")); im.put("classname", im.get("classname")); } - + operations.setImports(mergedImports); return operations; } @@ -580,6 +581,28 @@ private List> toTsImports(CodegenModel cm, Set impor return tsImports; } + /** + * Merge imports that belong to the same file + */ + private List> mergeImports(List> imports) { + Map importLookup = new HashMap<>(); + imports.forEach(importMap -> { + String importPackage = importMap.get("import"); + String importType = importMap.get("classname"); + String existingImportType = importLookup.get(importPackage); + if (existingImportType != null && !existingImportType.equals(importType)) { + String newImportType = String.join(", ", existingImportType, importType); + importLookup.put(importPackage, newImportType); + } else { + importLookup.put(importPackage, importType); + } + }); + + return importLookup.entrySet().stream() + .map(entry -> new HashMap<>(Map.of("import", entry.getKey(), "classname", entry.getValue()))) + .collect(Collectors.toList()); + } + @Override public String toApiName(String name) { if (name.length() == 0) { diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java index e3acfc556211..8a5e39a0f0f4 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java @@ -11,6 +11,8 @@ import org.openapitools.codegen.*; import org.openapitools.codegen.config.CodegenConfigurator; import org.openapitools.codegen.languages.TypeScriptAngularClientCodegen; +import org.openapitools.codegen.model.OperationMap; +import org.openapitools.codegen.model.OperationsMap; import org.openapitools.codegen.typescript.TypeScriptGroups; import org.testng.Assert; import org.testng.annotations.Test; @@ -19,12 +21,13 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; - @Test(groups = {TypeScriptGroups.TYPESCRIPT, TypeScriptGroups.TYPESCRIPT_ANGULAR}) public class TypeScriptAngularClientCodegenTest { @Test @@ -117,14 +120,14 @@ public void testModelFileSuffix() { @Test public void testOperationIdParser() { OpenAPI openAPI = TestUtils.createOpenAPI(); - Operation operation1 = new Operation().operationId("123_test_@#$%_special_tags").responses(new ApiResponses().addApiResponse("201", new ApiResponse().description("OK"))); + Operation operation1 = new Operation().operationId("123_test_@#$%_special_tags").responses(new ApiResponses().addApiResponse("201" + , new ApiResponse().description("OK"))); openAPI.path("another-fake/dummy/", new PathItem().get(operation1)); final TypeScriptAngularClientCodegen codegen = new TypeScriptAngularClientCodegen(); codegen.setOpenAPI(openAPI); CodegenOperation co1 = codegen.fromOperation("/another-fake/dummy/", "get", operation1, null); org.testng.Assert.assertEquals(co1.operationId, "_123testSpecialTags"); - } @Test @@ -148,7 +151,6 @@ public void testSnapshotVersion() { codegen.preprocessOpenAPI(openAPI); Assert.assertTrue(codegen.getNpmVersion().matches("^3.0.0-M1-SNAPSHOT.[0-9]{12}$")); - } @Test @@ -172,7 +174,6 @@ public void testWithoutSnapshotVersion() { codegen.preprocessOpenAPI(openAPI); Assert.assertTrue(codegen.getNpmVersion().matches("^3.0.0-M1$")); - } @Test @@ -426,9 +427,9 @@ public void testBasePath() throws IOException { // WHEN final CodegenConfigurator configurator = new CodegenConfigurator() - .setGeneratorName("typescript-angular") - .setInputSpec(specPath) - .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + .setGeneratorName("typescript-angular") + .setInputSpec(specPath) + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); final ClientOptInput clientOptInput = configurator.toClientOptInput(); @@ -450,9 +451,9 @@ public void testEnumAsConst() throws IOException { // WHEN final CodegenConfigurator configurator = new CodegenConfigurator() - .setGeneratorName("typescript-angular") - .setInputSpec(specPath) - .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + .setGeneratorName("typescript-angular") + .setInputSpec(specPath) + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); final ClientOptInput clientOptInput = configurator.toClientOptInput(); @@ -475,9 +476,9 @@ public void testDeepObject() throws IOException { // WHEN final CodegenConfigurator configurator = new CodegenConfigurator() - .setGeneratorName("typescript-angular") - .setInputSpec(specPath) - .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + .setGeneratorName("typescript-angular") + .setInputSpec(specPath) + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); final ClientOptInput clientOptInput = configurator.toClientOptInput(); @@ -511,7 +512,31 @@ public void testOpenIdCredentialsAreSet() throws IOException { //THEN final String fileContents = Files.readString(Paths.get(output + "/api/default.service.ts")); - String credentialsSet = "localVarHeaders = this.configuration.addCredentialToHeaders('oidc', 'Authorization', localVarHeaders, 'Bearer ');"; + String credentialsSet = "localVarHeaders = this.configuration.addCredentialToHeaders('oidc', 'Authorization', localVarHeaders, " + + "'Bearer ');"; assertThat(fileContents).contains(credentialsSet); } + + @Test + public void testMergingImports() { + TypeScriptAngularClientCodegen codegen = new TypeScriptAngularClientCodegen(); + + List> imports = new ArrayList<>(); + imports.add(Map.of("classname", "type1", "import", "npmPackage")); + imports.add(Map.of("classname", "type2", "import", "npmPackage")); + imports.add(Map.of("classname", "type3", "import", "npmPackage2")); + OperationMap operation = new OperationMap(); + operation.setClassname("classname"); + operation.setOperation(List.of()); + OperationsMap operationsMap = new OperationsMap(); + operationsMap.setImports(imports); + operationsMap.setOperation(operation); + + OperationsMap result = codegen.postProcessOperationsWithModels(operationsMap, List.of()); + + assertThat(result.getImports()).containsExactlyInAnyOrder( + Map.of("classname", "type1, type2", "filename", "npmPackage", "import", "npmPackage"), + Map.of("classname", "type3", "filename", "npmPackage2", "import", "npmPackage2") + ); + } } From eff323e95868805a70aab91fa3899bd9fede5915 Mon Sep 17 00:00:00 2001 From: Thomas Galicia Date: Mon, 11 May 2026 15:31:00 +0200 Subject: [PATCH 2/4] Update samples --- .../builds/query-param-deep-object/api/default.service.ts | 4 ++-- .../client/include/CppRestPetstoreClient/api/UserApi.h | 1 + samples/client/petstore/lua/petstore/api/pet_api.lua | 1 + samples/client/petstore/lua/petstore/api/store_api.lua | 1 + samples/client/petstore/lua/petstore/api/user_api.lua | 1 + samples/client/petstore/nim/petstore/apis/api_pet.nim | 1 + 6 files changed, 7 insertions(+), 2 deletions(-) diff --git a/samples/client/others/typescript-angular-v20/builds/query-param-deep-object/api/default.service.ts b/samples/client/others/typescript-angular-v20/builds/query-param-deep-object/api/default.service.ts index 0f653f71e65e..046ec9248d4b 100644 --- a/samples/client/others/typescript-angular-v20/builds/query-param-deep-object/api/default.service.ts +++ b/samples/client/others/typescript-angular-v20/builds/query-param-deep-object/api/default.service.ts @@ -16,10 +16,10 @@ import { HttpClient, HttpHeaders, HttpParams, import { Observable } from 'rxjs'; import { OpenApiHttpParams, QueryParamStyle } from '../query.params'; -// @ts-ignore -import { Car } from '../model/car'; // @ts-ignore import { CarFilter } from '../model/carFilter'; +// @ts-ignore +import { Car } from '../model/car'; // @ts-ignore import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; diff --git a/samples/client/petstore/cpp-restsdk/client/include/CppRestPetstoreClient/api/UserApi.h b/samples/client/petstore/cpp-restsdk/client/include/CppRestPetstoreClient/api/UserApi.h index b3aea8af56a8..1c31bef33f4b 100644 --- a/samples/client/petstore/cpp-restsdk/client/include/CppRestPetstoreClient/api/UserApi.h +++ b/samples/client/petstore/cpp-restsdk/client/include/CppRestPetstoreClient/api/UserApi.h @@ -25,6 +25,7 @@ #include "CppRestPetstoreClient/model/User.h" #include #include +#include #include namespace org { diff --git a/samples/client/petstore/lua/petstore/api/pet_api.lua b/samples/client/petstore/lua/petstore/api/pet_api.lua index f3385f1c4ca9..042b851593b6 100644 --- a/samples/client/petstore/lua/petstore/api/pet_api.lua +++ b/samples/client/petstore/lua/petstore/api/pet_api.lua @@ -18,6 +18,7 @@ local basexx = require "basexx" -- model import local petstore_api_response = require "petstore.model.api_response" local petstore_pet = require "petstore.model.pet" +local petstore_pet = require "petstore.model.pet" local pet_api = {} local pet_api_mt = { diff --git a/samples/client/petstore/lua/petstore/api/store_api.lua b/samples/client/petstore/lua/petstore/api/store_api.lua index aa2433d3befe..6cac5a7aa2bd 100644 --- a/samples/client/petstore/lua/petstore/api/store_api.lua +++ b/samples/client/petstore/lua/petstore/api/store_api.lua @@ -17,6 +17,7 @@ local basexx = require "basexx" -- model import local petstore_order = require "petstore.model.order" +local petstore_order = require "petstore.model.order" local store_api = {} local store_api_mt = { diff --git a/samples/client/petstore/lua/petstore/api/user_api.lua b/samples/client/petstore/lua/petstore/api/user_api.lua index ab6585ea375f..97318143d9bd 100644 --- a/samples/client/petstore/lua/petstore/api/user_api.lua +++ b/samples/client/petstore/lua/petstore/api/user_api.lua @@ -17,6 +17,7 @@ local basexx = require "basexx" -- model import local petstore_user = require "petstore.model.user" +local petstore_user = require "petstore.model.user" local user_api = {} local user_api_mt = { diff --git a/samples/client/petstore/nim/petstore/apis/api_pet.nim b/samples/client/petstore/nim/petstore/apis/api_pet.nim index 0544760014ac..41ae138de5dd 100644 --- a/samples/client/petstore/nim/petstore/apis/api_pet.nim +++ b/samples/client/petstore/nim/petstore/apis/api_pet.nim @@ -28,6 +28,7 @@ import ../models/model_pet_audit_log import ../models/model_pet_review import ../models/model_pet_reviews_response import ../models/model_unfavorite_pet_request +import ../models/model_unfavorite_pet_request const basepath = "http://petstore.swagger.io/v2" From 7babbd209eb6ed0c4a99d8366911328a83f4b4a1 Mon Sep 17 00:00:00 2001 From: Thomas Galicia Date: Tue, 12 May 2026 09:06:36 +0200 Subject: [PATCH 3/4] Revert "Update samples" This reverts commit eff323e95868805a70aab91fa3899bd9fede5915. --- .../builds/query-param-deep-object/api/default.service.ts | 4 ++-- .../client/include/CppRestPetstoreClient/api/UserApi.h | 1 - samples/client/petstore/lua/petstore/api/pet_api.lua | 1 - samples/client/petstore/lua/petstore/api/store_api.lua | 1 - samples/client/petstore/lua/petstore/api/user_api.lua | 1 - samples/client/petstore/nim/petstore/apis/api_pet.nim | 1 - 6 files changed, 2 insertions(+), 7 deletions(-) diff --git a/samples/client/others/typescript-angular-v20/builds/query-param-deep-object/api/default.service.ts b/samples/client/others/typescript-angular-v20/builds/query-param-deep-object/api/default.service.ts index 046ec9248d4b..0f653f71e65e 100644 --- a/samples/client/others/typescript-angular-v20/builds/query-param-deep-object/api/default.service.ts +++ b/samples/client/others/typescript-angular-v20/builds/query-param-deep-object/api/default.service.ts @@ -16,10 +16,10 @@ import { HttpClient, HttpHeaders, HttpParams, import { Observable } from 'rxjs'; import { OpenApiHttpParams, QueryParamStyle } from '../query.params'; -// @ts-ignore -import { CarFilter } from '../model/carFilter'; // @ts-ignore import { Car } from '../model/car'; +// @ts-ignore +import { CarFilter } from '../model/carFilter'; // @ts-ignore import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; diff --git a/samples/client/petstore/cpp-restsdk/client/include/CppRestPetstoreClient/api/UserApi.h b/samples/client/petstore/cpp-restsdk/client/include/CppRestPetstoreClient/api/UserApi.h index 1c31bef33f4b..b3aea8af56a8 100644 --- a/samples/client/petstore/cpp-restsdk/client/include/CppRestPetstoreClient/api/UserApi.h +++ b/samples/client/petstore/cpp-restsdk/client/include/CppRestPetstoreClient/api/UserApi.h @@ -25,7 +25,6 @@ #include "CppRestPetstoreClient/model/User.h" #include #include -#include #include namespace org { diff --git a/samples/client/petstore/lua/petstore/api/pet_api.lua b/samples/client/petstore/lua/petstore/api/pet_api.lua index 042b851593b6..f3385f1c4ca9 100644 --- a/samples/client/petstore/lua/petstore/api/pet_api.lua +++ b/samples/client/petstore/lua/petstore/api/pet_api.lua @@ -18,7 +18,6 @@ local basexx = require "basexx" -- model import local petstore_api_response = require "petstore.model.api_response" local petstore_pet = require "petstore.model.pet" -local petstore_pet = require "petstore.model.pet" local pet_api = {} local pet_api_mt = { diff --git a/samples/client/petstore/lua/petstore/api/store_api.lua b/samples/client/petstore/lua/petstore/api/store_api.lua index 6cac5a7aa2bd..aa2433d3befe 100644 --- a/samples/client/petstore/lua/petstore/api/store_api.lua +++ b/samples/client/petstore/lua/petstore/api/store_api.lua @@ -17,7 +17,6 @@ local basexx = require "basexx" -- model import local petstore_order = require "petstore.model.order" -local petstore_order = require "petstore.model.order" local store_api = {} local store_api_mt = { diff --git a/samples/client/petstore/lua/petstore/api/user_api.lua b/samples/client/petstore/lua/petstore/api/user_api.lua index 97318143d9bd..ab6585ea375f 100644 --- a/samples/client/petstore/lua/petstore/api/user_api.lua +++ b/samples/client/petstore/lua/petstore/api/user_api.lua @@ -17,7 +17,6 @@ local basexx = require "basexx" -- model import local petstore_user = require "petstore.model.user" -local petstore_user = require "petstore.model.user" local user_api = {} local user_api_mt = { diff --git a/samples/client/petstore/nim/petstore/apis/api_pet.nim b/samples/client/petstore/nim/petstore/apis/api_pet.nim index 41ae138de5dd..0544760014ac 100644 --- a/samples/client/petstore/nim/petstore/apis/api_pet.nim +++ b/samples/client/petstore/nim/petstore/apis/api_pet.nim @@ -28,7 +28,6 @@ import ../models/model_pet_audit_log import ../models/model_pet_review import ../models/model_pet_reviews_response import ../models/model_unfavorite_pet_request -import ../models/model_unfavorite_pet_request const basepath = "http://petstore.swagger.io/v2" From e63b695c686688062a0f7cc9b36242f7765d1642 Mon Sep 17 00:00:00 2001 From: Thomas Galicia Date: Tue, 12 May 2026 11:21:32 +0200 Subject: [PATCH 4/4] Remove accidental formatting --- .../TypeScriptAngularClientCodegenTest.java | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java index 8a5e39a0f0f4..e8bbc3635867 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java @@ -120,8 +120,7 @@ public void testModelFileSuffix() { @Test public void testOperationIdParser() { OpenAPI openAPI = TestUtils.createOpenAPI(); - Operation operation1 = new Operation().operationId("123_test_@#$%_special_tags").responses(new ApiResponses().addApiResponse("201" - , new ApiResponse().description("OK"))); + Operation operation1 = new Operation().operationId("123_test_@#$%_special_tags").responses(new ApiResponses().addApiResponse("201", new ApiResponse().description("OK"))); openAPI.path("another-fake/dummy/", new PathItem().get(operation1)); final TypeScriptAngularClientCodegen codegen = new TypeScriptAngularClientCodegen(); codegen.setOpenAPI(openAPI); @@ -427,9 +426,9 @@ public void testBasePath() throws IOException { // WHEN final CodegenConfigurator configurator = new CodegenConfigurator() - .setGeneratorName("typescript-angular") - .setInputSpec(specPath) - .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + .setGeneratorName("typescript-angular") + .setInputSpec(specPath) + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); final ClientOptInput clientOptInput = configurator.toClientOptInput(); @@ -451,9 +450,9 @@ public void testEnumAsConst() throws IOException { // WHEN final CodegenConfigurator configurator = new CodegenConfigurator() - .setGeneratorName("typescript-angular") - .setInputSpec(specPath) - .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + .setGeneratorName("typescript-angular") + .setInputSpec(specPath) + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); final ClientOptInput clientOptInput = configurator.toClientOptInput(); @@ -476,9 +475,9 @@ public void testDeepObject() throws IOException { // WHEN final CodegenConfigurator configurator = new CodegenConfigurator() - .setGeneratorName("typescript-angular") - .setInputSpec(specPath) - .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + .setGeneratorName("typescript-angular") + .setInputSpec(specPath) + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); final ClientOptInput clientOptInput = configurator.toClientOptInput(); @@ -501,9 +500,9 @@ public void testOpenIdCredentialsAreSet() throws IOException { // WHEN final CodegenConfigurator configurator = new CodegenConfigurator() - .setGeneratorName("typescript-angular") - .setInputSpec(specPath) - .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + .setGeneratorName("typescript-angular") + .setInputSpec(specPath) + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); final ClientOptInput clientOptInput = configurator.toClientOptInput(); @@ -512,8 +511,7 @@ public void testOpenIdCredentialsAreSet() throws IOException { //THEN final String fileContents = Files.readString(Paths.get(output + "/api/default.service.ts")); - String credentialsSet = "localVarHeaders = this.configuration.addCredentialToHeaders('oidc', 'Authorization', localVarHeaders, " + - "'Bearer ');"; + String credentialsSet = "localVarHeaders = this.configuration.addCredentialToHeaders('oidc', 'Authorization', localVarHeaders, 'Bearer ');"; assertThat(fileContents).contains(credentialsSet); }