Skip to content

Commit 1b57d4b

Browse files
authored
Better handling of metadata in allOf (#21342)
* better handling of metadata in allof * update samples
1 parent 5677f5b commit 1b57d4b

File tree

28 files changed

+199
-35
lines changed

28 files changed

+199
-35
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/InlineModelResolver.java

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -418,18 +418,7 @@ private void gatherInlineModels(Schema schema, String modelPrefix) {
418418
if (schema.getAllOf().size() == 1) {
419419
// handle earlier in this function when looping through properties
420420
} else if (schema.getAllOf().size() > 1) {
421-
// Check if there is only one "non metadata" schema.
422-
// For example, there may be an `description` only schema that is used to override the descrption.
423-
// In these cases, we can simply discard those schemas.
424-
List<Schema> nonMetadataOnlySchemas = (List<Schema>) schema.getAllOf().stream()
425-
.filter(v -> ModelUtils.isMetadataOnlySchema((Schema) v))
426-
.collect(Collectors.toList());
427-
428-
if (nonMetadataOnlySchemas.size() == 1) {
429-
schema.setAllOf(nonMetadataOnlySchemas);
430-
} else {
431-
LOGGER.warn("allOf schema `{}` containing multiple types (not model) is not supported at the moment.", schema.getName());
432-
}
421+
LOGGER.warn("allOf schema `{}` containing multiple types (not model) is not supported at the moment.", schema.getName());
433422
} else {
434423
LOGGER.error("allOf schema `{}` contains no items.", schema.getName());
435424
}

modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,52 @@ protected void normalizeProperties(Map<String, Schema> properties, Set<Schema> v
851851
}
852852
}
853853

854+
protected void refactorAllOfWithMetadataOnlySchemas(Schema schema) {
855+
if (schema.getAllOf() == null) {
856+
return;
857+
}
858+
859+
// Check if there are metadata schemas.
860+
// For example, there may be an `description` only schema that is used to override the descrption.
861+
List<Schema> nonMetadataOnlySchemas = new ArrayList<>();
862+
List<Schema> metadataOnlySchemas = new ArrayList<>();
863+
864+
for (Object s: schema.getAllOf()) {
865+
if (s instanceof Schema) {
866+
if (ModelUtils.isMetadataOnlySchema((Schema) s)) {
867+
metadataOnlySchemas.add((Schema) s);
868+
} else {
869+
nonMetadataOnlySchemas.add((Schema) s);
870+
}
871+
}
872+
}
873+
874+
// ReferenceNumber:
875+
// allOf:
876+
// - $ref: '#/components/schemas/IEAN8'
877+
// - description: Product Ref
878+
// - example: IEAN1234
879+
//
880+
// becomes the following after the following code block
881+
//
882+
// ReferenceNumber:
883+
// allOf:
884+
// - $ref: '#/components/schemas/IEAN8'
885+
// description: Product Ref
886+
// example: IEAN1234
887+
//
888+
// which can be further processed by the generator
889+
if (nonMetadataOnlySchemas.size() > 0) {
890+
// keep the non metadata schema(s)
891+
schema.setAllOf(nonMetadataOnlySchemas);
892+
893+
// copy metadata to the allOf schema
894+
for (Schema metadataOnlySchema: metadataOnlySchemas) {
895+
ModelUtils.copyMetadata(metadataOnlySchema, schema);
896+
}
897+
}
898+
}
899+
854900
/*
855901
* Remove unsupported schemas (e.g. if, then) from allOf.
856902
*
@@ -882,6 +928,8 @@ protected void removeUnsupportedSchemasFromAllOf(Schema schema) {
882928
protected Schema normalizeAllOf(Schema schema, Set<Schema> visitedSchemas) {
883929
removeUnsupportedSchemasFromAllOf(schema);
884930

931+
refactorAllOfWithMetadataOnlySchemas(schema);
932+
885933
if (schema.getAllOf() == null) {
886934
return schema;
887935
}

modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2353,6 +2353,63 @@ public static boolean isUnsupportedSchema(OpenAPI openAPI, Schema schema) {
23532353
return false;
23542354
}
23552355

2356+
/**
2357+
* Copy meta data (e.g. description, default, examples, etc) from one schema to another.
2358+
*
2359+
* @param from From schema
2360+
* @param to To schema
2361+
*/
2362+
public static void copyMetadata(Schema from, Schema to) {
2363+
if (from.getDescription() != null) {
2364+
to.setDescription(from.getDescription());
2365+
}
2366+
if (from.getDefault() != null) {
2367+
to.setDefault(from.getDefault());
2368+
}
2369+
if (from.getDeprecated() != null) {
2370+
to.setDeprecated(from.getDeprecated());
2371+
}
2372+
if (from.getNullable() != null) {
2373+
to.setNullable(from.getNullable());
2374+
}
2375+
if (from.getExample() != null) {
2376+
to.setExample(from.getExample());
2377+
}
2378+
if (from.getExamples() != null) {
2379+
to.setExample(from.getExamples());
2380+
}
2381+
if (from.getReadOnly() != null) {
2382+
to.setReadOnly(from.getReadOnly());
2383+
}
2384+
if (from.getWriteOnly() != null) {
2385+
to.setWriteOnly(from.getWriteOnly());
2386+
}
2387+
if (from.getExtensions() != null) {
2388+
to.setExtensions(from.getExtensions());
2389+
}
2390+
if (from.getMaxLength() != null) {
2391+
to.setMaxLength(from.getMaxLength());
2392+
}
2393+
if (from.getMinLength() != null) {
2394+
to.setMinLength(from.getMinLength());
2395+
}
2396+
if (from.getMaxItems() != null) {
2397+
to.setMaxItems(from.getMaxItems());
2398+
}
2399+
if (from.getMinItems() != null) {
2400+
to.setMinItems(from.getMinItems());
2401+
}
2402+
if (from.getMaximum() != null) {
2403+
to.setMaximum(from.getMaximum());
2404+
}
2405+
if (from.getMinimum() != null) {
2406+
to.setMinimum(from.getMinimum());
2407+
}
2408+
if (from.getTitle() != null) {
2409+
to.setTitle(from.getTitle());
2410+
}
2411+
}
2412+
23562413
/**
23572414
* Returns true if a schema is only metadata and not an actual type.
23582415
* For example, a schema that only has a `description` without any `properties` or `$ref` defined.
@@ -2361,7 +2418,7 @@ public static boolean isUnsupportedSchema(OpenAPI openAPI, Schema schema) {
23612418
* @return if the schema is only metadata and not an actual type
23622419
*/
23632420
public static boolean isMetadataOnlySchema(Schema schema) {
2364-
return schema.get$ref() != null ||
2421+
return !(schema.get$ref() != null ||
23652422
schema.getProperties() != null ||
23662423
schema.getType() != null ||
23672424
schema.getAdditionalProperties() != null ||
@@ -2375,7 +2432,7 @@ public static boolean isMetadataOnlySchema(Schema schema) {
23752432
schema.getContains() != null ||
23762433
schema.get$dynamicAnchor() != null ||
23772434
schema.get$anchor() != null ||
2378-
schema.getContentSchema() != null;
2435+
schema.getContentSchema() != null);
23792436
}
23802437

23812438

modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,25 @@ public void testOpenAPINormalizerRefAsParentInAllOfAndRefactorAllOfWithPropertie
7979
assertNull(schema4.getExtensions());
8080
}
8181

82+
@Test
83+
public void testOpenAPINormalizerRefactorAllofWithMetadataOnlySchemas() {
84+
// to test the rule REF_AS_PARENT_IN_ALLOF
85+
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/allof_with_metadata_only_schemas.yaml");
86+
87+
Schema schema = openAPI.getComponents().getSchemas().get("ReferenceNumber");
88+
assertEquals(schema.getAllOf().size(), 3);
89+
assertEquals(((Schema) schema.getAllOf().get(2)).getExample(), "IEAN1234");
90+
91+
Map<String, String> options = new HashMap<>();
92+
OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options);
93+
openAPINormalizer.normalize();
94+
95+
Schema schema2 = openAPI.getComponents().getSchemas().get("ReferenceNumber");
96+
assertEquals(schema2.getAllOf().size(), 1);
97+
assertEquals(schema2.getExample(), "IEAN1234");
98+
assertEquals(((Schema) schema2.getAllOf().get(0)).get$ref(), "#/components/schemas/IEAN8");
99+
}
100+
82101
@Test
83102
public void testOpenAPINormalizerEnableKeepOnlyFirstTagInOperation() {
84103
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/enableKeepOnlyFirstTagInOperation_test.yaml");
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
openapi: 3.0.3
2+
info:
3+
version: 1.0.0
4+
title: Test
5+
paths:
6+
/api/productRef:
7+
get:
8+
operationId: getProductRef
9+
summary: Retrieve product reference
10+
description: Returns a product reference based on a given product.
11+
responses:
12+
'200':
13+
description: Successful operation
14+
content:
15+
application/json:
16+
schema:
17+
$ref: '#/components/schemas/ProductType'
18+
components:
19+
schemas:
20+
ProductType:
21+
type: object
22+
required:
23+
- referenceNumber
24+
properties:
25+
referenceNumber:
26+
$ref: "#/components/schemas/ReferenceNumber"
27+
ReferenceNumber:
28+
allOf:
29+
- $ref: "#/components/schemas/IEAN8"
30+
- description: Product Ref
31+
- example: IEAN1234
32+
IEAN8:
33+
type: string
34+
minLength: 8
35+
maxLength: 8
36+
example: "IEAN1234"
37+
Order:
38+
type: object
39+
properties:
40+
id:
41+
type: integer
42+
format: int64
43+
foo:
44+
allOf:
45+
- $ref: '#/components/schemas/ProductType'
46+
- description: 'this is foo'
47+
- example: 'this is bar'

samples/client/petstore/rust/hyper/petstore/docs/TestAllOfWithMultiMetadataOnly.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Name | Type | Description | Notes
66
------------ | ------------- | ------------- | -------------
77
**id** | Option<**i64**> | | [optional]
8-
**foo** | Option<**Vec<String>**> | existing_tags_array | [optional]
8+
**foo** | Option<**Vec<String>**> | This is a test for allOf with metadata only fields | [optional]
99

1010
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
1111

samples/client/petstore/rust/hyper/petstore/src/models/test_all_of_with_multi_metadata_only.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize};
1515
pub struct TestAllOfWithMultiMetadataOnly {
1616
#[serde(rename = "id", skip_serializing_if = "Option::is_none")]
1717
pub id: Option<i64>,
18-
/// existing_tags_array
18+
/// This is a test for allOf with metadata only fields
1919
#[serde(rename = "foo", default, with = "::serde_with::rust::double_option", skip_serializing_if = "Option::is_none")]
2020
pub foo: Option<Option<Vec<String>>>,
2121
}

