From 784d0e05b5aa9011d42b367752eb6ecf53654307 Mon Sep 17 00:00:00 2001 From: William Cheng Date: Sat, 3 Dec 2022 01:04:38 +0800 Subject: [PATCH 01/11] add x-parent support --- .../codegen/utils/ModelUtils.java | 109 ++++++++++-------- .../codegen/java/JavaClientCodegenTest.java | 35 ++++++ .../resources/3_0/allOf_extension_parent.yaml | 80 +++++++++++++ 3 files changed, 179 insertions(+), 45 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/3_0/allOf_extension_parent.yaml 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..362733c711c6 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,35 @@ 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 +1585,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 +1612,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 +1638,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 +1735,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 +1747,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 +1776,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/java/JavaClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java index 81bdccd09a61..67d1ebdc1695 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 @@ -1688,4 +1688,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(), 24); + 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..12ba23003975 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/allOf_extension_parent.yaml @@ -0,0 +1,80 @@ +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 (abstrct) 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 + properties: + boosterSeat: + type: boolean + allOf: + - type: object + properties: + age: + type: integer + format: int32 + - $ref: '#/components/schemas/AnotherPerson' + AnotherPerson: + description: person object without x-parent extension + type: object + properties: + $_type: + type: string + lastName: + type: string + firstName: + type: string \ No newline at end of file From a886d89bb3dfb42f286506462cfdb767f6d8a3c0 Mon Sep 17 00:00:00 2001 From: William Cheng Date: Sat, 3 Dec 2022 10:36:04 +0800 Subject: [PATCH 02/11] add docstring --- .../org/openapitools/codegen/utils/ModelUtils.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) 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 362733c711c6..f94a03872614 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 @@ -1527,11 +1527,18 @@ private static boolean hasOrInheritsDiscriminator(Schema schema, Map Date: Sun, 4 Dec 2022 17:44:15 +0800 Subject: [PATCH 03/11] add openapi normalizer rule to use ref as parent in allof --- .../openapitools/codegen/CodegenConfig.java | 3 + .../openapitools/codegen/DefaultCodegen.java | 3 + .../codegen/DefaultGenerator.java | 8 + .../codegen/OpenAPINormalizer.java | 441 ++++++++++++++++++ .../codegen/utils/ModelUtils.java | 1 + 5 files changed, 456 insertions(+) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java 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..559b33a2a968 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 @@ -330,4 +330,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 0e580ae31d48..560c232e64dd 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 @@ -7918,6 +7918,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..4fe40f97143a 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,14 @@ void configureGeneratorProperties() { config.processOpts(); + // normalize the spec + if (config.getUseOpenAPINormalizer()) { + // TODO fix rules + OpenAPINormalizer openapiNormalizer = new OpenAPINormalizer(openAPI, new HashMap<>()); + //openapiNormalizer.setRules(); + //openapiNormalizer.normalize(openAPI); + } + // 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..51f90f4e1235 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java @@ -0,0 +1,441 @@ +/* + * 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 com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.swagger.v3.core.util.Json; +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.lang.reflect.Array; +import java.util.*; +import java.util.stream.Collectors; + +public class OpenAPINormalizer { + private OpenAPI openAPI; + private Map addedModels = new HashMap<>(); + private Map rules = new HashMap<>(); + + final Logger LOGGER = LoggerFactory.getLogger(OpenAPINormalizer.class); + + // ============= a list of rules ============= + // 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 USE_ALLOF_REF_AS_PARENT = "USE_ALLOF_REF_AS_PARENT"; + // ============= end of rules ============= + + /** + * Initializes OpenAPI Normalizer with a set of rules + */ + public OpenAPINormalizer(OpenAPI openAPI, Map rules) { + this.openAPI = openAPI; + // TODO remove hardcoded rule + this.rules.put(USE_ALLOF_REF_AS_PARENT, "true"); + } + + /** + * Gets the rule. + * + * @return a map of rules + */ + public Map getRules() { + return this.rules; + } + + /** + * Sets the rule. + * + * @param rules a map of rules + */ + public void setRules(Map rules) { + // + } + + /** + * Returns true if the rule is enabled. + * + * @param rule name of the rule + * @return boolean + */ + public boolean isRuleEnabled(String rule) { + if ("true".equalsIgnoreCase(this.rules.get(rule))) { + // true or false + return true; + } else if (StringUtils.isNotEmpty(this.rules.get(rule))) { + // string value, e.g. pascal, snake, original + return true; + } else if (StringUtils.isEmpty(this.rules.get(rule))) { + return false; + } else { + return false; + } + } + + /** + * Sets the rule. + * + * @param key rule name + * @param value rule value + */ + public void setRule(String key, String value) { + this.rules.put(key, value); + } + + /** + * Normalizes the OpenAPI input, which may not perfectly conform to + * the specification. + * + */ + void normalize() { + 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 visitedSchemas a set of visited schemas + * @apram schema Schema + */ + 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) { + if (schema.getItems() != null) { + normalizeSchema(schema.getItems(), visitedSchemas); + } else { + LOGGER.warn("Array item cannot be null: {}", schema); + } + + } else if (schema.getAdditionalProperties() instanceof Schema) { // map + if (schema.getAdditionalProperties() != null) { + normalizeSchema((Schema) schema.getAdditionalProperties(), visitedSchemas); + } else { + LOGGER.warn("Map item cannot be null: {}", schema); + } + } 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) { + + } + + private void normalizeProperties(Map properties, Set visitedSchemas) { + if (properties == null) { + return; + } + for (Map.Entry propertiesEntry : properties.entrySet()) { + Schema property = propertiesEntry.getValue(); + normalizeSchema(property, visitedSchemas); + + /* + if (property instanceof ObjectSchema && ((ObjectSchema) property).getProperties() != null + && ((ObjectSchema) property).getProperties().size() > 0) { + ObjectSchema op = (ObjectSchema) property; + } else if (property instanceof ArraySchema) { + ArraySchema ap = (ArraySchema) property; + Schema inner = ap.getItems(); + if (inner instanceof ObjectSchema) { + ObjectSchema op = (ObjectSchema) inner; + if (op.getProperties() != null && op.getProperties().size() > 0) { + normalizeProperties(op.getProperties()); + } + } + } + if (ModelUtils.isMapSchema(property)) { + Schema inner = ModelUtils.getAdditionalProperties(openAPI, property); + if (inner instanceof ObjectSchema) { + ObjectSchema op = (ObjectSchema) inner; + if (op.getProperties() != null && op.getProperties().size() > 0) { + normalizeProperties(op.getProperties()); + } + } + } + */ + } + } + + 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 ===================== + private void processUseAllOfRefAsParent(Schema schema) { + if (!isRuleEnabled(USE_ALLOF_REF_AS_PARENT)) { + 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<>()); + refSchema.getExtensions().put("x-parent", true); + } else { + 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/utils/ModelUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java index f94a03872614..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 @@ -1530,6 +1530,7 @@ private static boolean hasOrInheritsDiscriminator(Schema schema, Map Date: Mon, 5 Dec 2022 11:05:59 +0800 Subject: [PATCH 04/11] add openapi normalizer with 1 rule --- docs/customization.md | 13 ++++++ .../openapitools/codegen/cmd/ConfigHelp.java | 15 +++++++ .../openapitools/codegen/cmd/Generate.java | 8 ++++ .../codegen/config/GeneratorSettings.java | 44 ++++++++++++++++++ .../gradle/plugin/OpenApiGeneratorPlugin.kt | 1 + .../OpenApiGeneratorGenerateExtension.kt | 5 +++ .../gradle/plugin/tasks/GenerateTask.kt | 13 ++++++ .../codegen/plugin/CodeGenMojo.java | 17 +++++++ .../openapitools/codegen/CodegenConfig.java | 2 + .../openapitools/codegen/DefaultCodegen.java | 7 +++ .../codegen/DefaultGenerator.java | 6 +-- .../codegen/OpenAPINormalizer.java | 45 ++++++------------- .../codegen/config/CodegenConfigurator.java | 17 +++++++ .../config/CodegenConfiguratorUtils.java | 13 ++++++ .../codegen/DefaultCodegenTest.java | 16 ++++++- 15 files changed, 186 insertions(+), 36 deletions(-) diff --git a/docs/customization.md b/docs/customization.md index b180d783c2bd..2192dbfdba7e 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, which is a $ref, is considered a parent + + +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..166456e7f92b 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("OPENAIP NORMALIZER RULEA").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..ed2aedabb8b7 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 inlineSchemaNameMappings} and returns a reference to this Builder so that the methods can be chained together. + * + * @param key A key for the inline schema mapping + * @param value The value of inline schema mapping + * @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()) && 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 559b33a2a968..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(); 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 560c232e64dd..de2704129ac7 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; 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 4fe40f97143a..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 @@ -257,10 +257,8 @@ void configureGeneratorProperties() { // normalize the spec if (config.getUseOpenAPINormalizer()) { - // TODO fix rules - OpenAPINormalizer openapiNormalizer = new OpenAPINormalizer(openAPI, new HashMap<>()); - //openapiNormalizer.setRules(); - //openapiNormalizer.normalize(openAPI); + OpenAPINormalizer openapiNormalizer = new OpenAPINormalizer(openAPI, config.openapiNormalizer()); + openapiNormalizer.normalize(); } // resolve inline models 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 index 51f90f4e1235..42a1fcdf2d20 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java @@ -46,9 +46,10 @@ public class OpenAPINormalizer { final Logger LOGGER = LoggerFactory.getLogger(OpenAPINormalizer.class); // ============= a list of rules ============= + //final String ALL = "ALL"; // 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 USE_ALLOF_REF_AS_PARENT = "USE_ALLOF_REF_AS_PARENT"; + final String REF_AS_PARENT_IN_ALLOF = "REF_AS_PARENT_IN_ALLOF"; // ============= end of rules ============= /** @@ -56,8 +57,12 @@ public class OpenAPINormalizer { */ public OpenAPINormalizer(OpenAPI openAPI, Map rules) { this.openAPI = openAPI; - // TODO remove hardcoded rule - this.rules.put(USE_ALLOF_REF_AS_PARENT, "true"); + + if (rules == null) { + return; + } else { + this.rules = rules; + } } /** @@ -75,7 +80,7 @@ public Map getRules() { * @param rules a map of rules */ public void setRules(Map rules) { - // + this.rules = rules; } /** @@ -111,9 +116,12 @@ public void setRule(String key, String value) { /** * 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()); } @@ -342,31 +350,6 @@ private void normalizeProperties(Map properties, Set vis for (Map.Entry propertiesEntry : properties.entrySet()) { Schema property = propertiesEntry.getValue(); normalizeSchema(property, visitedSchemas); - - /* - if (property instanceof ObjectSchema && ((ObjectSchema) property).getProperties() != null - && ((ObjectSchema) property).getProperties().size() > 0) { - ObjectSchema op = (ObjectSchema) property; - } else if (property instanceof ArraySchema) { - ArraySchema ap = (ArraySchema) property; - Schema inner = ap.getItems(); - if (inner instanceof ObjectSchema) { - ObjectSchema op = (ObjectSchema) inner; - if (op.getProperties() != null && op.getProperties().size() > 0) { - normalizeProperties(op.getProperties()); - } - } - } - if (ModelUtils.isMapSchema(property)) { - Schema inner = ModelUtils.getAdditionalProperties(openAPI, property); - if (inner instanceof ObjectSchema) { - ObjectSchema op = (ObjectSchema) inner; - if (op.getProperties() != null && op.getProperties().size() > 0) { - normalizeProperties(op.getProperties()); - } - } - } - */ } } @@ -406,7 +389,7 @@ private void normalizeAnyOf(Schema schema, Set visitedSchemas) { // ===================== a list of rules ===================== private void processUseAllOfRefAsParent(Schema schema) { - if (!isRuleEnabled(USE_ALLOF_REF_AS_PARENT)) { + if (!isRuleEnabled(REF_AS_PARENT_IN_ALLOF)) { return; } 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/test/java/org/openapitools/codegen/DefaultCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/DefaultCodegenTest.java index 05fa241e40e9..654a0904df9b 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,19 @@ 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"); + Map options = new HashMap<>(); + options.put("REF_AS_PARENT_IN_ALLOF", "true"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); + openAPINormalizer.normalize(); + + Schema schema = openAPI.getComponents().getSchemas().get("AnotherPerson"); + assertEquals(schema.getExtensions().get("x-parent"), true); + + Schema schema2 = openAPI.getComponents().getSchemas().get("Person"); + assertEquals(schema2.getExtensions().get("x-parent"), "abstract"); + } } From b5d14a18f04df312245421aeab0c1ae75bd71951 Mon Sep 17 00:00:00 2001 From: William Cheng Date: Mon, 5 Dec 2022 11:11:56 +0800 Subject: [PATCH 05/11] revise wordings --- docs/customization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/customization.md b/docs/customization.md index 2192dbfdba7e..22c34373eacb 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -456,7 +456,7 @@ Note: Only arrayItemSuffix, mapItemSuffix are supported at the moment. `SKIP_SCH 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, which is a $ref, is considered a parent +- `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: From e0ec0bbc41cfceafbe7776af6a8e8f35e8cb0983 Mon Sep 17 00:00:00 2001 From: William Cheng Date: Mon, 5 Dec 2022 11:27:46 +0800 Subject: [PATCH 06/11] fix javadoc warnings --- .../java/org/openapitools/codegen/OpenAPINormalizer.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 index 42a1fcdf2d20..8b6ce7eaabff 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java @@ -54,6 +54,9 @@ public class OpenAPINormalizer { /** * 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; @@ -275,8 +278,8 @@ private void normalizeComponents() { /** * Normalizes a schema * + * @param schema Schema * @param visitedSchemas a set of visited schemas - * @apram schema Schema */ public void normalizeSchema(Schema schema, Set visitedSchemas) { if (schema == null) { From c5d02492146aea01e17d309b9a296845dc26d116 Mon Sep 17 00:00:00 2001 From: William Cheng Date: Mon, 5 Dec 2022 21:33:24 +0800 Subject: [PATCH 07/11] better test --- .../codegen/config/GeneratorSettings.java | 1 + .../openapitools/codegen/DefaultCodegenTest.java | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) 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 ed2aedabb8b7..f1f167cc1771 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 @@ -1160,6 +1160,7 @@ public int hashCode() { getSchemaMappings(), getInlineSchemaNameMappings(), getInlineSchemaNameDefaults(), + getOpenAPINormalizer(), getLanguageSpecificPrimitives(), getReservedWordsMappings(), getGitHost(), 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 654a0904df9b..276dc2661322 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 @@ -4303,15 +4303,22 @@ public void testInlineEnumType() { @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 schema = openAPI.getComponents().getSchemas().get("AnotherPerson"); - assertEquals(schema.getExtensions().get("x-parent"), true); + Schema schema3 = openAPI.getComponents().getSchemas().get("AnotherPerson"); + assertEquals(schema3.getExtensions().get("x-parent"), true); - Schema schema2 = openAPI.getComponents().getSchemas().get("Person"); - assertEquals(schema2.getExtensions().get("x-parent"), "abstract"); + Schema schema4 = openAPI.getComponents().getSchemas().get("Person"); + assertEquals(schema4.getExtensions().get("x-parent"), "abstract"); } } From 4bdfb048517e865634bd54297b6428410ef1173c Mon Sep 17 00:00:00 2001 From: William Cheng Date: Mon, 5 Dec 2022 23:15:06 +0800 Subject: [PATCH 08/11] fix docstring --- .../org/openapitools/codegen/config/GeneratorSettings.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 f1f167cc1771..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 @@ -926,10 +926,10 @@ public Builder withOpenAPINormalizer(Map openapiNormalizer) { } /** - * Sets a single {@code inlineSchemaNameMappings} and returns a reference to this Builder so that the methods can be chained together. + * 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 inline schema mapping - * @param value The value of inline schema mapping + * @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) { From e70c01cf59410aa11d6dfd0cebccef922d2b0d29 Mon Sep 17 00:00:00 2001 From: William Cheng Date: Fri, 16 Dec 2022 16:59:18 +0800 Subject: [PATCH 09/11] minor update --- .../openapitools/codegen/cmd/ConfigHelp.java | 2 +- .../codegen/OpenAPINormalizer.java | 48 +++++++++---------- 2 files changed, 24 insertions(+), 26 deletions(-) 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 166456e7f92b..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 @@ -498,7 +498,7 @@ private void generatePlainTextHelp(StringBuilder sb, CodegenConfig config) { } if (Boolean.TRUE.equals(openapiNormalizer)) { - sb.append(newline).append("OPENAIP NORMALIZER RULEA").append(newline).append(newline); + sb.append(newline).append("OPENAPI NORMALIZER RULES").append(newline).append(newline); Map map = config.openapiNormalizer() .entrySet() .stream() 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 index 8b6ce7eaabff..341dff0fbe82 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.sun.java.accessibility.util.AccessibilityListenerList; import io.swagger.v3.core.util.Json; import io.swagger.v3.oas.models.*; import io.swagger.v3.oas.models.callbacks.Callback; @@ -46,7 +47,8 @@ public class OpenAPINormalizer { final Logger LOGGER = LoggerFactory.getLogger(OpenAPINormalizer.class); // ============= a list of rules ============= - //final String ALL = "ALL"; + // when set to true, all rules are enabled + final String ALL = "ALL"; // 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"; @@ -56,7 +58,7 @@ public class OpenAPINormalizer { * Initializes OpenAPI Normalizer with a set of rules * * @param openAPI OpenAPI - * @param rules a map of rules + * @param rules a map of rules */ public OpenAPINormalizer(OpenAPI openAPI, Map rules) { this.openAPI = openAPI; @@ -93,14 +95,18 @@ public void setRules(Map rules) { * @return boolean */ public boolean isRuleEnabled(String rule) { - if ("true".equalsIgnoreCase(this.rules.get(rule))) { - // true or false + if ("true".equalsIgnoreCase(this.rules.get(ALL))) { + // enable all rules + return true; + } else if ("true".equalsIgnoreCase(this.rules.get(rule))) { + // true return true; + } else if ("false".equalsIgnoreCase(this.rules.get(rule))) { + // false + return false; } else if (StringUtils.isNotEmpty(this.rules.get(rule))) { // string value, e.g. pascal, snake, original return true; - } else if (StringUtils.isEmpty(this.rules.get(rule))) { - return false; } else { return false; } @@ -278,7 +284,7 @@ private void normalizeComponents() { /** * Normalizes a schema * - * @param schema Schema + * @param schema Schema * @param visitedSchemas a set of visited schemas */ public void normalizeSchema(Schema schema, Set visitedSchemas) { @@ -298,18 +304,9 @@ public void normalizeSchema(Schema schema, Set visitedSchemas) { } if (schema instanceof ArraySchema) { - if (schema.getItems() != null) { - normalizeSchema(schema.getItems(), visitedSchemas); - } else { - LOGGER.warn("Array item cannot be null: {}", schema); - } - + normalizeSchema(schema.getItems(), visitedSchemas); } else if (schema.getAdditionalProperties() instanceof Schema) { // map - if (schema.getAdditionalProperties() != null) { - normalizeSchema((Schema) schema.getAdditionalProperties(), visitedSchemas); - } else { - LOGGER.warn("Map item cannot be null: {}", schema); - } + normalizeSchema((Schema) schema.getAdditionalProperties(), visitedSchemas); } else if (ModelUtils.isComposedSchema(schema)) { ComposedSchema m = (ComposedSchema) schema; if (m.getAllOf() != null && !m.getAllOf().isEmpty()) { @@ -343,7 +340,7 @@ public void normalizeSchema(Schema schema, Set visitedSchemas) { } private void normalizeNonComposedSchema(Schema schema, Set visitedSchemas) { - + // normalize non-composed schema (e.g. schema with only properties) } private void normalizeProperties(Map properties, Set visitedSchemas) { @@ -391,6 +388,7 @@ private void normalizeAnyOf(Schema schema, Set visitedSchemas) { } // ===================== a list of rules ===================== + // all rules (fuctions) start with the word "process" private void processUseAllOfRefAsParent(Schema schema) { if (!isRuleEnabled(REF_AS_PARENT_IN_ALLOF)) { return; @@ -411,14 +409,14 @@ private void processUseAllOfRefAsParent(Schema schema) { } if (refSchema.getExtensions() == null) { refSchema.setExtensions(new HashMap<>()); - refSchema.getExtensions().put("x-parent", true); + } + + if (refSchema.getExtensions().containsKey("x-parent")) { + // doing nothing as x-parent already exists } else { - if (refSchema.getExtensions().containsKey("x-parent")) { - // doing nothing as x-parent already exists - } else { - refSchema.getExtensions().put("x-parent", true); - } + refSchema.getExtensions().put("x-parent", true); } + LOGGER.debug("processUseAllOfRefAsParent added `x-parent: true` to {}", refSchema); } } From 034a9baf66be385c9d27e36b41665a645f7ede88 Mon Sep 17 00:00:00 2001 From: William Cheng Date: Wed, 21 Dec 2022 00:55:36 +0800 Subject: [PATCH 10/11] minor improvements --- .../codegen/OpenAPINormalizer.java | 75 +++++-------------- .../codegen/DefaultCodegenTest.java | 7 +- .../codegen/java/JavaClientCodegenTest.java | 2 +- .../resources/3_0/allOf_extension_parent.yaml | 23 ++++-- 4 files changed, 38 insertions(+), 69 deletions(-) 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 index 341dff0fbe82..4f1de87ba93a 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java @@ -17,12 +17,6 @@ package org.openapitools.codegen; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.sun.java.accessibility.util.AccessibilityListenerList; -import io.swagger.v3.core.util.Json; import io.swagger.v3.oas.models.*; import io.swagger.v3.oas.models.callbacks.Callback; import io.swagger.v3.oas.models.media.*; @@ -35,13 +29,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.reflect.Array; import java.util.*; import java.util.stream.Collectors; public class OpenAPINormalizer { private OpenAPI openAPI; - private Map addedModels = new HashMap<>(); private Map rules = new HashMap<>(); final Logger LOGGER = LoggerFactory.getLogger(OpenAPINormalizer.class); @@ -49,9 +41,13 @@ public class OpenAPINormalizer { // ============= 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 ============= /** @@ -62,64 +58,27 @@ public class OpenAPINormalizer { */ public OpenAPINormalizer(OpenAPI openAPI, Map rules) { this.openAPI = openAPI; - - if (rules == null) { - return; - } else { - this.rules = rules; - } - } - - /** - * Gets the rule. - * - * @return a map of rules - */ - public Map getRules() { - return this.rules; + this.rules = rules; + parseRules(rules); } /** - * Sets the rule. + * Parses the rules. * * @param rules a map of rules */ - public void setRules(Map rules) { - this.rules = rules; - } + public void parseRules(Map rules) { + if (rules == null) { + return; + } - /** - * Returns true if the rule is enabled. - * - * @param rule name of the rule - * @return boolean - */ - public boolean isRuleEnabled(String rule) { - if ("true".equalsIgnoreCase(this.rules.get(ALL))) { - // enable all rules - return true; - } else if ("true".equalsIgnoreCase(this.rules.get(rule))) { - // true - return true; - } else if ("false".equalsIgnoreCase(this.rules.get(rule))) { - // false - return false; - } else if (StringUtils.isNotEmpty(this.rules.get(rule))) { - // string value, e.g. pascal, snake, original - return true; - } else { - return false; + if ("true".equalsIgnoreCase(rules.get(ALL))) { + enableAll = true; } - } - /** - * Sets the rule. - * - * @param key rule name - * @param value rule value - */ - public void setRule(String key, String value) { - this.rules.put(key, value); + if (enableAll || "true".equalsIgnoreCase(rules.get(REF_AS_PARENT_IN_ALLOF))) { + enableRefAsParentInAllOf = true; + } } /** @@ -390,7 +349,7 @@ private void normalizeAnyOf(Schema schema, Set visitedSchemas) { // ===================== a list of rules ===================== // all rules (fuctions) start with the word "process" private void processUseAllOfRefAsParent(Schema schema) { - if (!isRuleEnabled(REF_AS_PARENT_IN_ALLOF)) { + if (!enableRefAsParentInAllOf) { return; } 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 276dc2661322..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 @@ -4318,7 +4318,10 @@ public void testOpenAPINormalizer() { Schema schema3 = openAPI.getComponents().getSchemas().get("AnotherPerson"); assertEquals(schema3.getExtensions().get("x-parent"), true); - Schema schema4 = openAPI.getComponents().getSchemas().get("Person"); - assertEquals(schema4.getExtensions().get("x-parent"), "abstract"); + 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 f349d0dac5aa..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 @@ -1742,7 +1742,7 @@ public void testJdkHttpClientWithAndWithoutParentExtension() throws Exception { generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true"); List files = generator.opts(clientOptInput).generate(); - Assert.assertEquals(files.size(), 24); + Assert.assertEquals(files.size(), 30); validateJavaSourceFiles(files); TestUtils.assertFileContains(Paths.get(output + "/src/main/java/xyz/abcdef/model/Child.java"), 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 index 12ba23003975..a0e9ab47898f 100644 --- 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 @@ -58,9 +58,6 @@ components: - $ref: '#/components/schemas/Person' AnotherChild: description: another child class that does NOT extend/inherit AnotherPerson - properties: - boosterSeat: - type: boolean allOf: - type: object properties: @@ -71,10 +68,20 @@ components: 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: - $_type: - type: string - lastName: - type: string - firstName: + isParent: + type: boolean + mum_or_dad: type: string \ No newline at end of file From f58232b7464327144ab3a2dc1bd514590c6af07e Mon Sep 17 00:00:00 2001 From: William Cheng Date: Wed, 21 Dec 2022 21:53:13 +0800 Subject: [PATCH 11/11] fix typo --- .../src/test/resources/3_0/allOf_extension_parent.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index a0e9ab47898f..8b5e27936044 100644 --- 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 @@ -27,7 +27,7 @@ paths: components: schemas: Person: - description: person using x-parent (abstrct) to indicate it's a parent class + description: person using x-parent (abstract) to indicate it's a parent class type: object x-parent: "abstract" properties: @@ -84,4 +84,4 @@ components: isParent: type: boolean mum_or_dad: - type: string \ No newline at end of file + type: string