Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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