forked from loafle/openapi-generator-original
Validate spec on generation by default (#251)
* Validate spec on generation by default Adds a validation parameter to CodegenConfigurator, and passes through options from CLI, Maven Plugin and Gradle Plugin to that property. Default is to validate the spec during generation. If spec has errors, we will output errors as well as warnings to the user. Option can be disabled by passing false to validateSpec (Maven/Gradle) or --validate-spec (CLI). * Prepare version 3.1.1-SNAPSHOT * fix version * Use last prod version for the sample * Update README.md Fix * [cli] Option parser does not support true/false for boolean options
This commit is contained in:
parent
a8e8acead7
commit
77df3d6770
@ -72,4 +72,4 @@ public class ConfigHelp implements Runnable {
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -194,6 +194,11 @@ public class Generate implements Runnable {
|
||||
description = CodegenConstants.REMOVE_OPERATION_ID_PREFIX_DESC)
|
||||
private Boolean removeOperationIdPrefix;
|
||||
|
||||
@Option(name = {"--skip-validate-spec"},
|
||||
title = "skip spec validation",
|
||||
description = "Skips the default behavior of validating an input specification.")
|
||||
private Boolean skipValidateSpec;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@ -207,6 +212,10 @@ public class Generate implements Runnable {
|
||||
}
|
||||
|
||||
// now override with any specified parameters
|
||||
if (skipValidateSpec != null) {
|
||||
configurator.setValidateSpec(false);
|
||||
}
|
||||
|
||||
if (verbose != null) {
|
||||
configurator.setVerbose(verbose);
|
||||
}
|
||||
|
@ -59,6 +59,11 @@ The gradle plugin is not currently published to https://plugins.gradle.org/m2/.
|
||||
|false
|
||||
|The verbosity of generation
|
||||
|
||||
|validateSpec
|
||||
|Boolean
|
||||
|true
|
||||
|Whether or not we should validate the input spec before generation. Invalid specs result in an error.
|
||||
|
||||
|generatorName
|
||||
|String
|
||||
|None
|
||||
|
@ -11,6 +11,7 @@ gradle openApiGenerate
|
||||
gradle openApiMeta
|
||||
gradle openApiValidate
|
||||
gradle buildGoSdk
|
||||
gradle generateGoWithInvalidSpec
|
||||
```
|
||||
|
||||
The samples can be tested against other versions of the plugin using the `openApiGeneratorVersion` property. For example:
|
||||
|
@ -54,3 +54,16 @@ task buildGoSdk(type: org.openapitools.generator.gradle.plugin.tasks.GenerateTas
|
||||
dateLibrary: "threetenp"
|
||||
]
|
||||
}
|
||||
|
||||
task generateGoWithInvalidSpec(type: org.openapitools.generator.gradle.plugin.tasks.GenerateTask){
|
||||
validateSpec = true
|
||||
generatorName = "go"
|
||||
inputSpec = "$rootDir/petstore-v3.0-invalid.yaml".toString()
|
||||
additionalProperties = [
|
||||
packageName: "petstore"
|
||||
]
|
||||
outputDir = "$buildDir/go".toString()
|
||||
configOptions = [
|
||||
dateLibrary: "threetenp"
|
||||
]
|
||||
}
|
||||
|
@ -80,6 +80,7 @@ class OpenApiGeneratorPlugin : Plugin<Project> {
|
||||
description = "Generate code via Open API Tools Generator for Open API 2.0 or 3.x specification documents."
|
||||
|
||||
verbose.set(generate.verbose)
|
||||
validateSpec.set(generate.validateSpec)
|
||||
generatorName.set(generate.generatorName)
|
||||
outputDir.set(generate.outputDir)
|
||||
inputSpec.set(generate.inputSpec)
|
||||
|
@ -32,6 +32,11 @@ open class OpenApiGeneratorGenerateExtension(project: Project) {
|
||||
*/
|
||||
val verbose = project.objects.property<Boolean>()
|
||||
|
||||
/**
|
||||
* Whether or not an input specification should be validated upon generation.
|
||||
*/
|
||||
val validateSpec = project.objects.property<Boolean>()
|
||||
|
||||
/**
|
||||
* The name of the generator which will handle codegen. (see "openApiGenerators" task)
|
||||
*/
|
||||
@ -262,6 +267,11 @@ open class OpenApiGeneratorGenerateExtension(project: Project) {
|
||||
val configOptions = project.objects.property<Map<String, String>>()
|
||||
|
||||
init {
|
||||
applyDefaults()
|
||||
}
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
fun applyDefaults(){
|
||||
releaseNote.set("Minor update")
|
||||
modelNamePrefix.set("")
|
||||
modelNameSuffix.set("")
|
||||
@ -271,5 +281,6 @@ open class OpenApiGeneratorGenerateExtension(project: Project) {
|
||||
generateApiDocumentation.set(true)
|
||||
withXml.set(false)
|
||||
configOptions.set(mapOf())
|
||||
validateSpec.set(true)
|
||||
}
|
||||
}
|
@ -28,7 +28,6 @@ import org.gradle.kotlin.dsl.property
|
||||
import org.openapitools.codegen.CodegenConstants
|
||||
import org.openapitools.codegen.DefaultGenerator
|
||||
import org.openapitools.codegen.config.CodegenConfigurator
|
||||
import org.openapitools.codegen.config.CodegenConfiguratorUtils.*
|
||||
|
||||
|
||||
/**
|
||||
@ -48,6 +47,12 @@ open class GenerateTask : DefaultTask() {
|
||||
@get:Internal
|
||||
val verbose = project.objects.property<Boolean>()
|
||||
|
||||
/**
|
||||
* Whether or not an input specification should be validated upon generation.
|
||||
*/
|
||||
@get:Internal
|
||||
val validateSpec = project.objects.property<Boolean>()
|
||||
|
||||
/**
|
||||
* The name of the generator which will handle codegen. (see "openApiGenerators" task)
|
||||
*/
|
||||
@ -382,6 +387,10 @@ open class GenerateTask : DefaultTask() {
|
||||
configurator.isVerbose = value
|
||||
}
|
||||
|
||||
validateSpec.ifNotEmpty { value ->
|
||||
configurator.isValidateSpec = value
|
||||
}
|
||||
|
||||
skipOverwrite.ifNotEmpty { value ->
|
||||
configurator.isSkipOverwrite = value ?: false
|
||||
}
|
||||
@ -528,8 +537,7 @@ open class GenerateTask : DefaultTask() {
|
||||
|
||||
out.println("Successfully generated code to ${configurator.outputDir}")
|
||||
} catch (e: RuntimeException) {
|
||||
logger.error(e.message)
|
||||
throw GradleException("Code generation failed.")
|
||||
throw GradleException("Code generation failed.", e)
|
||||
}
|
||||
} finally {
|
||||
originalEnvironmentVariables.forEach { entry ->
|
||||
|
@ -31,7 +31,7 @@ class GenerateTaskDslTest : TestBase() {
|
||||
fun `openApiGenerate should create an expected file structure from DSL config`() {
|
||||
// Arrange
|
||||
val projectFiles = mapOf(
|
||||
"spec.yaml" to javaClass.classLoader.getResourceAsStream("specs/petstore-v3.0-invalid.yaml")
|
||||
"spec.yaml" to javaClass.classLoader.getResourceAsStream("specs/petstore-v3.0.yaml")
|
||||
)
|
||||
withProject(defaultBuildGradle, projectFiles)
|
||||
|
||||
|
@ -38,6 +38,7 @@ mvn clean compile
|
||||
### General Configuration parameters
|
||||
|
||||
- `inputSpec` - OpenAPI Spec file path
|
||||
- `validateSpec` - Whether or not to validate the input spec prior to generation. Invalid specifications will result in an error.
|
||||
- `language` - target generation language (deprecated, replaced by `generatorName` as values here don't represent only 'language' any longer)
|
||||
- `generatorName` - target generator name
|
||||
- `output` - target output path (default is `${project.build.directory}/generated-sources/swagger`)
|
||||
@ -102,4 +103,8 @@ Specifying a custom generator is a bit different. It doesn't support the classpa
|
||||
|
||||
### Sample configuration
|
||||
|
||||
- Please see [an example configuration](examples) for using the plugin
|
||||
Please see [an example configuration](examples) for using the plugin. To run these examples, explicitly pass the file to maven. Example:
|
||||
|
||||
```bash
|
||||
mvn -f non-java.xml compile
|
||||
```
|
||||
|
@ -12,7 +12,7 @@
|
||||
<plugin>
|
||||
<groupId>org.openapitools</groupId>
|
||||
<artifactId>openapi-generator-maven-plugin</artifactId>
|
||||
<version>3.0.1-SNAPSHOT</version>
|
||||
<version>3.1.1-SNAPSHOT</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
|
@ -0,0 +1,34 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.openapitools</groupId>
|
||||
<artifactId>sample-project</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<name>sample-project</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- activate the plugin -->
|
||||
<plugin>
|
||||
<groupId>org.openapitools</groupId>
|
||||
<artifactId>openapi-generator-maven-plugin</artifactId>
|
||||
<version>3.1.1-SNAPSHOT</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>generate</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<validateSpec>false</validateSpec>
|
||||
<inputSpec>petstore-v3.0-invalid.yaml</inputSpec>
|
||||
<generatorName>aspnetcore</generatorName>
|
||||
<configOptions>
|
||||
<additional-properties>optionalProjectFile=true</additional-properties>
|
||||
</configOptions>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -12,7 +12,7 @@
|
||||
<plugin>
|
||||
<groupId>org.openapitools</groupId>
|
||||
<artifactId>openapi-generator-maven-plugin</artifactId>
|
||||
<version>3.0.1-SNAPSHOT</version>
|
||||
<version>3.1.1-SNAPSHOT</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
|
@ -0,0 +1,103 @@
|
||||
openapi: "3.0.0"
|
||||
servers:
|
||||
- url: http://petstore.swagger.io/v1
|
||||
paths:
|
||||
/pets:
|
||||
get:
|
||||
summary: List all pets
|
||||
operationId: listPets
|
||||
tags:
|
||||
- pets
|
||||
parameters:
|
||||
- name: limit
|
||||
in: query
|
||||
description: How many items to return at one time (max 100)
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
format: int32
|
||||
responses:
|
||||
'200':
|
||||
description: A paged array of pets
|
||||
headers:
|
||||
x-next:
|
||||
description: A link to the next page of responses
|
||||
schema:
|
||||
type: string
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Pets"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
post:
|
||||
summary: Create a pet
|
||||
tags:
|
||||
- pets
|
||||
responses:
|
||||
'201':
|
||||
description: Null response
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
/pets/{petId}:
|
||||
get:
|
||||
summary: Info for a specific pet
|
||||
operationId: showPetById
|
||||
tags:
|
||||
- pets
|
||||
parameters:
|
||||
- name: petId
|
||||
in: path
|
||||
required: true
|
||||
description: The id of the pet to retrieve
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Expected response to a valid request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Pets"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
components:
|
||||
schemas:
|
||||
Pet:
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
name:
|
||||
type: string
|
||||
tag:
|
||||
type: string
|
||||
Pets:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Pet"
|
||||
Error:
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: integer
|
||||
format: int32
|
||||
message:
|
||||
type: string
|
@ -60,6 +60,9 @@ public class CodeGenMojo extends AbstractMojo {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CodeGenMojo.class);
|
||||
|
||||
@Parameter(name="validateSpec", required = false, defaultValue = "true")
|
||||
private Boolean validateSpec;
|
||||
|
||||
@Parameter(name = "verbose", required = false, defaultValue = "false")
|
||||
private boolean verbose;
|
||||
|
||||
@ -348,6 +351,11 @@ public class CodeGenMojo extends AbstractMojo {
|
||||
|
||||
configurator.setVerbose(verbose);
|
||||
|
||||
// now override with any specified parameters
|
||||
if (validateSpec != null) {
|
||||
configurator.setValidateSpec(validateSpec);
|
||||
}
|
||||
|
||||
if (skipOverwrite != null) {
|
||||
configurator.setSkipOverwrite(skipOverwrite);
|
||||
}
|
||||
|
@ -0,0 +1,133 @@
|
||||
package org.openapitools.codegen;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class SpecValidationException extends RuntimeException {
|
||||
|
||||
private Set<String> errors;
|
||||
private Set<String> warnings;
|
||||
|
||||
/**
|
||||
* Constructs a new runtime exception with {@code null} as its
|
||||
* detail message. The cause is not initialized, and may subsequently be
|
||||
* initialized by a call to {@link #initCause}.
|
||||
*/
|
||||
public SpecValidationException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new runtime exception with the specified detail message.
|
||||
* The cause is not initialized, and may subsequently be initialized by a
|
||||
* call to {@link #initCause}.
|
||||
*
|
||||
* @param message the detail message. The detail message is saved for
|
||||
* later retrieval by the {@link #getMessage()} method.
|
||||
*/
|
||||
public SpecValidationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new runtime exception with the specified detail message and
|
||||
* cause. <p>Note that the detail message associated with
|
||||
* {@code cause} is <i>not</i> automatically incorporated in
|
||||
* this runtime exception's detail message.
|
||||
*
|
||||
* @param message the detail message (which is saved for later retrieval
|
||||
* by the {@link #getMessage()} method).
|
||||
* @param cause the cause (which is saved for later retrieval by the
|
||||
* {@link #getCause()} method). (A <tt>null</tt> value is
|
||||
* permitted, and indicates that the cause is nonexistent or
|
||||
* unknown.)
|
||||
* @since 1.4
|
||||
*/
|
||||
public SpecValidationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new runtime exception with the specified cause and a
|
||||
* detail message of <tt>(cause==null ? null : cause.toString())</tt>
|
||||
* (which typically contains the class and detail message of
|
||||
* <tt>cause</tt>). This constructor is useful for runtime exceptions
|
||||
* that are little more than wrappers for other throwables.
|
||||
*
|
||||
* @param cause the cause (which is saved for later retrieval by the
|
||||
* {@link #getCause()} method). (A <tt>null</tt> value is
|
||||
* permitted, and indicates that the cause is nonexistent or
|
||||
* unknown.)
|
||||
* @since 1.4
|
||||
*/
|
||||
public SpecValidationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new runtime exception with the specified detail
|
||||
* message, cause, suppression enabled or disabled, and writable
|
||||
* stack trace enabled or disabled.
|
||||
*
|
||||
* @param message the detail message.
|
||||
* @param cause the cause. (A {@code null} value is permitted,
|
||||
* and indicates that the cause is nonexistent or unknown.)
|
||||
* @param enableSuppression whether or not suppression is enabled
|
||||
* or disabled
|
||||
* @param writableStackTrace whether or not the stack trace should
|
||||
* be writable
|
||||
* @since 1.7
|
||||
*/
|
||||
public SpecValidationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
public Set<String> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
public Set<String> getWarnings() {
|
||||
return warnings;
|
||||
}
|
||||
|
||||
public void setErrors(Set<String> errors) {
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
public void setWarnings(Set<String> warnings) {
|
||||
this.warnings = warnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the detail message string of this throwable.
|
||||
*
|
||||
* @return the detail message string of this {@code Throwable} instance
|
||||
* (which may be {@code null}).
|
||||
*/
|
||||
@Override
|
||||
public String getMessage() {
|
||||
int errorCount = 0;
|
||||
if (errors != null) {
|
||||
errorCount = errors.size();
|
||||
}
|
||||
int warningCount = 0;
|
||||
if (warnings != null) {
|
||||
warningCount = warnings.size();
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(System.lineSeparator())
|
||||
.append("Errors: ")
|
||||
.append(System.lineSeparator());
|
||||
errors.forEach(msg ->
|
||||
sb.append("\t-").append(msg).append(System.lineSeparator())
|
||||
);
|
||||
|
||||
if (!warnings.isEmpty()) {
|
||||
sb.append("Warnings: ").append(System.lineSeparator());
|
||||
warnings.forEach(msg ->
|
||||
sb.append("\t-").append(msg).append(System.lineSeparator())
|
||||
);
|
||||
}
|
||||
return super.getMessage() + " | " +
|
||||
"Error count: " + errorCount + ", Warning count: " + warningCount + sb.toString();
|
||||
}
|
||||
}
|
@ -19,12 +19,8 @@ package org.openapitools.codegen.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import org.openapitools.codegen.CliOption;
|
||||
import org.openapitools.codegen.ClientOptInput;
|
||||
import org.openapitools.codegen.ClientOpts;
|
||||
import org.openapitools.codegen.CodegenConfig;
|
||||
import org.openapitools.codegen.CodegenConfigLoader;
|
||||
import org.openapitools.codegen.CodegenConstants;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import org.openapitools.codegen.*;
|
||||
import org.openapitools.codegen.auth.AuthParser;
|
||||
import io.swagger.parser.OpenAPIParser;
|
||||
import io.swagger.v3.core.util.Json;
|
||||
@ -33,6 +29,7 @@ import io.swagger.v3.parser.core.models.ParseOptions;
|
||||
import io.swagger.v3.parser.core.models.SwaggerParseResult;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.openapitools.codegen.languages.*;
|
||||
import org.openapitools.codegen.utils.ModelUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -80,6 +77,7 @@ public class CodegenConfigurator implements Serializable {
|
||||
private boolean verbose;
|
||||
private boolean skipOverwrite;
|
||||
private boolean removeOperationIdPrefix;
|
||||
private boolean validateSpec;
|
||||
private String templateDir;
|
||||
private String auth;
|
||||
private String apiPackage;
|
||||
@ -108,6 +106,7 @@ public class CodegenConfigurator implements Serializable {
|
||||
private final Map<String, Object> dynamicProperties = new HashMap<String, Object>(); //the map that holds the JsonAnySetter/JsonAnyGetter values
|
||||
|
||||
public CodegenConfigurator() {
|
||||
this.validateSpec = true;
|
||||
this.setOutputDir(".");
|
||||
}
|
||||
|
||||
@ -211,6 +210,15 @@ public class CodegenConfigurator implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isValidateSpec() {
|
||||
return validateSpec;
|
||||
}
|
||||
|
||||
public CodegenConfigurator setValidateSpec(final boolean validateSpec) {
|
||||
this.validateSpec = validateSpec;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isSkipOverwrite() {
|
||||
return skipOverwrite;
|
||||
}
|
||||
@ -514,8 +522,45 @@ public class CodegenConfigurator implements Serializable {
|
||||
options.setResolve(true);
|
||||
options.setFlatten(true);
|
||||
SwaggerParseResult result = new OpenAPIParser().readLocation(inputSpec, authorizationValues, options);
|
||||
|
||||
Set<String> validationMessages = new HashSet<>(result.getMessages());
|
||||
OpenAPI specification = result.getOpenAPI();
|
||||
|
||||
// NOTE: We will only expose errors+warnings if there are already errors in the spec.
|
||||
if (validationMessages.size() > 0) {
|
||||
Set<String> warnings = new HashSet<>();
|
||||
if (specification != null) {
|
||||
List<String> unusedModels = ModelUtils.getUnusedSchemas(specification);
|
||||
if (unusedModels != null) unusedModels.forEach(name -> warnings.add("Unused model: " + name));
|
||||
}
|
||||
|
||||
if (this.isValidateSpec()) {
|
||||
SpecValidationException ex = new SpecValidationException("Specification has failed validation.");
|
||||
ex.setErrors(validationMessages);
|
||||
ex.setWarnings(warnings);
|
||||
throw ex;
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("There were issues with the specification, but validation has been explicitly disabled.");
|
||||
sb.append(System.lineSeparator());
|
||||
|
||||
sb.append("Errors: ").append(System.lineSeparator());
|
||||
validationMessages.forEach(msg ->
|
||||
sb.append("\t-").append(msg).append(System.lineSeparator())
|
||||
);
|
||||
|
||||
if (!warnings.isEmpty()) {
|
||||
sb.append("Warnings: ").append(System.lineSeparator());
|
||||
warnings.forEach(msg ->
|
||||
sb.append("\t-").append(msg).append(System.lineSeparator())
|
||||
);
|
||||
}
|
||||
LOGGER.warn(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
input.opts(new ClientOpts())
|
||||
.openAPI(result.getOpenAPI());
|
||||
.openAPI(specification);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user