diff --git a/docs/customization.md b/docs/customization.md index b180d783c2bd..22c34373eacb 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -451,3 +451,16 @@ Another useful option is `inlineSchemaNameDefaults`, which allows you to customi ``` Note: Only arrayItemSuffix, mapItemSuffix are supported at the moment. `SKIP_SCHEMA_REUSE=true` is a special value to skip reusing inline schemas. + +## OpenAPI Normalizer + +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: + +- `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) + + +Example: +``` +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 +``` + diff --git a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/ConfigHelp.java b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/ConfigHelp.java index d206a6749f94..25c1d3cd61c4 100644 --- a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/ConfigHelp.java +++ b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/ConfigHelp.java @@ -80,6 +80,9 @@ public class ConfigHelp extends OpenApiGeneratorCommand { @Option(name = {"--inline-schema-name-defaults"}, title = "inline schema name defaults", description = "default values used when naming inline schema name") private Boolean inlineSchemaNameDefaults; + @Option(name = {"--openapi-normalizer"}, title = "openapi normalizer rules", description = "displays the OpenAPI normalizer rules (none)") + private Boolean openapiNormalizer; + @Option(name = {"--metadata"}, title = "metadata", description = "displays the generator metadata like the help txt for the generator and generator type etc") private Boolean metadata; @@ -494,6 +497,18 @@ private void generatePlainTextHelp(StringBuilder sb, CodegenConfig config) { sb.append(newline); } + if (Boolean.TRUE.equals(openapiNormalizer)) { + sb.append(newline).append("OPENAPI NORMALIZER RULES").append(newline).append(newline); + Map map = config.openapiNormalizer() + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> { + throw new IllegalStateException(String.format(Locale.ROOT, "Duplicated options! %s and %s", a, b)); + }, TreeMap::new)); + writePlainTextFromMap(sb, map, optIndent, optNestedIndent, "OpenAPI normalizer rule", "Set to"); + sb.append(newline); + } + if (Boolean.TRUE.equals(instantiationTypes)) { sb.append(newline).append("INSTANTIATION TYPES").append(newline).append(newline); Map map = config.instantiationTypes() diff --git a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java index ae78b2f9fd94..2d473df2a3eb 100644 --- a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java +++ b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/Generate.java @@ -180,6 +180,13 @@ public class Generate extends OpenApiGeneratorCommand { + " ONLY arrayItemSuffix, mapItemSuffix are supported at the moment. `SKIP_SCHEMA_REUSE=true` is a special value to skip reusing inline schemas.") private List inlineSchemaNameDefaults = new ArrayList<>(); + @Option( + name = {"--openapi-normalizer"}, + title = "OpenAPI normalizer rules", + description = "specifies the rules to be enabled in OpenAPI normalizer in the form of RULE_1=true,RULE_2=original." + + " You can also have multiple occurrences of this option.") + private List openapiNormalizer = new ArrayList<>(); + @Option( name = {"--server-variables"}, title = "server variables", @@ -447,6 +454,7 @@ public void execute() { applySchemaMappingsKvpList(schemaMappings, configurator); applyInlineSchemaNameMappingsKvpList(inlineSchemaNameMappings, configurator); applyInlineSchemaNameDefaultsKvpList(inlineSchemaNameDefaults, configurator); + applyOpenAPINormalizerKvpList(openapiNormalizer, configurator); applyTypeMappingsKvpList(typeMappings, configurator); applyAdditionalPropertiesKvpList(additionalProperties, configurator); applyLanguageSpecificPrimitivesCsvList(languageSpecificPrimitives, configurator); diff --git a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/GeneratorSettings.java b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/GeneratorSettings.java index c14a06721e8d..207bf477580d 100644 --- a/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/GeneratorSettings.java +++ b/modules/openapi-generator-core/src/main/java/org/openapitools/codegen/config/GeneratorSettings.java @@ -53,6 +53,7 @@ public final class GeneratorSettings implements Serializable { private final Map schemaMappings; private final Map inlineSchemaNameMappings; private final Map inlineSchemaNameDefaults; + private final Map openapiNormalizer; private final Set languageSpecificPrimitives; private final Map reservedWordsMappings; private final Map serverVariables; @@ -264,6 +265,15 @@ public Map getInlineSchemaNameDefaults() { return inlineSchemaNameDefaults; } + /** + * Gets OpenAPI normalizer rules + * + * @return a map of rules + */ + public Map getOpenAPINormalizer() { + return openapiNormalizer; + } + /** * Gets language specific primitives. These are in addition to the "base" primitives defined in a generator. *

@@ -382,6 +392,7 @@ private GeneratorSettings(Builder builder) { schemaMappings = Collections.unmodifiableMap(builder.schemaMappings); inlineSchemaNameMappings = Collections.unmodifiableMap(builder.inlineSchemaNameMappings); inlineSchemaNameDefaults = Collections.unmodifiableMap(builder.inlineSchemaNameDefaults); + openapiNormalizer = Collections.unmodifiableMap(builder.openapiNormalizer); languageSpecificPrimitives = Collections.unmodifiableSet(builder.languageSpecificPrimitives); reservedWordsMappings = Collections.unmodifiableMap(builder.reservedWordsMappings); serverVariables = Collections.unmodifiableMap(builder.serverVariables); @@ -455,6 +466,7 @@ public GeneratorSettings() { schemaMappings = Collections.unmodifiableMap(new HashMap<>(0)); inlineSchemaNameMappings = Collections.unmodifiableMap(new HashMap<>(0)); inlineSchemaNameDefaults = Collections.unmodifiableMap(new HashMap<>(0)); + openapiNormalizer = Collections.unmodifiableMap(new HashMap<>(0)); languageSpecificPrimitives = Collections.unmodifiableSet(new HashSet<>(0)); reservedWordsMappings = Collections.unmodifiableMap(new HashMap<>(0)); serverVariables = Collections.unmodifiableMap(new HashMap<>(0)); @@ -515,6 +527,9 @@ public static Builder newBuilder(GeneratorSettings copy) { if (copy.getInlineSchemaNameDefaults() != null) { builder.inlineSchemaNameDefaults.putAll(copy.getInlineSchemaNameDefaults()); } + if (copy.getOpenAPINormalizer() != null) { + builder.openapiNormalizer.putAll(copy.getOpenAPINormalizer()); + } if (copy.getLanguageSpecificPrimitives() != null) { builder.languageSpecificPrimitives.addAll(copy.getLanguageSpecificPrimitives()); } @@ -557,6 +572,7 @@ public static final class Builder { private Map schemaMappings; private Map inlineSchemaNameMappings; private Map inlineSchemaNameDefaults; + private Map openapiNormalizer; private Set languageSpecificPrimitives; private Map reservedWordsMappings; private Map serverVariables; @@ -577,6 +593,7 @@ public Builder() { schemaMappings = new HashMap<>(); inlineSchemaNameMappings = new HashMap<>(); inlineSchemaNameDefaults = new HashMap<>(); + openapiNormalizer = new HashMap<>(); languageSpecificPrimitives = new HashSet<>(); reservedWordsMappings = new HashMap<>(); serverVariables = new HashMap<>(); @@ -897,6 +914,32 @@ public Builder withInlineSchemaNameMapping(String key, String value) { return this; } + /** + * Sets the {@code openapiNormalizer} and returns a reference to this Builder so that the methods can be chained together. + * + * @param openapiNormalizer the {@code openapiNormalizer} to set + * @return a reference to this Builder + */ + public Builder withOpenAPINormalizer(Map openapiNormalizer) { + this.openapiNormalizer = openapiNormalizer; + return this; + } + + /** + * Sets a single {@code openapiNormalizer} and returns a reference to this Builder so that the methods can be chained together. + * + * @param key A key for the OpenAPI normalizer rule + * @param value The value of the OpenAPI normalizer rule + * @return a reference to this Builder + */ + public Builder withOpenAPINormalizer(String key, String value) { + if (this.openapiNormalizer == null) { + this.openapiNormalizer = new HashMap<>(); + } + this.openapiNormalizer.put(key, value); + return this; + } + /** * Sets the {@code languageSpecificPrimitives} and returns a reference to this Builder so that the methods can be chained together. * @@ -1085,6 +1128,7 @@ public boolean equals(Object o) { Objects.equals(getSchemaMappings(), that.getSchemaMappings()) && Objects.equals(getInlineSchemaNameMappings(), that.getInlineSchemaNameMappings()) && Objects.equals(getInlineSchemaNameDefaults(), that.getInlineSchemaNameDefaults()) && + Objects.equals(getOpenAPINormalizer(), that.getOpenAPINormalizer()) && Objects.equals(getLanguageSpecificPrimitives(), that.getLanguageSpecificPrimitives()) && Objects.equals(getReservedWordsMappings(), that.getReservedWordsMappings()) && Objects.equals(getGitHost(), that.getGitHost()) && @@ -1116,6 +1160,7 @@ public int hashCode() { getSchemaMappings(), getInlineSchemaNameMappings(), getInlineSchemaNameDefaults(), + getOpenAPINormalizer(), getLanguageSpecificPrimitives(), getReservedWordsMappings(), getGitHost(), diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt index 4975fe216619..5dfccba04c94 100644 --- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt @@ -118,6 +118,7 @@ class OpenApiGeneratorPlugin : Plugin { schemaMappings.set(generate.schemaMappings) inlineSchemaNameMappings.set(generate.inlineSchemaNameMappings) inlineSchemaNameDefaults.set(generate.inlineSchemaNameDefaults) + openapiNormalizer.set(generate.openapiNormalizer) invokerPackage.set(generate.invokerPackage) groupId.set(generate.groupId) id.set(generate.id) diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt index 4d9cff54143f..21751733b67f 100644 --- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt @@ -162,6 +162,11 @@ open class OpenApiGeneratorGenerateExtension(project: Project) { */ val inlineSchemaNameDefaults = project.objects.mapProperty() + /** + * Specifies mappings (rules) in OpenAPI normalizer + */ + val openapiNormalizer = project.objects.mapProperty() + /** * Root package for generated code. */ diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt index f3ef513a2f22..b368945e77cc 100644 --- a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt @@ -250,6 +250,13 @@ open class GenerateTask : DefaultTask() { @Input val inlineSchemaNameDefaults = project.objects.mapProperty() + /** + * Specifies mappings (rules) in OpenAPI normalizer + */ + @Optional + @Input + val openapiNormalizer = project.objects.mapProperty() + /** * Root package for generated code. */ @@ -758,6 +765,12 @@ open class GenerateTask : DefaultTask() { } } + if (openapiNormalizer.isPresent) { + openapiNormalizer.get().forEach { entry -> + configurator.addOpenAPINormalizer(entry.key, entry.value) + } + } + if (typeMappings.isPresent) { typeMappings.get().forEach { entry -> configurator.addTypeMapping(entry.key, entry.value) diff --git a/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java b/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java index f796ca6002a9..8eb2073faa67 100644 --- a/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java +++ b/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java @@ -315,6 +315,12 @@ public class CodeGenMojo extends AbstractMojo { @Parameter(name = "inlineSchemaNameDefaults", property = "openapi.generator.maven.plugin.inlineSchemaNameDefaults") private List inlineSchemaNameDefaults; + /** + * A set of rules for OpenAPI normalizer + */ + @Parameter(name = "openapiNormalizer", property = "openapi.generator.maven.plugin.openapiNormalizer") + private List openapiNormalizer; + /** * A map of swagger spec types and the generated code types to use for them */ @@ -700,6 +706,12 @@ public void execute() throws MojoExecutionException { configurator); } + // Retained for backwards-compatibility with configOptions -> openapi-normalizer + if (openapiNormalizer == null && configOptions.containsKey("openapi-normalizer")) { + applyOpenAPINormalizerKvp(configOptions.get("openapi-normalizer").toString(), + configurator); + } + // Retained for backwards-compatibility with configOptions -> type-mappings if (typeMappings == null && configOptions.containsKey("type-mappings")) { applyTypeMappingsKvp(configOptions.get("type-mappings").toString(), configurator); @@ -753,6 +765,11 @@ public void execute() throws MojoExecutionException { applyInlineSchemaNameDefaultsKvpList(inlineSchemaNameDefaults, configurator); } + // Apply OpenAPI normalizer rules + if (openapiNormalizer != null && (configOptions == null || !configOptions.containsKey("openapi-normalizer"))) { + applyOpenAPINormalizerKvpList(openapiNormalizer, configurator); + } + // Apply Type Mappings if (typeMappings != null && (configOptions == null || !configOptions.containsKey("type-mappings"))) { applyTypeMappingsKvpList(typeMappings, configurator); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java index 2917c2ee0a9b..9b448b01e669 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConfig.java @@ -147,6 +147,8 @@ public interface CodegenConfig { Map inlineSchemaNameDefault(); + Map openapiNormalizer(); + Map apiTemplateFiles(); Map modelTemplateFiles(); @@ -330,4 +332,7 @@ public interface CodegenConfig { boolean getUseInlineModelResolver(); boolean getAddSuffixToDuplicateOperationNicknames(); + + boolean getUseOpenAPINormalizer(); + } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 30e1c099c5e3..ae20651a4b69 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -167,6 +167,8 @@ public class DefaultCodegen implements CodegenConfig { protected Map inlineSchemaNameMapping = new HashMap<>(); // a map to store the inline schema naming conventions protected Map inlineSchemaNameDefault = new HashMap<>(); + // a map to store the rules in OpenAPI Normalizer + protected Map openapiNormalizer = new HashMap<>(); protected String modelPackage = "", apiPackage = "", fileSuffix; protected String modelNamePrefix = "", modelNameSuffix = ""; protected String apiNamePrefix = "", apiNameSuffix = "Api"; @@ -1122,6 +1124,11 @@ public Map inlineSchemaNameDefault() { return inlineSchemaNameDefault; } + @Override + public Map openapiNormalizer() { + return openapiNormalizer; + } + @Override public String testPackage() { return testPackage; @@ -7924,6 +7931,9 @@ public List getSupportedVendorExtensions() { @Override public boolean getUseInlineModelResolver() { return true; } + @Override + public boolean getUseOpenAPINormalizer() { return true; } + /* A function to convert yaml or json ingested strings like property names And convert special characters like newline, tab, carriage return diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java index 2da1e53f84e2..e13a78096763 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java @@ -255,6 +255,12 @@ void configureGeneratorProperties() { config.processOpts(); + // normalize the spec + if (config.getUseOpenAPINormalizer()) { + OpenAPINormalizer openapiNormalizer = new OpenAPINormalizer(openAPI, config.openapiNormalizer()); + openapiNormalizer.normalize(); + } + // resolve inline models if (config.getUseInlineModelResolver()) { InlineModelResolver inlineModelResolver = new InlineModelResolver(); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java new file mode 100644 index 000000000000..4f1de87ba93a --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java @@ -0,0 +1,384 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * Copyright 2018 SmartBear Software + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.codegen; + +import io.swagger.v3.oas.models.*; +import io.swagger.v3.oas.models.callbacks.Callback; +import io.swagger.v3.oas.models.media.*; +import io.swagger.v3.oas.models.parameters.Parameter; +import io.swagger.v3.oas.models.parameters.RequestBody; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.oas.models.responses.ApiResponses; +import org.apache.commons.lang3.StringUtils; +import org.openapitools.codegen.utils.ModelUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.stream.Collectors; + +public class OpenAPINormalizer { + private OpenAPI openAPI; + private Map rules = new HashMap<>(); + + final Logger LOGGER = LoggerFactory.getLogger(OpenAPINormalizer.class); + + // ============= a list of rules ============= + // when set to true, all rules are enabled + final String ALL = "ALL"; + boolean enableAll; + + // when set to true, $ref in allOf is treated as parent so that x-parent: true will be added + // to the schema in $ref (if x-parent is not present) + final String REF_AS_PARENT_IN_ALLOF = "REF_AS_PARENT_IN_ALLOF"; + boolean enableRefAsParentInAllOf; + + // ============= end of rules ============= + + /** + * Initializes OpenAPI Normalizer with a set of rules + * + * @param openAPI OpenAPI + * @param rules a map of rules + */ + public OpenAPINormalizer(OpenAPI openAPI, Map rules) { + this.openAPI = openAPI; + this.rules = rules; + parseRules(rules); + } + + /** + * Parses the rules. + * + * @param rules a map of rules + */ + public void parseRules(Map rules) { + if (rules == null) { + return; + } + + if ("true".equalsIgnoreCase(rules.get(ALL))) { + enableAll = true; + } + + if (enableAll || "true".equalsIgnoreCase(rules.get(REF_AS_PARENT_IN_ALLOF))) { + enableRefAsParentInAllOf = true; + } + } + + /** + * Normalizes the OpenAPI input, which may not perfectly conform to + * the specification. + */ + void normalize() { + if (rules == null || rules.isEmpty()) { + return; + } + + if (this.openAPI.getComponents() == null) { + this.openAPI.setComponents(new Components()); + } + + if (this.openAPI.getComponents().getSchemas() == null) { + this.openAPI.getComponents().setSchemas(new HashMap()); + } + + normalizePaths(); + normalizeComponents(); + } + + /** + * Normalizes inline models in Paths + */ + private void normalizePaths() { + Paths paths = openAPI.getPaths(); + if (paths == null) { + return; + } + + for (Map.Entry pathsEntry : paths.entrySet()) { + PathItem path = pathsEntry.getValue(); + List operations = new ArrayList<>(path.readOperations()); + + // Include callback operation as well + for (Operation operation : path.readOperations()) { + Map callbacks = operation.getCallbacks(); + if (callbacks != null) { + operations.addAll(callbacks.values().stream() + .flatMap(callback -> callback.values().stream()) + .flatMap(pathItem -> pathItem.readOperations().stream()) + .collect(Collectors.toList())); + } + } + + for (Operation operation : operations) { + normalizeRequestBody(operation); + normalizeParameters(operation); + normalizeResponses(operation); + } + } + } + + /** + * Normalizes schemas in content + * + * @param content target content + */ + private void normalizeContent(Content content) { + if (content == null || content.isEmpty()) { + return; + } + + for (String contentType : content.keySet()) { + MediaType mediaType = content.get(contentType); + if (mediaType == null) { + continue; + } else if (mediaType.getSchema() == null) { + continue; + } else { + normalizeSchema(mediaType.getSchema(), new HashSet<>()); + } + } + } + + /** + * Normalizes schemas in RequestBody + * + * @param operation target operation + */ + private void normalizeRequestBody(Operation operation) { + RequestBody requestBody = operation.getRequestBody(); + if (requestBody == null) { + return; + } + + // unalias $ref + if (requestBody.get$ref() != null) { + String ref = ModelUtils.getSimpleRef(requestBody.get$ref()); + requestBody = openAPI.getComponents().getRequestBodies().get(ref); + + if (requestBody == null) { + return; + } + } + + normalizeContent(requestBody.getContent()); + } + + /** + * Normalizes schemas in parameters + * + * @param operation target operation + */ + private void normalizeParameters(Operation operation) { + List parameters = operation.getParameters(); + if (parameters == null) { + return; + } + + for (Parameter parameter : parameters) { + if (parameter.getSchema() == null) { + continue; + } else { + normalizeSchema(parameter.getSchema(), new HashSet<>()); + } + } + } + + /** + * Normalizes schemas in ApiResponses + * + * @param operation target operation + */ + private void normalizeResponses(Operation operation) { + ApiResponses responses = operation.getResponses(); + if (responses == null) { + return; + } + + for (Map.Entry responsesEntry : responses.entrySet()) { + if (responsesEntry.getValue() == null) { + continue; + } else { + normalizeContent(responsesEntry.getValue().getContent()); + } + } + } + + /** + * Normalizes schemas in components + */ + private void normalizeComponents() { + Map schemas = openAPI.getComponents().getSchemas(); + if (schemas == null) { + return; + } + + List schemaNames = new ArrayList(schemas.keySet()); + for (String schemaName : schemaNames) { + Schema schema = schemas.get(schemaName); + if (schema == null) { + LOGGER.warn("{} not fount found in openapi/components/schemas.", schemaName); + } else { + normalizeSchema(schema, new HashSet<>()); + } + } + } + + /** + * Normalizes a schema + * + * @param schema Schema + * @param visitedSchemas a set of visited schemas + */ + public void normalizeSchema(Schema schema, Set visitedSchemas) { + if (schema == null) { + return; + } + + if (StringUtils.isNotEmpty(schema.get$ref())) { + // not need to process $ref + return; + } + + if ((visitedSchemas.contains(schema))) { + return; // skip due to circular reference + } else { + visitedSchemas.add(schema); + } + + if (schema instanceof ArraySchema) { + normalizeSchema(schema.getItems(), visitedSchemas); + } else if (schema.getAdditionalProperties() instanceof Schema) { // map + normalizeSchema((Schema) schema.getAdditionalProperties(), visitedSchemas); + } else if (ModelUtils.isComposedSchema(schema)) { + ComposedSchema m = (ComposedSchema) schema; + if (m.getAllOf() != null && !m.getAllOf().isEmpty()) { + normalizeAllOf(m, visitedSchemas); + } + + if (m.getOneOf() != null && !m.getOneOf().isEmpty()) { + normalizeOneOf(m, visitedSchemas); + } + + if (m.getAnyOf() != null && !m.getAnyOf().isEmpty()) { + normalizeAnyOf(m, visitedSchemas); + } + + if (m.getProperties() != null && !m.getProperties().isEmpty()) { + normalizeProperties(m.getProperties(), visitedSchemas); + } + + if (m.getAdditionalProperties() != null) { + // normalizeAdditionalProperties(m); + } + } else if (schema.getNot() != null) {// not schema + normalizeSchema(schema.getNot(), visitedSchemas); + } else if (schema.getProperties() != null && !schema.getProperties().isEmpty()) { + normalizeProperties(schema.getProperties(), visitedSchemas); + } else if (schema instanceof Schema) { + normalizeNonComposedSchema(schema, visitedSchemas); + } else { + throw new RuntimeException("Unknown schema type found in normalizer: " + schema); + } + } + + private void normalizeNonComposedSchema(Schema schema, Set visitedSchemas) { + // normalize non-composed schema (e.g. schema with only properties) + } + + private void normalizeProperties(Map properties, Set visitedSchemas) { + if (properties == null) { + return; + } + for (Map.Entry propertiesEntry : properties.entrySet()) { + Schema property = propertiesEntry.getValue(); + normalizeSchema(property, visitedSchemas); + } + } + + private void normalizeAllOf(Schema schema, Set visitedSchemas) { + for (Object item : schema.getAllOf()) { + if (!(item instanceof Schema)) { + throw new RuntimeException("Error! allOf schema is not of the type Schema: " + item); + } + // normalize allOf sub schemas one by one + normalizeSchema((Schema) item, visitedSchemas); + } + // process rules here + processUseAllOfRefAsParent(schema); + } + + private void normalizeOneOf(Schema schema, Set visitedSchemas) { + for (Object item : schema.getAllOf()) { + if (!(item instanceof Schema)) { + throw new RuntimeException("Error! allOf schema is not of the type Schema: " + item); + } + // normalize oenOf sub schemas one by one + normalizeSchema((Schema) item, visitedSchemas); + } + // process rules here + } + + private void normalizeAnyOf(Schema schema, Set visitedSchemas) { + for (Object item : schema.getAllOf()) { + if (!(item instanceof Schema)) { + throw new RuntimeException("Error! allOf schema is not of the type Schema: " + item); + } + // normalize anyOf sub schemas one by one + normalizeSchema((Schema) item, visitedSchemas); + } + // process rules here + } + + // ===================== a list of rules ===================== + // all rules (fuctions) start with the word "process" + private void processUseAllOfRefAsParent(Schema schema) { + if (!enableRefAsParentInAllOf) { + return; + } + + for (Object item : schema.getAllOf()) { + if (!(item instanceof Schema)) { + throw new RuntimeException("Error! allOf schema is not of the type Schema: " + item); + } + Schema s = (Schema) item; + + if (StringUtils.isNotEmpty(s.get$ref())) { + String ref = ModelUtils.getSimpleRef(s.get$ref()); + // TODO need to check for requestBodies? + Schema refSchema = openAPI.getComponents().getSchemas().get(ref); + if (refSchema == null) { + throw new RuntimeException("schema cannot be null with ref " + ref); + } + if (refSchema.getExtensions() == null) { + refSchema.setExtensions(new HashMap<>()); + } + + if (refSchema.getExtensions().containsKey("x-parent")) { + // doing nothing as x-parent already exists + } else { + refSchema.getExtensions().put("x-parent", true); + } + + LOGGER.debug("processUseAllOfRefAsParent added `x-parent: true` to {}", refSchema); + } + } + } + // ===================== end of rules ===================== +} diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java index 3d2d9c3aaacc..8b984a23bed0 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfigurator.java @@ -71,6 +71,7 @@ public class CodegenConfigurator { private Map schemaMappings = new HashMap<>(); private Map inlineSchemaNameMappings = new HashMap<>(); private Map inlineSchemaNameDefaults = new HashMap<>(); + private Map openapiNormalizer = new HashMap<>(); private Set languageSpecificPrimitives = new HashSet<>(); private Map reservedWordsMappings = new HashMap<>(); private Map serverVariables = new HashMap<>(); @@ -123,6 +124,9 @@ public static CodegenConfigurator fromFile(String configFile, Module... modules) if(generatorSettings.getInlineSchemaNameDefaults() != null) { configurator.inlineSchemaNameDefaults.putAll(generatorSettings.getInlineSchemaNameDefaults()); } + if(generatorSettings.getOpenAPINormalizer() != null) { + configurator.openapiNormalizer.putAll(generatorSettings.getOpenAPINormalizer()); + } if(generatorSettings.getLanguageSpecificPrimitives() != null) { configurator.languageSpecificPrimitives.addAll(generatorSettings.getLanguageSpecificPrimitives()); } @@ -210,6 +214,12 @@ public CodegenConfigurator addInlineSchemaNameDefault(String key, String value) return this; } + public CodegenConfigurator addOpenAPINormalizer(String key, String value) { + this.openapiNormalizer.put(key, value); + generatorSettingsBuilder.withOpenAPINormalizer(key, value); + return this; + } + public CodegenConfigurator addInstantiationType(String key, String value) { this.instantiationTypes.put(key, value); generatorSettingsBuilder.withInstantiationType(key, value); @@ -382,6 +392,12 @@ public CodegenConfigurator setInlineSchemaNameDefaults(Map inlin return this; } + public CodegenConfigurator setOpenAPINormalizer(Map openapiNormalizer) { + this.openapiNormalizer = openapiNormalizer; + generatorSettingsBuilder.withOpenAPINormalizer(openapiNormalizer); + return this; + } + public CodegenConfigurator setInputSpec(String inputSpec) { this.inputSpec = inputSpec; workflowSettingsBuilder.withInputSpec(inputSpec); @@ -661,6 +677,7 @@ public ClientOptInput toClientOptInput() { config.schemaMapping().putAll(generatorSettings.getSchemaMappings()); config.inlineSchemaNameMapping().putAll(generatorSettings.getInlineSchemaNameMappings()); config.inlineSchemaNameDefault().putAll(generatorSettings.getInlineSchemaNameDefaults()); + config.openapiNormalizer().putAll(generatorSettings.getOpenAPINormalizer()); config.languageSpecificPrimitives().addAll(generatorSettings.getLanguageSpecificPrimitives()); config.reservedWordsMappings().putAll(generatorSettings.getReservedWordsMappings()); config.additionalProperties().putAll(generatorSettings.getAdditionalProperties()); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfiguratorUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfiguratorUtils.java index fb708d4b2f55..5a0b40d3a429 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfiguratorUtils.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/config/CodegenConfiguratorUtils.java @@ -120,6 +120,19 @@ public static void applyInlineSchemaNameDefaultsKvp(String inlineSchemaNameDefau } } + public static void applyOpenAPINormalizerKvpList(List openapiNormalizer, CodegenConfigurator configurator) { + for (String propString : openapiNormalizer) { + applyOpenAPINormalizerKvp(propString, configurator); + } + } + + public static void applyOpenAPINormalizerKvp(String openapiNormalizer, CodegenConfigurator configurator) { + final Map map = createMapFromKeyValuePairs(openapiNormalizer); + for (Map.Entry entry : map.entrySet()) { + configurator.addOpenAPINormalizer(entry.getKey().trim(), entry.getValue().trim()); + } + } + public static void applyTypeMappingsKvpList(List typeMappings, CodegenConfigurator configurator) { for (String propString : typeMappings) { applyTypeMappingsKvp(propString, configurator); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java index 107045d3958c..c1b98fc92202 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java @@ -308,15 +308,15 @@ private static void visitContent(OpenAPI openAPI, Content content, OpenAPISchema /** * Invoke the specified visitor function for every schema that matches mimeType in the OpenAPI document. - * + *

* To avoid infinite recursion, referenced schemas are visited only once. When a referenced schema is visited, * it is added to visitedSchemas. * - * @param openAPI the OpenAPI document that contains schema objects. - * @param schema the root schema object to be visited. - * @param mimeType the mime type. TODO: does not seem to be used in a meaningful way. + * @param openAPI the OpenAPI document that contains schema objects. + * @param schema the root schema object to be visited. + * @param mimeType the mime type. TODO: does not seem to be used in a meaningful way. * @param visitedSchemas the list of referenced schemas that have been visited. - * @param visitor the visitor function which is invoked for every visited schema. + * @param visitor the visitor function which is invoked for every visited schema. */ private static void visitSchema(OpenAPI openAPI, Schema schema, String mimeType, List visitedSchemas, OpenAPISchemaVisitor visitor) { visitor.visit(schema, mimeType); @@ -425,13 +425,14 @@ public static boolean isTypeObjectSchema(Schema schema) { /** * Return true if the specified schema is an object with a fixed number of properties. - * + *

* A ObjectSchema differs from a MapSchema in the following way: * - An ObjectSchema is not extensible, i.e. it has a fixed number of properties. * - A MapSchema is an object that can be extended with an arbitrary set of properties. * The payload may include dynamic properties. - * + *

* For example, an OpenAPI schema is considered an ObjectSchema in the following scenarios: + *

* * type: object * additionalProperties: false @@ -479,16 +480,17 @@ public static boolean isComposedSchema(Schema schema) { * Return true if the specified 'schema' is an object that can be extended with additional properties. * Additional properties means a Schema should support all explicitly defined properties plus any * undeclared properties. - * + *

* A MapSchema differs from an ObjectSchema in the following way: * - An ObjectSchema is not extensible, i.e. it has a fixed number of properties. * - A MapSchema is an object that can be extended with an arbitrary set of properties. - * The payload may include dynamic properties. - * + * The payload may include dynamic properties. + *

* Note that isMapSchema returns true for a composed schema (allOf, anyOf, oneOf) that also defines * additionalproperties. - * + *

* For example, an OpenAPI schema is considered a MapSchema in the following scenarios: + *

* * type: object * additionalProperties: true @@ -772,14 +774,14 @@ public static boolean hasValidation(Schema sc) { /** * Check to see if the schema is a free form object. - * + *

* A free form object is an object (i.e. 'type: object' in a OAS document) that: * 1) Does not define properties, and * 2) Is not a composed schema (no anyOf, oneOf, allOf), and * 3) additionalproperties is not defined, or additionalproperties: true, or additionalproperties: {}. - * + *

* Examples: - * + *

* components: * schemas: * arbitraryObject: @@ -798,7 +800,7 @@ public static boolean hasValidation(Schema sc) { * The value can be any type except the 'null' value. * * @param openAPI the object that encapsulates the OAS document. - * @param schema potentially containing a '$ref' + * @param schema potentially containing a '$ref' * @return true if it's a free-form object */ public static boolean isFreeFormObject(OpenAPI openAPI, Schema schema) { @@ -1054,10 +1056,10 @@ public static Schema getSchemaFromResponse(ApiResponse response) { /** * Return the first Schema from a specified OAS 'content' section. - * + *

* For example, given the following OAS, this method returns the schema * for the 'application/json' content type because it is listed first in the OAS. - * + *

* responses: * '200': * content: @@ -1099,8 +1101,8 @@ public static boolean hasSelfReference(OpenAPI openAPI, /** * Has self reference? * - * @param openAPI OpenAPI spec. - * @param schema Schema + * @param openAPI OpenAPI spec. + * @param schema Schema * @param visitedSchemaNames A set of visited schema names * @return boolean true if it has at least one self reference */ @@ -1257,7 +1259,7 @@ public static Schema unaliasSchema(OpenAPI openAPI, /** * Returns the additionalProperties Schema for the specified input schema. - * + *

* The additionalProperties keyword is used to control the handling of additional, undeclared * properties, that is, properties whose names are not listed in the properties keyword. * The additionalProperties keyword may be either a boolean or an object. @@ -1267,9 +1269,9 @@ public static Schema unaliasSchema(OpenAPI openAPI, * to the boolean value True or setting additionalProperties: {} * * @param openAPI the object that encapsulates the OAS document. - * @param schema the input schema that may or may not have the additionalProperties keyword. + * @param schema the input schema that may or may not have the additionalProperties keyword. * @return the Schema of the additionalProperties. The null value is returned if no additional - * properties are allowed. + * properties are allowed. */ public static Schema getAdditionalProperties(OpenAPI openAPI, Schema schema) { Object addProps = schema.getAdditionalProperties(); @@ -1380,10 +1382,10 @@ public static List getInterfaces(ComposedSchema composed) { * that specify a determinator. * If there are multiple elements in the composed schema and it is not clear * which one should be the parent, return null. - * + *

* For example, given the following OAS spec, the parent of 'Dog' is Animal * because 'Animal' specifies a discriminator. - * + *

* animal: * type: object * discriminator: @@ -1391,6 +1393,7 @@ public static List getInterfaces(ComposedSchema composed) { * properties: * type: string * + *

* dog: * allOf: * - $ref: '#/components/schemas/animal' @@ -1418,10 +1421,10 @@ public static String getParentName(ComposedSchema composedSchema, Map getAllParentsName(ComposedSchema composedSchema, Map< LOGGER.error("Failed to obtain schema from {}", parentName); names.add("UNKNOWN_PARENT_NAME"); } else if (hasOrInheritsDiscriminator(s, allSchemas)) { - // discriminator.propertyName is used + // discriminator.propertyName is used or x-parent is used names.add(parentName); if (includeAncestors && s instanceof ComposedSchema) { names.addAll(getAllParentsName((ComposedSchema) s, allSchemas, true)); @@ -1501,7 +1504,8 @@ public static List getAllParentsName(ComposedSchema composedSchema, Map< } private static boolean hasOrInheritsDiscriminator(Schema schema, Map allSchemas) { - if (schema.getDiscriminator() != null && StringUtils.isNotEmpty(schema.getDiscriminator().getPropertyName())) { + if ((schema.getDiscriminator() != null && StringUtils.isNotEmpty(schema.getDiscriminator().getPropertyName())) + || (isExtensionParent(schema))) { // x-parent is used return true; } else if (StringUtils.isNotEmpty(schema.get$ref())) { String parentName = getSimpleRef(schema.get$ref()); @@ -1523,18 +1527,43 @@ private static boolean hasOrInheritsDiscriminator(Schema schema, Map * In addition, if the OAS document is 3.1 or above, isNullable returns true if the input * schema is a 'oneOf' composed document with at most two children, and one of the children * is the 'null' type. - * + *

* The caller is responsible for resolving schema references before invoking isNullable. * If the input schema is a $ref and the referenced schema has 'nullable: true', this method * returns false (because the nullable attribute is defined in the referenced schema). - * + *

* The 'nullable' attribute was introduced in OAS 3.0. * The 'nullable' attribute is deprecated in OAS 3.1. In a OAS 3.1 document, the preferred way * to specify nullable properties is to use the 'null' type. @@ -1564,11 +1593,11 @@ public static boolean isNullable(Schema schema) { /** * Return true if the specified composed schema is 'oneOf', contains one or two elements, * and at least one of the elements is the 'null' type. - * + *

* The 'null' type is supported in OAS 3.1 and above. * In the example below, the 'OptionalOrder' can have the null value because the 'null' * type is one of the elements under 'oneOf'. - * + *

* OptionalOrder: * oneOf: * - type: 'null' @@ -1591,13 +1620,13 @@ public static boolean isNullableComposedSchema(ComposedSchema schema) { /** * isNullType returns true if the input schema is the 'null' type. - * + *

* The 'null' type is supported in OAS 3.1 and above. It is not supported * in OAS 2.0 and OAS 3.0.x. - * + *

* For example, the "null" type could be used to specify that a value must * either be null or a specified type: - * + *

* OptionalOrder: * oneOf: * - type: 'null' @@ -1617,6 +1646,7 @@ public static boolean isNullType(Schema schema) { * For when a type is not defined on a schema * Note: properties, additionalProperties, enums, validations, items, and composed schemas (oneOf/anyOf/allOf) * can be defined or omitted on these any type schemas + * * @param schema the schema that we are checking * @return boolean */ @@ -1713,7 +1743,7 @@ private static void setNumericValidations(Schema schema, BigDecimal multipleOf, private static ObjectMapper getRightMapper(String data) { ObjectMapper mapper; - if (data.trim().startsWith("{")) { + if (data.trim().startsWith("{")) { mapper = JSON_MAPPER; } else { mapper = YAML_MAPPER; @@ -1725,11 +1755,9 @@ private static ObjectMapper getRightMapper(String data) { * Parse and return a JsonNode representation of the input OAS document. * * @param location the URL of the OAS document. - * @param auths the list of authorization values to access the remote URL. - * - * @throws java.lang.Exception if an error occurs while retrieving the OpenAPI document. - * + * @param auths the list of authorization values to access the remote URL. * @return A JsonNode representation of the input OAS document. + * @throws java.lang.Exception if an error occurs while retrieving the OpenAPI document. */ public static JsonNode readWithInfo(String location, List auths) throws Exception { String data; @@ -1756,14 +1784,13 @@ public static JsonNode readWithInfo(String location, List au /** * Parse the OAS document at the specified location, get the swagger or openapi version * as specified in the source document, and return the version. - * + *

* For OAS 2.0 documents, return the value of the 'swagger' attribute. * For OAS 3.x documents, return the value of the 'openapi' attribute. * - * @param openAPI the object that encapsulates the OAS document. + * @param openAPI the object that encapsulates the OAS document. * @param location the URL of the OAS document. - * @param auths the list of authorization values to access the remote URL. - * + * @param auths the list of authorization values to access the remote URL. * @return the version of the OpenAPI document. */ public static SemVer getOpenApiVersion(OpenAPI openAPI, String location, List auths) { diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java index 05fa241e40e9..95dc96332992 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java @@ -56,7 +56,6 @@ import static org.testng.Assert.*; - public class DefaultCodegenTest { @Test @@ -4300,4 +4299,29 @@ public void testInlineEnumType() { Assert.assertFalse(inlineEnumSchemaProperty.isContainer); Assert.assertFalse(inlineEnumSchemaProperty.isPrimitiveType); } + + @Test + public void testOpenAPINormalizer() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/allOf_extension_parent.yaml"); + + Schema schema = openAPI.getComponents().getSchemas().get("AnotherPerson"); + assertNull(schema.getExtensions()); + + Schema schema2 = openAPI.getComponents().getSchemas().get("Person"); + assertEquals(schema2.getExtensions().get("x-parent"), "abstract"); + + Map options = new HashMap<>(); + options.put("REF_AS_PARENT_IN_ALLOF", "true"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); + openAPINormalizer.normalize(); + + Schema schema3 = openAPI.getComponents().getSchemas().get("AnotherPerson"); + assertEquals(schema3.getExtensions().get("x-parent"), true); + + Schema schema4 = openAPI.getComponents().getSchemas().get("AnotherParent"); + assertEquals(schema4.getExtensions().get("x-parent"), true); + + Schema schema5 = openAPI.getComponents().getSchemas().get("Person"); + assertEquals(schema5.getExtensions().get("x-parent"), "abstract"); + } } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java index 9efb39220365..b046fd2a344f 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java @@ -1717,4 +1717,39 @@ public void testNativeClientExplodedQueryParamWithArrayProperty() throws IOExcep "localVarQueryParams.addAll(ApiClient.parameterToPairs(\"multi\", \"values\", queryObject.getValues()));" ); } + + @Test + public void testJdkHttpClientWithAndWithoutParentExtension() throws Exception { + Map properties = new HashMap<>(); + properties.put(CodegenConstants.API_PACKAGE, "xyz.abcdef.api"); + properties.put(CodegenConstants.MODEL_PACKAGE, "xyz.abcdef.model"); + properties.put(CodegenConstants.INVOKER_PACKAGE, "xyz.abcdef.invoker"); + + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("java") + // use default `okhttp-gson` + //.setLibrary(JavaClientCodegen.NATIVE) + .setAdditionalProperties(properties) + .setInputSpec("src/test/resources/3_0/allOf_extension_parent.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true"); + generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true"); + List files = generator.opts(clientOptInput).generate(); + + Assert.assertEquals(files.size(), 30); + validateJavaSourceFiles(files); + + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/model/Child.java"), + "public class Child extends Person {"); + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/model/Adult.java"), + "public class Adult extends Person {"); + TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/model/AnotherChild.java"), + "public class AnotherChild {"); + } } diff --git a/modules/openapi-generator/src/test/resources/3_0/allOf_extension_parent.yaml b/modules/openapi-generator/src/test/resources/3_0/allOf_extension_parent.yaml new file mode 100644 index 000000000000..8b5e27936044 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/allOf_extension_parent.yaml @@ -0,0 +1,87 @@ +openapi: 3.0.1 +info: + version: 1.0.0 + title: Example + license: + name: MIT +servers: + - url: http://api.example.xyz/v1 +paths: + /person/display/{personId}: + get: + parameters: + - name: personId + in: path + required: true + description: The id of the person to retrieve + schema: + type: string + operationId: list + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Person" +components: + schemas: + Person: + description: person using x-parent (abstract) to indicate it's a parent class + type: object + x-parent: "abstract" + properties: + $_type: + type: string + lastName: + type: string + firstName: + type: string + Adult: + description: A representation of an adult + allOf: + - $ref: '#/components/schemas/Person' + - type: object + properties: + children: + type: array + items: + $ref: "#/components/schemas/Child" + Child: + description: A representation of a child + allOf: + - type: object + properties: + age: + type: integer + format: int32 + - $ref: '#/components/schemas/Person' + AnotherChild: + description: another child class that does NOT extend/inherit AnotherPerson + allOf: + - type: object + properties: + age: + type: integer + format: int32 + - $ref: '#/components/schemas/AnotherPerson' + AnotherPerson: + description: person object without x-parent extension + type: object + allOf: + - properties: + $_type: + type: string + lastName: + type: string + firstName: + type: string + - $ref: '#/components/schemas/AnotherParent' + AnotherParent: + description: parent object without x-parent extension + type: object + properties: + isParent: + type: boolean + mum_or_dad: + type: string