Add new method to detect self-referencing in ModelUtils (#12294)

* fix unaliasing object schema, add tests

* add self reference check

* add self reference check, add test
This commit is contained in:
William Cheng 2022-05-05 16:22:52 +08:00 committed by GitHub
parent b02fd28ba6
commit d97c152c14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 166 additions and 1 deletions

View File

@ -1065,6 +1065,96 @@ public class ModelUtils {
return entry.getValue().getSchema();
}
/**
* Has self reference?
*
* @param openAPI OpenAPI spec.
* @param schema Schema
* @return boolean true if it has at least one self reference
*/
public static boolean hasSelfReference(OpenAPI openAPI,
Schema schema) {
return hasSelfReference(openAPI, schema, null);
}
/**
* Has self reference?
*
* @param openAPI OpenAPI spec.
* @param schema Schema
* @param visitedSchemaNames A set of visited schema names
* @return boolean true if it has at least one self reference
*/
public static boolean hasSelfReference(OpenAPI openAPI,
Schema schema,
Set<String> visitedSchemaNames) {
if (visitedSchemaNames == null) {
visitedSchemaNames = new HashSet<String>();
}
if (schema.get$ref() != null) {
String ref = getSimpleRef(schema.get$ref());
if (!visitedSchemaNames.contains(ref)) {
visitedSchemaNames.add(ref);
Schema referencedSchema = getSchemas(openAPI).get(ref);
if (referencedSchema != null) {
return hasSelfReference(openAPI, referencedSchema, visitedSchemaNames);
} else {
LOGGER.error("Failed to obtain schema from `{}` in self reference check", ref);
return false;
}
} else {
return true;
}
}
if (schema instanceof ComposedSchema) {
List<Schema> oneOf = ((ComposedSchema) schema).getOneOf();
if (oneOf != null) {
for (Schema s : oneOf) {
if (hasSelfReference(openAPI, s, visitedSchemaNames)) {
return true;
}
}
}
List<Schema> allOf = ((ComposedSchema) schema).getAllOf();
if (allOf != null) {
for (Schema s : allOf) {
if (hasSelfReference(openAPI, s, visitedSchemaNames)) {
return true;
}
}
}
List<Schema> anyOf = ((ComposedSchema) schema).getAnyOf();
if (anyOf != null) {
for (Schema s : anyOf) {
if (hasSelfReference(openAPI, s, visitedSchemaNames)) {
return true;
}
}
}
} else if (isArraySchema(schema)) {
Schema itemsSchema = ((ArraySchema) schema).getItems();
if (itemsSchema != null) {
return hasSelfReference(openAPI, itemsSchema, visitedSchemaNames);
}
} else if (isMapSchema(schema)) {
Object additionalProperties = schema.getAdditionalProperties();
if (additionalProperties instanceof Schema) {
return hasSelfReference(openAPI, (Schema) additionalProperties, visitedSchemaNames);
}
} else if (schema.getNot() != null) {
return hasSelfReference(openAPI, schema.getNot(), visitedSchemaNames);
} else if (schema.getProperties() != null && !schema.getProperties().isEmpty()) {
// go through properties to see if there's any self-reference
for (Schema property : ((Map<String, Schema>)schema.getProperties()).values()) {
if (hasSelfReference(openAPI, property, visitedSchemaNames)) {
return true;
}
}
}
return false;
}
/**
* Get the actual schema from aliases. If the provided schema is not an alias, the schema itself will be returned.
*
@ -1131,7 +1221,14 @@ public class ModelUtils {
}
} else if (isObjectSchema(ref)) { // model
if (ref.getProperties() != null && !ref.getProperties().isEmpty()) { // has at least one property
return schema;
if (hasSelfReference(openAPI, ref)) {
// it's self referencing so returning itself instead
return schema;
} else {
// TODO we may revise below to return `ref` instead of schema
// which is the last reference to the actual model/object
return schema;
}
} else { // free form object (type: object)
return unaliasSchema(openAPI, allSchemas.get(ModelUtils.getSimpleRef(schema.get$ref())),
importMappings);

View File

@ -4125,4 +4125,29 @@ public class DefaultCodegenTest {
assertEquals(cp.baseName, "SchemaFor201ResponseBodyTextPlain");
assertTrue(cp.isString);
}
@Test
public void testUnalias() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/schema-unalias-test.yml");
final DefaultCodegen codegen = new DefaultCodegen();
codegen.setOpenAPI(openAPI);
Schema requestBodySchema = ModelUtils.getSchemaFromRequestBody(
openAPI.getPaths().get("/thingy/{date}").getPost().getRequestBody());
Assert.assertEquals(requestBodySchema.get$ref(), "#/components/schemas/updatePetWithForm_request");
Assert.assertEquals(ModelUtils.getSimpleRef(requestBodySchema.get$ref()), "updatePetWithForm_request");
Assert.assertNotNull(openAPI.getComponents().getSchemas().get(ModelUtils.getSimpleRef(requestBodySchema.get$ref())));
Schema requestBodySchema2 = ModelUtils.unaliasSchema(openAPI, requestBodySchema);
// get$ref is not null as unaliasSchem returns the schema with the last $ref to the actual schema
Assert.assertNotNull(requestBodySchema2.get$ref());
Assert.assertEquals(requestBodySchema2.get$ref(), "#/components/schemas/updatePetWithForm_request");
Schema requestBodySchema3 = ModelUtils.getReferencedSchema(openAPI, requestBodySchema);
CodegenParameter codegenParameter = codegen.fromFormProperty("visitDate",
(Schema) requestBodySchema3.getProperties().get("visitDate"), new HashSet<>());
Assert.assertEquals(codegenParameter.defaultValue, "1971-12-19T03:39:57-08:00");
Assert.assertEquals(codegenParameter.getSchema(), null);
}
}

View File

@ -0,0 +1,43 @@
openapi: 3.0.3
info:
description: Test schema unalias
types
title: Api Documentation
version: "1.0"
servers:
- url: /
paths:
/thingy/{date}:
post:
description: update with form data
operationId: updatePetWithForm
parameters:
- description: A date path parameter
explode: false
in: path
name: date
required: true
schema:
default: 1969-12-31T16:00:00.000+00:00
example: 2021-01-01
format: date
type: string
style: simple
requestBody:
content:
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/updatePetWithForm_request'
responses:
"405":
description: Invalid input
components:
schemas:
updatePetWithForm_request:
properties:
visitDate:
default: 1971-12-19T03:39:57-08:00
description: Updated last vist timestamp
format: date-time
type: string
type: object