Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -397,15 +397,15 @@ private void processNestedSchemas(Schema schema, Set<Schema> visitedSchemas) {
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);

}
} 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);
}
Expand All @@ -418,7 +418,7 @@ private void processNestedSchemas(Schema schema, Set<Schema> visitedSchemas) {
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);
Expand Down Expand Up @@ -1053,4 +1053,38 @@ public GeneratorLanguage generatorLanguage() {
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) {
Copy link
Member

@wing328 wing328 Apr 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please add some comments explaining what the code block does (e.g. what schema(s) it tries to handle) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wing328 thanks for reviewing the PR. I had added explaining comment on top of the code.

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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,22 @@ externalDocs:
components:
schemas:
Dog:
type: object
properties:
bark:
type: boolean
breed:
type: string
enum: [ Dingo, Husky, Retriever, Shepherd ]
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:
Expand Down Expand Up @@ -100,31 +109,26 @@ components:
format: int64
category:
$ref: '#/components/schemas/Category'
name:
type: string
photoUrls:
type: array
xml:
name: photoUrl
wrapped: true
items:
name:
type: string
tags:
type: array
xml:
name: tag
wrapped: true
items:
photoUrls:
type: array
additionalProperties:
$ref: '#/components/schemas/Tag'
status:
type: string
description: pet status in the store
deprecated: true
enum:
- available
- pending
- sold
xml:
name: Pet
xml:
name: photoUrl
wrapped: true
items:
type: string
tags:
type: array
items:
type: object
additionalProperties:
$ref: '#/components/schemas/Tag'
status:
type: string
description: pet status in the store
deprecated: true
enum:
- available
- pending
- sold
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ syntax = "proto3";

package petstore;

import public "models/pet.proto";

message Dog {

Expand All @@ -27,5 +28,7 @@ message Dog {

Breed breed = 2;

map<string, Pet> pet = 3;

}

Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,29 @@ syntax = "proto3";
package petstore;

import public "models/category.proto";
import public "models/tag_map.proto";

message Pet {

int64 id = 1;

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;

}

Original file line number Diff line number Diff line change
@@ -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;

}

Loading