From 413523c99da82c4371dc5dea3962a50e5a037dfd Mon Sep 17 00:00:00 2001 From: altro3 Date: Sat, 17 Aug 2024 18:27:37 +0700 Subject: [PATCH] Added schema duplication resolution mode Fixed #1698 --- ...openapi-custom-schema-for-class.properties | 10 +- ...ustom-schema-for-iterable-class.properties | 2 +- openapi/openapi-schema-decorators.properties | 4 +- .../openapi/visitor/ConfigUtils.java | 38 ++-- .../openapi/visitor/ContextProperty.java | 2 +- .../visitor/OpenApiConfigProperty.java | 163 ++++++++++-------- .../visitor/OpenApiExtraSchemaVisitor.java | 4 +- .../visitor/SchemaDefinitionUtils.java | 91 ++++++---- .../visitor/OpenApiBasicSchemaSpec.groovy | 105 ++++++++++- ...penApiControllerRequiresVisitorSpec.groovy | 52 ++---- .../OpenApiControllerVisitorSpec.groovy | 13 +- .../visitor/OpenApiDecoratorSpec.groovy | 3 +- .../openapi/visitor/OpenApiEnumSpec.groovy | 10 +- .../OpenApiExtraSchemaVisitorSpec.groovy | 4 +- .../OpenApiFileUploadBodyParameterSpec.groovy | 5 +- .../visitor/OpenApiIncludeVisitorSpec.groovy | 4 +- .../OpenApiInheritedPojoControllerSpec.groovy | 15 +- .../visitor/OpenApiNullableTypesSpec.groovy | 5 - .../OpenApiOperationHeadersSpec.groovy | 2 +- .../visitor/OpenApiParameterSchemaSpec.groovy | 5 +- .../visitor/OpenApiSchemaFieldSpec.groovy | 6 +- .../resources/application-schemadecorator.yml | 7 +- .../resources/application-schemaforclass.yml | 5 +- .../guide/configuration/availableOptions.adoc | 38 ++-- 24 files changed, 363 insertions(+), 230 deletions(-) diff --git a/openapi/openapi-custom-schema-for-class.properties b/openapi/openapi-custom-schema-for-class.properties index ee153f0503..b26df74d85 100644 --- a/openapi/openapi-custom-schema-for-class.properties +++ b/openapi/openapi-custom-schema-for-class.properties @@ -1,5 +1,5 @@ -micronaut.openapi.schema.io.micronaut.openapi.ObjectId=java.lang.String -micronaut.openapi.schema.io.micronaut.openapi.JAXBElement=io.micronaut.openapi.MyJaxbElement -micronaut.openapi.schema.io.micronaut.openapi.JAXBElement=io.micronaut.openapi.MyJaxbElement2 -micronaut.openapi.schema.io.micronaut.openapi.JAXBElement=io.micronaut.openapi.MyJaxbElement3 -micronaut.openapi.schema.io.micronaut.openapi.JAXBElement=io.micronaut.openapi.MyJaxbElement4 +micronaut.openapi.schema.mapping.io.micronaut.openapi.ObjectId=java.lang.String +micronaut.openapi.schema.mapping.io.micronaut.openapi.JAXBElement=io.micronaut.openapi.MyJaxbElement +micronaut.openapi.schema.mapping.io.micronaut.openapi.JAXBElement=io.micronaut.openapi.MyJaxbElement2 +micronaut.openapi.schema.mapping.io.micronaut.openapi.JAXBElement=io.micronaut.openapi.MyJaxbElement3 +micronaut.openapi.schema.mapping.io.micronaut.openapi.JAXBElement=io.micronaut.openapi.MyJaxbElement4 diff --git a/openapi/openapi-custom-schema-for-iterable-class.properties b/openapi/openapi-custom-schema-for-iterable-class.properties index 4c32be49fe..5f2dc7db0a 100644 --- a/openapi/openapi-custom-schema-for-iterable-class.properties +++ b/openapi/openapi-custom-schema-for-iterable-class.properties @@ -1 +1 @@ -micronaut.openapi.schema.io.micronaut.data.model.Page=io.micronaut.openapi.SwaggerPage +micronaut.openapi.schema.mapping.io.micronaut.data.model.Page=io.micronaut.openapi.SwaggerPage diff --git a/openapi/openapi-schema-decorators.properties b/openapi/openapi-schema-decorators.properties index 6a35946777..74753201db 100644 --- a/openapi/openapi-schema-decorators.properties +++ b/openapi/openapi-schema-decorators.properties @@ -1,2 +1,2 @@ -micronaut.openapi.schema-postfix.io.micronaut.openapi.api.v1_0_0=1_0_0 -micronaut.openapi.schema-postfix.io.micronaut.openapi.api.v2_0_1=2_0_1 +micronaut.openapi.schema.mapping-postfix.io.micronaut.openapi.api.v1_0_0=1_0_0 +micronaut.openapi.schema.mapping-postfix.io.micronaut.openapi.api.v2_0_1=2_0_1 diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/ConfigUtils.java b/openapi/src/main/java/io/micronaut/openapi/visitor/ConfigUtils.java index c4a7ba38c8..f724a4a40e 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/ConfigUtils.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/ConfigUtils.java @@ -90,11 +90,12 @@ import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_ENABLED; import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_ENVIRONMENTS; import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_EXPAND_PREFIX; -import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_EXTRA_SCHEMA_ENABLED; import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_GROUPS; import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_JSON_VIEW_DEFAULT_INCLUSION; import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_PROJECT_DIR; -import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_SCHEMA; +import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_SCHEMA_DUPLICATE_RESOLUTION; +import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_SCHEMA_EXTRA_ENABLED; +import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_SCHEMA_MAPPING; import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_SCHEMA_NAME_SEPARATOR_EMPTY; import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_SCHEMA_NAME_SEPARATOR_GENERIC; import static io.micronaut.openapi.visitor.OpenApiConfigProperty.MICRONAUT_OPENAPI_SCHEMA_NAME_SEPARATOR_INNER_CLASS; @@ -195,7 +196,7 @@ public static ClassElement getCustomSchema(String className, Map entry : environment.getProperties(MICRONAUT_OPENAPI_SCHEMA, StringConvention.RAW).entrySet()) { + for (Map.Entry entry : environment.getProperties(MICRONAUT_OPENAPI_SCHEMA_MAPPING, StringConvention.RAW).entrySet()) { String configuredClassName = entry.getKey(); String targetClassName = (String) entry.getValue(); readCustomSchema(configuredClassName, targetClassName, customSchemas, context); @@ -260,6 +261,14 @@ public static boolean isSchemaNameSeparatorEmpty(VisitorContext context) { return value; } + public static DuplicateResolution getSchemaDuplicateResolution(VisitorContext context) { + var value = getConfigProperty(MICRONAUT_OPENAPI_SCHEMA_DUPLICATE_RESOLUTION, context); + if (StringUtils.isNotEmpty(value) && DuplicateResolution.ERROR.name().equalsIgnoreCase(value)) { + return DuplicateResolution.ERROR; + } + return DuplicateResolution.AUTO; + } + public static String getGenericSeparator(VisitorContext context) { if (isSchemaNameSeparatorEmpty(context)) { return EMPTY_STRING; @@ -299,7 +308,7 @@ public static boolean isExtraSchemasEnabled(VisitorContext context) { if (loadedValue != null) { return loadedValue; } - boolean value = getBooleanProperty(MICRONAUT_OPENAPI_EXTRA_SCHEMA_ENABLED, true, context); + boolean value = getBooleanProperty(MICRONAUT_OPENAPI_SCHEMA_EXTRA_ENABLED, true, context); ContextUtils.put(MICRONAUT_INTERNAL_EXTRA_SCHEMA_ENABLED, value, context); return value; } @@ -581,7 +590,6 @@ public static Map getGroupsPropertiesMap(VisitorContext * Returns the EndpointsConfiguration. * * @param context The context. - * * @return The EndpointsConfiguration. */ public static EndpointsConfiguration endpointsConfiguration(VisitorContext context) { @@ -709,10 +717,10 @@ private static void readSchemaDecorators(Properties props, Map customSchemas, VisitorContext context) { for (String prop : props.stringPropertyNames()) { - if (!prop.startsWith(MICRONAUT_OPENAPI_SCHEMA) || prop.startsWith(MICRONAUT_OPENAPI_SCHEMA_PREFIX) || prop.startsWith(MICRONAUT_OPENAPI_SCHEMA_POSTFIX)) { + if (!prop.startsWith(MICRONAUT_OPENAPI_SCHEMA_MAPPING) || prop.startsWith(MICRONAUT_OPENAPI_SCHEMA_PREFIX) || prop.startsWith(MICRONAUT_OPENAPI_SCHEMA_POSTFIX)) { continue; } - String className = prop.substring(MICRONAUT_OPENAPI_SCHEMA.length() + 1); + String className = prop.substring(MICRONAUT_OPENAPI_SCHEMA_MAPPING.length() + 1); String targetClassName = props.getProperty(prop); readCustomSchema(className, targetClassName, customSchemas, context); } @@ -823,8 +831,8 @@ public static Properties readOpenApiConfigFile(VisitorContext context) { } var openApiProperties = new Properties(); String cfgFile = context != null - ? ContextUtils.getOptions(context).getOrDefault(MICRONAUT_OPENAPI_CONFIG_FILE, System.getProperty(MICRONAUT_OPENAPI_CONFIG_FILE, OPENAPI_CONFIG_FILE)) - : System.getProperty(MICRONAUT_OPENAPI_CONFIG_FILE, OPENAPI_CONFIG_FILE); + ? ContextUtils.getOptions(context).getOrDefault(MICRONAUT_OPENAPI_CONFIG_FILE, System.getProperty(MICRONAUT_OPENAPI_CONFIG_FILE, OPENAPI_CONFIG_FILE)) + : System.getProperty(MICRONAUT_OPENAPI_CONFIG_FILE, OPENAPI_CONFIG_FILE); if (StringUtils.isNotEmpty(cfgFile)) { Path cfg = resolve(context, Paths.get(cfgFile)); if (Files.isReadable(cfg)) { @@ -945,8 +953,8 @@ private static boolean isEnvEnabled(VisitorContext context) { * @param classElement class element */ record CustomSchema( - List typeArgs, - ClassElement classElement + List typeArgs, + ClassElement classElement ) { } @@ -974,4 +982,12 @@ public void setPostfix(String postfix) { this.postfix = postfix; } } + + /** + * Duplicate schema resolution mode + */ + public enum DuplicateResolution { + AUTO, + ERROR, + } } diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/ContextProperty.java b/openapi/src/main/java/io/micronaut/openapi/visitor/ContextProperty.java index 1f28d7cfaa..ea27331558 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/ContextProperty.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/ContextProperty.java @@ -112,7 +112,7 @@ public interface ContextProperty { */ String MICRONAUT_INTERNAL_GENERATION_SPEC_ENABLED = "micronaut.internal.generation.spec.enabled"; /** - * Loaded micronaut.openapi.extra.schema.enabled value. + * Loaded micronaut.openapi.schema.extra.enabled value. *
* Default: true */ diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiConfigProperty.java b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiConfigProperty.java index 3cef359084..a67eb9e8cb 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiConfigProperty.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiConfigProperty.java @@ -33,27 +33,6 @@ public interface OpenApiConfigProperty { * Default: true */ String MICRONAUT_OPENAPI_ENABLED = "micronaut.openapi.enabled"; - /** - * System property that enables or disables schema name separator for generics and inner classes. - * If it's true separators will be skipped. For example, schema name for class with name - * {@code MyClass.MyInnerClass} will be - * {@code MyClassMyInnerClassMyGeneric1MyGeneric2} - *
- * Default: false - */ - String MICRONAUT_OPENAPI_SCHEMA_NAME_SEPARATOR_EMPTY = "micronaut.openapi.schema.name.separator.empty"; - /** - * System property to set custom separator for generic classes. By default, it is "_". - *
- * Default: _ - */ - String MICRONAUT_OPENAPI_SCHEMA_NAME_SEPARATOR_GENERIC = "micronaut.openapi.schema.name.separator.generic"; - /** - * System property to set custom separator for inner classes. By default, it is ".". - *
- * Default: . - */ - String MICRONAUT_OPENAPI_SCHEMA_NAME_SEPARATOR_INNER_CLASS = "micronaut.openapi.schema.name.separator.inner-class"; /** * System property that enables generating OpenAPI version 3.1. *
@@ -68,10 +47,6 @@ public interface OpenApiConfigProperty { * System property that enables setting the open api config file. */ String MICRONAUT_OPENAPI_CONFIG_FILE = "micronaut.openapi.config.file"; - /** - * System property that enables extra schema processing. - */ - String MICRONAUT_OPENAPI_EXTRA_SCHEMA_ENABLED = "micronaut.openapi.extra.schema.enabled"; /** * Prefix for expandable properties. */ @@ -180,40 +155,79 @@ public interface OpenApiConfigProperty { */ String MICRONAUT_JACKSON_JSON_VIEW_ENABLED = "jackson.json-view.enabled"; + /** + * System property that enables extra schema processing. + */ + String MICRONAUT_OPENAPI_SCHEMA_EXTRA_ENABLED = "micronaut.openapi.schema.extra.enabled"; + /** + * System property to set schema duplicate resolution. Available values: + * - auto - micronaut-openapi automatically add index suffix to duplicate schema. + * - error - micronaut-openapi throws an exception when found duplicate schema. + *
+ * Default: auto + */ + String MICRONAUT_OPENAPI_SCHEMA_DUPLICATE_RESOLUTION = "micronaut.openapi.schema.duplicate-resolution"; + /** + * System property that enables or disables schema name separator for generics and inner classes. + * If it's true separators will be skipped. For example, schema name for class with name + * {@code MyClass.MyInnerClass} will be + * {@code MyClassMyInnerClassMyGeneric1MyGeneric2} + *
+ * Default: false + */ + String MICRONAUT_OPENAPI_SCHEMA_NAME_SEPARATOR_EMPTY = "micronaut.openapi.schema.name.separator.empty"; + /** + * System property to set custom separator for generic classes. By default, it is "_". + *
+ * Default: _ + */ + String MICRONAUT_OPENAPI_SCHEMA_NAME_SEPARATOR_GENERIC = "micronaut.openapi.schema.name.separator.generic"; + /** + * System property to set custom separator for inner classes. By default, it is ".". + *
+ * Default: . + */ + String MICRONAUT_OPENAPI_SCHEMA_NAME_SEPARATOR_INNER_CLASS = "micronaut.openapi.schema.name.separator.inner-class"; + /** * Properties prefix to set custom schema implementations for selected classes. * For example, if you want to set simple 'java.lang.String' class to some complex 'org.somepackage.MyComplexType' class you need to write: *

- * micronaut.openapi.schema.org.somepackage.MyComplexType=java.lang.String + * micronaut.openapi.schema.mapping.org.somepackage.MyComplexType=java.lang.String *

* Also, you can set it in your application.yml file like this: *

+ *

      * micronaut:
-     * openapi:
-     * schema:
-     * org.somepackage.MyComplexType: java.lang.String
-     * org.somepackage.MyComplexType2: java.lang.Integer
+     *   openapi:
+     *     schema:
+     *       mapping:
+     *         org.somepackage.MyComplexType: java.lang.String
+     *         org.somepackage.MyComplexType2: java.lang.Integer
+     * 
* ... */ - String MICRONAUT_OPENAPI_SCHEMA = "micronaut.openapi.schema"; + String MICRONAUT_OPENAPI_SCHEMA_MAPPING = "micronaut.openapi.schema.mapping"; /** * Properties prefix to set schema name prefix or postfix by package. * For example, if you have some classes with same names in different packages you can set postfix like this: *

- * micronaut.openapi.schema-postfix.org.api.v1_0_0=1_0_0 - * micronaut.openapi.schema-postfix.org.api.v2_0_0=2_0_0 + * micronaut.openapi.schema.mapping-postfix.org.api.v1_0_0=1_0_0 + * micronaut.openapi.schema.mapping-postfix.org.api.v2_0_0=2_0_0 *

* Also, you can set it in your application.yml file like this: *

+ *

      * micronaut:
-     * openapi:
-     * schema-postfix:
-     * org.api.v1_0_0: 1_0_0
-     * org.api.v2_0_0: 2_0_0
-     * ...
+     *   openapi:
+     *     schema:
+     *       mapping-postfix:
+     *         org.api.v1_0_0: 1_0_0
+     *         org.api.v2_0_0: 2_0_0
+     * 
*/ - String MICRONAUT_OPENAPI_SCHEMA_PREFIX = "micronaut.openapi.schema-prefix"; - String MICRONAUT_OPENAPI_SCHEMA_POSTFIX = "micronaut.openapi.schema-postfix"; + String MICRONAUT_OPENAPI_SCHEMA_PREFIX = "micronaut.openapi.schema.mapping-prefix"; + String MICRONAUT_OPENAPI_SCHEMA_POSTFIX = "micronaut.openapi.schema.mapping-postfix"; /** * Properties prefix to set custom schema implementations for selected classes. * For example, if you want to set simple 'java.lang.String' class to some complex 'org.somepackage.MyComplexType' class you need to write: @@ -222,15 +236,16 @@ public interface OpenApiConfigProperty { *

* Also, you can set it in your application.yml file like this: *

+ *

      * micronaut:
-     * openapi:
-     * group:
-     * my-group1:
-     * title: Title 1
-     * filename: swagger-${group}-${apiVersion}-${version}.yml
-     * my-group2:
-     * title: Title 2
-     * ...
+     *   openapi:
+     *     group:
+     *       my-group1:
+     *         title: Title 1
+     *         filename: swagger-${group}-${apiVersion}-${version}.yml
+     *       my-group2:
+     *         title: Title 2
+     * 
*/ String MICRONAUT_OPENAPI_GROUPS = "micronaut.openapi.groups"; @@ -275,31 +290,31 @@ public interface OpenApiConfigProperty { * All supported annotation processor properties. */ Set ALL = Set.of( - MICRONAUT_OPENAPI_ENABLED, - MICRONAUT_OPENAPI_CONTEXT_SERVER_PATH, - MICRONAUT_OPENAPI_PROPERTY_NAMING_STRATEGY, - MICRONAUT_OPENAPI_VIEWS_SPEC, - MICRONAUT_OPENAPI_FILENAME, - MICRONAUT_OPENAPI_JSON_FORMAT, - MICRONAUT_OPENAPI_ENVIRONMENTS, - MICRONAUT_ENVIRONMENT_ENABLED, - MICRONAUT_OPENAPI_FIELD_VISIBILITY_LEVEL, - MICRONAUT_CONFIG_FILE_LOCATIONS, - MICRONAUT_OPENAPI_TARGET_FILE, - MICRONAUT_OPENAPI_VIEWS_DEST_DIR, - MICRONAUT_OPENAPI_ADDITIONAL_FILES, - MICRONAUT_OPENAPI_CONFIG_FILE, - MICRONAUT_OPENAPI_SECURITY_ENABLED, - MICRONAUT_OPENAPI_VERSIONING_ENABLED, - MICRONAUT_OPENAPI_JSON_VIEW_DEFAULT_INCLUSION, - MICRONAUT_OPENAPI_PROJECT_DIR, - MICRONAUT_OPENAPI_ADOC_ENABLED, - MICRONAUT_OPENAPI_ADOC_TEMPLATES_DIR_PATH, - MICRONAUT_OPENAPI_ADOC_TEMPLATE_FILENAME, - MICRONAUT_OPENAPI_ADOC_OUTPUT_DIR_PATH, - MICRONAUT_OPENAPI_ADOC_OUTPUT_FILENAME, - MICRONAUT_OPENAPI_ADOC_OPENAPI_PATH, - MICRONAUT_OPENAPI_SWAGGER_FILE_GENERATION_ENABLED, - MICRONAUT_OPENAPI_EXTRA_SCHEMA_ENABLED + MICRONAUT_OPENAPI_ENABLED, + MICRONAUT_OPENAPI_CONTEXT_SERVER_PATH, + MICRONAUT_OPENAPI_PROPERTY_NAMING_STRATEGY, + MICRONAUT_OPENAPI_VIEWS_SPEC, + MICRONAUT_OPENAPI_FILENAME, + MICRONAUT_OPENAPI_JSON_FORMAT, + MICRONAUT_OPENAPI_ENVIRONMENTS, + MICRONAUT_ENVIRONMENT_ENABLED, + MICRONAUT_OPENAPI_FIELD_VISIBILITY_LEVEL, + MICRONAUT_CONFIG_FILE_LOCATIONS, + MICRONAUT_OPENAPI_TARGET_FILE, + MICRONAUT_OPENAPI_VIEWS_DEST_DIR, + MICRONAUT_OPENAPI_ADDITIONAL_FILES, + MICRONAUT_OPENAPI_CONFIG_FILE, + MICRONAUT_OPENAPI_SECURITY_ENABLED, + MICRONAUT_OPENAPI_VERSIONING_ENABLED, + MICRONAUT_OPENAPI_JSON_VIEW_DEFAULT_INCLUSION, + MICRONAUT_OPENAPI_PROJECT_DIR, + MICRONAUT_OPENAPI_ADOC_ENABLED, + MICRONAUT_OPENAPI_ADOC_TEMPLATES_DIR_PATH, + MICRONAUT_OPENAPI_ADOC_TEMPLATE_FILENAME, + MICRONAUT_OPENAPI_ADOC_OUTPUT_DIR_PATH, + MICRONAUT_OPENAPI_ADOC_OUTPUT_FILENAME, + MICRONAUT_OPENAPI_ADOC_OPENAPI_PATH, + MICRONAUT_OPENAPI_SWAGGER_FILE_GENERATION_ENABLED, + MICRONAUT_OPENAPI_SCHEMA_EXTRA_ENABLED ); } diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiExtraSchemaVisitor.java b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiExtraSchemaVisitor.java index c1695ec325..a747a5a876 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiExtraSchemaVisitor.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiExtraSchemaVisitor.java @@ -181,8 +181,8 @@ private void processExtraSchemaClass(ClassElement classEl, VisitorContext contex if (classEl == null) { return; } - String schemaName = stringValue(classEl, io.swagger.v3.oas.annotations.media.Schema.class, PROP_NAME) - .orElse(computeDefaultSchemaName(null, classEl, classEl.getTypeArguments(), context, null)); + String schemaName = computeDefaultSchemaName(stringValue(classEl, io.swagger.v3.oas.annotations.media.Schema.class, PROP_NAME).orElse(null), + null, classEl, classEl.getTypeArguments(), context, null); var schema = getSchemaDefinition(resolveOpenApi(context), context, classEl, classEl.getTypeArguments(), null, Collections.emptyList(), null); if (schema == null) { return; diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java b/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java index dfed998541..419d7824c0 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/SchemaDefinitionUtils.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; import com.fasterxml.jackson.databind.node.ObjectNode; +import io.micronaut.context.exceptions.ConfigurationException; import io.micronaut.core.annotation.AnnotationClassValue; import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.annotation.Internal; @@ -124,6 +125,7 @@ import static io.micronaut.openapi.visitor.ConfigUtils.getGenericSeparator; import static io.micronaut.openapi.visitor.ConfigUtils.getInnerClassSeparator; import static io.micronaut.openapi.visitor.ConfigUtils.getSchemaDecoration; +import static io.micronaut.openapi.visitor.ConfigUtils.getSchemaDuplicateResolution; import static io.micronaut.openapi.visitor.ConfigUtils.isJsonViewDefaultInclusion; import static io.micronaut.openapi.visitor.ContextUtils.warn; import static io.micronaut.openapi.visitor.ConvertUtils.parseJsonString; @@ -344,7 +346,7 @@ public static Schema getSchemaDefinition(OpenAPI openAPI, primitiveType = null; } if (primitiveType == null) { - String schemaName = computeDefaultSchemaName(definingElement, type, typeArgs, context, jsonViewClass); + String schemaName = computeDefaultSchemaName(null, definingElement, type, typeArgs, context, jsonViewClass); schema = schemas.get(schemaName); JavadocDescription javadoc = Utils.getJavadocParser().parse(type.getDocumentation().orElse(null)); if (schema == null) { @@ -382,10 +384,8 @@ public static Schema getSchemaDefinition(OpenAPI openAPI, } else { // Schema annotation property `name` on field level means, that this property must be with this name. // This is not a schema name - String schemaName = !schemaAnnOnField ? schemaValue.stringValue(PROP_NAME).orElse(null) : null; - if (schemaName == null) { - schemaName = computeDefaultSchemaName(definingElement, type, typeArgs, context, jsonViewClass); - } + var schemaName = computeDefaultSchemaName(!schemaAnnOnField ? schemaValue.stringValue(PROP_NAME).orElse(null) : null, + definingElement, type, typeArgs, context, jsonViewClass); schema = schemas.get(schemaName); if (schema == null) { if (inProgressSchemas.contains(schemaName)) { @@ -441,7 +441,7 @@ public static Schema getSchemaDefinition(OpenAPI openAPI, return null; } - public static String computeDefaultSchemaName(Element definingElement, Element type, Map typeArgs, VisitorContext context, + public static String computeDefaultSchemaName(String defaultSchemaName, Element definingElement, Element type, Map typeArgs, VisitorContext context, @Nullable ClassElement jsonViewClass) { var genericSeparator = getGenericSeparator(context); @@ -454,19 +454,54 @@ public static String computeDefaultSchemaName(Element definingElement, Element t jsonViewPostfix = genericSeparator + (jsonViewClassName.contains(DOT) ? jsonViewClassName.substring(jsonViewClassName.lastIndexOf(DOT) + 1) : jsonViewClassName); } - String metaAnnName = null; - if (definingElement != null) { - metaAnnName = definingElement.getAnnotationNameByStereotype(io.swagger.v3.oas.annotations.media.Schema.class).orElse(null); + var pair = computeFullClassNameWithGenerics(type, typeArgs, jsonViewPostfix, context); + var fullClassNameWithGenerics = pair.getSecond(); + + String resultSchemaName; + if (defaultSchemaName != null) { + resultSchemaName = defaultSchemaName; + } else { + + String metaAnnName = null; + if (definingElement != null) { + metaAnnName = definingElement.getAnnotationNameByStereotype(io.swagger.v3.oas.annotations.media.Schema.class).orElse(null); + } + if (metaAnnName != null && !io.swagger.v3.oas.annotations.media.Schema.class.getName().equals(metaAnnName)) { + resultSchemaName = NameUtils.getSimpleName(metaAnnName) + jsonViewPostfix; + if (!DOT.equals(innerClassSeparator)) { + resultSchemaName = resultSchemaName.replace(DOT, innerClassSeparator); + } + } else { + resultSchemaName = pair.getFirst(); + } } - if (metaAnnName != null && !io.swagger.v3.oas.annotations.media.Schema.class.getName().equals(metaAnnName)) { - var resultSchemaName = NameUtils.getSimpleName(metaAnnName) + jsonViewPostfix; - if (!DOT.equals(innerClassSeparator)) { - resultSchemaName = resultSchemaName.replace(DOT, innerClassSeparator); + + String storedClassName = schemaNameToClassNameMap.get(resultSchemaName); + // Check if the class exists in other packages. If so, you need to add a suffix, + // because there are two classes in different packages, but with the same class name. + if (storedClassName != null && !storedClassName.equals(fullClassNameWithGenerics)) { + if (getSchemaDuplicateResolution(context) == ConfigUtils.DuplicateResolution.ERROR) { + throw new ConfigurationException("Found 2 schemas with same name \"" + resultSchemaName + "\" for classes " + storedClassName + " and " + fullClassNameWithGenerics); } - return resultSchemaName; + int index = shemaNameSuffixCounterMap.getOrDefault(resultSchemaName, 0); + index++; + shemaNameSuffixCounterMap.put(resultSchemaName, index); + resultSchemaName += genericSeparator + index; } - String packageName; + schemaNameToClassNameMap.put(resultSchemaName, fullClassNameWithGenerics); + + return resultSchemaName; + } + + /** + * @return pair of package name and full className with generics + */ + private static Pair computeFullClassNameWithGenerics(Element type, Map typeArgs, String jsonViewPostfix, VisitorContext context) { + + var innerClassSeparator = getInnerClassSeparator(context); + String resultSchemaName; + String packageName; if (type instanceof TypedElement typedEl && !(type instanceof EnumElement)) { ClassElement typeType = typedEl.getType(); var isProtobufGenerated = isProtobufGenerated(typeType); @@ -485,23 +520,11 @@ public static String computeDefaultSchemaName(Element definingElement, Element t resultSchemaName = resultSchemaName.replace(DOLLAR, innerClassSeparator) + jsonViewPostfix; if (schemaDecorator != null) { resultSchemaName = (StringUtils.hasText(schemaDecorator.getPrefix()) ? schemaDecorator.getPrefix() : EMPTY_STRING) - + resultSchemaName - + (StringUtils.hasText(schemaDecorator.getPostfix()) ? schemaDecorator.getPostfix() : EMPTY_STRING); + + resultSchemaName + + (StringUtils.hasText(schemaDecorator.getPostfix()) ? schemaDecorator.getPostfix() : EMPTY_STRING); } - String fullClassNameWithGenerics = packageName + DOT + resultSchemaName; - // Check if the class exists in other packages. If so, you need to add a suffix, - // because there are two classes in different packages, but with the same class name. - String storedClassName = schemaNameToClassNameMap.get(resultSchemaName); - if (storedClassName != null && !storedClassName.equals(fullClassNameWithGenerics)) { - int index = shemaNameSuffixCounterMap.getOrDefault(resultSchemaName, 0); - index++; - shemaNameSuffixCounterMap.put(resultSchemaName, index); - resultSchemaName += genericSeparator + index; - } - schemaNameToClassNameMap.put(resultSchemaName, fullClassNameWithGenerics); - - return resultSchemaName; + return Pair.of(resultSchemaName, packageName + DOT + resultSchemaName); } public static List getEnumValues(EnumElement type, String schemaType, String schemaFormat, VisitorContext context) { @@ -1639,8 +1662,8 @@ private static Schema processSuperTypes(Schema schema, private static void readAllInterfaces(OpenAPI openAPI, VisitorContext context, @Nullable Element definingElement, List mediaTypes, Schema schema, ClassElement superType, Map schemas, Map superTypeArgs, @Nullable ClassElement jsonViewClass) { - String parentSchemaName = superType.stringValue(io.swagger.v3.oas.annotations.media.Schema.class, PROP_NAME) - .orElse(computeDefaultSchemaName(definingElement, superType, superTypeArgs, context, jsonViewClass)); + String parentSchemaName = computeDefaultSchemaName(superType.stringValue(io.swagger.v3.oas.annotations.media.Schema.class, PROP_NAME).orElse(null), + definingElement, superType, superTypeArgs, context, jsonViewClass); if (schemas.get(parentSchemaName) != null || getSchemaDefinition(openAPI, context, superType, superTypeArgs, null, mediaTypes, jsonViewClass) != null) { @@ -2675,8 +2698,8 @@ private static void handleUnwrapped(VisitorContext context, Element element, Cla Map schemas = SchemaUtils.resolveSchemas(Utils.resolveOpenApi(context)); ClassElement customElementType = getCustomSchema(elementType.getName(), elementType.getTypeArguments(), context); var elType = customElementType != null ? customElementType : elementType; - String schemaName = stringValue(element, io.swagger.v3.oas.annotations.media.Schema.class, PROP_NAME) - .orElse(computeDefaultSchemaName(null, elType, elementType.getTypeArguments(), context, null)); + String schemaName = computeDefaultSchemaName(stringValue(element, io.swagger.v3.oas.annotations.media.Schema.class, PROP_NAME).orElse(null), + null, elType, elementType.getTypeArguments(), context, null); Schema wrappedPropertySchema = schemas.get(schemaName); if (wrappedPropertySchema == null) { getSchemaDefinition(resolveOpenApi(context), context, elType, elType.getTypeArguments(), element, Collections.emptyList(), null); diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiBasicSchemaSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiBasicSchemaSpec.groovy index f636189edb..4d4d618d3b 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiBasicSchemaSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiBasicSchemaSpec.groovy @@ -1,11 +1,13 @@ package io.micronaut.openapi.visitor +import io.micronaut.context.exceptions.ConfigurationException import io.micronaut.openapi.AbstractOpenApiTypeElementSpec import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.Operation import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.parameters.Parameter import spock.lang.Issue +import spock.util.environment.RestoreSystemProperties import java.time.OffsetDateTime @@ -1162,18 +1164,15 @@ public class MyBean {} buildBeanDefinition("test.MyBean", ''' package test; -import io.swagger.v3.oas.annotations.enums.ParameterStyle;import jakarta.validation.constraints.NegativeOrZero; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.PositiveOrZero; - -import io.micronaut.core.annotation.Introspected; import io.micronaut.http.HttpResponse; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.enums.ParameterStyle; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; @Controller class PersonController { @@ -1584,6 +1583,102 @@ public class MyBean {} entityTest33.properties.fieldC.type == 'string' } + void "test dto schemas with same name. Mode: auto"() { + when: + buildBeanDefinition("test.MyBean", ''' +package test; + +import io.micronaut.http.annotation.Body; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Post; +import io.micronaut.openapi.test1.Entity; +import io.swagger.v3.oas.annotations.media.Schema; + +@Controller +class TestController { + + @Post("test1") + String test1(@Body Car1 car1) { + return null; + } + + @Post("test2") + String test2(@Body Car2 car2) { + return null; + } +} + +@Schema(name="Car") +record Car1(String p1) {} + +@Schema(name="Car") +record Car2(String p2) {} + +@jakarta.inject.Singleton +public class MyBean {} +''') + + OpenAPI openAPI = Utils.testReference + + then: + openAPI.components.schemas + openAPI.components.schemas.size() == 2 + Schema car1 = openAPI.components.schemas.Car + Schema car2 = openAPI.components.schemas.Car_1 + + car1 + car1.properties.p1 + car1.properties.p1.type == 'string' + + car2 + car2.properties.p2 + car2.properties.p2.type == 'string' + } + + @RestoreSystemProperties + void "test dto schemas with same name. Mode: error"() { + + setup: + System.setProperty(OpenApiConfigProperty.MICRONAUT_OPENAPI_SCHEMA_DUPLICATE_RESOLUTION, "error") + + when: + buildBeanDefinition("test.MyBean", ''' +package test; + +import io.micronaut.http.annotation.Body; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Post; +import io.micronaut.openapi.test1.Entity; +import io.swagger.v3.oas.annotations.media.Schema; + +@Controller +class TestController { + + @Post("test1") + String test1(@Body Car1 car1) { + return null; + } + + @Post("test2") + String test2(@Body Car2 car2) { + return null; + } +} + +@Schema(name="Car") +record Car1(String p1) {} + +@Schema(name="Car") +record Car2(String p2) {} + +@jakarta.inject.Singleton +public class MyBean {} +''') + then: + def e = thrown(RuntimeException) + e.cause.class == ConfigurationException.class + } + void "test empty default value for Map body type java"() { when: buildBeanDefinition("test.MyBean", ''' diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiControllerRequiresVisitorSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiControllerRequiresVisitorSpec.groovy index 3e2139e181..1e7dbd58e0 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiControllerRequiresVisitorSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiControllerRequiresVisitorSpec.groovy @@ -115,15 +115,10 @@ class MyBean {} buildBeanDefinition('test.MyBean', ''' package test; -import io.micronaut.context.annotation.Parameter; -import io.micronaut.context.annotation.Requires;import io.swagger.v3.oas.annotations.*; -import io.swagger.v3.oas.annotations.media.*; -import io.swagger.v3.oas.annotations.enums.*; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import io.micronaut.http.annotation.*; -import io.micronaut.http.*; -import java.util.List; +import io.micronaut.context.annotation.Requires; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Post; @Requires(env="env1",notEnv="env3") @Controller @@ -170,15 +165,10 @@ class MyBean {} buildBeanDefinition('test.MyBean', ''' package test; -import io.micronaut.context.annotation.Parameter; -import io.micronaut.context.annotation.Requires;import io.swagger.v3.oas.annotations.*; -import io.swagger.v3.oas.annotations.media.*; -import io.swagger.v3.oas.annotations.enums.*; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import io.micronaut.http.annotation.*; -import io.micronaut.http.*; -import java.util.List; +import io.micronaut.context.annotation.Requires; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Post; @Requires(env={"env1","env2"}) @Controller @@ -236,15 +226,10 @@ class MyBean {} buildBeanDefinition('test.MyBean', ''' package test; -import io.micronaut.context.annotation.Parameter; -import io.micronaut.context.annotation.Requires;import io.swagger.v3.oas.annotations.*; -import io.swagger.v3.oas.annotations.media.*; -import io.swagger.v3.oas.annotations.enums.*; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import io.micronaut.http.annotation.*; -import io.micronaut.http.*; -import java.util.List; +import io.micronaut.context.annotation.Requires; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Post; @Requires(notEnv={"env1","env2"}) @Controller @@ -291,15 +276,10 @@ class MyBean {} buildBeanDefinition('test.MyBean', ''' package test; -import io.micronaut.context.annotation.Parameter; -import io.micronaut.context.annotation.Requires;import io.swagger.v3.oas.annotations.*; -import io.swagger.v3.oas.annotations.media.*; -import io.swagger.v3.oas.annotations.enums.*; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import io.micronaut.http.annotation.*; -import io.micronaut.http.*; -import java.util.List; +import io.micronaut.context.annotation.Requires; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Post; @Requires(env="env1") @Requires(env={"env2","env3"}) diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiControllerVisitorSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiControllerVisitorSpec.groovy index b2d057e1dc..47b76459bb 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiControllerVisitorSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiControllerVisitorSpec.groovy @@ -1133,7 +1133,8 @@ class MyBean {} package test; import io.micronaut.http.HttpResponse; -import io.micronaut.http.MediaType;import io.micronaut.http.annotation.Controller; +import io.micronaut.http.MediaType; +import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -1183,7 +1184,8 @@ class MyBean {} package test; import io.micronaut.http.HttpResponse; -import io.micronaut.http.MediaType;import io.micronaut.http.annotation.Controller; +import io.micronaut.http.MediaType; +import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -1650,8 +1652,8 @@ package test; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; -import io.swagger.v3.oas.annotations.responses.ApiResponse;import io.swagger.v3.oas.annotations.responses.ApiResponses; - +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import org.reactivestreams.Publisher; @ApiResponses({ @@ -2327,7 +2329,8 @@ import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; import io.micronaut.http.bind.binders.TypedRequestArgumentBinder; import io.micronaut.serde.annotation.Serdeable; -import io.swagger.v3.oas.annotations.Parameter;import jakarta.inject.Singleton; +import io.swagger.v3.oas.annotations.Parameter; +import jakarta.inject.Singleton; import java.util.Optional; diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiDecoratorSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiDecoratorSpec.groovy index ef2edbf598..66ef63b69f 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiDecoratorSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiDecoratorSpec.groovy @@ -13,7 +13,8 @@ package test; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; -import io.micronaut.openapi.annotation.OpenAPIDecorator;import io.swagger.v3.oas.annotations.Operation; +import io.micronaut.openapi.annotation.OpenAPIDecorator; +import io.swagger.v3.oas.annotations.Operation; @OpenAPIDecorator(opIdPrefix = "cats-", opIdSuffix = "-suffix") @Controller("/cats") diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiEnumSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiEnumSpec.groovy index 2598d63df6..be1920544a 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiEnumSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiEnumSpec.groovy @@ -540,12 +540,12 @@ class MyBean {} buildBeanDefinition('test.MyBean', ''' package test; +import com.fasterxml.jackson.annotation.JsonIgnore; import io.micronaut.core.annotation.Introspected; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; -import io.swagger.v3.oas.annotations.Hidden;import io.swagger.v3.oas.annotations.media.Schema; - -import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.media.Schema; @Controller class OpenApiController { @@ -606,9 +606,7 @@ package test; import io.micronaut.core.annotation.Introspected; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; -import io.swagger.v3.oas.annotations.Hidden;import io.swagger.v3.oas.annotations.media.Schema; - -import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; @Controller class OpenApiController { diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiExtraSchemaVisitorSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiExtraSchemaVisitorSpec.groovy index dd51b097be..de49bc3216 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiExtraSchemaVisitorSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiExtraSchemaVisitorSpec.groovy @@ -210,7 +210,7 @@ class MyBean {} void "test disable OpenAPI extra schemas"() { given: - System.setProperty(OpenApiConfigProperty.MICRONAUT_OPENAPI_EXTRA_SCHEMA_ENABLED, "false") + System.setProperty(OpenApiConfigProperty.MICRONAUT_OPENAPI_SCHEMA_EXTRA_ENABLED, "false") when: buildBeanDefinition('test.MyBean', ''' @@ -218,7 +218,7 @@ package test; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; -import io.micronaut.http.annotation.Post;import io.micronaut.openapi.annotation.OpenAPIExtraSchema; +import io.micronaut.openapi.annotation.OpenAPIExtraSchema; import jakarta.inject.Singleton; @OpenAPIExtraSchema( diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiFileUploadBodyParameterSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiFileUploadBodyParameterSpec.groovy index 481995a12d..2026353951 100755 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiFileUploadBodyParameterSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiFileUploadBodyParameterSpec.groovy @@ -16,10 +16,8 @@ class OpenApiFileUploadBodyParameterSpec extends AbstractOpenApiTypeElementSpec buildBeanDefinition('test.MyBean', ''' package test; -import jakarta.inject.Singleton; - import io.micronaut.http.MediaType; -import io.micronaut.http.annotation.Body;import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; import io.micronaut.http.multipart.CompletedFileUpload; import io.micronaut.http.multipart.PartData; @@ -30,6 +28,7 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; @Singleton @Controller("/") diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiIncludeVisitorSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiIncludeVisitorSpec.groovy index 3e24d0191f..1de35f08e3 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiIncludeVisitorSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiIncludeVisitorSpec.groovy @@ -128,8 +128,8 @@ import io.swagger.v3.oas.annotations.info.Contact; import io.swagger.v3.oas.annotations.info.Info; import io.swagger.v3.oas.annotations.info.License; import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.security.SecurityRequirement;import io.swagger.v3.oas.annotations.tags.Tag; - +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.inject.Singleton; @OpenAPIDefinition( diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiInheritedPojoControllerSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiInheritedPojoControllerSpec.groovy index 51a0bd6b0d..366e74bf9a 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiInheritedPojoControllerSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiInheritedPojoControllerSpec.groovy @@ -545,18 +545,17 @@ class MyBean {} buildBeanDefinition('test.MyBean', ''' package test; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.Positive;import jakarta.validation.constraints.PositiveOrZero;import jakarta.validation.constraints.Size; - +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; - import jakarta.inject.Singleton; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import jakarta.validation.constraints.Size; @Controller("/pets") interface PetOperations { diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiNullableTypesSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiNullableTypesSpec.groovy index f13ad62029..732a68ec99 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiNullableTypesSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiNullableTypesSpec.groovy @@ -177,12 +177,7 @@ import io.micronaut.core.annotation.Nullable; import io.micronaut.http.HttpResponse; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; -import io.micronaut.http.annotation.Put; import io.micronaut.http.annotation.Post; -import io.micronaut.http.annotation.Status;import io.swagger.v3.oas.annotations.Parameter;import io.swagger.v3.oas.annotations.enums.ParameterIn; - -import java.util.List; -import java.util.Optional; class Pet { private String name; diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiOperationHeadersSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiOperationHeadersSpec.groovy index e62a6910ee..8e564afac1 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiOperationHeadersSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiOperationHeadersSpec.groovy @@ -121,7 +121,7 @@ package test; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; import io.swagger.v3.oas.annotations.headers.Header; -import io.swagger.v3.oas.annotations.media.Schema;import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponse; @Controller("/") class MyController { diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiParameterSchemaSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiParameterSchemaSpec.groovy index a3a3b4b0a9..eeba5ceeba 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiParameterSchemaSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiParameterSchemaSpec.groovy @@ -15,9 +15,10 @@ package test; import io.micronaut.http.HttpResponse; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; -import io.micronaut.http.annotation.Patch;import io.micronaut.http.annotation.Post; +import io.micronaut.http.annotation.Post; import io.micronaut.http.annotation.Put; -import io.micronaut.http.annotation.QueryValue;import io.swagger.v3.oas.annotations.Parameter; +import io.micronaut.http.annotation.QueryValue; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; import java.time.Period; diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaFieldSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaFieldSpec.groovy index 811bc2f759..c141901dc3 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaFieldSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiSchemaFieldSpec.groovy @@ -173,12 +173,12 @@ class MyBean {} package test; -import java.util.List; -import java.time.Instant; import io.micronaut.http.annotation.Body; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; -import io.swagger.v3.oas.annotations.extensions.Extension;import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.extensions.Extension; +import io.swagger.v3.oas.annotations.extensions.ExtensionProperty; +import io.swagger.v3.oas.annotations.media.Schema; @Controller class OpenApiController { diff --git a/openapi/src/test/resources/application-schemadecorator.yml b/openapi/src/test/resources/application-schemadecorator.yml index a0886e35f9..ecb8c085df 100644 --- a/openapi/src/test/resources/application-schemadecorator.yml +++ b/openapi/src/test/resources/application-schemadecorator.yml @@ -1,5 +1,6 @@ micronaut: openapi: - schema-postfix: - io.micronaut.openapi.api.v1_0_0: '1_0_0' - io.micronaut.openapi.api.v2_0_1: '2_0_1' + schema: + mapping-postfix: + io.micronaut.openapi.api.v1_0_0: '1_0_0' + io.micronaut.openapi.api.v2_0_1: '2_0_1' diff --git a/openapi/src/test/resources/application-schemaforclass.yml b/openapi/src/test/resources/application-schemaforclass.yml index ebb94cdb82..628f884ed5 100644 --- a/openapi/src/test/resources/application-schemaforclass.yml +++ b/openapi/src/test/resources/application-schemaforclass.yml @@ -1,5 +1,6 @@ micronaut: openapi: schema: - io.micronaut.openapi.ObjectId: java.lang.String - io.micronaut.openapi.JAXBElement: io.micronaut.openapi.MyJaxbElement + mapping: + io.micronaut.openapi.ObjectId: java.lang.String + io.micronaut.openapi.JAXBElement: io.micronaut.openapi.MyJaxbElement diff --git a/src/main/docs/guide/configuration/availableOptions.adoc b/src/main/docs/guide/configuration/availableOptions.adoc index 1f4571a287..9945f1f07c 100644 --- a/src/main/docs/guide/configuration/availableOptions.adoc +++ b/src/main/docs/guide/configuration/availableOptions.adoc @@ -2,9 +2,6 @@ |`*micronaut.openapi.enabled*` | System property that enables or disables open api annotation processing. | Default: `true` |`*micronaut.openapi.openapi31.enabled*` | System property that enables or disables OpenAPI 3.1.0 format. | Default: `false` |`*micronaut.openapi.openapi31.json-schema-dialect*` | System property that set JSON Schema Dialect for OpenAPI 3.1.0 format. | Default: `` -|`*micronaut.openapi.schema.name.separator.empty*` | If this property true, generic separators and inner class separators in schema name will be empty string. | Default: `false` -|`*micronaut.openapi.schema.name.separator.generic*` | System property that set generic class separator for schema name generation. | Default: `_` -|`*micronaut.openapi.schema.name.separator.inner-class*` | System property that set inner class separator for schema name generation. | Default: `.` |`*micronaut.openapi.swagger.file.generation.enabled*` | System property that enables or disables generation of a swagger (OpenAPI) specification file. This can be used whenever you already have a specification file and that you only need the Swagger UI. | Default: `true` |`*micronaut.openapi.config.file*` | System property that enables setting the open api config file. | |`*micronaut.openapi.server.context.path*` | System property for server context path. | @@ -38,7 +35,7 @@ PUBLIC | Default: `PUBLIC` You can set your custom paths separated by `,`. To set absolute paths use prefix `file:`, classpath paths use prefix `classpath:` or use prefix `project:` to set paths from project directory. | -|`*micronaut.openapi.schema.**` | Properties prefix to set custom schema implementations for selected classes. + +|`*micronaut.openapi.schema.mapping.**` | Properties prefix to set custom schema implementations for selected classes. + For example, if you want to set simple `java.lang.String` class to some complex `org.somepackage.MyComplexType` class you need to write: + {nbsp} + micronaut.openapi.schema.org.somepackage.MyComplexType=java.lang.String + @@ -48,28 +45,37 @@ Also, you can set it in your `application.yml` file like this: + micronaut: + {nbsp}{nbsp}openapi: + {nbsp}{nbsp}{nbsp}{nbsp}schema: + -{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}org.somepackage.MyComplexType: java.lang.String + -{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}org.somepackage.MyComplexType2: java.lang.Integer +{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}mapping: + +{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}org.somepackage.MyComplexType: java.lang.String + +{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}org.somepackage.MyComplexType2: java.lang.Integer | -|`*micronaut.openapi.schema-prefix*` + -`*micronaut.openapi.schema-postfix.**` | Properties prefix to set schema name prefix or postfix by package. + +|`*micronaut.openapi.schema.mapping-prefix*` + +`*micronaut.openapi.schema.mapping-postfix.**` | Properties prefix to set schema name prefix or postfix by package. + For example, if you have some classes with same names in different packages you can set postfix like this: + {nbsp} + -micronaut.openapi.schema-postfix.org.api.v1_0_0=1_0_0 + -micronaut.openapi.schema-postfix.org.api.v2_0_0=2_0_0 + +micronaut.openapi.schema.mapping-postfix.org.api.v1_0_0=1_0_0 + +micronaut.openapi.schema.mapping-postfix.org.api.v2_0_0=2_0_0 + {nbsp} + Also, you can set it in your `application.yml` file like this: + {nbsp} + micronaut: + {nbsp}{nbsp}openapi: + -{nbsp}{nbsp}{nbsp}{nbsp}schema-postfix: + -{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}org.api.v1_0_0: 1_0_0 + -{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}org.api.v2_0_0: 2_0_0 + -{nbsp}{nbsp}{nbsp}{nbsp}schema-prefix: + -{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}org.api.v1_0_0: public + -{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}org.api.v2_0_0: private + +{nbsp}{nbsp}{nbsp}{nbsp}schema: + +{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}mapping-postfix: + +{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}org.api.v1_0_0: 1_0_0 + +{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}org.api.v2_0_0: 2_0_0 + +{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}mapping-prefix: + +{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}org.api.v1_0_0: public + +{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}org.api.v2_0_0: private + {nbsp} + | +|`*micronaut.openapi.schema.extra.enable*` | If this property true, you can add some extra schemas to final openapi spec file. | Default: `false` +|`*micronaut.openapi.schema.duplicate-resolution*` | System property to set schema duplicate resolution. Available values: + +`*auto*` - micronaut-openapi automatically add index suffix to duplicate schema. + +`*error*` - micronaut-openapi throws an exception when found duplicate schema. | Default: `auto` +|`*micronaut.openapi.schema.name.separator.empty*` | If this property true, generic separators and inner class separators in schema name will be empty string. | Default: `false` +|`*micronaut.openapi.schema.name.separator.generic*` | System property that set generic class separator for schema name generation. | Default: `_` +|`*micronaut.openapi.schema.name.separator.inner-class*` | System property that set inner class separator for schema name generation. | Default: `.` |`*micronaut.openapi.groups.**` | Properties prefix to set custom schema implementations for selected classes. + For example, if you want to set simple 'java.lang.String' class to some complex 'org.somepackage.MyComplexType' class you need to write: + {nbsp} +