Skip to content
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

fix: ExampleGenerator correctly generates allOf composed schemas #17499

Merged
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 @@ -351,15 +351,57 @@ private Object resolveModelToExample(String name, String mediaType, Schema schem
return schema.getExample();
} else if (schema.getProperties() != null) {
LOGGER.debug("Creating example from model values");
for (Object propertyName : schema.getProperties().keySet()) {
Schema property = (Schema) schema.getProperties().get(propertyName.toString());
values.put(propertyName.toString(), resolvePropertyToExample(propertyName.toString(), mediaType, property, processedModels));
traverseSchemaProperties(mediaType, schema, processedModels, values);
schema.setExample(values);
return schema.getExample();
} else if (ModelUtils.isAllOf(schema) || ModelUtils.isAllOfWithProperties(schema)) {
LOGGER.debug("Resolving allOf model '{}' to example", name);
List<Schema> interfaces = schema.getAllOf();
for (Schema composed : interfaces) {
traverseSchemaProperties(mediaType, composed, processedModels, values);
if (composed.get$ref() != null) {
String ref = ModelUtils.getSimpleRef(composed.get$ref());
Schema resolved = ModelUtils.getSchema(openAPI, ref);
if (resolved != null) {
traverseSchemaProperties(mediaType, resolved, processedModels, values);
}
}
}
schema.setExample(values);
return schema.getExample();
} else if (ModelUtils.isAnyOf(schema) || ModelUtils.isOneOf(schema)) {
LOGGER.debug("Resolving anyOf/oneOf model '{}' using the first schema to example", name);
Optional<Schema> found = ModelUtils.getInterfaces(schema)
.stream()
.filter(this::hasValidRef)
.findFirst();

if (found.isEmpty()) {
return null;
}
return resolvePropertyToExample(name, mediaType, found.get(), processedModels);
} else {
// TODO log an error message as the model does not have any properties
return null;
}
}

private void traverseSchemaProperties(String mediaType, Schema schema, Set<String> processedModels, Map<String, Object> values) {
if (schema.getProperties() != null) {
for (Object propertyName : schema.getProperties().keySet()) {
Schema property = (Schema) schema.getProperties().get(propertyName.toString());
values.put(propertyName.toString(), resolvePropertyToExample(propertyName.toString(), mediaType, property, processedModels));
}
}
}

