[Protobuf Schema] Map Field Handling in Composed Schemas (#21002)

* Address map under composed schema

Add explanation to code block

* add comment to explain the code block
This commit is contained in:
lucy66hw 2025-04-16 01:33:22 -07:00 committed by GitHub
parent 1136872cd5
commit b4378a6277
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 117 additions and 36 deletions

View File

@ -406,7 +406,7 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf
if (ModelUtils.isMapSchema(schema) && ModelUtils.getAdditionalProperties(schema) != null) {
Schema mapValueSchema = ModelUtils.getAdditionalProperties(schema);
mapValueSchema = ModelUtils.getReferencedSchema(openAPI, mapValueSchema);
if (ModelUtils.isArraySchema(mapValueSchema) || ModelUtils.isMapSchema(mapValueSchema)) {
if (ModelUtils.isArraySchema(mapValueSchema) || (ModelUtils.isMapSchema(mapValueSchema) && !ModelUtils.isModel(mapValueSchema))) {
Schema innerSchema = generateNestedSchema(mapValueSchema, visitedSchemas);
schema.setAdditionalProperties(innerSchema);
@ -414,7 +414,7 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf
} else if (ModelUtils.isArraySchema(schema) && ModelUtils.getSchemaItems(schema) != null) {
Schema arrayItemSchema = ModelUtils.getSchemaItems(schema);
arrayItemSchema = ModelUtils.getReferencedSchema(openAPI, arrayItemSchema);
if (ModelUtils.isMapSchema(arrayItemSchema) || ModelUtils.isArraySchema(arrayItemSchema)) {
if ((ModelUtils.isMapSchema(arrayItemSchema) && !ModelUtils.isModel(arrayItemSchema)) || ModelUtils.isArraySchema(arrayItemSchema)) {
Schema innerSchema = generateNestedSchema(arrayItemSchema, visitedSchemas);
schema.setItems(innerSchema);
}
@ -427,7 +427,7 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf
Schema innerSchema = generateNestedSchema(oneOfSchema, visitedSchemas);
innerSchema.setTitle(oneOf.getTitle());
newOneOfs.add(innerSchema);
} else if (ModelUtils.isMapSchema(oneOfSchema)) {
} else if (ModelUtils.isMapSchema(oneOfSchema) && !ModelUtils.isModel(oneOfSchema)) {
Schema innerSchema = generateNestedSchema(oneOfSchema, visitedSchemas);
innerSchema.setTitle(oneOf.getTitle());
newOneOfs.add(innerSchema);
@ -1061,4 +1061,38 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf
return GeneratorLanguage.PROTOBUF;
}
/**
* Handles additionalProperties defined in composed schemas (e.g., allOf) by injecting into the model's properties.
* Example:
* components:
* schemas:
* Dog:
* allOf:
* - $ref: '#/components/schemas/DogBase'
* - type: object
* additionalProperties:
* title: pet
* $ref: '#/components/schemas/Pet'
* In this case, the second allOf that defines a map with string keys and Pet values will be part of model's property.
*/
@Override
protected void addProperties(Map<String, Schema> properties, List<String> required, Schema schema, Set<Schema> visitedSchemas){
super.addProperties(properties, required, schema, visitedSchemas);
if(schema.getAdditionalProperties() != null) {
String addtionalPropertiesName = "default_map";
if(schema.getTitle() != null) {
addtionalPropertiesName = schema.getTitle();
} else {
Schema additionalProperties = ModelUtils.getAdditionalProperties(schema);
if (additionalProperties.getTitle() != null) {
addtionalPropertiesName = additionalProperties.getTitle();
} else if (additionalProperties.get$ref() != null) {
String ref = ModelUtils.getSimpleRef(additionalProperties.get$ref());
addtionalPropertiesName = toVarName(toModelName(ref));
}
}
properties.put(addtionalPropertiesName, schema);
}
}
}

View File

@ -34,13 +34,22 @@ externalDocs:
components:
schemas:
Dog:
type: object
allOf:
- type: object
properties:
bark:
type: boolean
breed:
type: string
enum: [Dingo, Husky, Retriever, Shepherd]
- type: object
propertyNames:
title: field
type: string
additionalProperties:
title: pet
$ref: '#/components/schemas/Pet'
minProperties: 1
Cat:
type: object
properties:
@ -111,11 +120,8 @@ components:
type: string
tags:
type: array
xml:
name: tag
wrapped: true
items:
type: array
type: object
additionalProperties:
$ref: '#/components/schemas/Tag'
status:
@ -126,5 +132,3 @@ components:
- available
- pending
- sold
xml:
name: Pet

View File

@ -6,5 +6,6 @@ models/pet.proto
models/string_array.proto
models/string_map.proto
models/tag.proto
models/tag_map.proto
models/tag_name.proto
services/default_service.proto

View File

@ -12,6 +12,7 @@ syntax = "proto3";
package petstore;
import public "models/pet.proto";
message Dog {
@ -27,5 +28,7 @@ message Dog {
Breed breed = 2;
map<string, Pet> pet = 3;
}

View File

@ -13,6 +13,7 @@ syntax = "proto3";
package petstore;
import public "models/category.proto";
import public "models/tag_map.proto";
message Pet {
@ -20,5 +21,21 @@ message Pet {
Category category = 2;
string name = 3;
repeated string photo_urls = 4 [json_name="photoUrls"];
repeated TagMap tags = 5;
// pet status in the store
enum Status {
STATUS_UNSPECIFIED = 0;
STATUS_AVAILABLE = 1;
STATUS_PENDING = 2;
STATUS_SOLD = 3;
}
Status status = 6;
}

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;
import public "models/tag.proto";
message TagMap {
map<string, Tag> tag_map = 1;
}