diff --git a/openapi/openapi-placeholders.properties b/openapi/openapi-placeholders.properties new file mode 100644 index 0000000000..226fb5c41c --- /dev/null +++ b/openapi/openapi-placeholders.properties @@ -0,0 +1,2 @@ +micronaut.openapi.expand.api.version=2.2.2 +micronaut.openapi.expand.another.placeholder.value=monkey diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiVisitor.java b/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiVisitor.java index a838634899..58e991b4ae 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiVisitor.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/AbstractOpenApiVisitor.java @@ -1639,7 +1639,7 @@ private Schema doBindSchemaAnnotationValue(VisitorContext context, Element eleme JsonNode schemaJson, String elType, String elFormat, AnnotationValue schemaAnn, @Nullable ClassElement jsonViewClass) { - // need to set placeholders to set correct values to example field + // need to set placeholders to set correct values and types to example field schemaJson = resolvePlaceholders(schemaJson, s -> expandProperties(s, getExpandableProperties(context), context)); try { schemaToBind = ConvertUtils.getJsonMapper().readerForUpdating(schemaToBind).readValue(schemaJson); diff --git a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiApplicationVisitor.java b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiApplicationVisitor.java index d8e2093d42..b104a0bf26 100644 --- a/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiApplicationVisitor.java +++ b/openapi/src/main/java/io/micronaut/openapi/visitor/OpenApiApplicationVisitor.java @@ -25,7 +25,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -379,7 +378,7 @@ public class OpenApiApplicationVisitor extends AbstractOpenApiVisitor implements */ private static final String DEFAULT_SECURITY_SCHEMA_NAME = "Authorization"; - private static final Argument>> EXPANDABLE_PROPERTIES_ARGUMENT = new GenericArgument>>() { }; + private static final Argument>> EXPANDABLE_PROPERTIES_ARGUMENT = new GenericArgument>>() { }; private static final String EXT_YML = ".yml"; private static final String EXT_YAML = ".yaml"; private static final String EXT_JSON = ".json"; @@ -912,8 +911,12 @@ public static Environment getEnv(VisitorContext context) { } private static boolean isEnvEnabled(VisitorContext context) { - String isEnabledStr = System.getProperty(MICRONAUT_ENVIRONMENT_ENABLED, readOpenApiConfigFile(context).getProperty(MICRONAUT_ENVIRONMENT_ENABLED)); + boolean isEnabled = true; + String isEnabledStr = System.getProperty(MICRONAUT_ENVIRONMENT_ENABLED); + if (StringUtils.isEmpty(isEnabledStr)) { + isEnabledStr = readOpenApiConfigFile(context).getProperty(MICRONAUT_ENVIRONMENT_ENABLED); + } if (StringUtils.isNotEmpty(isEnabledStr)) { isEnabled = Boolean.parseBoolean(isEnabledStr); } @@ -1181,12 +1184,12 @@ private Optional getDefaultFilePath(String fileName, VisitorContext contex return Optional.empty(); } - private Optional openApiSpecFile(String fileName, VisitorContext visitorContext) { - Optional path = userDefinedSpecFile(visitorContext); + private Optional openApiSpecFile(String fileName, VisitorContext context) { + Optional path = userDefinedSpecFile(context); if (path.isPresent()) { return path; } - return getDefaultFilePath(fileName, visitorContext); + return getDefaultFilePath(fileName, context); } private Optional userDefinedSpecFile(VisitorContext context) { @@ -1209,21 +1212,21 @@ private Path getViewsDestDir(Path defaultSwaggerFilePath, VisitorContext context return defaultSwaggerFilePath.getParent().resolve("views"); } - private static void createDirectories(Path f, VisitorContext visitorContext) { + private static void createDirectories(Path f, VisitorContext context) { if (f.getParent() != null) { try { Files.createDirectories(f.getParent()); } catch (IOException e) { - visitorContext.warn("Fail to create directories for" + f + ": " + e.getMessage(), null); + context.warn("Fail to create directories for" + f + ": " + e.getMessage(), null); } } } - private void applyPropertyNamingStrategy(OpenAPI openAPI, VisitorContext visitorContext) { - final String namingStrategyName = getConfigurationProperty(MICRONAUT_OPENAPI_PROPERTY_NAMING_STRATEGY, visitorContext); + private void applyPropertyNamingStrategy(OpenAPI openAPI, VisitorContext context) { + final String namingStrategyName = getConfigurationProperty(MICRONAUT_OPENAPI_PROPERTY_NAMING_STRATEGY, context); final PropertyNamingStrategies.NamingBase propertyNamingStrategy = fromName(namingStrategyName); if (propertyNamingStrategy != null) { - visitorContext.info("Using " + namingStrategyName + " property naming strategy."); + context.info("Using " + namingStrategyName + " property naming strategy."); if (openAPI.getComponents() != null && CollectionUtils.isNotEmpty(openAPI.getComponents().getSchemas())) { openAPI.getComponents().getSchemas().values().forEach(model -> { Map properties = model.getProperties(); @@ -1245,12 +1248,12 @@ private void applyPropertyNamingStrategy(OpenAPI openAPI, VisitorContext visitor } } - private void applyPropertyServerContextPath(OpenAPI openAPI, VisitorContext visitorContext) { - final String serverContextPath = getConfigurationProperty(MICRONAUT_OPENAPI_CONTEXT_SERVER_PATH, visitorContext); + private void applyPropertyServerContextPath(OpenAPI openAPI, VisitorContext context) { + final String serverContextPath = getConfigurationProperty(MICRONAUT_OPENAPI_CONTEXT_SERVER_PATH, context); if (serverContextPath == null || serverContextPath.isEmpty()) { return; } - visitorContext.info("Applying server context path: " + serverContextPath + " to Paths."); + context.info("Applying server context path: " + serverContextPath + " to Paths."); io.swagger.v3.oas.models.Paths paths = openAPI.getPaths(); if (paths == null || paths.isEmpty()) { return; @@ -1303,15 +1306,15 @@ public static JsonNode resolvePlaceholders(JsonNode node, UnaryOperator } } - public static String expandProperties(String s, List> properties, VisitorContext context) { + public static String expandProperties(String s, List> properties, VisitorContext context) { if (StringUtils.isEmpty(s) || !s.contains(Utils.PLACEHOLDER_PREFIX)) { return s; } // form openapi file (expandable properties) if (CollectionUtils.isNotEmpty(properties)) { - for (Map.Entry entry : properties) { - s = s.replace(entry.getKey(), entry.getValue()); + for (Pair entry : properties) { + s = s.replaceAll(entry.getFirst(), entry.getSecond()); } } @@ -1343,74 +1346,74 @@ public static String replacePlaceholders(String value, VisitorContext context) { return value; } - public static List> getExpandableProperties(VisitorContext context) { + public static List> getExpandableProperties(VisitorContext context) { - List> expandableProperties = new ArrayList<>(); - Optional propertiesLoaded = context.get(MICRONAUT_INTERNAL_EXPANDBLE_PROPERTIES_LOADED, Boolean.class); - if (!propertiesLoaded.orElse(false)) { + boolean propertiesLoaded = context.get(MICRONAUT_INTERNAL_EXPANDBLE_PROPERTIES_LOADED, Boolean.class).orElse(false); + if (propertiesLoaded) { + return context.get(MICRONAUT_INTERNAL_EXPANDBLE_PROPERTIES, EXPANDABLE_PROPERTIES_ARGUMENT).orElse(null); + } - // first, check system properties and environmets config files - AnnProcessorEnvironment env = (AnnProcessorEnvironment) getEnv(context); - Map propertiesFromEnv = null; - if (env != null) { - try { - propertiesFromEnv = env.getProperties("micronaut.openapi.expand", null); - } catch (Exception e) { - context.warn("Error:\n" + Utils.printStackTrace(e), null); - } + List> expandableProperties = new ArrayList<>(); + + // first, check system properties and environmets config files + AnnProcessorEnvironment env = (AnnProcessorEnvironment) getEnv(context); + Map propertiesFromEnv = null; + if (env != null) { + try { + propertiesFromEnv = env.getProperties("micronaut.openapi.expand", null); + } catch (Exception e) { + context.warn("Error:\n" + Utils.printStackTrace(e), null); } + } - Map expandedPropsMap = new HashMap<>(); - if (CollectionUtils.isNotEmpty(propertiesFromEnv)) { - for (Map.Entry entry : propertiesFromEnv.entrySet()) { - expandedPropsMap.put(entry.getKey(), entry.getValue().toString()); - } + Map expandedPropsMap = new HashMap<>(); + if (CollectionUtils.isNotEmpty(propertiesFromEnv)) { + for (Map.Entry entry : propertiesFromEnv.entrySet()) { + expandedPropsMap.put(entry.getKey(), entry.getValue().toString()); + } + } + + // next, read openapi.properties file + Properties openapiProps = readOpenApiConfigFile(context); + for (Map.Entry entry : openapiProps.entrySet()) { + String key = entry.getKey().toString(); + if (!key.startsWith(MICRONAUT_OPENAPI_EXPAND_PREFIX)) { + continue; } + expandedPropsMap.put(key, entry.getValue().toString()); + } - // next, read openapi.properties file - Properties openapiProps = readOpenApiConfigFile(context); - for (Map.Entry entry : openapiProps.entrySet()) { + // next, read system properties + if (CollectionUtils.isNotEmpty(System.getProperties())) { + for (Map.Entry entry : System.getProperties().entrySet()) { String key = entry.getKey().toString(); if (!key.startsWith(MICRONAUT_OPENAPI_EXPAND_PREFIX)) { continue; } expandedPropsMap.put(key, entry.getValue().toString()); } + } - // next, read system properties - if (CollectionUtils.isNotEmpty(System.getProperties())) { - for (Map.Entry entry : System.getProperties().entrySet()) { - String key = entry.getKey().toString(); - if (!key.startsWith(MICRONAUT_OPENAPI_EXPAND_PREFIX)) { - continue; - } - expandedPropsMap.put(key, entry.getValue().toString()); - } - } - - for (Map.Entry entry : expandedPropsMap.entrySet()) { - String key = entry.getKey(); - if (key.startsWith(MICRONAUT_OPENAPI_EXPAND_PREFIX)) { - key = key.substring(MICRONAUT_OPENAPI_EXPAND_PREFIX.length()); - } - expandableProperties.add(new AbstractMap.SimpleImmutableEntry<>("${" + key + '}', entry.getValue())); + for (Map.Entry entry : expandedPropsMap.entrySet()) { + String key = entry.getKey(); + if (key.startsWith(MICRONAUT_OPENAPI_EXPAND_PREFIX)) { + key = key.substring(MICRONAUT_OPENAPI_EXPAND_PREFIX.length()); } - - context.put(MICRONAUT_INTERNAL_EXPANDBLE_PROPERTIES, expandableProperties); - context.put(MICRONAUT_INTERNAL_EXPANDBLE_PROPERTIES_LOADED, true); - } else { - expandableProperties = context.get(MICRONAUT_INTERNAL_EXPANDBLE_PROPERTIES, EXPANDABLE_PROPERTIES_ARGUMENT).orElse(null); + expandableProperties.add(Pair.of("\\$\\{" + key + '}', entry.getValue())); } + context.put(MICRONAUT_INTERNAL_EXPANDBLE_PROPERTIES, expandableProperties); + context.put(MICRONAUT_INTERNAL_EXPANDBLE_PROPERTIES_LOADED, true); + return expandableProperties; } - private static OpenAPI resolvePropertyPlaceHolders(OpenAPI openAPI, VisitorContext visitorContext) { - List> expandableProperties = getExpandableProperties(visitorContext); + private static OpenAPI resolvePropertyPlaceHolders(OpenAPI openAPI, VisitorContext context) { + List> expandableProperties = getExpandableProperties(context); if (CollectionUtils.isNotEmpty(expandableProperties)) { - visitorContext.info("Expanding properties: " + expandableProperties); + context.info("Expanding properties: " + expandableProperties); } - JsonNode root = resolvePlaceholders(ConvertUtils.getYamlMapper().convertValue(openAPI, ObjectNode.class), s -> expandProperties(s, expandableProperties, visitorContext)); + JsonNode root = resolvePlaceholders(ConvertUtils.getYamlMapper().convertValue(openAPI, ObjectNode.class), s -> expandProperties(s, expandableProperties, context)); return ConvertUtils.getYamlMapper().convertValue(root, OpenAPI.class); } @@ -1448,6 +1451,7 @@ public void finish(VisitorContext context) { openApi = openApiInfo.getOpenApi(); openApi = postProcessOpenApi(openApi, context); + openApiInfo.setOpenApi(openApi); // need to set test reference to openApi after post-processing if (Utils.isTestMode()) { Utils.setTestReference(openApi); @@ -1501,7 +1505,7 @@ public void finish(VisitorContext context) { fileName = fileName.replaceAll("\\$\\{group}", openApiInfo.getGroupName() != null ? openApiInfo.getGroupName() : StringUtils.EMPTY_STRING); } } - if (fileName.contains("${")) { + if (fileName.contains(Utils.PLACEHOLDER_PREFIX)) { context.warn("Can't set some placeholders in fileName: " + fileName, null); } diff --git a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiApplicationVisitorSpec.groovy b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiApplicationVisitorSpec.groovy index dd05f5834b..595e97da24 100644 --- a/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiApplicationVisitorSpec.groovy +++ b/openapi/src/test/groovy/io/micronaut/openapi/visitor/OpenApiApplicationVisitorSpec.groovy @@ -969,4 +969,47 @@ class MyBean {} cleanup: System.clearProperty(OpenApiApplicationVisitor.MICRONAUT_OPENAPI_CONFIG_FILE) } + + void "test build OpenAPIDefinition with placeholders"() { + + given: "An API definition" + Utils.testReference = null + Utils.testReferences = null + System.setProperty(OpenApiApplicationVisitor.MICRONAUT_OPENAPI_CONFIG_FILE, "openapi-placeholders.properties") + + when: + buildBeanDefinition('test.MyBean', ''' +package test; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.info.Info; + +@OpenAPIDefinition( + info = @Info( + title = "broken-micronaut-openapi-expand", + version = "${api.version}", + description = "${another.placeholder.value}" + ) +) +class Application { +} + +@jakarta.inject.Singleton +class MyBean {} +''') + then: + Utils.testReference != null + + when: + OpenAPI openAPI = Utils.testReference + + then: + openAPI.info + openAPI.info.title == "broken-micronaut-openapi-expand" + openAPI.info.description == 'monkey' + openAPI.info.version == '2.2.2' + + cleanup: + System.clearProperty(OpenApiApplicationVisitor.MICRONAUT_OPENAPI_CONFIG_FILE) + } }