Skip to content

Commit 32fea51

Browse files
committed
fix duplicated opeationId with new rule in normalizer
1 parent 2354d40 commit 32fea51

File tree

9 files changed

+269
-0
lines changed

9 files changed

+269
-0
lines changed

bin/configs/java-okhttp-gson-3.1.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ outputDir: samples/client/petstore/java/okhttp-gson-3.1
33
library: okhttp-gson
44
inputSpec: modules/openapi-generator/src/test/resources/3_1/java/petstore.yaml
55
templateDir: modules/openapi-generator/src/main/resources/Java
6+
validateSpec: false
67
nameMappings:
78
_type: underscoreType
89
type_: typeWithUnderscore
@@ -14,3 +15,5 @@ additionalProperties:
1415
hideGenerationTimestamp: "true"
1516
useOneOfDiscriminatorLookup: "true"
1617
disallowAdditionalPropertiesIfNotPresent: false
18+
openapiNormalizer:
19+
FIX_DUPLICATED_OPERATIONID: true

docs/customization.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,3 +614,10 @@ Example:
614614
```
615615
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -o /tmp/java-okhttp/ --openapi-normalizer SET_PRIMITIVE_TYPES_TO_NULLABLE="integer|number"
616616
```
617+
618+
- `FIX_DUPLICATED_OPERATIONID`: When set to true, an integer suffix will be added to duplicated operationId(s), e.g. getName => getName_0, getName_1, etc
619+
620+
Example:
621+
```
622+
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_1/java/petstore.yaml -o /tmp/java-okhttp/ --openapi-normalizer FIX_DUPLICATED_OPERATIONID=true
623+
```

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
import java.util.*;
3535
import java.util.stream.Collectors;
3636

