[core] Extracting recommendations to validation framework (#4979)

* [core] Extracting recommendations to validation framework

This is work to extract recommendation logic out of the CLI validate command
into a shared evaluation instance which can be used elsewhere (such as Gradle,
or the Online tool).

For now, these validations are in addition to those provided by swagger-parser and
are only the following recommendations:

* Apache/Nginx warning that header values with underscore are dropped by default
* Unused models/schemas
* Use of properties with oneOf, which is ambiguous in OpenAPI Specification

I've allowed for disabling recommendations via System properties, since this is
something that has been requested a few times by users. System properties in this
commit include:

* openapi.generator.rule.recommendations=false
  - Allows for disabling recommendations completely. This wouldn't include all warnings
    and errors, only those we deem to be suggestions
* openapi.generator.rule.apache-nginx-underscore=false
  - Allows for disabling the Apache/Nginx warning when header names have underscore
  - This is a legacy CGI configuration, and doesn't affect all web servers
* openapi.generator.rule.oneof-properties-ambiguity=false
  - We support this functionality, but the specification may not intend for it
  - This is more to reduce noise
* openapi.generator.rule.unused-schemas=false
  - We will warn when a schema is not referenced outside of Components, which
    users have requested to be able to turn off
* openapi.generator.rule.anti-patterns.uri-unexpected-body=false

* Move recommendation/validations to oas package and add javadoc comments

* Refactor and test recommendation validations

* Refactor validation function signatures to return explicit state rather than boolean

* Add operation recommendation for GET/HEAD w/body
This commit is contained in:
Jim Schubert
2020-01-31 12:19:16 -05:00
committed by GitHub
parent f06ac9d91c
commit 22c6c0ca68
20 changed files with 1089 additions and 75 deletions

View File

@@ -22,14 +22,14 @@ import io.airlift.airline.Option;
import io.swagger.parser.OpenAPIParser;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.ComposedSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import org.openapitools.codegen.utils.ModelUtils;
import org.apache.commons.lang3.text.WordUtils;
import org.openapitools.codegen.validation.ValidationResult;
import org.openapitools.codegen.validations.oas.OpenApiEvaluator;
import org.openapitools.codegen.validations.oas.RuleConfiguration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Command(name = "validate", description = "Validate specification")
@@ -54,42 +54,28 @@ public class Validate implements Runnable {
StringBuilder sb = new StringBuilder();
OpenAPI specification = result.getOpenAPI();
if (Boolean.TRUE.equals(recommend)) {
if (specification != null) {
// Add information about unused models to the warnings set.
List<String> unusedModels = ModelUtils.getUnusedSchemas(specification);
if (unusedModels != null) {
unusedModels.forEach(name -> warnings.add("Unused model: " + name));
}
RuleConfiguration ruleConfiguration = new RuleConfiguration();
ruleConfiguration.setEnableRecommendations(recommend != null ? recommend : false);
// check for loosely defined oneOf extension requirements.
// This is a recommendation because the 3.0.x spec is not clear enough on usage of oneOf.
// see https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.2.1.3 and the OAS section on 'Composition and Inheritance'.
Map<String, Schema> schemas = ModelUtils.getSchemas(specification);
schemas.forEach((key, schema) -> {
if (schema instanceof ComposedSchema) {
final ComposedSchema composed = (ComposedSchema) schema;
if (composed.getOneOf() != null && composed.getOneOf().size() > 0) {
if (composed.getProperties() != null && composed.getProperties().size() >= 1 && composed.getProperties().get("discriminator") == null) {
warnings.add("Schema (oneOf) should not contain properties: " + key);
}
}
}
});
}
}
OpenApiEvaluator evaluator = new OpenApiEvaluator(ruleConfiguration);
ValidationResult validationResult = evaluator.validate(specification);
// TODO: We could also provide description here along with getMessage. getMessage is either a "generic" message or specific (e.g. Model 'Cat' has issues).
// This would require that we parse the messageList coming from swagger-parser into a better structure.
validationResult.getWarnings().forEach(invalid -> warnings.add(invalid.getMessage()));
validationResult.getErrors().forEach(invalid -> errors.add(invalid.getMessage()));
if (!errors.isEmpty()) {
sb.append("Errors:").append(System.lineSeparator());
errors.forEach(msg ->
sb.append("\t-").append(msg).append(System.lineSeparator())
sb.append("\t- ").append(WordUtils.wrap(msg, 90).replace(System.lineSeparator(), System.lineSeparator() + "\t ")).append(System.lineSeparator())
);
}
if (!warnings.isEmpty()) {
sb.append("Warnings: ").append(System.lineSeparator());
warnings.forEach(msg ->
sb.append("\t-").append(msg).append(System.lineSeparator())
sb.append("\t- ").append(WordUtils.wrap(msg, 90).replace(System.lineSeparator(), System.lineSeparator() + "\t ")).append(System.lineSeparator())
);
}