[all] Adds strict spec option (#2783)

* [all] Adds strict spec option

Introduces an option to allow user customization of strict specification
behaviors. For instance, OpenAPI 3.x requires a path object name to be
prefixed with '/' so we append any missing '/', but this may not be
desirable to some users or generators. In this commit, this fix specifically is
the only modification affected.

* Clarify strict-spec docs, add option to README.md

* Update CLI options in docs/usage.md
This commit is contained in:
Jim Schubert
2019-05-03 10:57:47 -04:00
committed by GitHub
parent e71eb3020c
commit a2fb88c1c1
10 changed files with 143 additions and 10 deletions

View File

@@ -447,6 +447,7 @@ SYNOPSIS
[--remove-operation-id-prefix]
[--reserved-words-mappings <reserved word mappings>...]
[(-s | --skip-overwrite)] [--skip-validate-spec]
[--strict-spec <true/false strict behavior>]
[(-t <template directory> | --template-dir <template directory>)]
[--type-mappings <type mappings>...] [(-v | --verbose)]

View File

@@ -239,7 +239,9 @@ SYNOPSIS
[--api-package <api package>] [--artifact-id <artifact id>]
[--artifact-version <artifact version>]
[(-c <configuration file> | --config <configuration file>)]
[-D <system properties>...] [--enable-post-process-file]
[-D <system properties>...]
[(-e <templating engine> | --engine <templating engine>)]
[--enable-post-process-file]
[(-g <generator name> | --generator-name <generator name>)]
[--generate-alias-as-model] [--git-repo-id <git repo id>]
[--git-user-id <git user id>] [--group-id <group id>]
@@ -259,6 +261,7 @@ SYNOPSIS
[--remove-operation-id-prefix]
[--reserved-words-mappings <reserved word mappings>...]
[(-s | --skip-overwrite)] [--skip-validate-spec]
[--strict-spec <true/false strict behavior>]
[(-t <template directory> | --template-dir <template directory>)]
[--type-mappings <type mappings>...] [(-v | --verbose)]
@@ -289,9 +292,11 @@ OPTIONS
artifact version in generated pom.xml
-c <configuration file>, --config <configuration file>
Path to json configuration file. File content should be in a json
format {"optionKey":"optionValue", "optionKey1":"optionValue1"...}
Supported options can be different for each language. Run
Path to configuration file configuration file. It can be json or
yaml.If file is json, the content should have the format
{"optionKey":"optionValue", "optionKey1":"optionValue1"...}.If file
is yaml, the content should have the format optionKey:
optionValueSupported options can be different for each language. Run
config-help -g {generator name} command for language specific config
options.
@@ -299,11 +304,17 @@ OPTIONS
sets specified system properties in the format of
name=value,name=value (or multiple options, each with name=value)
-e <templating engine>, --engine <templating engine>
templating engine: "mustache" (default) or "handlebars" (beta)
--enable-post-process-file
enablePostProcessFile
-g <generator name>, --generator-name <generator name>
generator to use (see langs command for list)
generator to use (see list command for list)
--generate-alias-as-model
Generate alias to map, array as models
--git-repo-id <git repo id>
Git repo ID, e.g. openapi-generator.
@@ -354,6 +365,9 @@ OPTIONS
piping the JSON output of debug options (e.g. `-DdebugOperations`)
to an external parser directly while testing a generator.
--minimal-update
Only write output files that have changed.
--model-name-prefix <model name prefix>
Prefix that will be prepended to all model names. Default is the
empty string.
@@ -368,6 +382,9 @@ OPTIONS
-o <output directory>, --output <output directory>
where to write the generated files (current dir by default)
--package-name <package name>
package for generated classes (where supported)
--release-note <release note>
Release note, default to 'Minor update'.
@@ -386,12 +403,17 @@ OPTIONS
--skip-validate-spec
Skips the default behavior of validating an input specification.
--strict-spec <true/false strict behavior>
'MUST' and 'SHALL' wording in OpenAPI spec is strictly adhered to.
e.g. when false, no fixes will be applied to documents which pass
validation but don't follow the spec.
-t <template directory>, --template-dir <template directory>
folder containing the template files
--type-mappings <type mappings>
sets mappings between OpenAPI spec types and generated code types in
the format of OpenaAPIType=generatedType,OpenAPIType=generatedType.
the format of OpenAPIType=generatedType,OpenAPIType=generatedType.
For example: array=List,map=Map,string=String. You can also have
multiple occurrences of this option.

View File

@@ -214,6 +214,12 @@ public class Generate implements Runnable {
description = "Skips the default behavior of validating an input specification.")
private Boolean skipValidateSpec;
@Option(name = {"--strict-spec"},
title = "true/false strict behavior",
description = "'MUST' and 'SHALL' wording in OpenAPI spec is strictly adhered to. e.g. when false, no fixes will be applied to documents which pass validation but don't follow the spec.",
arity = 1)
private Boolean strictSpecBehavior;
@Option(name = {"--log-to-stderr"},
title = "Log to STDERR",
description = "write all log messages (not just errors) to STDOUT."
@@ -368,10 +374,15 @@ public class Generate implements Runnable {
if (generateAliasAsModel != null) {
configurator.setGenerateAliasAsModel(generateAliasAsModel);
}
if (minimalUpdate != null) {
configurator.setEnableMinimalUpdate(minimalUpdate);
}
if (strictSpecBehavior != null) {
configurator.setStrictSpecBehavior(strictSpecBehavior);
}
applySystemPropertiesKvpList(systemProperties, configurator);
applyInstantiationTypesKvpList(instantiationTypes, configurator);
applyImportMappingsKvpList(importMappings, configurator);

View File

@@ -277,6 +277,26 @@ public class GenerateTest {
};
}
@Test
public void testStrictSpec() throws Exception {
setupAndRunGenericTest("--strict-spec", "true");
new FullVerifications() {
{
configurator.setStrictSpecBehavior(true);
times = 1;
}
};
setupAndRunGenericTest("--strict-spec", "false");
new FullVerifications() {
{
configurator.setStrictSpecBehavior(false);
times = 1;
}
};
}
@Test
public void testPackageName() throws Exception {
final String value = "io.foo.bar.baz";

View File

@@ -57,6 +57,7 @@ mvn clean compile
- `logToStderr` - write all log messages (not just errors) to STDOUT
- `enablePostProcessFile` - enable file post-processing hook
- `skipValidateSpec` - Whether or not to skip validating the input spec prior to generation. By default, invalid specifications will result in an error.
- `strictSpec` - Whether or not to treat an input document strictly against the spec. 'MUST' and 'SHALL' wording in OpenAPI spec is strictly adhered to. e.g. when false, no fixes will be applied to documents which pass validation but don't follow the spec.
- `generateAliasAsModel` - generate alias (array, map) as model
- `generateApis` - generate the apis (`true` by default)
- `generateApiTests` - generate the api tests (`true` by default. Only available if `generateApis` is `true`)

View File

@@ -235,6 +235,12 @@ public class CodeGenMojo extends AbstractMojo {
@Parameter(name = "skipValidateSpec", required = false)
private Boolean skipValidateSpec;
/**
* To treat a document strictly against the spec.
*/
@Parameter(name = "strictSpec", required = false)
private Boolean strictSpecBehavior;
/**
* To generate alias (array, map) as model
*/
@@ -459,6 +465,10 @@ public class CodeGenMojo extends AbstractMojo {
configurator.setValidateSpec(!skipValidateSpec);
}
if (strictSpecBehavior != null) {
configurator.setStrictSpecBehavior(strictSpecBehavior);
}
if (logToStderr != null) {
configurator.setLogToStderr(logToStderr);
}

View File

@@ -270,4 +270,8 @@ public interface CodegenConfig {
public boolean isEnableMinimalUpdate();
public void setEnableMinimalUpdate(boolean isEnableMinimalUpdate);
boolean isStrictSpecBehavior();
void setStrictSpecBehavior(boolean strictSpecBehavior);
}

View File

@@ -118,6 +118,9 @@ public class DefaultCodegen implements CodegenConfig {
// flag to indicate whether to only update files whose contents have changed
protected boolean enableMinimalUpdate = false;
// acts strictly upon a spec, potentially modifying it to have consistent behavior across generators.
protected boolean strictSpecBehavior = true;
// make openapi available to all methods
protected OpenAPI openAPI;
@@ -2387,11 +2390,13 @@ public class DefaultCodegen implements CodegenConfig {
}
operationId = removeNonNameElementToCamelCase(operationId);
if (path.startsWith("/")) {
op.path = path;
} else {
if (isStrictSpecBehavior() && !path.startsWith("/")) {
// modifies an operation.path to strictly conform to OpenAPI Spec
op.path = "/" + path;
} else {
op.path = path;
}
op.operationId = toOperationId(operationId);
op.summary = escapeText(operation.getSummary());
op.unescapedNotes = operation.getDescription();
@@ -4900,4 +4905,24 @@ public class DefaultCodegen implements CodegenConfig {
this.enableMinimalUpdate = enableMinimalUpdate;
}
/**
* Indicates whether the codegen configuration should treat documents as strictly defined by the OpenAPI specification.
*
* @return true to act strictly upon spec documents, potentially modifying the spec to strictly fit the spec.
*/
@Override
public boolean isStrictSpecBehavior() {
return this.strictSpecBehavior;
}
/**
* Sets the boolean valid indicating whether generation will work strictly against the specification, potentially making
* minor changes to the input document.
*
* @param strictSpecBehavior true if we will behave strictly, false to allow specification documents which pass validation to be loosely interpreted against the spec.
*/
@Override
public void setStrictSpecBehavior(final boolean strictSpecBehavior) {
this.strictSpecBehavior = strictSpecBehavior;
}
}

View File

@@ -68,6 +68,7 @@ public class CodegenConfigurator implements Serializable {
private boolean validateSpec;
private boolean enablePostProcessFile;
private boolean enableMinimalUpdate;
private boolean strictSpecBehavior;
private String templateDir;
private String templatingEngineName;
private String auth;
@@ -100,6 +101,7 @@ public class CodegenConfigurator implements Serializable {
public CodegenConfigurator() {
this.validateSpec = true;
this.strictSpecBehavior = true;
this.setOutputDir(".");
}
@@ -225,6 +227,15 @@ public class CodegenConfigurator implements Serializable {
return this;
}
public boolean isStrictSpecBehavior() {
return strictSpecBehavior;
}
public CodegenConfigurator setStrictSpecBehavior(boolean strictSpecBehavior) {
this.strictSpecBehavior = strictSpecBehavior;
return this;
}
public boolean isVerbose() {
return verbose;
}

View File

@@ -48,7 +48,35 @@ public class DefaultGeneratorTest {
Assert.assertEquals(defaultList.get(3).path, "/path4");
Assert.assertEquals(defaultList.get(3).allParams.size(), 1);
}
@Test
public void testNonStrictProcessPaths() throws Exception {
OpenAPI openAPI = TestUtils.createOpenAPI();
openAPI.setPaths(new Paths());
openAPI.getPaths().addPathItem("path1/", new PathItem().get(new Operation().operationId("op1").responses(new ApiResponses().addApiResponse("201", new ApiResponse().description("OK")))));
openAPI.getPaths().addPathItem("path2/", new PathItem().get(new Operation().operationId("op2").addParametersItem(new QueryParameter().name("p1").schema(new StringSchema())).responses(new ApiResponses().addApiResponse("201", new ApiResponse().description("OK")))));
ClientOptInput opts = new ClientOptInput();
opts.setOpenAPI(openAPI);
CodegenConfig config = new DefaultCodegen();
config.setStrictSpecBehavior(false);
opts.setConfig(config);
opts.setOpts(new ClientOpts());
DefaultGenerator generator = new DefaultGenerator();
generator.opts(opts);
Map<String, List<CodegenOperation>> result = generator.processPaths(openAPI.getPaths());
Assert.assertEquals(result.size(), 1);
List<CodegenOperation> defaultList = result.get("Default");
Assert.assertEquals(defaultList.size(), 2);
Assert.assertEquals(defaultList.get(0).path, "path1/");
Assert.assertEquals(defaultList.get(0).allParams.size(), 0);
Assert.assertEquals(defaultList.get(1).path, "path2/");
Assert.assertEquals(defaultList.get(1).allParams.size(), 1);
}
@Test
public void minimalUpdateTest() throws IOException {
OpenAPI openAPI = TestUtils.createOpenAPI();