From b528e4a7a7e5ce2332864bff19bc1401ad24ac7a Mon Sep 17 00:00:00 2001 From: lucy66hw Date: Thu, 6 Mar 2025 23:10:07 -0800 Subject: [PATCH] [protobuf-schema] Support oneOf and AnyOf (#20798) * handle primitive oneof handle anyof * Add oneof and anyof test in petstore --- .../languages/ProtobufSchemaCodegen.java | 36 +++++++++++++- .../resources/protobuf-schema/model.mustache | 14 ++++++ .../protobuf/ProtobufSchemaCodegenTest.java | 48 +++++++++++++++++++ .../3_0/protobuf-schema/fruitAnyOf.proto | 25 ++++++++++ .../3_0/protobuf-schema/fruitOneOf.proto | 27 +++++++++++ .../test/resources/3_0/protobuf/petstore.yaml | 38 +++++++++++++++ .../.openapi-generator/FILES | 5 ++ .../protobuf-schema-config/models/cat.proto | 22 +++++++++ .../protobuf-schema-config/models/dog.proto | 30 ++++++++++++ .../models/pets_get_request.proto | 24 ++++++++++ .../models/pets_post_request.proto | 24 ++++++++++ .../services/default_service.proto | 35 ++++++++++++++ .../protobuf-schema/.openapi-generator/FILES | 5 ++ .../petstore/protobuf-schema/models/cat.proto | 22 +++++++++ .../petstore/protobuf-schema/models/dog.proto | 29 +++++++++++ .../models/pets_get_request.proto | 24 ++++++++++ .../models/pets_post_request.proto | 24 ++++++++++ .../services/default_service.proto | 35 ++++++++++++++ 18 files changed, 465 insertions(+), 2 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/3_0/protobuf-schema/fruitAnyOf.proto create mode 100644 modules/openapi-generator/src/test/resources/3_0/protobuf-schema/fruitOneOf.proto create mode 100644 samples/config/petstore/protobuf-schema-config/models/cat.proto create mode 100644 samples/config/petstore/protobuf-schema-config/models/dog.proto create mode 100644 samples/config/petstore/protobuf-schema-config/models/pets_get_request.proto create mode 100644 samples/config/petstore/protobuf-schema-config/models/pets_post_request.proto create mode 100644 samples/config/petstore/protobuf-schema-config/services/default_service.proto create mode 100644 samples/config/petstore/protobuf-schema/models/cat.proto create mode 100644 samples/config/petstore/protobuf-schema/models/dog.proto create mode 100644 samples/config/petstore/protobuf-schema/models/pets_get_request.proto create mode 100644 samples/config/petstore/protobuf-schema/models/pets_post_request.proto create mode 100644 samples/config/petstore/protobuf-schema/services/default_service.proto diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ProtobufSchemaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ProtobufSchemaCodegen.java index cb398cd421e..f5406a737f3 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ProtobufSchemaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ProtobufSchemaCodegen.java @@ -40,8 +40,7 @@ import java.util.Map.Entry; import java.util.regex.Pattern; import com.google.common.base.CaseFormat; -import static org.openapitools.codegen.utils.StringUtils.camelize; -import static org.openapitools.codegen.utils.StringUtils.underscore; +import static org.openapitools.codegen.utils.StringUtils.*; public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConfig { @@ -49,6 +48,10 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf private static final String IMPORTS = "imports"; + private static final String ARRAY_SUFFIX = "Array"; + + private static final String MAP_SUFFIX = "Map"; + public static final String NUMBERED_FIELD_NUMBER_LIST = "numberedFieldNumberList"; public static final String START_ENUMS_WITH_UNSPECIFIED = "startEnumsWithUnspecified"; @@ -295,6 +298,30 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf } } + public List processOneOfAnyOfItems(List composedSchemasProperty) { + for(CodegenProperty cd: composedSchemasProperty) { + if (cd.getTitle() != null) { + cd.name = cd.getTitle(); + cd.baseName = cd.getTitle(); + } else{ + cd.name = getNameFromDataType(cd); + cd.baseName = getNameFromDataType(cd); + } + } + return composedSchemasProperty; + } + + public String getNameFromDataType(CodegenProperty property) { + if (Boolean.TRUE.equals(property.getIsArray())){ + return underscore(property.mostInnerItems.dataType + ARRAY_SUFFIX); + } else if (Boolean.TRUE.equals(property.getIsMap())) { + return underscore(property.mostInnerItems.dataType + MAP_SUFFIX); + } else { + return underscore(property.dataType); + } + } + + @Override public ModelsMap postProcessModels(ModelsMap objs) { objs = postProcessModelsEnum(objs); @@ -312,6 +339,11 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf } } + if(cm.oneOf != null && !cm.oneOf.isEmpty()){ + cm.vars = processOneOfAnyOfItems(cm.getComposedSchemas().getOneOf()); + } else if (cm.anyOf != null && !cm.anyOf.isEmpty()) { + cm.vars = processOneOfAnyOfItems(cm.getComposedSchemas().getAnyOf()); + } int index = 1; for (CodegenProperty var : cm.vars) { // add x-protobuf-type: repeated if it's an array diff --git a/modules/openapi-generator/src/main/resources/protobuf-schema/model.mustache b/modules/openapi-generator/src/main/resources/protobuf-schema/model.mustache index 88f1644f592..6872f57f18d 100644 --- a/modules/openapi-generator/src/main/resources/protobuf-schema/model.mustache +++ b/modules/openapi-generator/src/main/resources/protobuf-schema/model.mustache @@ -13,6 +13,19 @@ import public "{{{modelPackage}}}/{{{import}}}.proto"; {{#model}} {{#isEnum}}{{>enum}}{{/isEnum}}{{^isEnum}}message {{classname}} { +{{#oneOf}} +{{#-first}} + oneof {{classVarName}} { + {{#vars}} + {{#description}} + // {{{.}}} + {{/description}} + {{#vendorExtensions.x-protobuf-type}}{{{.}}} {{/vendorExtensions.x-protobuf-type}}{{{vendorExtensions.x-protobuf-data-type}}} {{{name}}} = {{vendorExtensions.x-protobuf-index}}{{#vendorExtensions.x-protobuf-packed}} [packed=true]{{/vendorExtensions.x-protobuf-packed}}; + {{/vars}} + } +{{/-first}} +{{/oneOf}} +{{^oneOf}} {{#vars}} {{#description}} // {{{.}}} @@ -33,6 +46,7 @@ import public "{{{modelPackage}}}/{{{import}}}.proto"; {{/isEnum}} {{/vars}} +{{/oneOf}} } {{/isEnum}} {{/model}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/protobuf/ProtobufSchemaCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/protobuf/ProtobufSchemaCodegenTest.java index e621703e67d..0ab92922dd7 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/protobuf/ProtobufSchemaCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/protobuf/ProtobufSchemaCodegenTest.java @@ -82,6 +82,54 @@ public class ProtobufSchemaCodegenTest { assertEquals(generatedFile, expectedFile); } + @Test + public void testCodeGenWithPrimitiveOneOf() throws IOException { + // set line break to \n across all platforms + System.setProperty("line.separator", "\n"); + + File output = Files.createTempDirectory("test").toFile(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("protobuf-schema") + .setInputSpec("src/test/resources/3_0/oneOf.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + + TestUtils.ensureContainsFile(files, output, "models/fruit.proto"); + Path path = Paths.get(output + "/models/fruit.proto"); + + assertFileEquals(path, Paths.get("src/test/resources/3_0/protobuf-schema/fruitOneOf.proto")); + + output.deleteOnExit(); + } + + @Test + public void testCodeGenWithPrimitiveAnyOf() throws IOException { + // set line break to \n across all platforms + System.setProperty("line.separator", "\n"); + + File output = Files.createTempDirectory("test").toFile(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("protobuf-schema") + .setInputSpec("src/test/resources/3_0/anyOf.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + + TestUtils.ensureContainsFile(files, output, "models/fruit.proto"); + Path path = Paths.get(output + "/models/fruit.proto"); + + assertFileEquals(path, Paths.get("src/test/resources/3_0/protobuf-schema/fruitAnyOf.proto")); + + output.deleteOnExit(); + } + @Test(description = "convert a model with dollar signs") public void modelTest() { final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/dollar-in-names-pull14359.yaml"); diff --git a/modules/openapi-generator/src/test/resources/3_0/protobuf-schema/fruitAnyOf.proto b/modules/openapi-generator/src/test/resources/3_0/protobuf-schema/fruitAnyOf.proto new file mode 100644 index 00000000000..7d8adbbeaf6 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/protobuf-schema/fruitAnyOf.proto @@ -0,0 +1,25 @@ +/* + fruity + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 0.0.1 + + Generated by OpenAPI Generator: https://openapi-generator.tech +*/ + +syntax = "proto3"; + +package openapitools; + +import public "models/apple.proto"; +import public "models/banana.proto"; + +message Fruit { + + + Apple apple = 93029210; + + Banana banana = 322613405; + +} \ No newline at end of file diff --git a/modules/openapi-generator/src/test/resources/3_0/protobuf-schema/fruitOneOf.proto b/modules/openapi-generator/src/test/resources/3_0/protobuf-schema/fruitOneOf.proto new file mode 100644 index 00000000000..f73dd578ba1 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/protobuf-schema/fruitOneOf.proto @@ -0,0 +1,27 @@ +/* + fruity + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 0.0.1 + + Generated by OpenAPI Generator: https://openapi-generator.tech +*/ + +syntax = "proto3"; + +package openapitools; + +import public "models/apple.proto"; +import public "models/banana.proto"; + +message Fruit { + + oneof fruit { + Apple apple = 93029210; + + Banana banana = 322613405; + + } + +} \ No newline at end of file diff --git a/modules/openapi-generator/src/test/resources/3_0/protobuf/petstore.yaml b/modules/openapi-generator/src/test/resources/3_0/protobuf/petstore.yaml index 67a1d25efb6..2c4ee2647cd 100644 --- a/modules/openapi-generator/src/test/resources/3_0/protobuf/petstore.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/protobuf/petstore.yaml @@ -18,6 +18,29 @@ tags: - name: user description: Operations about user paths: + /pets: + get: + requestBody: + content: + application/json: + schema: + oneOf: + - $ref: "#/components/schemas/Cat" + - $ref: "#/components/schemas/Dog" + responses: + "200": + description: Updated + post: + requestBody: + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/Cat" + - $ref: "#/components/schemas/Dog" + responses: + "200": + description: Updated /pet: post: tags: @@ -604,6 +627,21 @@ components: name: api_key in: header schemas: + Dog: + type: object + properties: + bark: + type: boolean + breed: + type: string + enum: [ Dingo, Husky, Retriever, Shepherd ] + Cat: + type: object + properties: + hunts: + type: boolean + age: + type: integer Order: title: Pet Order description: An order for a pets from the pet store diff --git a/samples/config/petstore/protobuf-schema-config/.openapi-generator/FILES b/samples/config/petstore/protobuf-schema-config/.openapi-generator/FILES index c65218a7166..f4da595c7e4 100644 --- a/samples/config/petstore/protobuf-schema-config/.openapi-generator/FILES +++ b/samples/config/petstore/protobuf-schema-config/.openapi-generator/FILES @@ -1,11 +1,16 @@ README.md models/api_response.proto +models/cat.proto models/category.proto +models/dog.proto models/order.proto models/other_test.proto models/pet.proto +models/pets_get_request.proto +models/pets_post_request.proto models/tag.proto models/user.proto +services/default_service.proto services/pet_service.proto services/store_service.proto services/user_service.proto diff --git a/samples/config/petstore/protobuf-schema-config/models/cat.proto b/samples/config/petstore/protobuf-schema-config/models/cat.proto new file mode 100644 index 00000000000..e3c752c6e07 --- /dev/null +++ b/samples/config/petstore/protobuf-schema-config/models/cat.proto @@ -0,0 +1,22 @@ +/* + OpenAPI Petstore + + This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + + The version of the OpenAPI document: 1.0.0 + + Generated by OpenAPI Generator: https://openapi-generator.tech +*/ + +syntax = "proto3"; + +package petstore; + + +message Cat { + + bool hunts = 1; + + int32 age = 2; + +} diff --git a/samples/config/petstore/protobuf-schema-config/models/dog.proto b/samples/config/petstore/protobuf-schema-config/models/dog.proto new file mode 100644 index 00000000000..ced49dd8d38 --- /dev/null +++ b/samples/config/petstore/protobuf-schema-config/models/dog.proto @@ -0,0 +1,30 @@ +/* + OpenAPI Petstore + + This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + + The version of the OpenAPI document: 1.0.0 + + Generated by OpenAPI Generator: https://openapi-generator.tech +*/ + +syntax = "proto3"; + +package petstore; + + +message Dog { + + bool bark = 1; + + enum Breed { + BREED_UNSPECIFIED = 0; + BREED_DINGO = 1; + BREED_HUSKY = 2; + BREED_RETRIEVER = 3; + BREED_SHEPHERD = 4; + } + + Breed breed = 2; + +} diff --git a/samples/config/petstore/protobuf-schema-config/models/pets_get_request.proto b/samples/config/petstore/protobuf-schema-config/models/pets_get_request.proto new file mode 100644 index 00000000000..fbc5191fa4a --- /dev/null +++ b/samples/config/petstore/protobuf-schema-config/models/pets_get_request.proto @@ -0,0 +1,24 @@ +/* + OpenAPI Petstore + + This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + + The version of the OpenAPI document: 1.0.0 + + Generated by OpenAPI Generator: https://openapi-generator.tech +*/ + +syntax = "proto3"; + +package petstore; + +import public "models/cat.proto"; +import public "models/dog.proto"; + +message PetsGetRequest { + + oneof pets_get_request { + Cat cat = 1; + Dog dog = 2; + } +} diff --git a/samples/config/petstore/protobuf-schema-config/models/pets_post_request.proto b/samples/config/petstore/protobuf-schema-config/models/pets_post_request.proto new file mode 100644 index 00000000000..ae8fed2bbe4 --- /dev/null +++ b/samples/config/petstore/protobuf-schema-config/models/pets_post_request.proto @@ -0,0 +1,24 @@ +/* + OpenAPI Petstore + + This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + + The version of the OpenAPI document: 1.0.0 + + Generated by OpenAPI Generator: https://openapi-generator.tech +*/ + +syntax = "proto3"; + +package petstore; + +import public "models/cat.proto"; +import public "models/dog.proto"; + +message PetsPostRequest { + + Cat cat = 1; + + Dog dog = 2; + +} diff --git a/samples/config/petstore/protobuf-schema-config/services/default_service.proto b/samples/config/petstore/protobuf-schema-config/services/default_service.proto new file mode 100644 index 00000000000..d04c2d4cedd --- /dev/null +++ b/samples/config/petstore/protobuf-schema-config/services/default_service.proto @@ -0,0 +1,35 @@ +/* + OpenAPI Petstore + + This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + + The version of the OpenAPI document: 1.0.0 + + Generated by OpenAPI Generator: https://openapi-generator.tech +*/ + +syntax = "proto3"; + +package petstore.services.defaultservice; + +import "google/protobuf/empty.proto"; +import public "models/pets_get_request.proto"; +import public "models/pets_post_request.proto"; + +service DefaultService { + rpc PetsGet (PetsGetRequest) returns (google.protobuf.Empty); + + rpc PetsPost (PetsPostRequest) returns (google.protobuf.Empty); + +} + +message PetsGetRequest { + PetsGetRequest pets_get_request = 1; + +} + +message PetsPostRequest { + PetsPostRequest pets_post_request = 1; + +} + diff --git a/samples/config/petstore/protobuf-schema/.openapi-generator/FILES b/samples/config/petstore/protobuf-schema/.openapi-generator/FILES index c65218a7166..f4da595c7e4 100644 --- a/samples/config/petstore/protobuf-schema/.openapi-generator/FILES +++ b/samples/config/petstore/protobuf-schema/.openapi-generator/FILES @@ -1,11 +1,16 @@ README.md models/api_response.proto +models/cat.proto models/category.proto +models/dog.proto models/order.proto models/other_test.proto models/pet.proto +models/pets_get_request.proto +models/pets_post_request.proto models/tag.proto models/user.proto +services/default_service.proto services/pet_service.proto services/store_service.proto services/user_service.proto diff --git a/samples/config/petstore/protobuf-schema/models/cat.proto b/samples/config/petstore/protobuf-schema/models/cat.proto new file mode 100644 index 00000000000..e03098050fa --- /dev/null +++ b/samples/config/petstore/protobuf-schema/models/cat.proto @@ -0,0 +1,22 @@ +/* + OpenAPI Petstore + + This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + + The version of the OpenAPI document: 1.0.0 + + Generated by OpenAPI Generator: https://openapi-generator.tech +*/ + +syntax = "proto3"; + +package petstore; + + +message Cat { + + bool hunts = 99641152; + + int32 age = 96511; + +} diff --git a/samples/config/petstore/protobuf-schema/models/dog.proto b/samples/config/petstore/protobuf-schema/models/dog.proto new file mode 100644 index 00000000000..149370c2afd --- /dev/null +++ b/samples/config/petstore/protobuf-schema/models/dog.proto @@ -0,0 +1,29 @@ +/* + OpenAPI Petstore + + This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + + The version of the OpenAPI document: 1.0.0 + + Generated by OpenAPI Generator: https://openapi-generator.tech +*/ + +syntax = "proto3"; + +package petstore; + + +message Dog { + + bool bark = 3016376; + + enum Breed { + BREED_DINGO = 0; + BREED_HUSKY = 1; + BREED_RETRIEVER = 2; + BREED_SHEPHERD = 3; + } + + Breed breed = 94001524; + +} diff --git a/samples/config/petstore/protobuf-schema/models/pets_get_request.proto b/samples/config/petstore/protobuf-schema/models/pets_get_request.proto new file mode 100644 index 00000000000..40dc11586d6 --- /dev/null +++ b/samples/config/petstore/protobuf-schema/models/pets_get_request.proto @@ -0,0 +1,24 @@ +/* + OpenAPI Petstore + + This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + + The version of the OpenAPI document: 1.0.0 + + Generated by OpenAPI Generator: https://openapi-generator.tech +*/ + +syntax = "proto3"; + +package petstore; + +import public "models/cat.proto"; +import public "models/dog.proto"; + +message PetsGetRequest { + + oneof pets_get_request { + Cat cat = 98262; + Dog dog = 99644; + } +} diff --git a/samples/config/petstore/protobuf-schema/models/pets_post_request.proto b/samples/config/petstore/protobuf-schema/models/pets_post_request.proto new file mode 100644 index 00000000000..d77af7e8a18 --- /dev/null +++ b/samples/config/petstore/protobuf-schema/models/pets_post_request.proto @@ -0,0 +1,24 @@ +/* + OpenAPI Petstore + + This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + + The version of the OpenAPI document: 1.0.0 + + Generated by OpenAPI Generator: https://openapi-generator.tech +*/ + +syntax = "proto3"; + +package petstore; + +import public "models/cat.proto"; +import public "models/dog.proto"; + +message PetsPostRequest { + + Cat cat = 98262; + + Dog dog = 99644; + +} diff --git a/samples/config/petstore/protobuf-schema/services/default_service.proto b/samples/config/petstore/protobuf-schema/services/default_service.proto new file mode 100644 index 00000000000..d04c2d4cedd --- /dev/null +++ b/samples/config/petstore/protobuf-schema/services/default_service.proto @@ -0,0 +1,35 @@ +/* + OpenAPI Petstore + + This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters. + + The version of the OpenAPI document: 1.0.0 + + Generated by OpenAPI Generator: https://openapi-generator.tech +*/ + +syntax = "proto3"; + +package petstore.services.defaultservice; + +import "google/protobuf/empty.proto"; +import public "models/pets_get_request.proto"; +import public "models/pets_post_request.proto"; + +service DefaultService { + rpc PetsGet (PetsGetRequest) returns (google.protobuf.Empty); + + rpc PetsPost (PetsPostRequest) returns (google.protobuf.Empty); + +} + +message PetsGetRequest { + PetsGetRequest pets_get_request = 1; + +} + +message PetsPostRequest { + PetsPostRequest pets_post_request = 1; + +} +