37+
import static org.openapitools.codegen.utils.StringUtils.getUniqueString;
38+
import static org.openapitools.codegen.utils.StringUtils.underscore;
39+
3740
public class OpenAPINormalizer {
3841
private OpenAPI openAPI;
3942
private Map<String, String> inputRules = new HashMap<>();
@@ -88,6 +91,12 @@ public class OpenAPINormalizer {
8891
final String SET_TAGS_TO_OPERATIONID = "SET_TAGS_TO_OPERATIONID";
8992
String setTagsToOperationId;
9093

94+
// when set to true, tags in all operations will be set to operationId or "default" if operationId
95+
// is empty
96+
final String FIX_DUPLICATED_OPERATIONID = "FIX_DUPLICATED_OPERATIONID";
97+
String fixDuplicatedOperationId;
98+
HashSet<String> operationIdSet = new HashSet<>();
99+
91100
// when set to true, auto fix integer with maximum value 4294967295 (2^32-1) or long with 18446744073709551615 (2^64-1)
92101
// by adding x-unsigned to the schema
93102
final String ADD_UNSIGNED_TO_INTEGER_WITH_INVALID_MAX_VALUE = "ADD_UNSIGNED_TO_INTEGER_WITH_INVALID_MAX_VALUE";
@@ -149,6 +158,7 @@ public OpenAPINormalizer(OpenAPI openAPI, Map<String, String> inputRules) {
149158
ruleNames.add(KEEP_ONLY_FIRST_TAG_IN_OPERATION);
150159
ruleNames.add(SET_TAGS_FOR_ALL_OPERATIONS);
151160
ruleNames.add(SET_TAGS_TO_OPERATIONID);
161+
ruleNames.add(FIX_DUPLICATED_OPERATIONID);
152162
ruleNames.add(ADD_UNSIGNED_TO_INTEGER_WITH_INVALID_MAX_VALUE);
153163
ruleNames.add(REFACTOR_ALLOF_WITH_PROPERTIES_ONLY);
154164
ruleNames.add(NORMALIZE_31SPEC);
@@ -360,6 +370,8 @@ private void normalizeOperation(Operation operation) {
360370
processSetTagsForAllOperations(operation);
361371

362372
processSetTagsToOperationId(operation);
373+
374+
processFixDuplicatedOperationId(operation);
363375
}
364376

365377
/**
@@ -876,6 +888,26 @@ private void processSetTagsToOperationId(Operation operation) {
876888
}
877889
}
878890

891+
private void processFixDuplicatedOperationId(Operation operation) {
892+
if (!getRule(FIX_DUPLICATED_OPERATIONID)) {
893+
return;
894+
}
895+
896+
// skip null as default codegen will automatically generate one using path, http verb, etc
897+
if (operation.getOperationId() == null) {
898+
return;
899+
}
900+
901+
String uniqueName = getUniqueString(operationIdSet, operation.getOperationId());
902+
903+
if (!uniqueName.equals(operation.getOperationId())) {
904+
LOGGER.info("operationId {} renamed to {} to ensure uniqueness (enabled by openapi normalizer rule `FIX_DUPLICATED_OPERATIONID`)", operation.getOperationId(), uniqueName);
905+
operation.setOperationId(uniqueName);
906+
}
907+
}
908+
909+
910+
879911
/**
880912
* If the schema contains anyOf/oneOf and properties, remove oneOf/anyOf as these serve as rules to
881913
* ensure inter-dependency between properties. It's a workaround as such validation is not supported at the moment.

modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/StringUtils.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,4 +283,32 @@ public static String escape(final String name, final Map<String, String> replace
283283
throw new RuntimeException("Word '" + name + "' could not be escaped.");
284284
});
285285
}
286+
287+
/**
288+
* Return a unique string based on a set of processed strings.
289+
*
290+
* @param processedStrings a set of strings that have been processed
291+
* @param input input to be checked for uniqueness
292+
* @return a unique string
293+
*/
294+
public static String getUniqueString(Set<String> processedStrings, String input) {
295+
if (input == null) {
296+
return null;
297+
}
298+
299+
String uniqueName = input;
300+
// check for input uniqueness
301+
int counter = 0;
302+
303+
if (processedStrings.contains(uniqueName)) {
304+
// look for next unique next, e.g. getName_7
305+
while (processedStrings.contains(uniqueName)) {
306+
uniqueName = uniqueName + "_" + counter;
307+
counter++;
308+
}
309+
}
310+
311+
processedStrings.add(uniqueName);
312+
return uniqueName;
313+
}
286314
}

modules/openapi-generator/src/test/resources/3_1/java/petstore.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,20 @@ paths:
728728
items:
729729
"$ref": "#/components/schemas/myObject"
730730
- type: 'null'
731+
"/fake/duplicated/operationId":
732+
get:
733+
tags:
734+
- fake
735+
operationId: getPetById
736+
responses:
737+
'200':
738+
description: ''
739+
content:
740+
application/json:
741+
schema:
742+
anyOf:
743+
- type: 'null'
744+
- "$ref": "#/components/schemas/myObject"
731745
externalDocs:
732746
description: Find out more about Swagger
733747
url: 'http://swagger.io'

samples/client/petstore/java/okhttp-gson-3.1/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ Class | Method | HTTP request | Description
116116
*FakeApi* | [**fakeInlineSchemaAnyofPath1Get**](docs/FakeApi.md#fakeInlineSchemaAnyofPath1Get) | **GET** /fake/inline/schema/anyof/path1 |
117117
*FakeApi* | [**fakeInlineSchemaAnyofPath2Get**](docs/FakeApi.md#fakeInlineSchemaAnyofPath2Get) | **GET** /fake/inline/schema/anyof/path2 |
118118
*FakeApi* | [**fakeInlineSchemaAnyofPath3Get**](docs/FakeApi.md#fakeInlineSchemaAnyofPath3Get) | **GET** /fake/inline/schema/anyof/path3 |
119+
*FakeApi* | [**getPetById0**](docs/FakeApi.md#getPetById0) | **GET** /fake/duplicated/operationId |
119120
*FakeApi* | [**op1**](docs/FakeApi.md#op1) | **POST** /fake/api/changeowner | op1
120121
*FakeApi* | [**op2**](docs/FakeApi.md#op2) | **POST** /fake/api/changename | op2
121122
*FakeApi* | [**op3**](docs/FakeApi.md#op3) | **POST** /fake/api/query/enum | op3

samples/client/petstore/java/okhttp-gson-3.1/api/openapi.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,20 @@ paths:
821821
- fake
822822
x-accepts:
823823
- application/json
824+
/fake/duplicated/operationId:
825+
get:
826+
operationId: getPetById_0
827+
responses:
828+
"200":
829+
content:
830+
application/json:
831+
schema:
832+
$ref: '#/components/schemas/myObject'
833+
description: ""
834+
tags:
835+
- fake
836+
x-accepts:
837+
- application/json
824838
components:
825839
parameters:
826840
ref_to_uuid:

samples/client/petstore/java/okhttp-gson-3.1/docs/FakeApi.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ All URIs are relative to *http://petstore.swagger.io/v2*
77
| [**fakeInlineSchemaAnyofPath1Get**](FakeApi.md#fakeInlineSchemaAnyofPath1Get) | **GET** /fake/inline/schema/anyof/path1 | |
88
| [**fakeInlineSchemaAnyofPath2Get**](FakeApi.md#fakeInlineSchemaAnyofPath2Get) | **GET** /fake/inline/schema/anyof/path2 | |
99
| [**fakeInlineSchemaAnyofPath3Get**](FakeApi.md#fakeInlineSchemaAnyofPath3Get) | **GET** /fake/inline/schema/anyof/path3 | |
10+
| [**getPetById0**](FakeApi.md#getPetById0) | **GET** /fake/duplicated/operationId | |
1011
| [**op1**](FakeApi.md#op1) | **POST** /fake/api/changeowner | op1 |
1112
| [**op2**](FakeApi.md#op2) | **POST** /fake/api/changename | op2 |
1213
| [**op3**](FakeApi.md#op3) | **POST** /fake/api/query/enum | op3 |
@@ -186,6 +187,62 @@ No authorization required
186187
|-------------|-------------|------------------|
187188
| **200** | | - |
188189

190+
<a id="getPetById0"></a>
191+
# **getPetById0**
192+
> Object getPetById0()
193+
194+
195+
196+
### Example
197+
```java
198+
// Import classes:
199+
import org.openapitools.client.ApiClient;
200+
import org.openapitools.client.ApiException;
201+
import org.openapitools.client.Configuration;
202+
import org.openapitools.client.models.*;
203+
import org.openapitools.client.api.FakeApi;
204+
205+
public class Example {
206+
public static void main(String[] args) {
207+
ApiClient defaultClient = Configuration.getDefaultApiClient();
208+
defaultClient.setBasePath("http://petstore.swagger.io/v2");
209+
210+
FakeApi apiInstance = new FakeApi(defaultClient);
211+
try {
212+
Object result = apiInstance.getPetById0();
213+
System.out.println(result);
214+
} catch (ApiException e) {
215+
System.err.println("Exception when calling FakeApi#getPetById0");
216+
System.err.println("Status code: " + e.getCode());
217+
System.err.println("Reason: " + e.getResponseBody());
218+
System.err.println("Response headers: " + e.getResponseHeaders());
219+
e.printStackTrace();
220+
}
221+
}
222+
}
223+
```
224+
225+
### Parameters
226+
This endpoint does not need any parameter.
227+
228+
### Return type
229+
230+
**Object**
231+
232+
### Authorization
233+
234+
No authorization required
235+
236+
### HTTP request headers
237+
238+
- **Content-Type**: Not defined
239+
- **Accept**: application/json
240+
241+
### HTTP response details
242+
| Status code | Description | Response headers |
243+
|-------------|-------------|------------------|
244+
| **200** | | - |
245+
189246
<a id="op1"></a>
190247
# **op1**
191248
> Object op1()

samples/client/petstore/java/okhttp-gson-3.1/src/main/java/org/openapitools/client/api/FakeApi.java

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,119 @@ public okhttp3.Call fakeInlineSchemaAnyofPath3GetAsync(final ApiCallback<List<Ob
414414
localVarApiClient.executeAsync(localVarCall, localVarReturnType, _callback);
415415
return localVarCall;
416416
}
417+
/**
418+
* Build call for getPetById0
419+
* @param _callback Callback for upload/download progress
420+
* @return Call to execute
421+
* @throws ApiException If fail to serialize the request body object
422+
* @http.response.details
423+
<table summary="Response Details" border="1">
424+
<tr><td> Status Code </td><td> Description </td><td> Response Headers </td></tr>
425+
<tr><td> 200 </td><td> </td><td> - </td></tr>
426+
</table>
427+
*/
428+
public okhttp3.Call getPetById0Call(final ApiCallback _callback) throws ApiException {
429+
String basePath = null;
430+
// Operation Servers
431+
String[] localBasePaths = new String[] { };
432+
433+
// Determine Base Path to Use
434+
if (localCustomBaseUrl != null){
435+
basePath = localCustomBaseUrl;
436+
} else if ( localBasePaths.length > 0 ) {
437+
basePath = localBasePaths[localHostIndex];
438+
} else {
439+
basePath = null;
440+
}
441+
442+
Object localVarPostBody = null;
443+
444+
// create path and map variables
445+
String localVarPath = "/fake/duplicated/operationId";
446+
447+
List<Pair> localVarQueryParams = new ArrayList<Pair>();
448+
List<Pair> localVarCollectionQueryParams = new ArrayList<Pair>();
449+
Map<String, String> localVarHeaderParams = new HashMap<String, String>();
450+
Map<String, String> localVarCookieParams = new HashMap<String, String>();
451+
Map<String, Object> localVarFormParams = new HashMap<String, Object>();
452+
453+
final String[] localVarAccepts = {
454+
"application/json"
455+
};
456+
final String localVarAccept = localVarApiClient.selectHeaderAccept(localVarAccepts);
457+
if (localVarAccept != null) {
458+
localVarHeaderParams.put("Accept", localVarAccept);
459+
}
460+
461+
final String[] localVarContentTypes = {
462+
};
463+
final String localVarContentType = localVarApiClient.selectHeaderContentType(localVarContentTypes);
464+
if (localVarContentType != null) {
465+
localVarHeaderParams.put("Content-Type", localVarContentType);
466+
}
467+
468+
String[] localVarAuthNames = new String[] { };
469+
return localVarApiClient.buildCall(basePath, localVarPath, "GET", localVarQueryParams, localVarCollectionQueryParams, localVarPostBody, localVarHeaderParams, localVarCookieParams, localVarFormParams, localVarAuthNames, _callback);
470+
}
471+
472+
@SuppressWarnings("rawtypes")
473+
private okhttp3.Call getPetById0ValidateBeforeCall(final ApiCallback _callback) throws ApiException {
474+
return getPetById0Call(_callback);
475+
476+
}
477+
478+
/**
479+
*
480+
*
481+
* @return Object
482+
* @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body
483+
* @http.response.details
484+
<table summary="Response Details" border="1">
485+
<tr><td> Status Code </td><td> Description </td><td> Response Headers </td></tr>
486+
<tr><td> 200 </td><td> </td><td> - </td></tr>
487+
</table>
488+
*/
489+
public Object getPetById0() throws ApiException {
490+
ApiResponse<Object> localVarResp = getPetById0WithHttpInfo();
491+
return localVarResp.getData();
492+
}
493+
494+
/**
495+
*
496+
*
497+
* @return ApiResponse&lt;Object&gt;
498+
* @throws ApiException If fail to call the API, e.g. server error or cannot deserialize the response body
499+
* @http.response.details
500+
<table summary="Response Details" border="1">
501+
<tr><td> Status Code </td><td> Description </td><td> Response Headers </td></tr>
502+
<tr><td> 200 </td><td> </td><td> - </td></tr>
503+
</table>
504+
*/
505+
public ApiResponse<Object> getPetById0WithHttpInfo() throws ApiException {
506+
okhttp3.Call localVarCall = getPetById0ValidateBeforeCall(null);
507+
Type localVarReturnType = new TypeToken<Object>(){}.getType();
508+
return localVarApiClient.execute(localVarCall, localVarReturnType);
509+
}
510+
511+
/**
512+
* (asynchronously)
513+
*
514+
* @param _callback The callback to be executed when the API call finishes
515+
* @return The request call
516+
* @throws ApiException If fail to process the API call, e.g. serializing the request body object
517+
* @http.response.details
518+
<table summary="Response Details" border="1">
519+
<tr><td> Status Code </td><td> Description </td><td> Response Headers </td></tr>
520+
<tr><td> 200 </td><td> </td><td> - </td></tr>
521+
</table>
522+
*/
523+
public okhttp3.Call getPetById0Async(final ApiCallback<Object> _callback) throws ApiException {
524+
525+
okhttp3.Call localVarCall = getPetById0ValidateBeforeCall(_callback);
526+
Type localVarReturnType = new TypeToken<Object>(){}.getType();
527+
localVarApiClient.executeAsync(localVarCall, localVarReturnType, _callback);
528+
return localVarCall;
529+
}
417530
/**
418531
* Build call for op1
419532
* @param _callback Callback for upload/download progress

0 commit comments

Comments
 (0)