Skip to content

Commit

Permalink
fix: ExampleGenerator correctly generates anyOf and oneOf composed sc…
Browse files Browse the repository at this point in the history
…hemas

Changes the previous behavior of generating `null` examples for anyOf and oneOf composed schemas.

To generate a oneOf/anyOf example, we generate the example using the first valid schema available. In case of a $ref, we use the first valid reference.

Fixes #17497
  • Loading branch information
acouvreur committed Jan 6, 2024
1 parent ffe092f commit 31bf3c6
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,17 @@ private Object resolveModelToExample(String name, String mediaType, Schema schem
}
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;
Expand All @@ -383,4 +394,14 @@ private void traverseSchemaProperties(String mediaType, Schema schema, Set<Strin
}
}
}

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 @@ -180,4 +180,62 @@ public void generateFromResponseSchemaWithAllOfComposedModel() {
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 @@ -63,14 +63,34 @@ paths:
$ref: '#/components/schemas/ExampleSchema'
/generate_from_response_schema_with_allOf_composed_model:
get:
operationId: generateFromResponseSchemaWithComposedModel
operationId: generateFromResponseSchemaWithAllOfModel
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/ExampleComposedSchema'
$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 @@ -82,7 +102,7 @@ components:
example_schema_property:
type: string
example: example schema property value
ExampleComposedSchema:
ExampleAllOfSchema:
type: object
allOf:
- $ref: '#/components/schemas/ExampleSchema'
Expand All @@ -91,3 +111,21 @@ components:
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
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
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

0 comments on commit 31bf3c6

Please sign in to comment.