[protobuf-schema] Support oneOf and AnyOf (#20798)

* handle primitive oneof

handle anyof

* Add oneof and anyof test in petstore
This commit is contained in:
lucy66hw 2025-03-06 23:10:07 -08:00 committed by GitHub
parent 74e49ac1d2
commit b528e4a7a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 465 additions and 2 deletions

View File

@ -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<CodegenProperty> processOneOfAnyOfItems(List<CodegenProperty> 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

View File

@ -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}}

View File

@ -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<File> 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<File> 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");

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}