From df15eca7f0830cf7a240c60dbbdf74a0070ff293 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Thu, 7 Apr 2022 16:55:05 +0100 Subject: [PATCH] Supported indexed properties with Env variables (#743) --- .../config/common/utils/StringUtil.java | 20 ++++++ .../config/ConfigMappingProvider.java | 71 ++++++++++++++++++- .../config/ConfigMappingInterfaceTest.java | 17 +++-- .../smallrye/config/EnvConfigSourceTest.java | 25 +++++++ 4 files changed, 128 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/io/smallrye/config/common/utils/StringUtil.java b/common/src/main/java/io/smallrye/config/common/utils/StringUtil.java index 73830f31f..24bead265 100644 --- a/common/src/main/java/io/smallrye/config/common/utils/StringUtil.java +++ b/common/src/main/java/io/smallrye/config/common/utils/StringUtil.java @@ -106,11 +106,30 @@ public static String replaceNonAlphanumericByUnderscores(final String name) { public static String toLowerCaseAndDotted(final String name) { int length = name.length(); + int beginSegment = 0; boolean quotesOpen = false; StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { char c = name.charAt(i); if ('_' == c) { + String segment = sb.substring(beginSegment, i); + try { + Integer.parseInt(segment); + sb.replace(beginSegment - 1, beginSegment, "[").append("]"); + + int j = i + 1; + if (j < length) { + if ('_' == name.charAt(j)) { + sb.append("."); + i = j; + } + } + + continue; + } catch (NumberFormatException e) { + // Ignore + } + int j = i + 1; if (j < length) { if ('_' == name.charAt(j) && !quotesOpen) { @@ -129,6 +148,7 @@ public static String toLowerCaseAndDotted(final String name) { } else { sb.append("."); } + beginSegment = j; } else { sb.append(Character.toLowerCase(c)); } diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java index 8b1651d61..9bb9aad98 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java @@ -900,12 +900,64 @@ private static Set additionalMappedProperties(final Set mappedPr Set additionalMappedProperties = new HashSet<>(); // Look for unmatched properties if we can find one in the Env ones and add it for (String mappedProperty : mappedProperties) { + Set matchedEnvProperties = new HashSet<>(); for (String envProperty : envProperties) { - // TODO - handled indexed. if (envProperty.equalsIgnoreCase(replaceNonAlphanumericByUnderscores(mappedProperty))) { additionalMappedProperties.add(mappedProperty); + matchedEnvProperties.add(envProperty); + break; + } + + NameIterator ni = new NameIterator(mappedProperty); + StringBuilder sb = new StringBuilder(); + while (ni.hasNext()) { + String propertySegment = ni.getNextSegment(); + if (isIndexed(propertySegment)) { + // A mapped index property is represented as foo.bar[*] or foo.bar[*].baz + // The env property is represented as FOO_BAR_0_ or FOO_BAR_0__BAZ + // We need to match these somehow + int position = ni.getPosition(); + int indexStart = propertySegment.indexOf("[") + position + 1; + // If the segment is indexed, we try to match all previous segments with the env candidates + if (envProperty.length() >= indexStart + && envProperty.toLowerCase().startsWith(replaceNonAlphanumericByUnderscores( + sb + propertySegment.substring(0, indexStart - position - 1) + "_"))) { + + // Search for the ending _ to retrieve the possible index + int indexEnd = -1; + for (int i = indexStart + 1; i < envProperty.length(); i++) { + if (envProperty.charAt(i) == '_') { + indexEnd = i; + break; + } + } + + // Extract the index from the env property + // We don't care if this is numeric, it will be validated on the mapping retrieval + String index = envProperty.substring(indexStart + 1, indexEnd); + sb.append(propertySegment, 0, propertySegment.indexOf("[") + 1) + .append(index) + .append("]"); + } + } else { + sb.append(propertySegment); + } + + ni.next(); + + if (ni.hasNext()) { + sb.append("."); + } + } + + String mappedPropertyToMatch = sb.toString(); + if (envProperty.equalsIgnoreCase(replaceNonAlphanumericByUnderscores(mappedPropertyToMatch))) { + additionalMappedProperties.add(mappedPropertyToMatch); + matchedEnvProperties.add(envProperty); + // We cannot break here because if there are indexed properties they may match multiple envs } } + envProperties.removeAll(matchedEnvProperties); } return additionalMappedProperties; @@ -939,6 +991,23 @@ private static String anyIfIndexed(final String propertyName) { return builder.toString(); } + private static boolean isIndexed(final String propertyName) { + int indexStart = propertyName.indexOf("["); + int indexEnd = propertyName.indexOf("]"); + if (indexStart != -1 && indexEnd != -1) { + String index = propertyName.substring(indexStart + 1, indexEnd); + if (index.equals("*")) { + return true; + } + try { + Integer.parseInt(index); + } catch (NumberFormatException e) { + return false; + } + } + return false; + } + private static boolean validateUnknown(final boolean validateUnknown, final SmallRyeConfig config) { return config.getOptionalValue(SMALLRYE_CONFIG_MAPPING_VALIDATE_UNKNOWN, Boolean.class) .orElse(validateUnknown); diff --git a/implementation/src/test/java/io/smallrye/config/ConfigMappingInterfaceTest.java b/implementation/src/test/java/io/smallrye/config/ConfigMappingInterfaceTest.java index 42ab3ff3f..26b2e9c38 100644 --- a/implementation/src/test/java/io/smallrye/config/ConfigMappingInterfaceTest.java +++ b/implementation/src/test/java/io/smallrye/config/ConfigMappingInterfaceTest.java @@ -1337,7 +1337,7 @@ interface RestClientConfig { KeystoreConfig keystore(); - //List endpoints(); + List endpoints(); interface KeystoreConfig { Optional type(); @@ -1349,6 +1349,8 @@ interface KeystoreConfig { interface Endpoint { String path(); + + List methods(); } } } @@ -1360,8 +1362,9 @@ void envPropertiesWithoutDottedProperties() { put("MY_APP_REST_CONFIG_MY_CLIENT_BASE_URI", "http://localhost:8080"); put("MY_APP_REST_CONFIG_MY_CLIENT_KEYSTORE_PATH", "config/keystores/my-keys.p12"); put("MY_APP_REST_CONFIG_MY_CLIENT_KEYSTORE_PASSWORD", "p@ssw0rd"); - put("MY_APP_REST_CONFIG_MY_CLIENT_ENDPOINTS_1_PATH", "/hello"); - //put("my-app.rest-config.my-client.endpoints[1].path", "/hello"); + put("MY_APP_REST_CONFIG_MY_CLIENT_ENDPOINTS_0__PATH", "/hello"); + put("MY_APP_REST_CONFIG_MY_CLIENT_ENDPOINTS_0__METHODS_0_", "GET"); + put("MY_APP_REST_CONFIG_MY_CLIENT_ENDPOINTS_0__METHODS_1_", "POST"); } }; @@ -1379,13 +1382,19 @@ void envPropertiesWithoutDottedProperties() { assertTrue(properties.contains("my-app.rest-config.my-client.base-uri")); assertTrue(properties.contains("my-app.rest-config.my-client.keystore.path")); assertTrue(properties.contains("my-app.rest-config.my-client.keystore.password")); + assertTrue(properties.contains("my-app.rest-config.my-client.endpoints[0].path")); + assertTrue(properties.contains("my-app.rest-config.my-client.endpoints[0].methods[0]")); + assertTrue(properties.contains("my-app.rest-config.my-client.endpoints[0].methods[1]")); MyRestClientConfig mapping = config.getConfigMapping(MyRestClientConfig.class); assertTrue(mapping.client().isPresent()); assertEquals(URI.create("http://localhost:8080"), mapping.client().get().baseUri()); assertEquals(Paths.get("config/keystores/my-keys.p12"), mapping.client().get().keystore().path()); assertEquals("p@ssw0rd", mapping.client().get().keystore().password()); - //assertFalse(mapping.client().get().endpoints().isEmpty()); + assertFalse(mapping.client().get().endpoints().isEmpty()); + assertEquals("/hello", mapping.client().get().endpoints().get(0).path()); + assertEquals("GET", mapping.client().get().endpoints().get(0).methods().get(0)); + assertEquals("POST", mapping.client().get().endpoints().get(0).methods().get(1)); } @ConfigMapping(prefix = "optionals") diff --git a/implementation/src/test/java/io/smallrye/config/EnvConfigSourceTest.java b/implementation/src/test/java/io/smallrye/config/EnvConfigSourceTest.java index 79e5ea679..585382c2e 100644 --- a/implementation/src/test/java/io/smallrye/config/EnvConfigSourceTest.java +++ b/implementation/src/test/java/io/smallrye/config/EnvConfigSourceTest.java @@ -23,6 +23,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import java.util.NoSuchElementException; import java.util.stream.StreamSupport; @@ -95,4 +98,26 @@ void ordinal() { assertTrue(configSource instanceof EnvConfigSource); assertEquals(configSource.getOrdinal(), 301); } + + @Test + void indexed() { + Map env = new HashMap() { + { + put("INDEXED_0_", "foo"); + put("INDEXED_0__PROP", "bar"); + put("INDEXED_0__PROPS_0_", "0"); + put("INDEXED_0__PROPS_1_", "1"); + } + }; + + EnvConfigSource envConfigSource = new EnvConfigSource(env, 300); + SmallRyeConfig config = new SmallRyeConfigBuilder() + .addDefaultInterceptors() + .withSources(envConfigSource) + .build(); + + assertTrue(config.getValues("indexed", String.class, ArrayList::new).contains("foo")); + assertTrue(config.getValues("indexed[0].props", String.class, ArrayList::new).contains("0")); + assertTrue(config.getValues("indexed[0].props", String.class, ArrayList::new).contains("1")); + } }