samples/client/petstore/rust/hyper0x/petstore/docs/TestAllOfWithMultiMetadataOnly.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Name | Type | Description | Notes
66
------------ | ------------- | ------------- | -------------
77
**id** | Option<**i64**> | | [optional]
8-
**foo** | Option<**Vec<String>**> | existing_tags_array | [optional]
8+
**foo** | Option<**Vec<String>**> | This is a test for allOf with metadata only fields | [optional]
99

1010
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
1111

samples/client/petstore/rust/hyper0x/petstore/src/models/test_all_of_with_multi_metadata_only.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize};
1515
pub struct TestAllOfWithMultiMetadataOnly {
1616
#[serde(rename = "id", skip_serializing_if = "Option::is_none")]
1717
pub id: Option<i64>,
18-
/// existing_tags_array
18+
/// This is a test for allOf with metadata only fields
1919
#[serde(rename = "foo", default, with = "::serde_with::rust::double_option", skip_serializing_if = "Option::is_none")]
2020
pub foo: Option<Option<Vec<String>>>,
2121
}

samples/client/petstore/rust/reqwest-trait/petstore/docs/TestAllOfWithMultiMetadataOnly.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Name | Type | Description | Notes
66
------------ | ------------- | ------------- | -------------
77
**id** | Option<**i64**> | | [optional]
8-
**foo** | Option<**Vec<String>**> | existing_tags_array | [optional]
8+
**foo** | Option<**Vec<String>**> | This is a test for allOf with metadata only fields | [optional]
99

1010
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
1111

0 commit comments

Comments
 (0)