Skip to content

Commit 3b0bb0a

Browse files
authored
[Protobuf Schema] Add Support for Multiple Response (#20989)
* Protobuf Schema Support Multiple Response * change the supportMultiple response to true * address build failure * address build failure
1 parent 36287ac commit 3b0bb0a

File tree

13 files changed

+273
-45
lines changed

13 files changed

+273
-45
lines changed

bin/configs/protobuf-schema-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ additionalProperties:
88
numberedFieldNumberList: true
99
startEnumsWithUnspecified: true
1010
wrapComplexType: false
11+
supportMultipleResponses: false
1112
aggregateModelsName: data
1213
typeMappings:
1314
object: "google.protobuf.Struct"

docs/generators/protobuf-schema.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
2222
|aggregateModelsName|Aggregated model filename. If set, all generated models will be combined into this single file.| |null|
2323
|numberedFieldNumberList|Field numbers in order.| |false|
2424
|startEnumsWithUnspecified|Introduces "UNSPECIFIED" as the first element of enumerations.| |false|
25+
|supportMultipleResponses|Support multiple responses| |true|
2526
|wrapComplexType|Generate Additional message for complex type| |true|
2627

2728
## IMPORT MAPPING

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

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf
6868

6969
public static final String AGGREGATE_MODELS_NAME = "aggregateModelsName";
7070

71+
public static final String SUPPORT_MULTIPLE_RESPONSES = "supportMultipleResponses";
72+
7173
private final Logger LOGGER = LoggerFactory.getLogger(ProtobufSchemaCodegen.class);
7274

7375
@Setter protected String packageName = "openapitools";
@@ -82,6 +84,8 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf
8284

8385
private boolean wrapComplexType = true;
8486

87+
private boolean supportMultipleResponses = true;
88+
8589
@Override
8690
public CodegenType getTag() {
8791
return CodegenType.SCHEMA;
@@ -192,6 +196,7 @@ public ProtobufSchemaCodegen() {
192196
addSwitch(START_ENUMS_WITH_UNSPECIFIED, "Introduces \"UNSPECIFIED\" as the first element of enumerations.", startEnumsWithUnspecified);
193197
addSwitch(ADD_JSON_NAME_ANNOTATION, "Append \"json_name\" annotation to message field when the specification name differs from the protobuf field name", addJsonNameAnnotation);
194198
addSwitch(WRAP_COMPLEX_TYPE, "Generate Additional message for complex type", wrapComplexType);
199+
addSwitch(SUPPORT_MULTIPLE_RESPONSES, "Support multiple responses", supportMultipleResponses);
195200
addOption(AGGREGATE_MODELS_NAME, "Aggregated model filename. If set, all generated models will be combined into this single file.", null);
196201
}
197202

@@ -239,6 +244,12 @@ public void processOpts() {
239244
this.setAggregateModelsName((String) additionalProperties.get(AGGREGATE_MODELS_NAME));
240245
}
241246

247+
if (additionalProperties.containsKey(this.SUPPORT_MULTIPLE_RESPONSES)) {
248+
this.supportMultipleResponses = convertPropertyToBooleanAndWriteBack(SUPPORT_MULTIPLE_RESPONSES);
249+
} else {
250+
additionalProperties.put(this.SUPPORT_MULTIPLE_RESPONSES, this.supportMultipleResponses);
251+
}
252+
242253
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
243254
}
244255

@@ -442,7 +453,6 @@ private void wrapModels() {
442453
} else if (ModelUtils.isAnyOf(schema)) {
443454
wrapComposedChildren(schema.getAnyOf(), visitedSchema);
444455
}
445-
446456
}
447457
}
448458

@@ -475,7 +485,6 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
475485
}
476486
}
477487

