forked from loafle/openapi-generator-original
fix: ExampleGenerator correctly generates allOf composed schemas (#17499)
* fix: ExampleGenerator correctly generates allOf composed schemas Changes the previous behavior of generating `null` examples for allOf composed schemas. Fixes #17497 * fix: ExampleGenerator correctly generates anyOf and oneOf composed schemas 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
This commit is contained in:
parent
8bab0ceb53
commit
dd5c7e3b9a
@ -351,15 +351,57 @@ public class ExampleGenerator {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -151,4 +151,91 @@ public class ExampleGeneratorTest {
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ paths:
|
||||
example: primitive types example value
|
||||
/generate_from_response_schema_with_model:
|
||||
get:
|
||||
operationId: generateFromResponseSchemaWithArrayOfPrimitiveTypes
|
||||
operationId: generateFromResponseSchemaWithModel
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
@ -61,6 +61,36 @@ paths:
|
||||
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:
|
||||
@ -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
|
||||
|
@ -1942,6 +1942,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1942,6 +1942,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -2411,7 +2411,7 @@ components:
|
||||
description: Value object
|
||||
example:
|
||||
name: variable_1
|
||||
value: null
|
||||
value: Scalar
|
||||
properties:
|
||||
name:
|
||||
example: variable_1
|
||||
|
@ -1942,6 +1942,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1942,6 +1942,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1942,6 +1942,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1942,6 +1942,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1942,6 +1942,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1942,6 +1942,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1942,6 +1942,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -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
|
||||
|
||||
|
@ -71,7 +71,7 @@ public interface BarApi {
|
||||
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;
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ public interface FooApi {
|
||||
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;
|
||||
}
|
||||
@ -109,7 +109,7 @@ public interface FooApi {
|
||||
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;
|
||||
}
|
||||
|
@ -189,7 +189,9 @@ components:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Entity'
|
||||
example:
|
||||
foo: null
|
||||
foo:
|
||||
fooPropA: fooPropA
|
||||
fooPropB: fooPropB
|
||||
id: id
|
||||
fooPropB: fooPropB
|
||||
barPropA: barPropA
|
||||
|
@ -1902,6 +1902,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1902,6 +1902,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1942,6 +1942,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1942,6 +1942,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1902,6 +1902,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1902,6 +1902,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1902,6 +1902,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1902,6 +1902,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1902,6 +1902,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1902,6 +1902,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1902,6 +1902,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1902,6 +1902,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
@ -1902,6 +1902,10 @@ components:
|
||||
otherProperty:
|
||||
type: string
|
||||
type: object
|
||||
example:
|
||||
otherProperty: otherProperty
|
||||
nullableProperty: nullableProperty
|
||||
type: ChildWithNullable
|
||||
StringBooleanMap:
|
||||
additionalProperties:
|
||||
type: boolean
|
||||
|
Loading…
x
Reference in New Issue
Block a user