Skip to content

Commit 5ee92ca

Browse files
author
Olha Yelisieieva
committed
Issue-19: Added MIME type validator with tests
1 parent f530f91 commit 5ee92ca

23 files changed

+520
-27
lines changed

src/main/java/com/github/sylvainlaurent/maven/swaggervalidator/ValidateMojo.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,16 @@ public class ValidateMojo extends AbstractMojo {
4242
@Parameter(defaultValue = "true")
4343
private boolean failOnErrors;
4444

45+
@Parameter
46+
private String[] customMimeTypes;
47+
4548
private final ValidationService validationService = new ValidationService();
4649

4750
@Override
4851
public void execute() throws MojoExecutionException {
4952
validationService.setCustomModelValidatorsPackage(customModelValidatorsPackage);
5053
validationService.setCustomPathValidatorsPackage(customPathValidatorsPackage);
54+
validationService.setCustomMimeTypes(customMimeTypes);
5155

5256
Instrumentation.init();
5357

src/main/java/com/github/sylvainlaurent/maven/swaggervalidator/semantic/validator/ValidationContext.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
11
package com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator;
22

33
import static org.apache.commons.collections4.MapUtils.emptyIfNull;
4+
import static org.apache.commons.lang3.reflect.FieldUtils.readDeclaredStaticField;
45

56
import java.util.ArrayList;
67
import java.util.HashMap;
8+
import java.util.HashSet;
79
import java.util.List;
810
import java.util.Map;
11+
import java.util.Set;
12+
import java.util.stream.Collectors;
913

1014
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.VisitableParameterFactory;
1115
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.node.VisitableParameter;
1216
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.node.path.PathWrapper;
13-
17+
import com.google.common.net.MediaType;
18+
import edu.emory.mathcs.backport.java.util.Arrays;
1419
import io.swagger.models.Model;
1520
import io.swagger.models.Path;
1621
import io.swagger.models.Swagger;
1722
import io.swagger.models.auth.SecuritySchemeDefinition;
1823
import io.swagger.models.parameters.Parameter;
1924

2025
public class ValidationContext {
21-
26+
private final Set<String> definedMimeTypes;
2227
protected List<PathWrapper> paths = new ArrayList<>();
2328
protected Map<String, Model> definitions = new HashMap<>();
2429
private Swagger swagger;
@@ -31,6 +36,7 @@ public ValidationContext(Swagger swagger) {
3136
this.swagger = swagger;
3237
setPaths(emptyIfNull(swagger.getPaths()));
3338
setDefinitions(emptyIfNull(swagger.getDefinitions()));
39+
definedMimeTypes = getDefinedMimeTypes();
3440
}
3541

3642
public Swagger getSwagger() {
@@ -66,4 +72,26 @@ public Map<String, VisitableParameter<? extends Parameter>> getParameters() {
6672
public Map<String, SecuritySchemeDefinition> getSecurityDefinitions() {
6773
return swagger.getSecurityDefinitions();
6874
}
75+
76+
@SuppressWarnings("unchecked")
77+
private Set<String> getDefinedMimeTypes() {
78+
Set<String> set;
79+
try {
80+
set = ((Map<MediaType, MediaType>) readDeclaredStaticField(MediaType.class, "KNOWN_TYPES", true)).keySet().stream()
81+
.map(x -> x.withoutParameters().toString()).collect(Collectors.toSet());
82+
} catch (IllegalAccessException e) {
83+
set = new HashSet<>();
84+
}
85+
return set;
86+
}
87+
88+
public Set<String> getMimeTypes() {
89+
return definedMimeTypes;
90+
}
91+
92+
public void addCustomMimeTypes(String[] customMimeTypes) {
93+
if (customMimeTypes != null) {
94+
definedMimeTypes.addAll(Arrays.asList(customMimeTypes));
95+
}
96+
}
6997
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator.path;
2+
3+
import static org.apache.commons.collections4.ListUtils.emptyIfNull;
4+
5+
import java.util.ArrayList;
6+
import java.util.Collection;
7+
import java.util.List;
8+
9+
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.node.path.OperationWrapper;
10+
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator.error.SemanticError;
11+
12+
public class MimeTypeValidator extends PathValidatorTemplate {
13+
14+
@Override
15+
public void validate(OperationWrapper operation) {
16+
validateMimeTypes(operation);
17+
validateConsumes(operation);
18+
validateProduces(operation);
19+
}
20+
21+
private void validateConsumes(OperationWrapper operation) {
22+
String operationName = operation.getName();
23+
List<String> consumes = operation.getConsumes();
24+
if (consumes == null) {
25+
consumes = new ArrayList<>(emptyIfNull(context.getSwagger().getConsumes()));
26+
}
27+
if ("get".equals(operationName) || "delete".equals(operationName) ||
28+
(operation.getParameters("body").isEmpty() && operation.getParameters("formData").isEmpty())) {
29+
if (consumes == null || !consumes.isEmpty()) {
30+
validationErrors
31+
.add(new SemanticError(holder.getCurrentPath(), "'consumes' must be equal to 'consumes: []' "));
32+
}
33+
} else {
34+
if (consumes == null || consumes.isEmpty()) {
35+
validationErrors
36+
.add(new SemanticError(holder.getCurrentPath(), "'consumes' cannot be empty"));
37+
}
38+
}
39+
}
40+
41+
private void validateProduces(OperationWrapper operation) {
42+
List<String> produces = operation.getProduces();
43+
if (produces == null) {
44+
produces = new ArrayList<>(emptyIfNull(context.getSwagger().getProduces()));
45+
}
46+
boolean hasSuccessfullResponseWithSchema = operation.getResponses().stream().filter(x -> x.getName().startsWith("2"))
47+
.anyMatch(r -> r.getSchema() != null);
48+
if (hasSuccessfullResponseWithSchema) {
49+
if (produces == null || produces.isEmpty()) {
50+
validationErrors
51+
.add(new SemanticError(holder.getCurrentPath(), "'produces' cannot be empty"));
52+
}
53+
} else {
54+
if (produces == null || !produces.isEmpty()) {
55+
validationErrors
56+
.add(new SemanticError(holder.getCurrentPath(), "'produces' must be equal to 'produces: []'"));
57+
}
58+
}
59+
}
60+
61+
public void validateMimeTypes(OperationWrapper operation) {
62+
validateMimeTypesInCollection(operation.getConsumes(), "consumes");
63+
validateMimeTypesInCollection(operation.getProduces(), "produces");
64+
}
65+
66+
private void validateMimeTypesInCollection(Collection<String> collection, String message) {
67+
if (collection != null) {
68+
collection.stream().filter(x ->
69+
{
70+
try {
71+
return !(context.getMimeTypes().contains(x));
72+
} catch (Exception e) {
73+
return true;
74+
}
75+
})
76+
.forEach(x -> validationErrors
77+
.add(new SemanticError(holder.getCurrentPath(), "invalid MIME type '" + x + "' in '" + message + "'")));
78+
}
79+
}
80+
}

src/main/java/com/github/sylvainlaurent/maven/swaggervalidator/service/SemanticValidationService.java

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
package com.github.sylvainlaurent.maven.swaggervalidator.service;
22

3+
import static com.github.sylvainlaurent.maven.swaggervalidator.semantic.VisitableModelFactory.createVisitableModel;
4+
5+
import java.util.ArrayList;
6+
import java.util.HashSet;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.Set;
10+
311
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.node.VisitableModel;
12+
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.node.path.OperationWrapper;
413
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator.SwaggerValidator;
514
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator.ValidationContext;
615
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator.definition.InheritanceChainPropertiesValidator;
716
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator.definition.ReferenceValidator;
817
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator.definition.VisitableModelValidator;
918
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator.error.SemanticError;
1019
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator.path.FormDataValidator;
20+
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator.path.MimeTypeValidator;
1121
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator.path.OperationParametersReferenceValidator;
1222
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator.path.OperationValidator;
1323
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator.path.PathValidator;
@@ -16,16 +26,9 @@
1626
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator.path.SwaggerPathValidator;
1727
import com.github.sylvainlaurent.maven.swaggervalidator.util.Util;
1828
import io.swagger.models.Model;
29+
import io.swagger.models.Operation;
1930
import io.swagger.models.Swagger;
2031

21-
import java.util.ArrayList;
22-
import java.util.HashSet;
23-
import java.util.List;
24-
import java.util.Map;
25-
import java.util.Set;
26-
27-
import static com.github.sylvainlaurent.maven.swaggervalidator.semantic.VisitableModelFactory.createVisitableModel;
28-
2932
public class SemanticValidationService {
3033

3134
private ValidationContext context;
@@ -44,21 +47,30 @@ public SemanticValidationService(Swagger swagger) {
4447
validators.add(new OperationValidator());
4548
validators.add(new OperationParametersReferenceValidator());
4649
validators.add(new SecurityValidator());
50+
validators.add(new MimeTypeValidator());
4751
}
4852

49-
public SemanticValidationService(Swagger swagger, String validatorsPackageName, String pathValidatorsPackageName) {
53+
public SemanticValidationService(Swagger swagger, String validatorsPackageName, String pathValidatorsPackageName, String[] customMimeTypes) {
5054
this(swagger);
5155
if (validatorsPackageName != null) {
5256
modelValidators.addAll(Util.createInstances(validatorsPackageName, VisitableModelValidator.class));
5357
}
5458
if (pathValidatorsPackageName != null) {
5559
validators.addAll(Util.createInstances(pathValidatorsPackageName, SwaggerPathValidator.class));
5660
}
61+
context.addCustomMimeTypes(customMimeTypes);
5762
}
5863

5964
public List<SemanticError> validate() {
65+
6066
Set<SemanticError> uniqueValidationErrors = new HashSet<>();
6167

68+
MimeTypeValidator mimeTypeValidator = new MimeTypeValidator();
69+
mimeTypeValidator.setValidationContext(context);
70+
mimeTypeValidator.validateMimeTypes(new OperationWrapper("swagger-root", new Operation().consumes(context.getSwagger().getConsumes())
71+
.produces(context.getSwagger().getProduces()), null));
72+
uniqueValidationErrors.addAll(mimeTypeValidator.getErrors());
73+
6274
for (SwaggerValidator validator : validators) {
6375
validator.setValidationContext(context);
6476
validator.validate();

src/main/java/com/github/sylvainlaurent/maven/swaggervalidator/service/ValidationService.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public class ValidationService {
3434
private JsonSchema schema;
3535
private String customModelValidatorsPackage;
3636
private String customPathValidatorsPackage;
37+
private String[] customMimeTypes;
3738

3839
public ValidationService() {
3940
final InputStream is = this.getClass().getClassLoader().getResourceAsStream(SCHEMA_FILE);
@@ -95,7 +96,7 @@ private void validateSwagger(final JsonNode spec, final ValidationResult validat
9596
}
9697

9798
List<SemanticError> semanticValidationResult = new SemanticValidationService(swagger,
98-
customModelValidatorsPackage, customPathValidatorsPackage).validate();
99+
customModelValidatorsPackage, customPathValidatorsPackage, customMimeTypes).validate();
99100
if (!semanticValidationResult.isEmpty()){
100101
for (SemanticError error: semanticValidationResult) {
101102
validationResult.addMessage(error.toString());
@@ -119,4 +120,8 @@ public void setCustomModelValidatorsPackage(String customModelValidatorsPackage)
119120
public void setCustomPathValidatorsPackage(String customPathValidatorsPackage) {
120121
this.customPathValidatorsPackage = customPathValidatorsPackage;
121122
}
123+
124+
public void setCustomMimeTypes(String[] customMimeTypes) {
125+
this.customMimeTypes = customMimeTypes;
126+
}
122127
}

src/test/java/com/github/sylvainlaurent/maven/swaggervalidator/semantic/validator/path/FormDataValidatorTest.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public void form_data_without_consumes() {
4848
logger.info(errors.toString());
4949

5050
assertFalse(errors.isEmpty());
51-
assertEquals(2, errors.size());
51+
assertEquals(3, errors.size());
5252
SemanticError error1 = errors.get(0);
5353
assertEquals("Operations with Parameters of 'in: formData' must include 'application/x-www-form-urlencoded' or 'multipart/form-data' in their 'consumes' property.",
5454
error1.getMessage());
@@ -57,5 +57,9 @@ public void form_data_without_consumes() {
5757
assertEquals("Operations with Parameters of 'in: formData' must include 'application/x-www-form-urlencoded' or 'multipart/form-data' in their 'consumes' property.",
5858
error2.getMessage());
5959
assertEquals("paths./category.post", error2.getPath());
60+
SemanticError error3 = errors.get(2);
61+
assertEquals("'consumes' cannot be empty",
62+
error3.getMessage());
63+
assertEquals("paths./category.post", error3.getPath());
6064
}
61-
}
65+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator.path;
2+
3+
import static com.github.sylvainlaurent.maven.swaggervalidator.SemanticValidationServiceTest.readDoc;
4+
import static org.junit.Assert.assertFalse;
5+
import static org.junit.Assert.assertTrue;
6+
7+
import java.util.List;
8+
9+
import com.github.sylvainlaurent.maven.swaggervalidator.SemanticValidationServiceTest;
10+
import com.github.sylvainlaurent.maven.swaggervalidator.ValidatorJunitRunner;
11+
import com.github.sylvainlaurent.maven.swaggervalidator.semantic.validator.error.SemanticError;
12+
import com.github.sylvainlaurent.maven.swaggervalidator.service.SemanticValidationService;
13+
import io.swagger.parser.util.SwaggerDeserializationResult;
14+
import org.junit.Test;
15+
import org.junit.runner.RunWith;
16+
import org.slf4j.Logger;
17+
import org.slf4j.LoggerFactory;
18+
19+
@RunWith(ValidatorJunitRunner.class)
20+
public class MimeTypeValidatorTest {
21+
22+
23+
public static final String RESOURCE_FOLDER = "src/test/resources/semantic-validation/";
24+
private static Logger logger = LoggerFactory.getLogger(SemanticValidationServiceTest.class);
25+
26+
@Test
27+
public void fail_when_invalid_mime_types_in_consumes_and_produces() {
28+
SwaggerDeserializationResult swaggerResult = readDoc(
29+
RESOURCE_FOLDER + "consumes-and-produces-invalid-MIME-types.yml");
30+
List<SemanticError> errors = new SemanticValidationService(swaggerResult.getSwagger()).validate();
31+
logger.info(errors.toString());
32+
assertFalse(errors.isEmpty());
33+
assertTrue(errors.contains(new SemanticError("paths./producs.post", "invalid MIME type 'application/mytype4' in 'produces'")));
34+
assertTrue(errors.contains(new SemanticError("paths./producs.post", "invalid MIME type 'application/mytype3' in 'consumes'")));
35+
assertTrue(errors.contains(new SemanticError("", "invalid MIME type 'application/mytype2' in 'consumes'")));
36+
assertTrue(errors.contains(new SemanticError("", "invalid MIME type 'application/mytype1' in 'produces'")));
37+
}
38+
39+
@Test
40+
public void succes_when_valid_mime_types_in_consumes_and_produces() {
41+
SwaggerDeserializationResult swaggerResult = readDoc(
42+
RESOURCE_FOLDER + "consumes-and-produces-valid-MIME-types.yml");
43+
List<SemanticError> errors = new SemanticValidationService(swaggerResult.getSwagger()).validate();
44+
logger.info(errors.toString());
45+
assertTrue(errors.isEmpty());
46+
}
47+
48+
@Test
49+
public void fail_when_consumes_and_produces_are_absent_at_all() {
50+
SwaggerDeserializationResult swaggerResult = readDoc(
51+
RESOURCE_FOLDER + "consumes-and-produces-absent-at-all.yml");
52+
List<SemanticError> errors = new SemanticValidationService(swaggerResult.getSwagger()).validate();
53+
logger.info(errors.toString());
54+
assertFalse(errors.isEmpty());
55+
assertTrue(errors.contains(new SemanticError("paths./producs.post", "'produces' cannot be empty")));
56+
assertTrue(errors.contains(new SemanticError("paths./producs.post", "'consumes' cannot be empty")));
57+
assertTrue(errors.contains(new SemanticError("paths./producs.get", "'produces' cannot be empty")));
58+
}
59+
60+
@Test
61+
public void success_when_consumes_and_produces_are_absent_in_swagger_root_but_present_in_operations() {
62+
SwaggerDeserializationResult swaggerResult = readDoc(
63+
RESOURCE_FOLDER + "consumes-and-produces-absent-in-root.yml");
64+
List<SemanticError> errors = new SemanticValidationService(swaggerResult.getSwagger()).validate();
65+
logger.info(errors.toString());
66+
assertTrue(errors.isEmpty());
67+
}
68+
69+
70+
@Test
71+
public void fail_when_consumes_and_produces_are_missed_or_invalid_in_operations() {
72+
SwaggerDeserializationResult swaggerResult = readDoc(
73+
RESOURCE_FOLDER + "consumes-and-produces-invalid-values-in-operations.yml");
74+
List<SemanticError> errors = new SemanticValidationService(swaggerResult.getSwagger()).validate();
75+
logger.info(errors.toString());
76+
assertFalse(errors.isEmpty());
77+
assertTrue(errors.contains(new SemanticError("paths./producs.post", "'produces' cannot be empty")));
78+
assertTrue(errors.contains(new SemanticError("paths./producs.post", "'consumes' cannot be empty")));
79+
assertTrue(errors.contains(new SemanticError("paths./producs.get", "'consumes' must be equal to 'consumes: []' ")));
80+
assertTrue(errors.contains(new SemanticError("paths./producs.get", "'produces' cannot be empty")));
81+
}
82+
}

0 commit comments

Comments
 (0)