478-
479488
/**
480489
* Adds prefix to the enum allowable values
481490
* NOTE: Enum values use C++ scoping rules, meaning that enum values are siblings of their type, not children of it. Therefore, enum value must be unique
@@ -522,7 +531,9 @@ public void addUnspecifiedToAllowableValues(Map<String, Object> allowableValues)
522531

523532
if (allowableValues.containsKey("values")) {
524533
List<String> values = (List<String>) allowableValues.get("values");
525-
values.add(0, "UNSPECIFIED");
534+
List<String> modifiableValues = new ArrayList<>(values);
535+
modifiableValues.add(0, "UNSPECIFIED");
536+
allowableValues.put("values", modifiableValues);
526537
}
527538
}
528539
}
@@ -542,24 +553,28 @@ public void addEnumIndexes(List<Map<String, Object>> enumVars) {
542553

543554
public List<CodegenProperty> processOneOfAnyOfItems(List<CodegenProperty> composedSchemasProperty) {
544555
for(CodegenProperty cd: composedSchemasProperty) {
545-
if (cd.getTitle() != null) {
546-
cd.name = cd.getTitle();
547-
cd.baseName = cd.getTitle();
548-
} else{
549-
cd.name = getNameFromDataType(cd);
550-
cd.baseName = getNameFromDataType(cd);
551-
}
556+
cd.name = resolveVarName(cd);
557+
cd.baseName = resolveVarName(cd);
552558
}
553559
return composedSchemasProperty;
554560
}
555561

562+
563+
private String resolveVarName(CodegenProperty property) {
564+
if(property.getTitle() != null) {
565+
return toVarName(property.getTitle());
566+
} else {
567+
return getNameFromDataType(property);
568+
}
569+
}
570+
556571
public String getNameFromDataType(CodegenProperty property) {
557572
if (Boolean.TRUE.equals(property.getIsArray())){
558-
return underscore(property.mostInnerItems.dataType + ARRAY_SUFFIX);
573+
return toVarName(property.mostInnerItems.dataType + ARRAY_SUFFIX);
559574
} else if (Boolean.TRUE.equals(property.getIsMap())) {
560-
return underscore(property.mostInnerItems.dataType + MAP_SUFFIX);
575+
return toVarName(property.mostInnerItems.dataType + MAP_SUFFIX);
561576
} else {
562-
return underscore(property.dataType);
577+
return toVarName(property.dataType);
563578
}
564579
}
565580

@@ -944,12 +959,41 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
944959
}
945960
}
946961
}
962+
963+
if(this.supportMultipleResponses) {
964+
int responseIdx = 1;
965+
op.vendorExtensions.put("x-grpc-response", op.operationId+"Response");
966+
for (CodegenResponse r : op.responses) {
967+
if (r.returnProperty == null) {
968+
r.vendorExtensions.put("x-oneOf-response-type", "google.protobuf.Empty");
969+
r.vendorExtensions.put("x-oneOf-response-name", "empty");
970+
} else if (r.isMap && r.additionalProperties != null) {
971+
r.vendorExtensions.put("x-oneOf-response-type", r.returnProperty.additionalProperties.dataType);
972+
r.vendorExtensions.put("x-oneOf-response-name", resolveVarName(r.returnProperty.additionalProperties));
973+
LOGGER.warn("Mapping responses for operations with supportMultipleResponses flag (operation ID: {}) is not currently supported.", op.operationId);
974+
} else if (r.isArray && r.items != null) {
975+
r.vendorExtensions.put("x-oneOf-response-type", r.returnProperty.items.dataType);
976+
r.vendorExtensions.put("x-oneOf-response-name", resolveVarName(r.returnProperty.items));
977+
LOGGER.warn("Array responses for operations with supportMultipleResponses flag (operation ID: {}) is not currently supported.", op.operationId);
978+
}
979+
else {
980+
r.vendorExtensions.put("x-oneOf-response-type", r.returnProperty.dataType);
981+
r.vendorExtensions.put("x-oneOf-response-name", resolveVarName(r.returnProperty));
982+
}
983+
r.vendorExtensions.put("x-oneOf-response-index", responseIdx++);
984+
}
985+
}
947986
}
948987

949988
if (this.aggregateModelsName != null) {
989+
List<Map<String, String>> imports = objs.getImports().stream()
990+
.filter(importMap -> !importMap.get("import").startsWith("models/"))
991+
.collect(Collectors.toList());
992+
950993
List<Map<String, String>> aggregate_imports = Collections.singletonList(Collections
951994
.singletonMap(IMPORT, toModelImport(this.aggregateModelsName)));
952-
objs.setImports(aggregate_imports);
995+
imports.addAll(aggregate_imports);
996+
objs.setImports(imports);
953997
}
954998
return objs;
955999
}

modules/openapi-generator/src/main/resources/protobuf-schema/api.mustache

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,14 @@ message {{operationId}}Response {
4242
}
4343

4444
{{/vendorExtensions.x-grpc-response}}
45+
{{#supportMultipleResponses}}
46+
message {{operationId}}Response {
47+
oneof response {
48+
{{#responses}}
49+
{{{vendorExtensions.x-oneOf-response-type}}} {{vendorExtensions.x-oneOf-response-name}}_{{code}} = {{vendorExtensions.x-oneOf-response-index}};
50+
{{/responses}}
51+
}
52+
}
53+
{{/supportMultipleResponses}}
4554
{{/operation}}
4655
{{/operations}}

modules/openapi-generator/src/test/resources/3_0/protobuf/petstore.yaml

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ paths:
5959
schema:
6060
$ref: '#/components/schemas/Pet'
6161
'405':
62-
description: Invalid input
62+
$ref: '#/components/responses/ErrorResponse'
6363
security:
6464
- petstore_auth:
6565
- 'write:pets'
@@ -83,11 +83,11 @@ paths:
8383
schema:
8484
$ref: '#/components/schemas/Pet'
8585
'400':
86-
description: Invalid ID supplied
86+
$ref: '#/components/responses/ErrorResponse'
8787
'404':
88-
description: Pet not found
88+
$ref: '#/components/responses/ErrorResponse'
8989
'405':
90-
description: Validation exception
90+
$ref: '#/components/responses/ErrorResponse'
9191
security:
9292
- petstore_auth:
9393
- 'write:pets'
@@ -593,6 +593,13 @@ externalDocs:
593593
description: Find out more about Swagger
594594
url: 'http://swagger.io'
595595
components:
596+
responses:
597+
ErrorResponse:
598+
description: An error response.
599+
content:
600+
application/json:
601+
schema:
602+
$ref: '#/components/schemas/Error'
596603
requestBodies:
597604
UserArray:
598605
content:
@@ -786,3 +793,16 @@ components:
786793
uniqueItems: true
787794
items:
788795
type: string
796+
Error:
797+
type: object
798+
required:
799+
- code
800+
- message
801+
properties:
802+
code:
803+
type: integer
804+
format: int32
805+
description: Error code.
806+
message:
807+
type: string
808+
description: Detailed error message.

samples/config/petstore/protobuf-schema-config-complex/services/default_service.proto

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import "google/protobuf/empty.proto";
1616
import public "models/cat.proto";
1717

1818
service DefaultService {
19-
rpc PetsGet (PetsGetRequest) returns (google.protobuf.Empty);
19+
rpc PetsGet (PetsGetRequest) returns (PetsGetResponse);
2020

2121
}
2222

@@ -25,3 +25,8 @@ message PetsGetRequest {
2525

2626
}
2727

28+
message PetsGetResponse {
29+
oneof response {
30+
google.protobuf.Empty empty_200 = 1;
31+
}
32+
}

samples/config/petstore/protobuf-schema-config/models/data.proto

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ message Dog {
5656

5757
}
5858

59+
message Error {
60+
61+
// Error code.
62+
int32 code = 1;
63+
64+
// Detailed error message.
65+
string message = 2;
66+
67+
}
68+
5969
message Order {
6070

6171
int64 id = 1;

samples/config/petstore/protobuf-schema/.openapi-generator/FILES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ models/api_response.proto
33
models/cat.proto
44
models/category.proto
55
models/dog.proto
6+
models/error.proto
67
models/order.proto
78
models/other_test.proto
89
models/pet.proto
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
OpenAPI Petstore
3+
4+
This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
5+
6+
The version of the OpenAPI document: 1.0.0
7+
8+
Generated by OpenAPI Generator: https://openapi-generator.tech
9+
*/
10+
11+
syntax = "proto3";
12+
13+
package petstore;
14+
15+
16+
message Error {
17+
18+
// Error code.
19+
int32 code = 3059181;
20+
21+
// Detailed error message.
22+
string message = 418054152;
23+
24+
}
25+

samples/config/petstore/protobuf-schema/services/default_service.proto

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ import public "models/pets_get_request.proto";
1717
import public "models/pets_post_request.proto";
1818

1919
service DefaultService {
20-
rpc PetsGet (PetsGetRequest) returns (google.protobuf.Empty);
20+
rpc PetsGet (PetsGetRequest) returns (PetsGetResponse);
2121

22-
rpc PetsPost (PetsPostRequest) returns (google.protobuf.Empty);
22+
rpc PetsPost (PetsPostRequest) returns (PetsPostResponse);
2323

2424
}
2525

@@ -28,8 +28,18 @@ message PetsGetRequest {
2828

2929
}
3030

31+
message PetsGetResponse {
32+
oneof response {
33+
google.protobuf.Empty empty_200 = 1;
34+
}
35+
}
3136
message PetsPostRequest {
3237
PetsPostRequest pets_post_request = 1;
3338

3439
}
3540

41+
message PetsPostResponse {
42+
oneof response {
43+
google.protobuf.Empty empty_200 = 1;
44+
}
45+
}

0 commit comments

Comments
 (0)