Fix ModelUtils.getUnusedSchema() (#253)

Fix #252

`ModelUtils.getUnusedSchema()` consider Schemas referenced in other Schemas. Implemented for:

* array
* object
* maps
* ComposedSchema
  - oneOf
  - anyOf
  - allOf
* not
This commit is contained in:
Jérémie Bresson 2018-06-09 11:56:08 +02:00 committed by GitHub
parent 8de5c62cf2
commit 992afd51eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 283 additions and 4 deletions

View File

@ -159,6 +159,7 @@ public class ModelUtils {
*/ */
private static void visitOpenAPI(OpenAPI openAPI, OpenAPISchemaVisitor visitor) { private static void visitOpenAPI(OpenAPI openAPI, OpenAPISchemaVisitor visitor) {
Map<String, PathItem> paths = openAPI.getPaths(); Map<String, PathItem> paths = openAPI.getPaths();
List<String> visitedSchemas = new ArrayList<>();
if (paths != null) { if (paths != null) {
for (PathItem path : paths.values()) { for (PathItem path : paths.values()) {
@ -170,7 +171,7 @@ public class ModelUtils {
for (Parameter p : operation.getParameters()) { for (Parameter p : operation.getParameters()) {
Parameter parameter = getReferencedParameter(openAPI, p); Parameter parameter = getReferencedParameter(openAPI, p);
if (parameter.getSchema() != null) { if (parameter.getSchema() != null) {
visitor.visit(parameter.getSchema(), null); visitSchema(openAPI, parameter.getSchema(), null, visitedSchemas, visitor);
} }
} }
} }
@ -180,7 +181,7 @@ public class ModelUtils {
if (requestBody != null && requestBody.getContent() != null) { if (requestBody != null && requestBody.getContent() != null) {
for (Entry<String, MediaType> e : requestBody.getContent().entrySet()) { for (Entry<String, MediaType> e : requestBody.getContent().entrySet()) {
if (e.getValue().getSchema() != null) { if (e.getValue().getSchema() != null) {
visitor.visit(e.getValue().getSchema(), e.getKey()); visitSchema(openAPI, e.getValue().getSchema(), e.getKey(), visitedSchemas, visitor);
} }
} }
} }
@ -192,7 +193,7 @@ public class ModelUtils {
if (apiResponse != null && apiResponse.getContent() != null) { if (apiResponse != null && apiResponse.getContent() != null) {
for (Entry<String, MediaType> e : apiResponse.getContent().entrySet()) { for (Entry<String, MediaType> e : apiResponse.getContent().entrySet()) {
if (e.getValue().getSchema() != null) { if (e.getValue().getSchema() != null) {
visitor.visit(e.getValue().getSchema(), e.getKey()); visitSchema(openAPI, e.getValue().getSchema(), e.getKey(), visitedSchemas, visitor);
} }
} }
} }
@ -204,6 +205,59 @@ public class ModelUtils {
} }
} }
private static void visitSchema(OpenAPI openAPI, Schema schema, String mimeType, List<String> visitedSchemas, OpenAPISchemaVisitor visitor) {
visitor.visit(schema, mimeType);
if(schema.get$ref() != null) {
String ref = getSimpleRef(schema.get$ref());
if(!visitedSchemas.contains(ref)) {
visitedSchemas.add(ref);
Schema referencedSchema = getSchemas(openAPI).get(ref);
if(referencedSchema != null) {
visitSchema(openAPI, referencedSchema, mimeType, visitedSchemas, visitor);
}
}
}
if(schema instanceof ComposedSchema) {
List<Schema> oneOf = ((ComposedSchema) schema).getOneOf();
if(oneOf != null) {
for (Schema s : oneOf) {
visitSchema(openAPI, s, mimeType, visitedSchemas, visitor);
}
}
List<Schema> allOf = ((ComposedSchema) schema).getAllOf();
if(allOf != null) {
for (Schema s : allOf) {
visitSchema(openAPI, s, mimeType, visitedSchemas, visitor);
}
}
List<Schema> anyOf = ((ComposedSchema) schema).getAnyOf();
if(anyOf != null) {
for (Schema s : anyOf) {
visitSchema(openAPI, s, mimeType, visitedSchemas, visitor);
}
}
} else if(schema instanceof ArraySchema) {
Schema itemsSchema = ((ArraySchema) schema).getItems();
if(itemsSchema != null) {
visitSchema(openAPI, itemsSchema, mimeType, visitedSchemas, visitor);
}
} else if(isMapSchema(schema)) {
Object additionalProperties = schema.getAdditionalProperties();
if(additionalProperties instanceof Schema) {
visitSchema(openAPI, (Schema) additionalProperties, mimeType, visitedSchemas, visitor);
}
}
if(schema.getNot() != null) {
visitSchema(openAPI, schema.getNot(), mimeType, visitedSchemas, visitor);
}
Map<String, Schema> properties = schema.getProperties();
if(properties != null) {
for (Schema property : properties.values()) {
visitSchema(openAPI, property, mimeType, visitedSchemas, visitor);
}
}
}
@FunctionalInterface @FunctionalInterface
private static interface OpenAPISchemaVisitor { private static interface OpenAPISchemaVisitor {

View File

@ -40,7 +40,7 @@ public class ModelUtilsTest {
public void testGetAllUsedSchemas() { public void testGetAllUsedSchemas() {
final OpenAPI openAPI = new OpenAPIParser().readLocation("src/test/resources/3_0/unusedSchemas.yaml", null, new ParseOptions()).getOpenAPI(); final OpenAPI openAPI = new OpenAPIParser().readLocation("src/test/resources/3_0/unusedSchemas.yaml", null, new ParseOptions()).getOpenAPI();
List<String> allUsedSchemas = ModelUtils.getAllUsedSchemas(openAPI); List<String> allUsedSchemas = ModelUtils.getAllUsedSchemas(openAPI);
Assert.assertEquals(allUsedSchemas.size(), 12); Assert.assertEquals(allUsedSchemas.size(), 28);
Assert.assertTrue(allUsedSchemas.contains("SomeObjShared"), "contains 'SomeObjShared'"); Assert.assertTrue(allUsedSchemas.contains("SomeObjShared"), "contains 'SomeObjShared'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj1"), "contains 'UnusedObj1'"); Assert.assertTrue(allUsedSchemas.contains("SomeObj1"), "contains 'UnusedObj1'");
@ -54,6 +54,22 @@ public class ModelUtilsTest {
Assert.assertTrue(allUsedSchemas.contains("SomeObj10A"), "contains 'SomeObj10A'"); Assert.assertTrue(allUsedSchemas.contains("SomeObj10A"), "contains 'SomeObj10A'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj10B"), "contains 'SomeObj10B'"); Assert.assertTrue(allUsedSchemas.contains("SomeObj10B"), "contains 'SomeObj10B'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj11"), "contains 'SomeObj11'"); Assert.assertTrue(allUsedSchemas.contains("SomeObj11"), "contains 'SomeObj11'");
Assert.assertTrue(allUsedSchemas.contains("SomeArrayObj12"), "contains 'SomeArrayObj12'");
Assert.assertTrue(allUsedSchemas.contains("ArrayItem12"), "contains 'ArrayItem12'");
Assert.assertTrue(allUsedSchemas.contains("SomeArrayObj13"), "contains 'SomeArrayObj13'");
Assert.assertTrue(allUsedSchemas.contains("ArrayItem13"), "contains 'ArrayItem13'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj14"), "contains 'SomeObj14'");
Assert.assertTrue(allUsedSchemas.contains("PropertyObj14"), "contains 'PropertyObj14'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj15"), "contains 'SomeObj15'");
Assert.assertTrue(allUsedSchemas.contains("SomeMapObj16"), "contains 'SomeMapObj16'");
Assert.assertTrue(allUsedSchemas.contains("MapItem16"), "contains 'MapItem16'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj17"), "contains 'SomeObj17'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj18"), "contains 'SomeObj18'");
Assert.assertTrue(allUsedSchemas.contains("Common18"), "contains 'Common18'");
Assert.assertTrue(allUsedSchemas.contains("Obj19ByAge"), "contains 'Obj19ByAge'");
Assert.assertTrue(allUsedSchemas.contains("Obj19ByType"), "contains 'Obj19ByType'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj20"), "contains 'SomeObj20'");
Assert.assertTrue(allUsedSchemas.contains("OtherObj20"), "contains 'OtherObj20'");
} }
@Test @Test

View File

@ -135,6 +135,104 @@ paths:
200: 200:
description: Successful Operation description: Successful Operation
content: {} content: {}
/some/p12:
post:
operationId: p12
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SomeArrayObj12'
responses:
200:
description: Successful Operation
content: {}
/some/p13:
get:
operationId: p13
responses:
200:
description: Successful Operation
content:
application/json:
schema:
$ref: '#/components/schemas/SomeArrayObj13'
/some/p14:
get:
operationId: p14
responses:
200:
description: Successful Operation
content:
application/json:
schema:
$ref: '#/components/schemas/SomeObj14'
/some/p15:
get:
operationId: p15
responses:
'200':
description: OK
content:
text/plain:
schema:
$ref: '#/components/schemas/SomeObj15'
/some/p16:
get:
operationId: p16
responses:
'200':
description: OK
content:
text/plain:
schema:
$ref: '#/components/schemas/SomeMapObj16'
/some/p17:
get:
operationId: p17
responses:
'200':
description: OK
content:
text/plain:
schema:
oneOf:
- type: string
- $ref: '#/components/schemas/SomeObj17'
/some/p18:
get:
operationId: p18
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SomeObj18'
responses:
200:
description: Successful Operation
content: {}
/some/p19:
patch:
requestBody:
content:
application/json:
schema:
anyOf:
- $ref: '#/components/schemas/Obj19ByAge'
- $ref: '#/components/schemas/Obj19ByType'
responses:
'200':
description: Updated
/some/p20:
get:
operationId: op20
responses:
'200':
description: OK
content:
text/plain:
schema:
$ref: '#/components/schemas/SomeObj20'
components: components:
schemas: schemas:
UnusedObj1: UnusedObj1:
@ -254,6 +352,117 @@ components:
- v1 - v1
- v2 - v2
default: v1 default: v1
SomeArrayObj12:
type: array
items:
$ref: "#/components/schemas/ArrayItem12"
ArrayItem12:
type: object
properties:
prop1:
type: string
prop2:
type: integer
format: int32
SomeArrayObj13:
type: array
items:
type: array
items:
$ref: "#/components/schemas/ArrayItem13"
ArrayItem13:
type: object
properties:
prop1:
type: string
prop2:
type: integer
format: int64
SomeObj14:
type: object
properties:
obj14:
$ref: "#/components/schemas/PropertyObj14"
PropertyObj14:
type: object
properties:
prop1:
type: string
SomeObj15:
type: object
properties:
message:
type: string
details:
type: array
items:
$ref: '#/components/schemas/SomeObj15'
SomeMapObj16:
type: array
items:
$ref: "#/components/schemas/MapItem16"
MapItem16:
type: object
properties:
prop1:
type: integer
format: int32
prop2:
type: integer
format: int64
SomeObj17:
type: object
properties:
id:
type: String
message:
type: String
SomeObj18:
allOf:
- $ref: '#/components/schemas/Common18'
- type: object
properties:
firstName:
type: String
lastName:
type: String
Common18:
type: object
properties:
id:
type: String
active:
type: boolean
Obj19ByAge:
type: object
properties:
age:
type: integer
firstName:
type: String
lastName:
type: String
required:
- age
Obj19ByType:
type: object
properties:
sometype:
type: string
enum: [A, B, C]
enabled:
type: boolean
required:
- sometype
SomeObj20:
type: object
properties:
other:
not:
$ref: '#/components/schemas/OtherObj20'
OtherObj20:
type: string
enum: [A, B, C]
SomeObjShared: SomeObjShared:
type: object type: object
properties: properties: