[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) { if (ModelUtils.isMapSchema(schema) && ModelUtils.getAdditionalProperties(schema) != null) {
Schema mapValueSchema = ModelUtils.getAdditionalProperties(schema); Schema mapValueSchema = ModelUtils.getAdditionalProperties(schema);
mapValueSchema = ModelUtils.getReferencedSchema(openAPI, mapValueSchema); 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 innerSchema = generateNestedSchema(mapValueSchema, visitedSchemas);
schema.setAdditionalProperties(innerSchema); schema.setAdditionalProperties(innerSchema);
@ -414,7 +414,7 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf
} else if (ModelUtils.isArraySchema(schema) && ModelUtils.getSchemaItems(schema) != null) { } else if (ModelUtils.isArraySchema(schema) && ModelUtils.getSchemaItems(schema) != null) {
Schema arrayItemSchema = ModelUtils.getSchemaItems(schema); Schema arrayItemSchema = ModelUtils.getSchemaItems(schema);
arrayItemSchema = ModelUtils.getReferencedSchema(openAPI, arrayItemSchema); 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 innerSchema = generateNestedSchema(arrayItemSchema, visitedSchemas);
schema.setItems(innerSchema); schema.setItems(innerSchema);
} }
@ -427,7 +427,7 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf
Schema innerSchema = generateNestedSchema(oneOfSchema, visitedSchemas); Schema innerSchema = generateNestedSchema(oneOfSchema, visitedSchemas);
innerSchema.setTitle(oneOf.getTitle()); innerSchema.setTitle(oneOf.getTitle());
newOneOfs.add(innerSchema); newOneOfs.add(innerSchema);
} else if (ModelUtils.isMapSchema(oneOfSchema)) { } else if (ModelUtils.isMapSchema(oneOfSchema) && !ModelUtils.isModel(oneOfSchema)) {
Schema innerSchema = generateNestedSchema(oneOfSchema, visitedSchemas); Schema innerSchema = generateNestedSchema(oneOfSchema, visitedSchemas);
innerSchema.setTitle(oneOf.getTitle()); innerSchema.setTitle(oneOf.getTitle());
newOneOfs.add(innerSchema); newOneOfs.add(innerSchema);
@ -1061,4 +1061,38 @@ public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConf
return GeneratorLanguage.PROTOBUF; 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: components:
schemas: schemas:
Dog: Dog:
type: object allOf:
properties: - type: object
bark: properties:
type: boolean bark:
breed: type: boolean
type: string breed:
enum: [ Dingo, Husky, Retriever, Shepherd ] type: string
enum: [Dingo, Husky, Retriever, Shepherd]
- type: object
propertyNames:
title: field
type: string
additionalProperties:
title: pet
$ref: '#/components/schemas/Pet'
minProperties: 1
Cat: Cat:
type: object type: object
properties: properties:
@ -100,31 +109,26 @@ components:
format: int64 format: int64
category: category:
$ref: '#/components/schemas/Category' $ref: '#/components/schemas/Category'
name: name:
type: string
photoUrls:
type: array
xml:
name: photoUrl
wrapped: true
items:
type: string type: string
tags: photoUrls:
type: array
xml:
name: tag
wrapped: true
items:
type: array type: array
additionalProperties: xml:
$ref: '#/components/schemas/Tag' name: photoUrl
status: wrapped: true
type: string items:
description: pet status in the store type: string
deprecated: true tags:
enum: type: array
- available items:
- pending type: object
- sold additionalProperties:
xml: $ref: '#/components/schemas/Tag'
name: Pet status:
type: string
description: pet status in the store
deprecated: true
enum:
- available
- pending
- sold

View File

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

View File

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

View File

@ -13,6 +13,7 @@ syntax = "proto3";
package petstore; package petstore;
import public "models/category.proto"; import public "models/category.proto";
import public "models/tag_map.proto";
message Pet { message Pet {
@ -20,5 +21,21 @@ message Pet {
Category category = 2; 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;
}