blob: 8f3390128d83140e80e6cd2404a7c747dc276126 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ignite.internal.configuration.hocon;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
import static org.apache.ignite.internal.configuration.hocon.HoconConverter.hoconSource;
import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigRenderOptions;
import com.typesafe.config.ConfigValue;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.ignite.configuration.ConfigurationChangeException;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.InjectedName;
import org.apache.ignite.configuration.annotation.Name;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
import org.apache.ignite.configuration.annotation.PolymorphicConfig;
import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
import org.apache.ignite.configuration.annotation.PolymorphicId;
import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.internal.configuration.ConfigurationRegistry;
import org.apache.ignite.internal.configuration.ConfigurationTreeGenerator;
import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
import org.apache.ignite.internal.configuration.validation.TestConfigurationValidator;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
/**
* Tests for the {@link HoconConverter}.
*/
public class HoconConverterTest {
/**
* Hocon root configuration schema.
*/
@ConfigurationRoot(rootName = "root", type = LOCAL)
public static class HoconRootConfigurationSchema {
@NamedConfigValue(syntheticKeyName = "a")
public HoconArraysConfigurationSchema arraysList;
@NamedConfigValue(syntheticKeyName = "p")
public HoconPrimitivesConfigurationSchema primitivesList;
@NamedConfigValue(syntheticKeyName = "poly")
public HoconPolymorphicConfigurationSchema polymorphicCfg;
}
/**
* Configuration schema for testing the support of arrays of primitives.
*/
@Config
public static class HoconArraysConfigurationSchema {
@Value(hasDefault = true)
public boolean[] booleans = {false};
@Value(hasDefault = true)
public byte[] bytes = {0};
@Value(hasDefault = true)
public short[] shorts = {0};
@Value(hasDefault = true)
public int[] ints = {0};
@Value(hasDefault = true)
public long[] longs = {0L};
@Value(hasDefault = true)
public char[] chars = {0};
@Value(hasDefault = true)
public float[] floats = {0};
@Value(hasDefault = true)
public double[] doubles = {0};
@Value(hasDefault = true)
public String[] strings = {""};
@Value(hasDefault = true)
public UUID[] uuids = {new UUID(1111, 2222)};
}
/**
* Configuration schema for testing the support of primitives.
*/
@Config
public static class HoconPrimitivesConfigurationSchema {
@Value(hasDefault = true)
public boolean booleanVal = false;
@Value(hasDefault = true)
public byte byteVal = 0;
@Value(hasDefault = true)
public short shortVal = 0;
@Value(hasDefault = true)
public int intVal = 0;
@Value(hasDefault = true)
public long longVal = 0L;
@Value(hasDefault = true)
public char charVal = 0;
@Value(hasDefault = true)
public float floatVal = 0;
@Value(hasDefault = true)
public double doubleVal = 0;
@Value(hasDefault = true)
public String stringVal = "";
@Value(hasDefault = true)
public UUID uuidVal = new UUID(100, 200);
}
/**
* Configuration schema for testing the support of polymorphic configuration.
*/
@PolymorphicConfig
public static class HoconPolymorphicConfigurationSchema {
@PolymorphicId
public String typeId;
}
/**
* Configuration schema for testing the support of polymorphic configuration.
*/
@PolymorphicConfigInstance("first")
public static class HoconFirstPolymorphicInstanceConfigurationSchema extends HoconPolymorphicConfigurationSchema {
@Value(hasDefault = true)
public int longVal = 0;
}
/**
* Configuration schema for testing the support of polymorphic configuration.
*/
@PolymorphicConfigInstance("second")
public static class HoconSecondPolymorphicInstanceConfigurationSchema extends HoconPolymorphicConfigurationSchema {
@Value(hasDefault = true)
public int intVal = 0;
}
/**
* Hocon root configuration schema for testing the support of {@link InjectedName}.
*/
@ConfigurationRoot(rootName = "rootInjectedName", type = LOCAL)
public static class HoconInjectedNameRootConfigurationSchema {
@Name("testDefault")
@org.apache.ignite.configuration.annotation.ConfigValue
public HoconInjectedNameConfigurationSchema nested;
@NamedConfigValue(syntheticKeyName = "superName")
public HoconInjectedNameConfigurationSchema nestedNamed;
}
/**
* Configuration schema for testing the support of {@link InjectedName}.
*/
@Config
public static class HoconInjectedNameConfigurationSchema {
@InjectedName
public String someName;
}
private static ConfigurationRegistry registry;
private static HoconRootConfiguration configuration;
private static HoconInjectedNameRootConfiguration injectedNameRootConfig;
/**
* Before all.
*/
@BeforeAll
public static void beforeAll() {
registry = new ConfigurationRegistry(
List.of(HoconRootConfiguration.KEY, HoconInjectedNameRootConfiguration.KEY),
new TestConfigurationStorage(LOCAL),
new ConfigurationTreeGenerator(
List.of(HoconRootConfiguration.KEY, HoconInjectedNameRootConfiguration.KEY),
List.of(),
List.of(
HoconFirstPolymorphicInstanceConfigurationSchema.class,
HoconSecondPolymorphicInstanceConfigurationSchema.class
)
),
new TestConfigurationValidator()
);
assertThat(registry.startAsync(), willCompleteSuccessfully());
configuration = registry.getConfiguration(HoconRootConfiguration.KEY);
injectedNameRootConfig = registry.getConfiguration(HoconInjectedNameRootConfiguration.KEY);
}
/**
* After all.
*/
@AfterAll
public static void after() {
assertThat(registry.stopAsync(), willCompleteSuccessfully());
registry = null;
configuration = null;
injectedNameRootConfig = null;
}
/**
* Before each.
*/
@BeforeEach
public void before() throws Exception {
configuration.change(cfg -> cfg
.changePrimitivesList(list -> list.namedListKeys().forEach(list::delete))
.changeArraysList(list -> list.namedListKeys().forEach(list::delete))
.changePolymorphicCfg(c -> {})
).get(1, SECONDS);
injectedNameRootConfig.change(c -> c
.changeNested(c0 -> {})
.changeNestedNamed(list -> list.namedListKeys().forEach(list::delete))
).get(1, SECONDS);
}
@Test
public void toHoconBasic() {
assertEquals(
"root{arraysList=[],polymorphicCfg=[],primitivesList=[]},rootInjectedName{nested{},nestedNamed=[]}",
asHoconStr(List.of())
);
assertEquals("arraysList=[],polymorphicCfg=[],primitivesList=[]", asHoconStr(List.of("root")));
assertEquals("[]", asHoconStr(List.of("root", "arraysList")));
assertThrowsIllegalArgException(
() -> HoconConverter.represent(registry.superRoot(), List.of("doot")),
"Configuration value 'doot' has not been found"
);
assertThrowsIllegalArgException(
() -> HoconConverter.represent(registry.superRoot(), List.of("root", "x")),
"Configuration value 'root.x' has not been found"
);
assertEquals("null", asHoconStr(List.of("root", "primitivesList", "foo")));
}
/**
* Tests that the {@code HoconConverter} supports serialization of {@link String}, {@link UUID} and primitives.
*/
@Test
public void testHoconPrimitivesSerialization() throws Exception {
configuration.change(cfg -> cfg
.changePrimitivesList(primitivesList -> primitivesList
.create("name", primitives -> {
})
)
).get(1, SECONDS);
var basePath = List.of("root", "primitivesList", "name");
UUID uuid = new UUID(100, 200);
assertEquals(
"booleanVal=false,byteVal=0,charVal=\"\\u0000\",doubleVal=0.0,floatVal=0,intVal=0,longVal=0,shortVal=0,stringVal=\"\""
+ ",uuidVal=\"" + uuid + "\"",
asHoconStr(basePath)
);
assertEquals("false", asHoconStr(basePath, "booleanVal"));
assertEquals("0", asHoconStr(basePath, "byteVal"));
assertEquals("0", asHoconStr(basePath, "shortVal"));
assertEquals("0", asHoconStr(basePath, "intVal"));
assertEquals("0", asHoconStr(basePath, "longVal"));
assertEquals("\"\\u0000\"", asHoconStr(basePath, "charVal"));
assertEquals("0", asHoconStr(basePath, "floatVal"));
assertEquals("0.0", asHoconStr(basePath, "doubleVal"));
assertEquals("\"\"", asHoconStr(basePath, "stringVal"));
assertEquals("\"" + uuid + "\"", asHoconStr(basePath, "uuidVal"));
}
/**
* Tests that the {@code HoconConverter} supports serialization of arrays of {@link String}, {@link UUID} and primitives.
*/
@Test
public void testHoconArraysSerialization() throws Exception {
configuration.change(cfg -> cfg
.changeArraysList(arraysList -> arraysList
.create("name", arrays -> {
})
)
).get(1, SECONDS);
var basePath = List.of("root", "arraysList", "name");
UUID uuid = new UUID(1111, 2222);
assertEquals(
"booleans=[false],bytes=[0],chars=[\"\\u0000\"],doubles=[0.0],floats=[0],ints=[0],longs=[0],shorts=[0],strings=[\"\"]"
+ ",uuids=[\"" + uuid + "\"]",
asHoconStr(basePath)
);
assertEquals("[false]", asHoconStr(basePath, "booleans"));
assertEquals("[0]", asHoconStr(basePath, "bytes"));
assertEquals("[0]", asHoconStr(basePath, "shorts"));
assertEquals("[0]", asHoconStr(basePath, "ints"));
assertEquals("[0]", asHoconStr(basePath, "longs"));
assertEquals("[\"\\u0000\"]", asHoconStr(basePath, "chars"));
assertEquals("[0]", asHoconStr(basePath, "floats"));
assertEquals("[0.0]", asHoconStr(basePath, "doubles"));
assertEquals("[\"\"]", asHoconStr(basePath, "strings"));
assertEquals("[\"" + uuid + "\"]", asHoconStr(basePath, "uuids"));
}
/**
* Retrieves the HOCON configuration located at the given path.
*/
private static String asHoconStr(List<String> basePath, String... path) {
List<String> fullPath = Stream.concat(basePath.stream(), Arrays.stream(path)).collect(Collectors.toList());
ConfigValue hoconCfg = HoconConverter.represent(registry.superRoot(), fullPath);
return hoconCfg.render(ConfigRenderOptions.concise().setJson(false));
}
@Test
public void fromHoconBasic() {
// Wrong names:
assertThrowsIllegalArgException(
() -> change("doot : {}"),
"'doot' configuration root doesn't exist"
);
assertThrowsIllegalArgException(
() -> change("root.foo : {}"),
"'root' configuration doesn't have the 'foo' sub-configuration"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.x = 1"),
"'root.arraysList.name' configuration doesn't have the 'x' sub-configuration"
);
// Wrong node types:
assertThrowsIllegalArgException(
() -> change("root = foo"),
"'root' is expected to be a composite configuration node, not a single value"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList = foo"),
"'root.arraysList' is expected to be a composite configuration node, not a single value"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name = foo"),
"'root.arraysList.name' is expected to be a composite configuration node, not a single value"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.ints = {}"),
"'int[]' is expected as a type for the 'root.arraysList.name.ints' configuration value"
);
// Wrong ordered named list syntax:
assertThrowsIllegalArgException(
() -> change("root.arraysList = [1]"),
"'root.arraysList[0]' is expected to be a composite configuration node, not a single value"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList = [{}]"),
"'root.arraysList[0].a' configuration value is mandatory and must be a String"
);
}
/**
* Tests that the {@code HoconConverter} supports deserialization of {@link String}, {@link UUID} and primitives.
*/
@Test
public void testHoconPrimitivesDeserialization() throws Throwable {
change("root.primitivesList = [{p = name}]");
HoconPrimitivesConfiguration primitives = configuration.primitivesList().get("name");
assertNotNull(primitives);
change("root.primitivesList.name.booleanVal = true");
assertThat(primitives.booleanVal().value(), is(true));
change("root.primitivesList.name.byteVal = 123");
assertThat(primitives.byteVal().value(), is((byte) 123));
change("root.primitivesList.name.shortVal = 12345");
assertThat(primitives.shortVal().value(), is((short) 12345));
change("root.primitivesList.name.intVal = 12345");
assertThat(primitives.intVal().value(), is(12345));
change("root.primitivesList.name.longVal = 12345678900");
assertThat(primitives.longVal().value(), is(12345678900L));
change("root.primitivesList.name.charVal = p");
assertThat(primitives.charVal().value(), is('p'));
change("root.primitivesList.name.floatVal = 2.5");
assertThat(primitives.floatVal().value(), is(2.5f));
change("root.primitivesList.name.doubleVal = 2.5");
assertThat(primitives.doubleVal().value(), is(2.5d));
change("root.primitivesList.name.stringVal = foo");
assertThat(primitives.stringVal().value(), is("foo"));
UUID newUuid0 = UUID.randomUUID();
UUID newUuid1 = UUID.randomUUID();
change("root.primitivesList.name.uuidVal = " + newUuid0);
assertThat(primitives.uuidVal().value(), is(newUuid0));
change("root.primitivesList.name.uuidVal = \"" + newUuid1 + "\"");
assertThat(primitives.uuidVal().value(), is(newUuid1));
}
/**
* Tests deserialization errors that can happen during the deserialization of primitives.
*/
@Test
public void testInvalidHoconPrimitivesDeserialization() {
assertThrowsIllegalArgException(
() -> change("root.primitivesList.name.booleanVal = \"true\""),
"'boolean' is expected as a type for the 'root.primitivesList.name.booleanVal' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.primitivesList.name.byteVal = 290"),
"Value '290' of 'root.primitivesList.name.byteVal' is out of its declared bounds"
);
assertThrowsIllegalArgException(
() -> change("root.primitivesList.name.byteVal = false"),
"'byte' is expected as a type for the 'root.primitivesList.name.byteVal' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.primitivesList.name.shortVal = 12345678900"),
"Value '12345678900' of 'root.primitivesList.name.shortVal' is out of its declared bounds"
);
assertThrowsIllegalArgException(
() -> change("root.primitivesList.name.shortVal = false"),
"'short' is expected as a type for the 'root.primitivesList.name.shortVal' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.primitivesList.name.intVal = 12345678900"),
"Value '12345678900' of 'root.primitivesList.name.intVal' is out of its declared bounds"
);
assertThrowsIllegalArgException(
() -> change("root.primitivesList.name.intVal = false"),
"'int' is expected as a type for the 'root.primitivesList.name.intVal' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.primitivesList.name.longVal = false"),
"'long' is expected as a type for the 'root.primitivesList.name.longVal' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.primitivesList.name.charVal = 10"),
"'char' is expected as a type for the 'root.primitivesList.name.charVal' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.primitivesList.name.floatVal = false"),
"'float' is expected as a type for the 'root.primitivesList.name.floatVal' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.primitivesList.name.doubleVal = []"),
"'double' is expected as a type for the 'root.primitivesList.name.doubleVal' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.primitivesList.name.stringVal = 10"),
"'String' is expected as a type for the 'root.primitivesList.name.stringVal' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.primitivesList.name.uuidVal = 123"),
"'UUID' is expected as a type for the 'root.primitivesList.name.uuidVal' configuration value"
);
}
/**
* Tests that the {@code HoconConverter} supports deserialization of arrays of {@link String}, {@link UUID} and primitives.
*/
@Test
public void testHoconArraysDeserialization() throws Throwable {
change("root.arraysList = [{a = name}]");
HoconArraysConfiguration arrays = configuration.arraysList().get("name");
assertNotNull(arrays);
change("root.arraysList.name.booleans = [true]");
assertThat(arrays.booleans().value(), is(new boolean[]{true}));
change("root.arraysList.name.bytes = [123]");
assertThat(arrays.bytes().value(), is(new byte[]{123}));
change("root.arraysList.name.shorts = [123]");
assertThat(arrays.shorts().value(), is(new short[]{123}));
change("root.arraysList.name.ints = [12345]");
assertThat(arrays.ints().value(), is(new int[]{12345}));
change("root.arraysList.name.longs = [12345678900]");
assertThat(arrays.longs().value(), is(new long[]{12345678900L}));
change("root.arraysList.name.chars = [p]");
assertThat(arrays.chars().value(), is(new char[]{'p'}));
change("root.arraysList.name.floats = [2.5]");
assertThat(arrays.floats().value(), is(new float[]{2.5f}));
change("root.arraysList.name.doubles = [2.5]");
assertThat(arrays.doubles().value(), is(new double[]{2.5d}));
change("root.arraysList.name.strings = [foo]");
assertThat(arrays.strings().value(), is(new String[]{"foo"}));
UUID newUuid0 = UUID.randomUUID();
UUID newUuid1 = UUID.randomUUID();
change("root.arraysList.name.uuids = [" + newUuid0 + "]");
assertThat(arrays.uuids().value(), is(new UUID[]{newUuid0}));
change("root.arraysList.name.uuids = [\"" + newUuid1 + "\"]");
assertThat(arrays.uuids().value(), is(new UUID[]{newUuid1}));
}
/**
* Tests deserialization errors that can happen during the deserialization of arrays of primitives.
*/
@Test
public void testInvalidHoconArraysDeserialization() {
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.booleans = true"),
"'boolean[]' is expected as a type for the 'root.arraysList.name.booleans' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.booleans = [{}]"),
"'boolean' is expected as a type for the 'root.arraysList.name.booleans[0]' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.bytes = [123, 290]"),
"Value '290' of 'root.arraysList.name.bytes[1]' is out of its declared bounds"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.bytes = false"),
"'byte[]' is expected as a type for the 'root.arraysList.name.bytes' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.shorts = [12345678900]"),
"Value '12345678900' of 'root.arraysList.name.shorts[0]' is out of its declared bounds"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.shorts = [123, false]"),
"'short' is expected as a type for the 'root.arraysList.name.shorts[1]' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.ints = [5, 12345678900]"),
"Value '12345678900' of 'root.arraysList.name.ints[1]' is out of its declared bounds"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.ints = false"),
"'int[]' is expected as a type for the 'root.arraysList.name.ints' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.longs = [foo]"),
"'long' is expected as a type for the 'root.arraysList.name.longs[0]' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.chars = 10"),
"'char[]' is expected as a type for the 'root.arraysList.name.chars' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.chars = [abc]"),
"'char' is expected as a type for the 'root.arraysList.name.chars[0]' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.floats = [1.2, foo]"),
"'float' is expected as a type for the 'root.arraysList.name.floats[1]' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.doubles = foo"),
"'double[]' is expected as a type for the 'root.arraysList.name.doubles' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.strings = 10"),
"'String[]' is expected as a type for the 'root.arraysList.name.strings' configuration value"
);
assertThrowsIllegalArgException(
() -> change("root.arraysList.name.uuids = uuids"),
"'UUID[]' is expected as a type for the 'root.arraysList.name.uuids' configuration value"
);
}
@Test
void testPolymorphicConfig() throws Throwable {
// Check defaults.
assertEquals("arraysList=[],polymorphicCfg=[],primitivesList=[]", asHoconStr(List.of("root")));
// Check change type.
change("root.polymorphicCfg = [{poly = name, typeId = second}]");
assertInstanceOf(HoconSecondPolymorphicInstanceConfiguration.class, configuration.polymorphicCfg().get("name"));
assertEquals("arraysList=[],polymorphicCfg=[{intVal=0,poly=name,typeId=second}],primitivesList=[]", asHoconStr(List.of("root")));
// Check change field.
change("root.polymorphicCfg.name.intVal = 10");
assertEquals("arraysList=[],polymorphicCfg=[{intVal=10,poly=name,typeId=second}],primitivesList=[]", asHoconStr(List.of("root")));
// Check error: unknown typeId.
assertThrowsIllegalArgException(
() -> change("root.polymorphicCfg.name.typeId = ERROR"),
"Polymorphic configuration type is not correct: ERROR"
);
// Check error: try update field from typeId = first.
assertThrowsIllegalArgException(
() -> change("root.polymorphicCfg.name.longVal = 10"),
"'root.polymorphicCfg.name' configuration doesn't have the 'longVal' sub-configuration"
);
}
@Test
void testInjectedName() throws Throwable {
// Check defaults.
assertEquals("nested{},nestedNamed=[]", asHoconStr(List.of("rootInjectedName")));
// Checks get/set field with @InjectedName for nested config.
assertThrowsIllegalArgException(
() -> change("rootInjectedName.nested.someName = testName"),
"rootInjectedName.nested' configuration doesn't have the 'someName' sub-configuration"
);
assertThrowsIllegalArgException(
() -> asHoconStr(List.of("rootInjectedName"), "nested", "someName"),
"Configuration value 'rootInjectedName.nested.someName' has not been found"
);
// Checks get/set field with @InjectedName for nested named config.
change("rootInjectedName.nestedNamed = [{someName = foo}]");
assertEquals("nested{},nestedNamed=[{someName=foo}]", asHoconStr(List.of("rootInjectedName")));
assertEquals("[someName=foo]", asHoconStr(List.of("rootInjectedName", "nestedNamed")));
assertEquals("{}", asHoconStr(List.of("rootInjectedName", "nestedNamed", "foo")));
// Let's check that the NamedConfigValue#syntheticKeyName key will not work.
assertThrowsIllegalArgException(
() -> change("rootInjectedName.nestedNamed = [{superName = foo}]"),
"'rootInjectedName.nestedNamed[0].someName' configuration value is mandatory and must be a String"
);
}
/**
* Updates the configuration using the provided HOCON string.
*/
private static void change(String hocon) throws Throwable {
try {
registry.change(hoconSource(ConfigFactory.parseString(hocon).root())).get(1, SECONDS);
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof ConfigurationChangeException) {
throw cause.getCause();
} else {
throw cause;
}
}
}
private static void assertThrowsIllegalArgException(Executable executable, String msg) {
IllegalArgumentException e = assertThrows(IllegalArgumentException.class, executable);
assertThat(e.getMessage(), containsString(msg));
}
}