diff --git a/docs/customization.md b/docs/customization.md index c4c36bf782f..a7be58f96b9 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -601,6 +601,13 @@ Example: java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/enableKeepOnlyFirstTagInOperation_test.yaml -o /tmp/java-okhttp/ --openapi-normalizer REMOVE_X_INTERNAL=true ``` +- `NORMALIZER_CLASS`: Set to full classname of a class extending the default org.openapitools.codegen.OpenAPINormalizer. It allows customization of the default normalizer. + +Example: +``` +java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/required-properties.yaml -o /tmp/java-okhttp/ --openapi-normalizer NORMALIZER_CLASS=org.openapitools.codegen.OpenAPINormalizerTest$RemoveRequiredNormalizer +``` + - `FILTER` The `FILTER` parameter allows selective inclusion of API operations based on specific criteria. It applies the `x-internal: true` property to operations that do **not** match the specified values, preventing them from being generated. 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 1d913e1c737..b2e5424c8da 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 @@ -261,7 +261,7 @@ public class DefaultGenerator implements Generator { if (version.atLeast("3.1.0")) { config.openapiNormalizer().put("NORMALIZE_31SPEC", "true"); } - OpenAPINormalizer openapiNormalizer = new OpenAPINormalizer(openAPI, config.openapiNormalizer()); + OpenAPINormalizer openapiNormalizer = OpenAPINormalizer.createNormalizer(openAPI, config.openapiNormalizer()); openapiNormalizer.normalize(); } } catch (Exception e) { 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 f75cb1fbef3..2b16a88d580 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 @@ -32,6 +32,8 @@ import org.openapitools.codegen.utils.ModelUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -39,13 +41,13 @@ import java.util.stream.Collectors; import static org.openapitools.codegen.utils.StringUtils.getUniqueString; public class OpenAPINormalizer { - private OpenAPI openAPI; + protected OpenAPI openAPI; private Map inputRules = new HashMap<>(); private Map rules = new HashMap<>(); private TreeSet anyTypeTreeSet = new TreeSet<>(); - final Logger LOGGER = LoggerFactory.getLogger(OpenAPINormalizer.class); + protected final Logger LOGGER = LoggerFactory.getLogger(OpenAPINormalizer.class); Set ruleNames = new TreeSet<>(); Set rulesDefaultToTrue = new TreeSet<>(); @@ -59,6 +61,9 @@ public class OpenAPINormalizer { final String DISABLE_ALL = "DISABLE_ALL"; boolean disableAll; + // when defined, use the class name to instantiate the normalizer + static final String NORMALIZER_CLASS = "NORMALIZER_CLASS"; + // 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"; @@ -146,6 +151,25 @@ public class OpenAPINormalizer { // ============= end of rules ============= + /** + * Factory constructor for OpenAPINormalizer. + * + * Default can be overriden by setting the NORMALIZER_CLASS rule + */ + public static OpenAPINormalizer createNormalizer(OpenAPI openAPI, Map inputRules) { + if (inputRules.containsKey(NORMALIZER_CLASS)) { + try { + Class clazz = Class.forName(inputRules.get(NORMALIZER_CLASS)); + Constructor constructor = clazz.getConstructor(OpenAPI.class, Map.class); + return (OpenAPINormalizer) constructor.newInstance(openAPI, inputRules); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } else { + return new OpenAPINormalizer(openAPI, inputRules); + } + } + /** * Initializes OpenAPI Normalizer with a set of rules * @@ -162,6 +186,7 @@ public class OpenAPINormalizer { } // a set of ruleNames + ruleNames.add(NORMALIZER_CLASS); ruleNames.add(REF_AS_PARENT_IN_ALLOF); ruleNames.add(REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY); ruleNames.add(SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING); @@ -318,7 +343,7 @@ public class OpenAPINormalizer { * Normalizes the OpenAPI input, which may not perfectly conform to * the specification. */ - void normalize() { + protected void normalize() { if (rules == null || rules.isEmpty() || disableAll) { return; } @@ -341,7 +366,7 @@ public class OpenAPINormalizer { /** * Pre-populate info if it's not defined. */ - private void normalizeInfo() { + protected void normalizeInfo() { if (this.openAPI.getInfo() == null) { Info info = new Info(); info.setTitle("OpenAPI"); @@ -354,7 +379,7 @@ public class OpenAPINormalizer { /** * Normalizes inline models in Paths */ - private void normalizePaths() { + protected void normalizePaths() { Paths paths = openAPI.getPaths(); if (paths == null) { return; @@ -429,7 +454,7 @@ public class OpenAPINormalizer { * * @param operation Operation */ - private void normalizeOperation(Operation operation) { + protected void normalizeOperation(Operation operation) { processRemoveXInternalFromOperation(operation); processKeepOnlyFirstTagInOperation(operation); @@ -448,7 +473,7 @@ public class OpenAPINormalizer { * * @param content target content */ - private void normalizeContent(Content content) { + protected void normalizeContent(Content content) { if (content == null || content.isEmpty()) { return; } @@ -472,7 +497,7 @@ public class OpenAPINormalizer { * * @param operation target operation */ - private void normalizeRequestBody(Operation operation) { + protected void normalizeRequestBody(Operation operation) { RequestBody requestBody = operation.getRequestBody(); if (requestBody == null) { return; @@ -496,7 +521,7 @@ public class OpenAPINormalizer { * * @param parameters List parameters */ - private void normalizeParameters(List parameters) { + protected void normalizeParameters(List parameters) { if (parameters == null) { return; } @@ -519,7 +544,7 @@ public class OpenAPINormalizer { * * @param operation target operation */ - private void normalizeResponses(Operation operation) { + protected void normalizeResponses(Operation operation) { ApiResponses responses = operation.getResponses(); if (responses == null) { return; @@ -535,7 +560,7 @@ public class OpenAPINormalizer { * * @param apiResponse API response */ - private void normalizeResponse(ApiResponse apiResponse) { + protected void normalizeResponse(ApiResponse apiResponse) { if (apiResponse != null) { normalizeContent(ModelUtils.getReferencedApiResponse(openAPI, apiResponse).getContent()); normalizeHeaders(ModelUtils.getReferencedApiResponse(openAPI, apiResponse).getHeaders()); @@ -547,7 +572,7 @@ public class OpenAPINormalizer { * * @param headers a map of headers */ - private void normalizeHeaders(Map headers) { + protected void normalizeHeaders(Map headers) { if (headers == null || headers.isEmpty()) { return; } @@ -562,7 +587,7 @@ public class OpenAPINormalizer { /** * Normalizes securitySchemes in components */ - private void normalizeComponentsSecuritySchemes() { + protected void normalizeComponentsSecuritySchemes() { if (StringUtils.isEmpty(bearerAuthSecuritySchemeName)) { return; } @@ -592,7 +617,7 @@ public class OpenAPINormalizer { /** * Normalizes schemas in components */ - private void normalizeComponentsSchemas() { + protected void normalizeComponentsSchemas() { Map schemas = openAPI.getComponents().getSchemas(); if (schemas == null) { return; @@ -623,7 +648,7 @@ public class OpenAPINormalizer { /** * Normalizes schemas in component's responses. */ - private void normalizeComponentsResponses() { + protected void normalizeComponentsResponses() { Map apiResponses = openAPI.getComponents().getResponses(); if (apiResponses == null) { return; @@ -675,7 +700,7 @@ public class OpenAPINormalizer { } - private boolean isSelfReference(String name, Schema subSchema) { + protected boolean isSelfReference(String name, Schema subSchema) { if (subSchema != null && name.equals(ModelUtils.getSimpleRef(subSchema.get$ref()))) { return true; } else { @@ -691,20 +716,10 @@ public class OpenAPINormalizer { * @return Schema */ public Schema normalizeSchema(Schema schema, Set visitedSchemas) { - if (schema == null) { + if (skipNormalization(schema, visitedSchemas)) { return schema; } - - if (StringUtils.isNotEmpty(schema.get$ref())) { - // no need to process $ref - return schema; - } - - if (visitedSchemas.contains(schema)) { - return schema; // skip due to circular reference - } else { - visitedSchemas.add(schema); - } + markSchemaAsVisited(schema, visitedSchemas); if (ModelUtils.isArraySchema(schema)) { // array Schema result = normalizeArraySchema(schema); @@ -758,35 +773,73 @@ public class OpenAPINormalizer { } else { throw new RuntimeException("Unknown schema type found in normalizer: " + schema); } - return schema; } - private Schema normalizeArraySchema(Schema schema) { + + /** + * Check if normalization is needed. + * + * No normalization needed if the schema is null or is a $ref or already processed. + * + * @param schema Schema + * @param visitedSchemas a set of visited schemas + * @return false if normalization is needed + */ + protected boolean skipNormalization(Schema schema, Set visitedSchemas) { + if (schema == null) { + return true; + } + + if (StringUtils.isNotEmpty(schema.get$ref())) { + // no need to process $ref + return true; + } + + if (visitedSchemas.contains(schema)) { + return true; // skip due to circular reference + } else { + return false; + } + } + + /** + * Add the schema to the collection of visited schemas. + * + * @param schema schema to add + * @param visitedSchemas current visited schemas + */ + protected void markSchemaAsVisited(Schema schema, Set visitedSchemas) { + if (schema != null) { + visitedSchemas.add(schema); + } + } + + protected Schema normalizeArraySchema(Schema schema) { Schema result = processNormalize31Spec(schema, new HashSet<>()); return processSetArraytoNullable(result); } - private Schema normalizeMapSchema(Schema schema) { + protected Schema normalizeMapSchema(Schema schema) { return processSetMapToNullable(schema); } - private Schema normalizeSimpleSchema(Schema schema, Set visitedSchemas) { + protected Schema normalizeSimpleSchema(Schema schema, Set visitedSchemas) { Schema result = processNormalize31Spec(schema, visitedSchemas); return processSetPrimitiveTypesToNullable(result); } - private void normalizeBooleanSchema(Schema schema, Set visitedSchemas) { + protected void normalizeBooleanSchema(Schema schema, Set visitedSchemas) { processSimplifyBooleanEnum(schema); processSetPrimitiveTypesToNullable(schema); } - private void normalizeIntegerSchema(Schema schema, Set visitedSchemas) { + protected void normalizeIntegerSchema(Schema schema, Set visitedSchemas) { processAddUnsignedToIntegerWithInvalidMaxValue(schema); processSetPrimitiveTypesToNullable(schema); } - private void normalizeProperties(Map properties, Set visitedSchemas) { + protected void normalizeProperties(Map properties, Set visitedSchemas) { if (properties == null) { return; } @@ -802,7 +855,7 @@ public class OpenAPINormalizer { * * @param schema Schema */ - private void removeUnsupportedSchemasFromAllOf(Schema schema) { + protected void removeUnsupportedSchemasFromAllOf(Schema schema) { if (schema.getAllOf() == null) { return; } @@ -825,7 +878,7 @@ public class OpenAPINormalizer { } } - private Schema normalizeAllOf(Schema schema, Set visitedSchemas) { + protected Schema normalizeAllOf(Schema schema, Set visitedSchemas) { removeUnsupportedSchemasFromAllOf(schema); if (schema.getAllOf() == null) { @@ -846,7 +899,7 @@ public class OpenAPINormalizer { return schema; } - private Schema normalizeAllOfWithProperties(Schema schema, Set visitedSchemas) { + protected Schema normalizeAllOfWithProperties(Schema schema, Set visitedSchemas) { removeUnsupportedSchemasFromAllOf(schema); if (schema.getAllOf() == null) { @@ -867,7 +920,7 @@ public class OpenAPINormalizer { return schema; } - private Schema normalizeOneOf(Schema schema, Set visitedSchemas) { + protected Schema normalizeOneOf(Schema schema, Set visitedSchemas) { // simplify first as the schema may no longer be a oneOf after processing the rule below schema = processSimplifyOneOf(schema); @@ -895,7 +948,7 @@ public class OpenAPINormalizer { return schema; } - private Schema normalizeAnyOf(Schema schema, Set visitedSchemas) { + protected Schema normalizeAnyOf(Schema schema, Set visitedSchemas) { for (int i = 0; i < schema.getAnyOf().size(); i++) { // normalize anyOf sub schemas one by one Object item = schema.getAnyOf().get(i); @@ -919,7 +972,7 @@ public class OpenAPINormalizer { return normalizeSchema(processSimplifyAnyOfStringAndEnumString(schema), visitedSchemas); } - private Schema normalizeComplexComposedSchema(Schema schema, Set visitedSchemas) { + protected Schema normalizeComplexComposedSchema(Schema schema, Set visitedSchemas) { // loop through properties, if any if (schema.getProperties() != null && !schema.getProperties().isEmpty()) { normalizeProperties(schema.getProperties(), visitedSchemas); @@ -938,7 +991,7 @@ public class OpenAPINormalizer { * * @param schema Schema */ - private void processUseAllOfRefAsParent(Schema schema) { + protected void processUseAllOfRefAsParent(Schema schema) { if (!getRule(REF_AS_PARENT_IN_ALLOF)) { return; } @@ -984,7 +1037,7 @@ public class OpenAPINormalizer { * * @param operation Operation */ - private void processRemoveXInternalFromOperation(Operation operation) { + protected void processRemoveXInternalFromOperation(Operation operation) { if (!getRule(REMOVE_X_INTERNAL)) { return; } @@ -1004,7 +1057,7 @@ public class OpenAPINormalizer { * * @param operation Operation */ - private void processKeepOnlyFirstTagInOperation(Operation operation) { + protected void processKeepOnlyFirstTagInOperation(Operation operation) { if (!getRule(KEEP_ONLY_FIRST_TAG_IN_OPERATION)) { return; } @@ -1022,7 +1075,7 @@ public class OpenAPINormalizer { * * @param operation Operation */ - private void processSetTagsForAllOperations(Operation operation) { + protected void processSetTagsForAllOperations(Operation operation) { if (StringUtils.isEmpty(setTagsForAllOperations)) { return; } @@ -1036,7 +1089,7 @@ public class OpenAPINormalizer { * * @param operation Operation */ - private void processSetTagsToOperationId(Operation operation) { + protected void processSetTagsToOperationId(Operation operation) { if (!getRule(SET_TAGS_TO_OPERATIONID)) { return; } @@ -1054,7 +1107,7 @@ public class OpenAPINormalizer { * * @param operation Operation */ - private void processSetTagsToVendorExtension(Operation operation) { + protected void processSetTagsToVendorExtension(Operation operation) { if (StringUtils.isEmpty(setTagsToVendorExtension)) { return; } @@ -1077,7 +1130,7 @@ public class OpenAPINormalizer { } } - private void processFixDuplicatedOperationId(Operation operation) { + protected void processFixDuplicatedOperationId(Operation operation) { if (!getRule(FIX_DUPLICATED_OPERATIONID)) { return; } @@ -1101,7 +1154,7 @@ public class OpenAPINormalizer { * * @param schema Schema */ - private void processRemoveAnyOfOneOfAndKeepPropertiesOnly(Schema schema) { + protected void processRemoveAnyOfOneOfAndKeepPropertiesOnly(Schema schema) { if (!getRule(REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY)) { return; } @@ -1123,7 +1176,7 @@ public class OpenAPINormalizer { * @param schema Schema * @return Schema */ - private Schema processSimplifyAnyOfStringAndEnumString(Schema schema) { + protected Schema processSimplifyAnyOfStringAndEnumString(Schema schema) { if (!getRule(SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING)) { return schema; } @@ -1179,7 +1232,7 @@ public class OpenAPINormalizer { * @param schema Schema * @return Schema */ - private Schema processSimplifyOneOf(Schema schema) { + protected Schema processSimplifyOneOf(Schema schema) { if (!getRule(SIMPLIFY_ONEOF_ANYOF)) { return schema; } @@ -1240,7 +1293,7 @@ public class OpenAPINormalizer { * @param schema Schema * @return Schema */ - private Schema processSetArraytoNullable(Schema schema) { + protected Schema processSetArraytoNullable(Schema schema) { if (!getRule(SET_CONTAINER_TO_NULLABLE)) { return schema; } @@ -1264,7 +1317,7 @@ public class OpenAPINormalizer { * @param schema Schema * @return Schema */ - private Schema processSetPrimitiveTypesToNullable(Schema schema) { + protected Schema processSetPrimitiveTypesToNullable(Schema schema) { if (!getRule(SET_PRIMITIVE_TYPES_TO_NULLABLE)) { return schema; } @@ -1282,7 +1335,7 @@ public class OpenAPINormalizer { return schema; } - private Schema setNullable(Schema schema) { + protected Schema setNullable(Schema schema) { if (schema.getNullable() != null || (schema.getExtensions() != null && schema.getExtensions().containsKey("x-nullable"))) { // already set, don't overwrite return schema; @@ -1297,7 +1350,7 @@ public class OpenAPINormalizer { * @param schema Schema * @return Schema */ - private Schema processSetMapToNullable(Schema schema) { + protected Schema processSetMapToNullable(Schema schema) { if (!getRule(SET_CONTAINER_TO_NULLABLE)) { return schema; } @@ -1316,7 +1369,7 @@ public class OpenAPINormalizer { * @param schema Schema * @return Schema */ - private Schema processSimplifyAnyOf(Schema schema) { + protected Schema processSimplifyAnyOf(Schema schema) { if (!getRule(SIMPLIFY_ONEOF_ANYOF)) { return schema; } @@ -1373,7 +1426,7 @@ public class OpenAPINormalizer { * @param schema Schema * @return Schema */ - private void processSimplifyBooleanEnum(Schema schema) { + protected void processSimplifyBooleanEnum(Schema schema) { if (!getRule(SIMPLIFY_BOOLEAN_ENUM)) { return; } @@ -1393,7 +1446,7 @@ public class OpenAPINormalizer { * @param schema Schema * @return Schema */ - private void processAddUnsignedToIntegerWithInvalidMaxValue(Schema schema) { + protected void processAddUnsignedToIntegerWithInvalidMaxValue(Schema schema) { if (!getRule(ADD_UNSIGNED_TO_INTEGER_WITH_INVALID_MAX_VALUE)) { return; } @@ -1420,7 +1473,7 @@ public class OpenAPINormalizer { * @param schema Schema * @return Schema */ - private Schema processRefactorAllOfWithPropertiesOnly(Schema schema) { + protected Schema processRefactorAllOfWithPropertiesOnly(Schema schema) { if (!getRule(REFACTOR_ALLOF_WITH_PROPERTIES_ONLY)) { return schema; } @@ -1460,7 +1513,7 @@ public class OpenAPINormalizer { * @param visitedSchemas a set of visited schemas * @return Schema */ - private Schema processNormalize31Spec(Schema schema, Set visitedSchemas) { + protected Schema processNormalize31Spec(Schema schema, Set visitedSchemas) { if (!getRule(NORMALIZE_31SPEC)) { return schema; } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java index 85104bc2ea0..3c533fbf731 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java @@ -24,10 +24,7 @@ import io.swagger.v3.oas.models.security.SecurityScheme; import org.openapitools.codegen.utils.ModelUtils; import org.testng.annotations.Test; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import static org.testng.Assert.*; @@ -965,4 +962,32 @@ public class OpenAPINormalizerTest { assertEquals(scheme.getName(), null); assertEquals(scheme.getIn(), null); } + + @Test + public void testNormalizerClass() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/required-properties.yaml"); + Map inputRules = Map.of( + "NORMALIZER_CLASS", RemoveRequiredNormalizer.class.getName() + ); + OpenAPINormalizer openAPINormalizer = OpenAPINormalizer.createNormalizer(openAPI, inputRules); + openAPINormalizer.normalize(); + Schema requiredProperties = openAPI.getComponents().getSchemas().get("RequiredProperties"); + assertEquals(requiredProperties.getRequired(), null); + } + + public static class RemoveRequiredNormalizer extends OpenAPINormalizer { + + public RemoveRequiredNormalizer(OpenAPI openAPI, Map inputRules) { + super(openAPI, inputRules); + } + + @Override + public Schema normalizeSchema(Schema schema, Set visitedSchemas) { + if (skipNormalization(schema, visitedSchemas)) { + return schema; + } + schema.setRequired(null); + return super.normalizeSchema(schema, visitedSchemas); + } + } }