Skip to content

Commit a2fb88c

Browse files
authored
[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
1 parent e71eb30 commit a2fb88c

File tree

10 files changed

+143
-10
lines changed

10 files changed

+143
-10
lines changed

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@ SYNOPSIS
447447
[--remove-operation-id-prefix]
448448
[--reserved-words-mappings <reserved word mappings>...]
449449
[(-s | --skip-overwrite)] [--skip-validate-spec]
450+
[--strict-spec <true/false strict behavior>]
450451
[(-t <template directory> | --template-dir <template directory>)]
451452
[--type-mappings <type mappings>...] [(-v | --verbose)]
452453

Diff for: docs/usage.md

+28-6
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,9 @@ SYNOPSIS
239239
[--api-package <api package>] [--artifact-id <artifact id>]
240240
[--artifact-version <artifact version>]
241241
[(-c <configuration file> | --config <configuration file>)]
242-
[-D <system properties>...] [--enable-post-process-file]
242+
[-D <system properties>...]
243+
[(-e <templating engine> | --engine <templating engine>)]
244+
[--enable-post-process-file]
243245
[(-g <generator name> | --generator-name <generator name>)]
244246
[--generate-alias-as-model] [--git-repo-id <git repo id>]
245247
[--git-user-id <git user id>] [--group-id <group id>]
@@ -259,6 +261,7 @@ SYNOPSIS
259261
[--remove-operation-id-prefix]
260262
[--reserved-words-mappings <reserved word mappings>...]
261263
[(-s | --skip-overwrite)] [--skip-validate-spec]
264+
[--strict-spec <true/false strict behavior>]
262265
[(-t <template directory> | --template-dir <template directory>)]
263266
[--type-mappings <type mappings>...] [(-v | --verbose)]
264267
@@ -289,21 +292,29 @@ OPTIONS
289292
artifact version in generated pom.xml
290293
291294
-c <configuration file>, --config <configuration file>
292-
Path to json configuration file. File content should be in a json
293-
format {"optionKey":"optionValue", "optionKey1":"optionValue1"...}
294-
Supported options can be different for each language. Run
295+
Path to configuration file configuration file. It can be json or
296+
yaml.If file is json, the content should have the format
297+
{"optionKey":"optionValue", "optionKey1":"optionValue1"...}.If file
298+
is yaml, the content should have the format optionKey:
299+
optionValueSupported options can be different for each language. Run
295300
config-help -g {generator name} command for language specific config
296301
options.
297302
298303
-D <system properties>
299304
sets specified system properties in the format of
300305
name=value,name=value (or multiple options, each with name=value)
301306
307+
-e <templating engine>, --engine <templating engine>
308+
templating engine: "mustache" (default) or "handlebars" (beta)
309+
302310
--enable-post-process-file
303311
enablePostProcessFile
304312
305313
-g <generator name>, --generator-name <generator name>
306-
generator to use (see langs command for list)
314+
generator to use (see list command for list)
315+
316+
--generate-alias-as-model
317+
Generate alias to map, array as models
307318
308319
--git-repo-id <git repo id>
309320
Git repo ID, e.g. openapi-generator.
@@ -354,6 +365,9 @@ OPTIONS
354365
piping the JSON output of debug options (e.g. `-DdebugOperations`)
355366
to an external parser directly while testing a generator.
356367
368+
--minimal-update
369+
Only write output files that have changed.
370+
357371
--model-name-prefix <model name prefix>
358372
Prefix that will be prepended to all model names. Default is the
359373
empty string.
@@ -368,6 +382,9 @@ OPTIONS
368382
-o <output directory>, --output <output directory>
369383
where to write the generated files (current dir by default)
370384
385+
--package-name <package name>
386+
package for generated classes (where supported)
387+
371388
--release-note <release note>
372389
Release note, default to 'Minor update'.
373390
@@ -386,12 +403,17 @@ OPTIONS
386403
--skip-validate-spec
387404
Skips the default behavior of validating an input specification.
388405
406+
--strict-spec <true/false strict behavior>
407+
'MUST' and 'SHALL' wording in OpenAPI spec is strictly adhered to.
408+
e.g. when false, no fixes will be applied to documents which pass
409+
validation but don't follow the spec.
410+
389411
-t <template directory>, --template-dir <template directory>
390412
folder containing the template files
391413
392414
--type-mappings <type mappings>
393415
sets mappings between OpenAPI spec types and generated code types in
394-
the format of OpenaAPIType=generatedType,OpenAPIType=generatedType.
416+
the format of OpenAPIType=generatedType,OpenAPIType=generatedType.
395417
For example: array=List,map=Map,string=String. You can also have
396418
multiple occurrences of this option.
397419

Diff for: modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java

+11
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,12 @@ public class Generate implements Runnable {
214214
description = "Skips the default behavior of validating an input specification.")
215215
private Boolean skipValidateSpec;
216216

217+
@Option(name = {"--strict-spec"},
218+
title = "true/false strict behavior",
219+
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.",
220+
arity = 1)
221+
private Boolean strictSpecBehavior;
222+
217223
@Option(name = {"--log-to-stderr"},
218224
title = "Log to STDERR",
219225
description = "write all log messages (not just errors) to STDOUT."
@@ -368,10 +374,15 @@ public void run() {
368374
if (generateAliasAsModel != null) {
369375
configurator.setGenerateAliasAsModel(generateAliasAsModel);
370376
}
377+
371378
if (minimalUpdate != null) {
372379
configurator.setEnableMinimalUpdate(minimalUpdate);
373380
}
374381

382+
if (strictSpecBehavior != null) {
383+
configurator.setStrictSpecBehavior(strictSpecBehavior);
384+
}
385+
375386
applySystemPropertiesKvpList(systemProperties, configurator);
376387
applyInstantiationTypesKvpList(instantiationTypes, configurator);
377388
applyImportMappingsKvpList(importMappings, configurator);

Diff for: modules/openapi-generator-cli/src/test/java/org/openapitools/codegen/cmd/GenerateTest.java

+20
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,26 @@ public void testSkipOverwrite() throws Exception {
277277
};
278278
}
279279

280+
@Test
281+
public void testStrictSpec() throws Exception {
282+
283+
setupAndRunGenericTest("--strict-spec", "true");
284+
new FullVerifications() {
285+
{
286+
configurator.setStrictSpecBehavior(true);
287+
times = 1;
288+
}
289+
};
290+
291+
setupAndRunGenericTest("--strict-spec", "false");
292+
new FullVerifications() {
293+
{
294+
configurator.setStrictSpecBehavior(false);
295+
times = 1;
296+
}
297+
};
298+
}
299+
280300
@Test
281301
public void testPackageName() throws Exception {
282302
final String value = "io.foo.bar.baz";

Diff for: modules/openapi-generator-maven-plugin/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ mvn clean compile
5757
- `logToStderr` - write all log messages (not just errors) to STDOUT
5858
- `enablePostProcessFile` - enable file post-processing hook
5959
- `skipValidateSpec` - Whether or not to skip validating the input spec prior to generation. By default, invalid specifications will result in an error.
60+
- `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.
6061
- `generateAliasAsModel` - generate alias (array, map) as model
6162
- `generateApis` - generate the apis (`true` by default)
6263
- `generateApiTests` - generate the api tests (`true` by default. Only available if `generateApis` is `true`)

Diff for: modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java

+10
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,12 @@ public class CodeGenMojo extends AbstractMojo {
235235
@Parameter(name = "skipValidateSpec", required = false)
236236
private Boolean skipValidateSpec;
237237

238+
/**
239+
* To treat a document strictly against the spec.
240+
*/
241+
@Parameter(name = "strictSpec", required = false)
242+
private Boolean strictSpecBehavior;
243+
238244
/**
239245
* To generate alias (array, map) as model
240246
*/
@@ -459,6 +465,10 @@ public void execute() throws MojoExecutionException {
459465
configurator.setValidateSpec(!skipValidateSpec);
460466
}
461467

468+
if (strictSpecBehavior != null) {
469+
configurator.setStrictSpecBehavior(strictSpecBehavior);
470+
}
471+
462472
if (logToStderr != null) {
463473
configurator.setLogToStderr(logToStderr);
464474
}

Diff for: modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java

+4
Original file line numberDiff line numberDiff line change
@@ -270,4 +270,8 @@ public interface CodegenConfig {
270270
public boolean isEnableMinimalUpdate();
271271

272272
public void setEnableMinimalUpdate(boolean isEnableMinimalUpdate);
273+
274+
boolean isStrictSpecBehavior();
275+
276+
void setStrictSpecBehavior(boolean strictSpecBehavior);
273277
}

Diff for: modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java

+28-3
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ public class DefaultCodegen implements CodegenConfig {
118118
// flag to indicate whether to only update files whose contents have changed
119119
protected boolean enableMinimalUpdate = false;
120120

121+
// acts strictly upon a spec, potentially modifying it to have consistent behavior across generators.
122+
protected boolean strictSpecBehavior = true;
123+
121124
// make openapi available to all methods
122125
protected OpenAPI openAPI;
123126

@@ -2387,11 +2390,13 @@ public CodegenOperation fromOperation(String path,
23872390
}
23882391
operationId = removeNonNameElementToCamelCase(operationId);
23892392

2390-
if (path.startsWith("/")) {
2391-
op.path = path;
2392-
} else {
2393+
if (isStrictSpecBehavior() && !path.startsWith("/")) {
2394+
// modifies an operation.path to strictly conform to OpenAPI Spec
23932395
op.path = "/" + path;
2396+
} else {
2397+
op.path = path;
23942398
}
2399+
23952400
op.operationId = toOperationId(operationId);
23962401
op.summary = escapeText(operation.getSummary());
23972402
op.unescapedNotes = operation.getDescription();
@@ -4900,4 +4905,24 @@ public void setEnableMinimalUpdate(boolean enableMinimalUpdate) {
49004905
this.enableMinimalUpdate = enableMinimalUpdate;
49014906
}
49024907

4908+
/**
4909+
* Indicates whether the codegen configuration should treat documents as strictly defined by the OpenAPI specification.
4910+
*
4911+
* @return true to act strictly upon spec documents, potentially modifying the spec to strictly fit the spec.
4912+
*/
4913+
@Override
4914+
public boolean isStrictSpecBehavior() {
4915+
return this.strictSpecBehavior;
4916+
}
4917+
4918+
/**
4919+
* Sets the boolean valid indicating whether generation will work strictly against the specification, potentially making
4920+
* minor changes to the input document.
4921+
*
4922+
* @param strictSpecBehavior true if we will behave strictly, false to allow specification documents which pass validation to be loosely interpreted against the spec.
4923+
*/
4924+
@Override
4925+
public void setStrictSpecBehavior(final boolean strictSpecBehavior) {
4926+
this.strictSpecBehavior = strictSpecBehavior;
4927+
}
49034928
}

Diff for: modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java

+11
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public class CodegenConfigurator implements Serializable {
6868
private boolean validateSpec;
6969
private boolean enablePostProcessFile;
7070
private boolean enableMinimalUpdate;
71+
private boolean strictSpecBehavior;
7172
private String templateDir;
7273
private String templatingEngineName;
7374
private String auth;
@@ -100,6 +101,7 @@ public class CodegenConfigurator implements Serializable {
100101

101102
public CodegenConfigurator() {
102103
this.validateSpec = true;
104+
this.strictSpecBehavior = true;
103105
this.setOutputDir(".");
104106
}
105107

@@ -225,6 +227,15 @@ public CodegenConfigurator setModelNameSuffix(String suffix) {
225227
return this;
226228
}
227229

230+
public boolean isStrictSpecBehavior() {
231+
return strictSpecBehavior;
232+
}
233+
234+
public CodegenConfigurator setStrictSpecBehavior(boolean strictSpecBehavior) {
235+
this.strictSpecBehavior = strictSpecBehavior;
236+
return this;
237+
}
238+
228239
public boolean isVerbose() {
229240
return verbose;
230241
}

Diff for: modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultGeneratorTest.java

+29-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,35 @@ public void testProcessPaths() throws Exception {
4848
Assert.assertEquals(defaultList.get(3).path, "/path4");
4949
Assert.assertEquals(defaultList.get(3).allParams.size(), 1);
5050
}
51-
51+
52+
53+
@Test
54+
public void testNonStrictProcessPaths() throws Exception {
55+
OpenAPI openAPI = TestUtils.createOpenAPI();
56+
openAPI.setPaths(new Paths());
57+
openAPI.getPaths().addPathItem("path1/", new PathItem().get(new Operation().operationId("op1").responses(new ApiResponses().addApiResponse("201", new ApiResponse().description("OK")))));
58+
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")))));
59+
60+
ClientOptInput opts = new ClientOptInput();
61+
opts.setOpenAPI(openAPI);
62+
CodegenConfig config = new DefaultCodegen();
63+
config.setStrictSpecBehavior(false);
64+
opts.setConfig(config);
65+
opts.setOpts(new ClientOpts());
66+
67+
DefaultGenerator generator = new DefaultGenerator();
68+
generator.opts(opts);
69+
Map<String, List<CodegenOperation>> result = generator.processPaths(openAPI.getPaths());
70+
Assert.assertEquals(result.size(), 1);
71+
List<CodegenOperation> defaultList = result.get("Default");
72+
Assert.assertEquals(defaultList.size(), 2);
73+
Assert.assertEquals(defaultList.get(0).path, "path1/");
74+
Assert.assertEquals(defaultList.get(0).allParams.size(), 0);
75+
Assert.assertEquals(defaultList.get(1).path, "path2/");
76+
Assert.assertEquals(defaultList.get(1).allParams.size(), 1);
77+
}
78+
79+
5280
@Test
5381
public void minimalUpdateTest() throws IOException {
5482
OpenAPI openAPI = TestUtils.createOpenAPI();

0 commit comments

Comments
 (0)