Skip to content

Commit

Permalink
DefaultGenerator: ignore only form param schemas (#74)
Browse files Browse the repository at this point in the history
* Rewrite ModelUtils.getUnusedSchemas(OpenAPI)
* Add ModelUtils.getAllUnusedSchemas(OpenAPI)
* Add ModelUtils.getSchemasUsedOnlyInFormParam(OpenAPI)
  • Loading branch information
jmini authored May 24, 2018
1 parent a3aabd3 commit 76b7307
Show file tree
Hide file tree
Showing 5 changed files with 553 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -778,9 +778,9 @@ public List<File> generate() {

List<File> files = new ArrayList<File>();
// models
List<String> unusedSchemas = ModelUtils.getUnusedSchemas(openAPI);
List<String> filteredSchemas = ModelUtils.getSchemasUsedOnlyInFormParam(openAPI);
List<Object> allModels = new ArrayList<Object>();
generateModels(files, allModels, unusedSchemas);
generateModels(files, allModels, filteredSchemas);
// apis
List<Object> allOperations = new ArrayList<Object>();
generateApis(files, allOperations, allModels);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.media.UUIDSchema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.parser.util.SchemaTypeUtil;
Expand All @@ -52,6 +53,8 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;


public class ModelUtils {
Expand Down Expand Up @@ -85,49 +88,126 @@ public static CodegenModel getModelByName(final String name, final Map<String, O
return null;
}

/**
* Return the list of all schemas in the 'components/schemas' section used in the openAPI specification
* @param openAPI specification
* @return schemas a list of used schemas
*/
public static List<String> getAllUsedSchemas(OpenAPI openAPI) {
List<String> allUsedSchemas = new ArrayList<String>();
visitOpenAPI(openAPI, (s, t) -> {
if(s.get$ref() != null) {
String ref = getSimpleRef(s.get$ref());
if(!allUsedSchemas.contains(ref)) {
allUsedSchemas.add(ref);
}
}
});
return allUsedSchemas;
}

/**
* Return the list of unused schemas in the 'components/schemas' section of an openAPI specification
* @param openAPI specification
* @return schemas a list of unused schemas
*/
public static List<String> getUnusedSchemas(OpenAPI openAPI) {
List<String> unusedSchemas = new ArrayList<String>();

// operations
Map<String, PathItem> paths = openAPI.getPaths();
Map<String, Schema> schemas = getSchemas(openAPI);
unusedSchemas.addAll(schemas.keySet());

visitOpenAPI(openAPI, (s, t) -> {
if(s.get$ref() != null) {
unusedSchemas.remove(getSimpleRef(s.get$ref()));
}
});
return unusedSchemas;
}

/**
* Return the list of schemas in the 'components/schemas' used only in a 'application/x-www-form-urlencoded' or 'multipart/form-data' mime time
* @param openAPI specification
* @return schemas a list of schemas
*/
public static List<String> getSchemasUsedOnlyInFormParam(OpenAPI openAPI) {
List<String> schemasUsedInFormParam = new ArrayList<String>();
List<String> schemasUsedInOtherCases = new ArrayList<String>();

visitOpenAPI(openAPI, (s, t) -> {
if(s.get$ref() != null) {
String ref = getSimpleRef(s.get$ref());
if ("application/x-www-form-urlencoded".equalsIgnoreCase(t) ||
"multipart/form-data".equalsIgnoreCase(t)) {
schemasUsedInFormParam.add(ref);
} else {
schemasUsedInOtherCases.add(ref);
}
}
});
return schemasUsedInFormParam.stream().filter(n -> !schemasUsedInOtherCases.contains(n)).collect(Collectors.toList());
}

/**
* Private method used by several methods ({@link #getAllUsedSchemas(OpenAPI)},
* {@link #getUnusedSchemas(OpenAPI)},
* {@link #getSchemasUsedOnlyInFormParam(OpenAPI)}, ...) to traverse all paths of an
* OpenAPI instance and call the visitor functional interface when a schema is found.
*
* @param openAPI specification
* @param visitor functional interface (can be defined as a lambda) called each time a schema is found.
*/
private static void visitOpenAPI(OpenAPI openAPI, OpenAPISchemaVisitor visitor) {
Map<String, PathItem> paths = openAPI.getPaths();

if (paths != null) {
for (String pathname : paths.keySet()) {
PathItem path = paths.get(pathname);
Map<PathItem.HttpMethod, Operation> operationMap = path.readOperationsMap();
if (operationMap != null) {
for (PathItem.HttpMethod method : operationMap.keySet()) {
Operation operation = operationMap.get(method);
RequestBody requestBody = operation.getRequestBody();

if (requestBody == null) {
continue;
for (PathItem path : paths.values()) {
List<Operation> allOperations = path.readOperations();
if (allOperations != null) {
for (Operation operation : allOperations) {
//Params:
if(operation.getParameters() != null) {
for (Parameter p : operation.getParameters()) {
Parameter parameter = getReferencedParameter(openAPI, p);
if (parameter.getSchema() != null) {
visitor.visit(parameter.getSchema(), null);
}
}
}

//LOGGER.info("debugging resolver: " + requestBody.toString());
if (requestBody.getContent() == null) {
continue;
//RequestBody:
RequestBody requestBody = getReferencedRequestBody(openAPI, operation.getRequestBody());
if (requestBody != null && requestBody.getContent() != null) {
for (Entry<String, MediaType> e : requestBody.getContent().entrySet()) {
if (e.getValue().getSchema() != null) {
visitor.visit(e.getValue().getSchema(), e.getKey());
}
}
}

// go through "content"
for (String mimeType : requestBody.getContent().keySet()) {
if ("application/x-www-form-urlencoded".equalsIgnoreCase(mimeType) ||
"multipart/form-data".equalsIgnoreCase(mimeType)) {
// remove the schema that's automatically created by the parser
MediaType mediaType = requestBody.getContent().get(mimeType);
if (mediaType.getSchema().get$ref() != null) {
LOGGER.debug("mark schema (form parameters) as unused: " + getSimpleRef(mediaType.getSchema().get$ref()));
unusedSchemas.add(getSimpleRef(mediaType.getSchema().get$ref()));
//Responses:
if(operation.getResponses() != null) {
for (ApiResponse r : operation.getResponses().values()) {
ApiResponse apiResponse = getReferencedApiResponse(openAPI, r);
if (apiResponse != null && apiResponse.getContent() != null) {
for (Entry<String, MediaType> e : apiResponse.getContent().entrySet()) {
if (e.getValue().getSchema() != null) {
visitor.visit(e.getValue().getSchema(), e.getKey());
}
}
}
}
}
}
}
}
}
}

return unusedSchemas;
@FunctionalInterface
private static interface OpenAPISchemaVisitor {

public void visit(Schema schema, String mimeType);
}

public static String getSimpleRef(String ref) {
Expand Down Expand Up @@ -344,7 +424,7 @@ public static boolean isEmailSchema(Schema schema) {

/**
* If a Schema contains a reference to an other Schema with '$ref', returns the referenced Schema or the actual Schema in the other cases.
* @param openAPI
* @param openAPI specification being checked
* @param schema potentially containing a '$ref'
* @return schema without '$ref'
*/
Expand Down Expand Up @@ -373,7 +453,7 @@ public static Map<String, Schema> getSchemas(OpenAPI openAPI) {

/**
* If a RequestBody contains a reference to an other RequestBody with '$ref', returns the referenced RequestBody or the actual RequestBody in the other cases.
* @param openAPI
* @param openAPI specification being checked
* @param requestBody potentially containing a '$ref'
* @return requestBody without '$ref'
*/
Expand All @@ -398,7 +478,7 @@ public static RequestBody getRequestBody(OpenAPI openAPI, String name) {

/**
* If a ApiResponse contains a reference to an other ApiResponse with '$ref', returns the referenced ApiResponse or the actual ApiResponse in the other cases.
* @param openAPI
* @param openAPI specification being checked
* @param apiResponse potentially containing a '$ref'
* @return apiResponse without '$ref'
*/
Expand All @@ -420,12 +500,46 @@ public static ApiResponse getApiResponse(OpenAPI openAPI, String name) {
}
return null;
}


/**
* If a Parameter contains a reference to an other Parameter with '$ref', returns the referenced Parameter or the actual Parameter in the other cases.
* @param openAPI specification being checked
* @param parameter potentially containing a '$ref'
* @return parameter without '$ref'
*/
public static Parameter getReferencedParameter(OpenAPI openAPI, Parameter parameter) {
if (parameter != null && StringUtils.isNotEmpty(parameter.get$ref())) {
String name = getSimpleRef(parameter.get$ref());
return getParameter(openAPI, name);
}
return parameter;
}

public static Parameter getParameter(OpenAPI openAPI, String name) {
if (name == null) {
return null;
}

if (openAPI != null && openAPI.getComponents() != null && openAPI.getComponents().getRequestBodies() != null) {
return openAPI.getComponents().getParameters().get(name);
}
return null;
}

/**
* Return the first defined Schema for a RequestBody
* @param requestBody request body of the operation
* @return firstSchema
*/
public static Schema getSchemaFromRequestBody(RequestBody requestBody) {
return getSchemaFromContent(requestBody.getContent());
}

/**
* Return the first defined Schema for a ApiResponse
* @param response api response of the operation
* @return firstSchema
*/
public static Schema getSchemaFromResponse(ApiResponse response) {
return getSchemaFromContent(response.getContent());
}
Expand All @@ -434,6 +548,9 @@ private static Schema getSchemaFromContent(Content content) {
if (content == null || content.isEmpty()) {
return null;
}
if(content.size() > 1) {
LOGGER.warn("Multiple schemas found, returning only the first one");
}
MediaType mediaType = content.values().iterator().next();
return mediaType.getSchema();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,64 @@
public class ModelUtilsTest {

@Test
public void testEnsureNoDuplicateProduces() {
final OpenAPI openAPI = new OpenAPIParser().readLocation("src/test/resources/3_0/ping.yaml", null, new ParseOptions()).getOpenAPI();
public void testGetAllUsedSchemas() {
final OpenAPI openAPI = new OpenAPIParser().readLocation("src/test/resources/3_0/unusedSchemas.yaml", null, new ParseOptions()).getOpenAPI();
List<String> allUsedSchemas = ModelUtils.getAllUsedSchemas(openAPI);
Assert.assertEquals(allUsedSchemas.size(), 12);

Assert.assertTrue(allUsedSchemas.contains("SomeObjShared"), "contains 'SomeObjShared'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj1"), "contains 'UnusedObj1'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj2"), "contains 'SomeObj2'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj3"), "contains 'SomeObj3'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj6"), "contains 'SomeObj6'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj7"), "contains 'SomeObj7'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj8"), "contains 'SomeObj8'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj9A"), "contains 'SomeObj9A'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj9B"), "contains 'SomeObj9B'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj10A"), "contains 'SomeObj10A'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj10B"), "contains 'SomeObj10B'");
Assert.assertTrue(allUsedSchemas.contains("SomeObj11"), "contains 'SomeObj11'");
}

@Test
public void testGetUnusedSchemas() {
final OpenAPI openAPI = new OpenAPIParser().readLocation("src/test/resources/3_0/unusedSchemas.yaml", null, new ParseOptions()).getOpenAPI();
List<String> unusedSchemas = ModelUtils.getUnusedSchemas(openAPI);
Assert.assertEquals(unusedSchemas.size(), 4);
//UnusedObj is not used at all:
Assert.assertTrue(unusedSchemas.contains("UnusedObj1"), "contains 'UnusedObj1'");
//SomeObjUnused is used in a request body that is not used.
Assert.assertTrue(unusedSchemas.contains("UnusedObj2"), "contains 'UnusedObj2'");
//SomeObjUnused is used in a response that is not used.
Assert.assertTrue(unusedSchemas.contains("UnusedObj3"), "contains 'UnusedObj3'");
//SomeObjUnused is used in a parameter that is not used.
Assert.assertTrue(unusedSchemas.contains("UnusedObj4"), "contains 'UnusedObj4'");
}

@Test
public void testSchemasUsedOnlyInFormParam() {
final OpenAPI openAPI = new OpenAPIParser().readLocation("src/test/resources/3_0/unusedSchemas.yaml", null, new ParseOptions()).getOpenAPI();
List<String> unusedSchemas = ModelUtils.getSchemasUsedOnlyInFormParam(openAPI);
Assert.assertEquals(unusedSchemas.size(), 3);
//SomeObj2 is only used in a 'application/x-www-form-urlencoded' request
Assert.assertTrue(unusedSchemas.contains("SomeObj2"), "contains 'SomeObj2'");
//SomeObj3 is only used in a 'multipart/form-data' request
Assert.assertTrue(unusedSchemas.contains("SomeObj3"), "contains 'SomeObj3'");
//SomeObj7 is only used in a 'application/x-www-form-urlencoded' request (with referenced request body)
Assert.assertTrue(unusedSchemas.contains("SomeObj7"), "contains 'SomeObj7'");
}

@Test
public void testNoComponentsSection() {
final OpenAPI openAPI = new OpenAPIParser().readLocation("src/test/resources/3_0/ping.yaml", null, new ParseOptions()).getOpenAPI();
List<String> unusedSchemas = ModelUtils.getSchemasUsedOnlyInFormParam(openAPI);
Assert.assertEquals(unusedSchemas.size(), 0);
}

@Test
public void testGlobalProducesConsumes() {
final OpenAPI openAPI = new OpenAPIParser().readLocation("src/test/resources/2_0/globalProducesConsumesTest.yaml", null, new ParseOptions()).getOpenAPI();
List<String> unusedSchemas = ModelUtils.getSchemasUsedOnlyInFormParam(openAPI);
Assert.assertEquals(unusedSchemas.size(), 0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
swagger: '2.0'
info:
title: Test
description: Test API
version: 1.0.0
host: some.example.com
basePath: /v1
schemes:
- https
- http
consumes:
- application/json
- application/x-www-form-urlencoded
produces:
- application/json
paths:
/testMe:
post:
tags:
- db
operationId: testMeOp
parameters:
- in: body
name: body
required: false
schema:
$ref: '#/definitions/SomeObject'
responses:
'200':
description: Successful Operation
definitions:
SomeObject:
type: object
properties:
p1:
type: string
p2:
type: integer
format: int32
Loading

0 comments on commit 76b7307

Please sign in to comment.