Skip to content
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 @@ -322,7 +322,7 @@ private Object resolvePropertyToExample(String propertyName, String mediaType, S
return resolveModelToExample(simpleName, mediaType, schema, processedModels);

} else if (ModelUtils.isObjectSchema(property)) {
return "{}";
return new LinkedHashMap<>();
}

return "";
Expand Down Expand Up @@ -379,6 +379,12 @@ private Object resolveModelToExample(String name, String mediaType, Schema schem
return resolvePropertyToExample(name, mediaType, found.get(), processedModels);
} else if (ModelUtils.isArraySchema(schema) || ModelUtils.isEnumSchema(schema)) {
return resolvePropertyToExample(schema.getName(), mediaType, schema, processedModels);
} else if (ModelUtils.isMapSchema(schema)) {
schema.setExample(resolvePropertyToExample(name, mediaType, schema, processedModels));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: For a named map model whose additionalProperties contains a $ref back to that same model, this branch caches {"key": null} on the shared component schema: schema.setExample(...) runs only after resolvePropertyToExample returns, so the recursion guard (processedModels.contains(name) → return schema.getExample()) reads the example before it is set. The null-laden map is then persisted via setExample, so every later use of the model (other operations, media types, and codegen reads of schema.getExample()) reuses it. Set a placeholder on the schema before descending (or resolve the value maps first and merge recursion results) so circular references don't freeze null into the cached example.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At modules/openapi-generator/src/main/java/org/openapitools/codegen/examples/ExampleGenerator.java, line 383:

<comment>For a named map model whose `additionalProperties` contains a `$ref` back to that same model, this branch caches `{"key": null}` on the shared component schema: `schema.setExample(...)` runs only after `resolvePropertyToExample` returns, so the recursion guard (`processedModels.contains(name)` → `return schema.getExample()`) reads the example before it is set. The null-laden map is then persisted via `setExample`, so every later use of the model (other operations, media types, and codegen reads of `schema.getExample()`) reuses it. Set a placeholder on the schema before descending (or resolve the value maps first and merge recursion results) so circular references don't freeze `null` into the cached example.</comment>

<file context>
@@ -379,6 +379,9 @@ private Object resolveModelToExample(String name, String mediaType, Schema schem
         } else if (ModelUtils.isArraySchema(schema) || ModelUtils.isEnumSchema(schema)) {
             return resolvePropertyToExample(schema.getName(), mediaType, schema, processedModels);
+        } else if (ModelUtils.isMapSchema(schema)) {
+            schema.setExample(resolvePropertyToExample(name, mediaType, schema, processedModels));
+            return schema.getExample();
         } else if (ModelUtils.isObjectSchema(schema)) {
</file context>

return schema.getExample();
} else if (ModelUtils.isObjectSchema(schema)) {
schema.setExample(values);
return schema.getExample();
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
} else {
// TODO log an error message as the model does not have any properties
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,168 @@
package org.openapitools.codegen;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.JsonSchema;
import io.swagger.v3.oas.models.media.MapSchema;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.IntegerSchema;
import io.swagger.v3.oas.models.media.StringSchema;
import org.openapitools.codegen.examples.ExampleGenerator;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.util.*;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertTrue;

public class ExampleGeneratorTest {
@DataProvider(name = "namedMapSchemas")
public Object[][] namedMapSchemas() {
return new Object[][] {
{new ObjectSchema().additionalProperties(new StringSchema().example("value"))},
{new Schema<>().type("object").additionalProperties(new StringSchema().example("value"))},
{new MapSchema().additionalProperties(new StringSchema().example("value"))},
{new JsonSchema().type("object").additionalProperties(new StringSchema().example("value"))}
};
}

@Test(dataProvider = "namedMapSchemas")
public void generateNamedMapKeepsAdditionalPropertyValues(Schema<?> mapSchema) throws Exception {
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("Labels", mapSchema);
ExampleGenerator generator = new ExampleGenerator(openAPI.getComponents().getSchemas(), openAPI);

List<Map<String, String>> examples = generator.generate(null, Arrays.asList("application/json", "*/*"), "Labels");
ObjectMapper mapper = new ObjectMapper();

assertEquals(2, examples.size());
for (Map<String, String> example : examples) {
assertEquals(mapper.readTree("{\"key\":\"value\"}"), mapper.readTree(example.get("example")));
}
}

@Test(dataProvider = "namedMapSchemas")
public void generateRepeatedReferencesToNamedMap(Schema<?> mapSchema) throws Exception {
Schema<?> reading = new ObjectSchema()
.addProperties("first", new Schema<>().$ref("#/components/schemas/Labels"))
.addProperties("second", new Schema<>().$ref("#/components/schemas/Labels"));
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("Reading", reading);
openAPI.getComponents().addSchemas("Labels", mapSchema);
ExampleGenerator generator = new ExampleGenerator(openAPI.getComponents().getSchemas(), openAPI);

List<Map<String, String>> examples = generator.generate(null, Arrays.asList("application/json", "*/*"), "Reading");
ObjectMapper mapper = new ObjectMapper();

assertEquals(2, examples.size());
for (Map<String, String> generated : examples) {
JsonNode example = mapper.readTree(generated.get("example"));
assertEquals(mapper.readTree("{\"key\":\"value\"}"), example.get("first"));
assertEquals(example.get("first"), example.get("second"));
}
}

@DataProvider(name = "emptyObjectSchemas")
public Object[][] emptyObjectSchemas() {
return new Object[][] {
{new ObjectSchema()},
{new Schema<>().type("object")},
{new ObjectSchema().properties(new LinkedHashMap<>())},
{new Schema<>().type("object").additionalProperties(false)}
};
}

@Test(dataProvider = "emptyObjectSchemas")
public void generateNestedEmptyObject(Schema<?> emptyObject) throws Exception {
Schema<?> reading = new ObjectSchema().addProperties("metadata", emptyObject);
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("Reading", reading);
ExampleGenerator generator = new ExampleGenerator(openAPI.getComponents().getSchemas(), openAPI);

List<Map<String, String>> examples = generator.generate(null, Collections.singletonList("application/json"), "Reading");
JsonNode metadata = new ObjectMapper().readTree(examples.get(0).get("example")).get("metadata");

assertTrue(metadata.isObject());
assertEquals(0, metadata.size());
}

@Test(dataProvider = "emptyObjectSchemas")
public void generateTopLevelEmptyObject(Schema<?> emptyObject) throws Exception {
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("Anything", emptyObject);
ExampleGenerator generator = new ExampleGenerator(openAPI.getComponents().getSchemas(), openAPI);

List<Map<String, String>> examples = generator.generate(null, Collections.singletonList("application/json"), "Anything");
JsonNode example = new ObjectMapper().readTree(examples.get(0).get("example"));

assertTrue(example.isObject());
assertEquals(0, example.size());
}

@Test
public void generateRepeatedReferencesToEmptyObject() throws Exception {
Schema<?> reading = new ObjectSchema()
.addProperties("first", new Schema<>().$ref("#/components/schemas/Anything"))
.addProperties("second", new Schema<>().$ref("#/components/schemas/Anything"));
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("Reading", reading);
openAPI.getComponents().addSchemas("Anything", new ObjectSchema());
ExampleGenerator generator = new ExampleGenerator(openAPI.getComponents().getSchemas(), openAPI);

List<Map<String, String>> examples = generator.generate(null, Collections.singletonList("application/json"), "Reading");
JsonNode example = new ObjectMapper().readTree(examples.get(0).get("example"));

assertTrue(example.get("first").isObject());
assertEquals(0, example.get("first").size());
assertEquals(example.get("first"), example.get("second"));
}

@Test
public void generateArrayOfEmptyObjects() throws Exception {
Schema<?> array = new ArraySchema().items(new ObjectSchema());
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("Array", array);
ExampleGenerator generator = new ExampleGenerator(openAPI.getComponents().getSchemas(), openAPI);

List<Map<String, String>> examples = generator.generate(null, Collections.singletonList("application/json"), "Array");
JsonNode example = new ObjectMapper().readTree(examples.get(0).get("example"));

assertTrue(example.isArray());
assertEquals(2, example.size());
assertTrue(example.get(0).isObject());
assertEquals(0, example.get(0).size());
assertEquals(example.get(0), example.get(1));
}

@Test
public void generateObjectKeepsExplicitExampleAndMapValues() throws Exception {
Schema<?> reading = new ObjectSchema()
.addProperties("metadata", new Schema<>().type("object").example(Collections.singletonMap("name", "explicit")))
.addProperties("labels", new MapSchema().additionalProperties(new StringSchema().example("value")));
OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("Reading", reading);
ExampleGenerator generator = new ExampleGenerator(openAPI.getComponents().getSchemas(), openAPI);

List<Map<String, String>> examples = generator.generate(null, Collections.singletonList("application/json"), "Reading");
JsonNode example = new ObjectMapper().readTree(examples.get(0).get("example"));

assertEquals(new ObjectMapper().readTree("{\"name\":\"explicit\"}"), example.get("metadata"));
assertEquals(new ObjectMapper().readTree("{\"key\":\"value\"}"), example.get("labels"));
}

@Test
public void generateUnresolvedReferenceKeepsExistingFallback() throws Exception {
OpenAPI openAPI = TestUtils.createOpenAPI();
ExampleGenerator generator = new ExampleGenerator(Collections.emptyMap(), openAPI);
Schema<?> reference = new Schema<>().$ref("#/components/schemas/Missing");

List<Map<String, String>> examples = generator.generate(null, Collections.singletonList("application/json"), reference);
JsonNode example = new ObjectMapper().readTree(examples.get(0).get("example"));

// An unresolved reference does not establish that the missing schema is an object.
assertTrue(example.isTextual());
assertEquals("{}", example.asText());
}

@Test
public void generateFromResponseSchemaWithPrimitiveType() {
OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/example_generator_test.yaml");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1459,8 +1459,8 @@ components:
password: password
phone: phone
userStatus: 6
objectWithNoDeclaredProps: "{}"
objectWithNoDeclaredPropsNullable: "{}"
objectWithNoDeclaredProps: {}
objectWithNoDeclaredPropsNullable: {}
anyTypeProp: ""
anyTypePropNullable: ""
properties:
Expand Down Expand Up @@ -2815,15 +2815,17 @@ components:
Custom-Variableobject-Response:
additionalProperties: true
description: A Variable object without predefined property names
example:
key: ""
type: object
Field-pkiNotificationtestID:
type: integer
notificationtest-getElements-v1-Response-mPayload:
example:
pkiNotificationtestID: 0
a_objVariableobject:
- null
- null
- key: ""
- key: ""
properties:
pkiNotificationtestID:
type: integer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1423,8 +1423,8 @@ components:
password: password
phone: phone
userStatus: 6
objectWithNoDeclaredProps: "{}"
objectWithNoDeclaredPropsNullable: "{}"
objectWithNoDeclaredProps: {}
objectWithNoDeclaredPropsNullable: {}
anyTypeProp: ""
anyTypePropNullable: ""
properties:
Expand Down Expand Up @@ -2637,15 +2637,17 @@ components:
Custom-Variableobject-Response:
additionalProperties: true
description: A Variable object without predefined property names
example:
key: ""
type: object
Field-pkiNotificationtestID:
type: integer
notificationtest-getElements-v1-Response-mPayload:
example:
pkiNotificationtestID: 0
a_objVariableobject:
- null
- null
- key: ""
- key: ""
properties:
pkiNotificationtestID:
type: integer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1459,8 +1459,8 @@ components:
password: password
phone: phone
userStatus: 6
objectWithNoDeclaredProps: "{}"
objectWithNoDeclaredPropsNullable: "{}"
objectWithNoDeclaredProps: {}
objectWithNoDeclaredPropsNullable: {}
anyTypeProp: ""
anyTypePropNullable: ""
properties:
Expand Down Expand Up @@ -2815,15 +2815,17 @@ components:
Custom-Variableobject-Response:
additionalProperties: true
description: A Variable object without predefined property names
example:
key: ""
type: object
Field-pkiNotificationtestID:
type: integer
notificationtest-getElements-v1-Response-mPayload:
example:
pkiNotificationtestID: 0
a_objVariableobject:
- null
- null
- key: ""
- key: ""
properties:
pkiNotificationtestID:
type: integer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1459,8 +1459,8 @@ components:
password: password
phone: phone
userStatus: 6
objectWithNoDeclaredProps: "{}"
objectWithNoDeclaredPropsNullable: "{}"
objectWithNoDeclaredProps: {}
objectWithNoDeclaredPropsNullable: {}
anyTypeProp: ""
anyTypePropNullable: ""
properties:
Expand Down Expand Up @@ -2815,15 +2815,17 @@ components:
Custom-Variableobject-Response:
additionalProperties: true
description: A Variable object without predefined property names
example:
key: ""
type: object
Field-pkiNotificationtestID:
type: integer
notificationtest-getElements-v1-Response-mPayload:
example:
pkiNotificationtestID: 0
a_objVariableobject:
- null
- null
- key: ""
- key: ""
properties:
pkiNotificationtestID:
type: integer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1459,8 +1459,8 @@ components:
password: password
phone: phone
userStatus: 6
objectWithNoDeclaredProps: "{}"
objectWithNoDeclaredPropsNullable: "{}"
objectWithNoDeclaredProps: {}
objectWithNoDeclaredPropsNullable: {}
anyTypeProp: ""
anyTypePropNullable: ""
properties:
Expand Down Expand Up @@ -2815,15 +2815,17 @@ components:
Custom-Variableobject-Response:
additionalProperties: true
description: A Variable object without predefined property names
example:
key: ""
type: object
Field-pkiNotificationtestID:
type: integer
notificationtest-getElements-v1-Response-mPayload:
example:
pkiNotificationtestID: 0
a_objVariableobject:
- null
- null
- key: ""
- key: ""
properties:
pkiNotificationtestID:
type: integer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1423,8 +1423,8 @@ components:
password: password
phone: phone
userStatus: 6
objectWithNoDeclaredProps: "{}"
objectWithNoDeclaredPropsNullable: "{}"
objectWithNoDeclaredProps: {}
objectWithNoDeclaredPropsNullable: {}
anyTypeProp: ""
anyTypePropNullable: ""
properties:
Expand Down Expand Up @@ -2637,15 +2637,17 @@ components:
Custom-Variableobject-Response:
additionalProperties: true
description: A Variable object without predefined property names
example:
key: ""
type: object
Field-pkiNotificationtestID:
type: integer
notificationtest-getElements-v1-Response-mPayload:
example:
pkiNotificationtestID: 0
a_objVariableobject:
- null
- null
- key: ""
- key: ""
properties:
pkiNotificationtestID:
type: integer
Expand Down
Loading
Loading