private boolean hasValidRef(Schema schema) {
if (schema.get$ref() != null) {
String ref = ModelUtils.getSimpleRef(schema.get$ref());
Schema resolved = ModelUtils.getSchema(openAPI, ref);
return resolved != null;
}

return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,91 @@ public void generateFromResponseSchemaWithModel() {
assertEquals(String.format(Locale.ROOT, "{%n \"example_schema_property\" : \"example schema property value\"%n}"), examples.get(0).get("example"));
assertEquals("200", examples.get(0).get("statusCode"));
}

@Test
public void generateFromResponseSchemaWithAllOfComposedModel() {
OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/example_generator_test.yaml");

new InlineModelResolver().flatten(openAPI);

ExampleGenerator exampleGenerator = new ExampleGenerator(openAPI.getComponents().getSchemas(), openAPI);
Set<String> mediaTypeKeys = new TreeSet<>();
mediaTypeKeys.add("application/json");
List<Map<String, String>> examples = exampleGenerator.generateFromResponseSchema(
"200",
openAPI
.getPaths()
.get("/generate_from_response_schema_with_allOf_composed_model")
.getGet()
.getResponses()
.get("200")
.getContent()
.get("application/json")
.getSchema(),
mediaTypeKeys
);

assertEquals(1, examples.size());
assertEquals("application/json", examples.get(0).get("contentType"));
assertEquals(String.format(Locale.ROOT, "{%n \"example_schema_property_composed\" : \"example schema property value composed\",%n \"example_schema_property\" : \"example schema property value\"%n}"), examples.get(0).get("example"));
assertEquals("200", examples.get(0).get("statusCode"));
}

@Test
public void generateFromResponseSchemaWithOneOfComposedModel() {
OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/example_generator_test.yaml");

new InlineModelResolver().flatten(openAPI);

ExampleGenerator exampleGenerator = new ExampleGenerator(openAPI.getComponents().getSchemas(), openAPI);
Set<String> mediaTypeKeys = new TreeSet<>();
mediaTypeKeys.add("application/json");
List<Map<String, String>> examples = exampleGenerator.generateFromResponseSchema(
"200",
openAPI
.getPaths()
.get("/generate_from_response_schema_with_oneOf_composed_model")
.getGet()
.getResponses()
.get("200")
.getContent()
.get("application/json")
.getSchema(),
mediaTypeKeys
);

assertEquals(1, examples.size());
assertEquals("application/json", examples.get(0).get("contentType"));
assertEquals(String.format(Locale.ROOT, "{%n \"example_schema_property\" : \"example schema property value\"%n}"), examples.get(0).get("example"));
assertEquals("200", examples.get(0).get("statusCode"));
}

@Test
public void generateFromResponseSchemaWithAnyOfComposedModel() {
OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/example_generator_test.yaml");

new InlineModelResolver().flatten(openAPI);

ExampleGenerator exampleGenerator = new ExampleGenerator(openAPI.getComponents().getSchemas(), openAPI);
Set<String> mediaTypeKeys = new TreeSet<>();
mediaTypeKeys.add("application/json");
List<Map<String, String>> examples = exampleGenerator.generateFromResponseSchema(
"200",
openAPI
.getPaths()
.get("/generate_from_response_schema_with_anyOf_composed_model")
.getGet()
.getResponses()
.get("200")
.getContent()
.get("application/json")
.getSchema(),
mediaTypeKeys
);

assertEquals(1, examples.size());
assertEquals("application/json", examples.get(0).get("contentType"));
assertEquals(String.format(Locale.ROOT, "{%n \"example_schema_property\" : \"example schema property value\"%n}"), examples.get(0).get("example"));
assertEquals("200", examples.get(0).get("statusCode"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,44 @@ paths:
example: primitive types example value
/generate_from_response_schema_with_model:
get:
operationId: generateFromResponseSchemaWithArrayOfPrimitiveTypes
operationId: generateFromResponseSchemaWithModel
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/ExampleSchema'
/generate_from_response_schema_with_allOf_composed_model:
get:
operationId: generateFromResponseSchemaWithAllOfModel
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/ExampleAllOfSchema'
/generate_from_response_schema_with_anyOf_composed_model:
get:
operationId: generateFromResponseSchemaWithAnyOfModel
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/ExampleAnyOfSchema'
/generate_from_response_schema_with_oneOf_composed_model:
get:
operationId: generateFromResponseSchemaWithOneOfModel
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/ExampleOneOfSchema'
components:
schemas:
StringSchema:
Expand All @@ -72,3 +102,30 @@ components:
example_schema_property:
type: string
example: example schema property value
ExampleAllOfSchema:
type: object
allOf:
- $ref: '#/components/schemas/ExampleSchema'
- type: object
properties:
example_schema_property_composed:
type: string
example: example schema property value composed
ExampleAnyOfSchema:
type: object
anyOf:
- $ref: '#/components/schemas/ExampleSchema'
- type: object
properties:
example_schema_property_composed:
type: string
example: example schema property value composed
ExampleOneOfSchema:
type: object
oneOf:
- $ref: '#/components/schemas/ExampleSchema'
- type: object
properties:
example_schema_property_composed:
type: string
example: example schema property value composed
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,10 @@ components:
otherProperty:
type: string
type: object
example:
otherProperty: otherProperty
nullableProperty: nullableProperty
type: ChildWithNullable
StringBooleanMap:
additionalProperties:
type: boolean
Expand Down
4 changes: 4 additions & 0 deletions samples/client/petstore/java/feign/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,10 @@ components:
otherProperty:
type: string
type: object
example:
otherProperty: otherProperty
nullableProperty: nullableProperty
type: ChildWithNullable
StringBooleanMap:
additionalProperties:
type: boolean
Expand Down
2 changes: 1 addition & 1 deletion samples/client/petstore/java/okhttp-gson/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2411,7 +2411,7 @@ components:
description: Value object
example:
name: variable_1
value: null
value: Scalar
properties:
name:
example: variable_1
Expand Down
4 changes: 4 additions & 0 deletions samples/client/petstore/java/resteasy/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,10 @@ components:
otherProperty:
type: string
type: object
example:
otherProperty: otherProperty
nullableProperty: nullableProperty
type: ChildWithNullable
StringBooleanMap:
additionalProperties:
type: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,10 @@ components:
otherProperty:
type: string
type: object
example:
otherProperty: otherProperty
nullableProperty: nullableProperty
type: ChildWithNullable
StringBooleanMap:
additionalProperties:
type: boolean
Expand Down
4 changes: 4 additions & 0 deletions samples/client/petstore/java/resttemplate/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,10 @@ components:
otherProperty:
type: string
type: object
example:
otherProperty: otherProperty
nullableProperty: nullableProperty
type: ChildWithNullable
StringBooleanMap:
additionalProperties:
type: boolean
Expand Down
4 changes: 4 additions & 0 deletions samples/client/petstore/java/vertx/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,10 @@ components:
otherProperty:
type: string
type: object
example:
otherProperty: otherProperty
nullableProperty: nullableProperty
type: ChildWithNullable
StringBooleanMap:
additionalProperties:
type: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,10 @@ components:
otherProperty:
type: string
type: object
example:
otherProperty: otherProperty
nullableProperty: nullableProperty
type: ChildWithNullable
StringBooleanMap:
additionalProperties:
type: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,10 @@ components:
otherProperty:
type: string
type: object
example:
otherProperty: otherProperty
nullableProperty: nullableProperty
type: ChildWithNullable
StringBooleanMap:
additionalProperties:
type: boolean
Expand Down
4 changes: 4 additions & 0 deletions samples/client/petstore/java/webclient/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,10 @@ components:
otherProperty:
type: string
type: object
example:
otherProperty: otherProperty
nullableProperty: nullableProperty
type: ChildWithNullable
StringBooleanMap:
additionalProperties:
type: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,7 @@ components:
\ characters. The sanitization rules should make it possible to generate a\
\ language-specific classname with allowed characters in that programming\
\ language."
example:
prop2: prop2
objectType: objectType

Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ default ResponseEntity<Bar> createBar(
getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "{ \"id\" : \"id\", \"fooPropB\" : \"fooPropB\", \"barPropA\" : \"barPropA\" }";
String exampleString = "{ \"foo\" : { \"fooPropA\" : \"fooPropA\", \"fooPropB\" : \"fooPropB\" }, \"id\" : \"id\", \"fooPropB\" : \"fooPropB\", \"barPropA\" : \"barPropA\" }";
ApiUtil.setExampleResponse(request, "application/json", exampleString);
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ default ResponseEntity<FooRefOrValue> createFoo(
getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "null";
String exampleString = "{ \"fooPropA\" : \"fooPropA\", \"fooPropB\" : \"fooPropB\" }";
ApiUtil.setExampleResponse(request, "application/json", exampleString);
break;
}
Expand Down Expand Up @@ -109,7 +109,7 @@ default ResponseEntity<List<FooRefOrValue>> getAllFoos(
getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json;charset=utf-8"))) {
String exampleString = "[ null, null ]";
String exampleString = "[ { \"fooPropA\" : \"fooPropA\", \"fooPropB\" : \"fooPropB\" }, { \"fooPropA\" : \"fooPropA\", \"fooPropB\" : \"fooPropB\" } ]";
ApiUtil.setExampleResponse(request, "application/json;charset=utf-8", exampleString);
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,9 @@ components:
allOf:
- $ref: '#/components/schemas/Entity'
example:
foo: null
foo:
fooPropA: fooPropA
fooPropB: fooPropB
id: id
fooPropB: fooPropB
barPropA: barPropA
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1902,6 +1902,10 @@ components:
otherProperty:
type: string
type: object
example:
otherProperty: otherProperty
nullableProperty: nullableProperty
type: ChildWithNullable
StringBooleanMap:
additionalProperties:
type: boolean
Expand Down
Loading
Loading