Skip to content

Commit b71aecb

Browse files
authored
Add OpenAPI Normalizer (#14172)
* add x-parent support * add docstring * add openapi normalizer rule to use ref as parent in allof * add openapi normalizer with 1 rule * revise wordings * fix javadoc warnings * better test * fix docstring * minor update * minor improvements * fix typo
1 parent 3a8265b commit b71aecb

File tree

18 files changed

+771
-46
lines changed

18 files changed

+771
-46
lines changed

docs/customization.md

+13
Original file line numberDiff line numberDiff line change
@@ -451,3 +451,16 @@ Another useful option is `inlineSchemaNameDefaults`, which allows you to customi
451451
```
452452
453453
Note: Only arrayItemSuffix, mapItemSuffix are supported at the moment. `SKIP_SCHEMA_REUSE=true` is a special value to skip reusing inline schemas.
454+
455+
## OpenAPI Normalizer
456+
457+
OpenAPI Normalizer (off by default) transforms the input OpenAPI doc/spec (which may not perfectly conform to the specification) to make it workable with OpenAPI Generator. Here is a list of rules supported:
458+
459+
- `REF_AS_PARENT_IN_ALLOF`: when set to `true`, child schemas in `allOf` is considered a parent if it's a `$ref` (instead of inline schema)
460+
461+
462+
Example:
463+
```
464+
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/allOf_extension_parent.yaml -o /tmp/java-okhttp/ --additional-properties hideGenerationTimestamp="true" --openapi-normalizer REF_AS_PARENT_IN_ALLOF=true
465+
```
466+

modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/ConfigHelp.java

+15
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ public class ConfigHelp extends OpenApiGeneratorCommand {
8080
@Option(name = {"--inline-schema-name-defaults"}, title = "inline schema name defaults", description = "default values used when naming inline schema name")
8181
private Boolean inlineSchemaNameDefaults;
8282

83+
@Option(name = {"--openapi-normalizer"}, title = "openapi normalizer rules", description = "displays the OpenAPI normalizer rules (none)")
84+
private Boolean openapiNormalizer;
85+
8386
@Option(name = {"--metadata"}, title = "metadata", description = "displays the generator metadata like the help txt for the generator and generator type etc")
8487
private Boolean metadata;
8588

@@ -494,6 +497,18 @@ private void generatePlainTextHelp(StringBuilder sb, CodegenConfig config) {
494497
sb.append(newline);
495498
}
496499

500+
if (Boolean.TRUE.equals(openapiNormalizer)) {
501+
sb.append(newline).append("OPENAPI NORMALIZER RULES").append(newline).append(newline);
502+
Map<String, String> map = config.openapiNormalizer()
503+
.entrySet()
504+
.stream()
505+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> {
506+
throw new IllegalStateException(String.format(Locale.ROOT, "Duplicated options! %s and %s", a, b));
507+
}, TreeMap::new));
508+
writePlainTextFromMap(sb, map, optIndent, optNestedIndent, "OpenAPI normalizer rule", "Set to");
509+
sb.append(newline);
510+
}
511+
497512
if (Boolean.TRUE.equals(instantiationTypes)) {
498513
sb.append(newline).append("INSTANTIATION TYPES").append(newline).append(newline);
499514
Map<String, String> map = config.instantiationTypes()

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

+8
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,13 @@ public class Generate extends OpenApiGeneratorCommand {
180180
+ " ONLY arrayItemSuffix, mapItemSuffix are supported at the moment. `SKIP_SCHEMA_REUSE=true` is a special value to skip reusing inline schemas.")
181181
private List<String> inlineSchemaNameDefaults = new ArrayList<>();
182182

183+
@Option(
184+
name = {"--openapi-normalizer"},
185+
title = "OpenAPI normalizer rules",
186+
description = "specifies the rules to be enabled in OpenAPI normalizer in the form of RULE_1=true,RULE_2=original."
187+
+ " You can also have multiple occurrences of this option.")
188+
private List<String> openapiNormalizer = new ArrayList<>();
189+
183190
@Option(
184191
name = {"--server-variables"},
185192
title = "server variables",
@@ -447,6 +454,7 @@ public void execute() {
447454
applySchemaMappingsKvpList(schemaMappings, configurator);
448455
applyInlineSchemaNameMappingsKvpList(inlineSchemaNameMappings, configurator);
449456
applyInlineSchemaNameDefaultsKvpList(inlineSchemaNameDefaults, configurator);
457+
applyOpenAPINormalizerKvpList(openapiNormalizer, configurator);
450458
applyTypeMappingsKvpList(typeMappings, configurator);
451459
applyAdditionalPropertiesKvpList(additionalProperties, configurator);
452460
applyLanguageSpecificPrimitivesCsvList(languageSpecificPrimitives, configurator);

modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/GeneratorSettings.java

+45
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public final class GeneratorSettings implements Serializable {
5353
private final Map<String, String> schemaMappings;
5454
private final Map<String, String> inlineSchemaNameMappings;
5555
private final Map<String, String> inlineSchemaNameDefaults;
56+
private final Map<String, String> openapiNormalizer;
5657
private final Set<String> languageSpecificPrimitives;
5758
private final Map<String, String> reservedWordsMappings;
5859
private final Map<String, String> serverVariables;
@@ -264,6 +265,15 @@ public Map<String, String> getInlineSchemaNameDefaults() {
264265
return inlineSchemaNameDefaults;
265266
}
266267

268+
/**
269+
* Gets OpenAPI normalizer rules
270+
*
271+
* @return a map of rules
272+
*/
273+
public Map<String, String> getOpenAPINormalizer() {
274+
return openapiNormalizer;
275+
}
276+
267277
/**
268278
* Gets language specific primitives. These are in addition to the "base" primitives defined in a generator.
269279
* <p>
@@ -382,6 +392,7 @@ private GeneratorSettings(Builder builder) {
382392
schemaMappings = Collections.unmodifiableMap(builder.schemaMappings);
383393
inlineSchemaNameMappings = Collections.unmodifiableMap(builder.inlineSchemaNameMappings);
384394
inlineSchemaNameDefaults = Collections.unmodifiableMap(builder.inlineSchemaNameDefaults);
395+
openapiNormalizer = Collections.unmodifiableMap(builder.openapiNormalizer);
385396
languageSpecificPrimitives = Collections.unmodifiableSet(builder.languageSpecificPrimitives);
386397
reservedWordsMappings = Collections.unmodifiableMap(builder.reservedWordsMappings);
387398
serverVariables = Collections.unmodifiableMap(builder.serverVariables);
@@ -455,6 +466,7 @@ public GeneratorSettings() {
455466
schemaMappings = Collections.unmodifiableMap(new HashMap<>(0));
456467
inlineSchemaNameMappings = Collections.unmodifiableMap(new HashMap<>(0));
457468
inlineSchemaNameDefaults = Collections.unmodifiableMap(new HashMap<>(0));
469+
openapiNormalizer = Collections.unmodifiableMap(new HashMap<>(0));
458470
languageSpecificPrimitives = Collections.unmodifiableSet(new HashSet<>(0));
459471
reservedWordsMappings = Collections.unmodifiableMap(new HashMap<>(0));
460472
serverVariables = Collections.unmodifiableMap(new HashMap<>(0));
@@ -515,6 +527,9 @@ public static Builder newBuilder(GeneratorSettings copy) {
515527
if (copy.getInlineSchemaNameDefaults() != null) {
516528
builder.inlineSchemaNameDefaults.putAll(copy.getInlineSchemaNameDefaults());
517529
}
530+
if (copy.getOpenAPINormalizer() != null) {
531+
builder.openapiNormalizer.putAll(copy.getOpenAPINormalizer());
532+
}
518533
if (copy.getLanguageSpecificPrimitives() != null) {
519534
builder.languageSpecificPrimitives.addAll(copy.getLanguageSpecificPrimitives());
520535
}
@@ -557,6 +572,7 @@ public static final class Builder {
557572
private Map<String, String> schemaMappings;
558573
private Map<String, String> inlineSchemaNameMappings;
559574
private Map<String, String> inlineSchemaNameDefaults;
575+
private Map<String, String> openapiNormalizer;
560576
private Set<String> languageSpecificPrimitives;
561577
private Map<String, String> reservedWordsMappings;
562578
private Map<String, String> serverVariables;
@@ -577,6 +593,7 @@ public Builder() {
577593
schemaMappings = new HashMap<>();
578594
inlineSchemaNameMappings = new HashMap<>();
579595
inlineSchemaNameDefaults = new HashMap<>();
596+
openapiNormalizer = new HashMap<>();
580597
languageSpecificPrimitives = new HashSet<>();
581598
reservedWordsMappings = new HashMap<>();
582599
serverVariables = new HashMap<>();
@@ -897,6 +914,32 @@ public Builder withInlineSchemaNameMapping(String key, String value) {
897914
return this;
898915
}
899916

917+
/**
918+
* Sets the {@code openapiNormalizer} and returns a reference to this Builder so that the methods can be chained together.
919+
*
920+
* @param openapiNormalizer the {@code openapiNormalizer} to set
921+
* @return a reference to this Builder
922+
*/
923+
public Builder withOpenAPINormalizer(Map<String, String> openapiNormalizer) {
924+
this.openapiNormalizer = openapiNormalizer;
925+
return this;
926+
}
927+
928+
/**
929+
* Sets a single {@code openapiNormalizer} and returns a reference to this Builder so that the methods can be chained together.
930+
*
931+
* @param key A key for the OpenAPI normalizer rule
932+
* @param value The value of the OpenAPI normalizer rule
933+
* @return a reference to this Builder
934+
*/
935+
public Builder withOpenAPINormalizer(String key, String value) {
936+
if (this.openapiNormalizer == null) {
937+
this.openapiNormalizer = new HashMap<>();
938+
}
939+
this.openapiNormalizer.put(key, value);
940+
return this;
941+
}
942+
900943
/**
901944
* Sets the {@code languageSpecificPrimitives} and returns a reference to this Builder so that the methods can be chained together.
902945
*
@@ -1085,6 +1128,7 @@ public boolean equals(Object o) {
10851128
Objects.equals(getSchemaMappings(), that.getSchemaMappings()) &&
10861129
Objects.equals(getInlineSchemaNameMappings(), that.getInlineSchemaNameMappings()) &&
10871130
Objects.equals(getInlineSchemaNameDefaults(), that.getInlineSchemaNameDefaults()) &&
1131+
Objects.equals(getOpenAPINormalizer(), that.getOpenAPINormalizer()) &&
10881132
Objects.equals(getLanguageSpecificPrimitives(), that.getLanguageSpecificPrimitives()) &&
10891133
Objects.equals(getReservedWordsMappings(), that.getReservedWordsMappings()) &&
10901134
Objects.equals(getGitHost(), that.getGitHost()) &&
@@ -1116,6 +1160,7 @@ public int hashCode() {
11161160
getSchemaMappings(),
11171161
getInlineSchemaNameMappings(),
11181162
getInlineSchemaNameDefaults(),
1163+
getOpenAPINormalizer(),
11191164
getLanguageSpecificPrimitives(),
11201165
getReservedWordsMappings(),
11211166
getGitHost(),

modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt

+1
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class OpenApiGeneratorPlugin : Plugin<Project> {
118118
schemaMappings.set(generate.schemaMappings)
119119
inlineSchemaNameMappings.set(generate.inlineSchemaNameMappings)
120120
inlineSchemaNameDefaults.set(generate.inlineSchemaNameDefaults)
121+
openapiNormalizer.set(generate.openapiNormalizer)
121122
invokerPackage.set(generate.invokerPackage)
122123
groupId.set(generate.groupId)
123124
id.set(generate.id)

modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt

+5
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ open class OpenApiGeneratorGenerateExtension(project: Project) {
162162
*/
163163
val inlineSchemaNameDefaults = project.objects.mapProperty<String, String>()
164164

165+
/**
166+
* Specifies mappings (rules) in OpenAPI normalizer
167+
*/
168+
val openapiNormalizer = project.objects.mapProperty<String, String>()
169+
165170
/**
166171
* Root package for generated code.
167172
*/

modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt

+13
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,13 @@ open class GenerateTask : DefaultTask() {
250250
@Input
251251
val inlineSchemaNameDefaults = project.objects.mapProperty<String, String>()
252252

253+
/**
254+
* Specifies mappings (rules) in OpenAPI normalizer
255+
*/
256+
@Optional
257+
@Input
258+
val openapiNormalizer = project.objects.mapProperty<String, String>()
259+
253260
/**
254261
* Root package for generated code.
255262
*/
@@ -758,6 +765,12 @@ open class GenerateTask : DefaultTask() {
758765
}
759766
}
760767

768+
if (openapiNormalizer.isPresent) {
769+
openapiNormalizer.get().forEach { entry ->
770+
configurator.addOpenAPINormalizer(entry.key, entry.value)
771+
}
772+
}
773+
761774
if (typeMappings.isPresent) {
762775
typeMappings.get().forEach { entry ->
763776
configurator.addTypeMapping(entry.key, entry.value)

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

+17
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,12 @@ public class CodeGenMojo extends AbstractMojo {
315315
@Parameter(name = "inlineSchemaNameDefaults", property = "openapi.generator.maven.plugin.inlineSchemaNameDefaults")
316316
private List<String> inlineSchemaNameDefaults;
317317

318+
/**
319+
* A set of rules for OpenAPI normalizer
320+
*/
321+
@Parameter(name = "openapiNormalizer", property = "openapi.generator.maven.plugin.openapiNormalizer")
322+
private List<String> openapiNormalizer;
323+
318324
/**
319325
* A map of swagger spec types and the generated code types to use for them
320326
*/
@@ -700,6 +706,12 @@ public void execute() throws MojoExecutionException {
700706
configurator);
701707
}
702708

709+
// Retained for backwards-compatibility with configOptions -> openapi-normalizer
710+
if (openapiNormalizer == null && configOptions.containsKey("openapi-normalizer")) {
711+
applyOpenAPINormalizerKvp(configOptions.get("openapi-normalizer").toString(),
712+
configurator);
713+
}
714+
703715
// Retained for backwards-compatibility with configOptions -> type-mappings
704716
if (typeMappings == null && configOptions.containsKey("type-mappings")) {
705717
applyTypeMappingsKvp(configOptions.get("type-mappings").toString(), configurator);
@@ -753,6 +765,11 @@ public void execute() throws MojoExecutionException {
753765
applyInlineSchemaNameDefaultsKvpList(inlineSchemaNameDefaults, configurator);
754766
}
755767

768+
// Apply OpenAPI normalizer rules
769+
if (openapiNormalizer != null && (configOptions == null || !configOptions.containsKey("openapi-normalizer"))) {
770+
applyOpenAPINormalizerKvpList(openapiNormalizer, configurator);
771+
}
772+
756773
// Apply Type Mappings
757774
if (typeMappings != null && (configOptions == null || !configOptions.containsKey("type-mappings"))) {
758775
applyTypeMappingsKvpList(typeMappings, configurator);

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

+5
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ public interface CodegenConfig {
147147

148148
Map<String, String> inlineSchemaNameDefault();
149149

150+
Map<String, String> openapiNormalizer();
151+
150152
Map<String, String> apiTemplateFiles();
151153

152154
Map<String, String> modelTemplateFiles();
@@ -330,4 +332,7 @@ public interface CodegenConfig {
330332
boolean getUseInlineModelResolver();
331333

332334
boolean getAddSuffixToDuplicateOperationNicknames();
335+
336+
boolean getUseOpenAPINormalizer();
337+
333338
}

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

+10
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ public class DefaultCodegen implements CodegenConfig {
167167
protected Map<String, String> inlineSchemaNameMapping = new HashMap<>();
168168
// a map to store the inline schema naming conventions
169169
protected Map<String, String> inlineSchemaNameDefault = new HashMap<>();
170+
// a map to store the rules in OpenAPI Normalizer
171+
protected Map<String, String> openapiNormalizer = new HashMap<>();
170172
protected String modelPackage = "", apiPackage = "", fileSuffix;
171173
protected String modelNamePrefix = "", modelNameSuffix = "";
172174
protected String apiNamePrefix = "", apiNameSuffix = "Api";
@@ -1137,6 +1139,11 @@ public Map<String, String> inlineSchemaNameDefault() {
11371139
return inlineSchemaNameDefault;
11381140
}
11391141

1142+
@Override
1143+
public Map<String, String> openapiNormalizer() {
1144+
return openapiNormalizer;
1145+
}
1146+
11401147
@Override
11411148
public String testPackage() {
11421149
return testPackage;
@@ -7939,6 +7946,9 @@ public List<VendorExtension> getSupportedVendorExtensions() {
79397946
@Override
79407947
public boolean getUseInlineModelResolver() { return true; }
79417948

7949+
@Override
7950+
public boolean getUseOpenAPINormalizer() { return true; }
7951+
79427952
/*
79437953
A function to convert yaml or json ingested strings like property names
79447954
And convert special characters like newline, tab, carriage return

modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java

+6
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,12 @@ void configureGeneratorProperties() {
255255

256256
config.processOpts();
257257

258+
// normalize the spec
259+
if (config.getUseOpenAPINormalizer()) {
260+
OpenAPINormalizer openapiNormalizer = new OpenAPINormalizer(openAPI, config.openapiNormalizer());
261+
openapiNormalizer.normalize();
262+
}
263+
258264
// resolve inline models
259265
if (config.getUseInlineModelResolver()) {
260266
InlineModelResolver inlineModelResolver = new InlineModelResolver();

0 commit comments

Comments
 (0)