Skip to content

Commit 3e8baaf

Browse files
committed
Add guard against duplicate operation function name
1 parent 9bb4175 commit 3e8baaf

3 files changed

Lines changed: 75 additions & 2 deletions

File tree

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public class RustServerCodegen extends AbstractRustCodegen implements CodegenCon
7070
protected String externCrateName;
7171
protected Map<String, Map<String, String>> pathSetMap = new HashMap();
7272
protected Map<String, Map<String, String>> callbacksPathSetMap = new HashMap();
73+
protected Set<String> globalOperationIds = new HashSet<>();
7374

7475
private static final String uuidType = "uuid::Uuid";
7576
private static final String bytesType = "swagger::ByteArray";
@@ -583,8 +584,20 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
583584
}
584585

585586
String underscoredOperationId = underscore(op.operationId);
586-
op.vendorExtensions.put("x-operation-id", underscoredOperationId);
587-
op.vendorExtensions.put("x-uppercase-operation-id", underscoredOperationId.toUpperCase(Locale.ROOT));
587+
// Deduplicate x-operation-id across all tag groups. All operations are merged into a single
588+
// mod.rs, so handle_<x-operation-id>() functions must be globally unique, not just per-tag.
589+
String uniqueOperationId = underscoredOperationId;
590+
int opIdCounter = 0;
591+
while (globalOperationIds.contains(uniqueOperationId)) {
592+
uniqueOperationId = underscoredOperationId + "_" + opIdCounter;
593+
opIdCounter++;
594+
}
595+
globalOperationIds.add(uniqueOperationId);
596+
if (!uniqueOperationId.equals(underscoredOperationId)) {
597+
LOGGER.warn("generated unique x-operation-id `{}` for operationId `{}`", uniqueOperationId, op.operationId);
598+
}
599+
op.vendorExtensions.put("x-operation-id", uniqueOperationId);
600+
op.vendorExtensions.put("x-uppercase-operation-id", uniqueOperationId.toUpperCase(Locale.ROOT));
588601
String vendorExtensionPath = op.path.replace("{", ":").replace("}", "");
589602
op.vendorExtensions.put("x-path", vendorExtensionPath);
590603
op.vendorExtensions.put("x-path-id", pathId);

modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustServerCodegenTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.nio.file.Files;
1111
import java.nio.file.Path;
1212
import java.util.List;
13+
import java.util.regex.Pattern;
1314

1415
/**
1516
* Tests for RustServerCodegen.
@@ -60,6 +61,35 @@ public void testIntegerParameterTypeFitting() throws IOException {
6061
target.toFile().deleteOnExit();
6162
}
6263

64+
/**
65+
* Test that two operations whose operationIds normalize to the same snake_case string
66+
* (e.g., "fooBar" and "foo_bar" both become "foo_bar") receive distinct x-operation-id
67+
* values. All operations are emitted as handle_<x-operation-id>() free functions in a
68+
* single mod.rs, so duplicates would cause a Rust compile error.
69+
*/
70+
@Test
71+
public void testDuplicateOperationIdDeduplication() throws IOException {
72+
Path target = Files.createTempDirectory("test");
73+
final CodegenConfigurator configurator = new CodegenConfigurator()
74+
.setGeneratorName("rust-server")
75+
.setInputSpec("src/test/resources/3_0/rust-server/duplicate-operation-id.yaml")
76+
.setSkipOverwrite(false)
77+
.setOutputDir(target.toAbsolutePath().toString().replace("\\", "/"));
78+
List<File> files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate();
79+
files.forEach(File::deleteOnExit);
80+
81+
Path serverModPath = Path.of(target.toString(), "/src/server/mod.rs");
82+
TestUtils.assertFileExists(serverModPath);
83+
84+
// Both operations produce snake_case "foo_bar". The second should be renamed to
85+
// "foo_bar_0" so there are two distinct handle_*() functions, not a duplicate.
86+
TestUtils.assertFileContains(serverModPath, "handle_foo_bar(");
87+
TestUtils.assertFileContains(serverModPath, "handle_foo_bar_0(");
88+
89+
// Clean up
90+
target.toFile().deleteOnExit();
91+
}
92+
6393
/**
6494
* Test that required query params without examples disable the client example.
6595
*/
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
# Test that two operations in different tags with the same operationId (after snake_case
3+
# normalization) produce distinct x-operation-id values, avoiding a Rust compile error
4+
# from duplicate handle_<x-operation-id>() functions in the same mod.rs.
5+
openapi: 3.1.1
6+
info:
7+
title: Duplicate OperationId Test
8+
description: Spec with two operations that normalize to the same snake_case operation ID
9+
version: 1.0.0
10+
servers:
11+
- url: http://localhost:8080
12+
paths:
13+
/foo/bar:
14+
get:
15+
operationId: fooBar
16+
summary: First operation
17+
tags:
18+
- TagA
19+
responses:
20+
"200":
21+
description: OK
22+
/foo/baz:
23+
get:
24+
operationId: foo_bar
25+
summary: Second operation - normalizes to same snake_case as fooBar
26+
tags:
27+
- TagB
28+
responses:
29+
"200":
30+
description: OK

0 commit comments

Comments
 (0)