In checkValid() allow indexed obj to override a list

Fixes #101
This commit is contained in:
Havoc Pennington 2013-11-06 09:38:53 -05:00
parent a5df1fb8a2
commit c5a9ad6974
2 changed files with 90 additions and 20 deletions

View File

@ -728,7 +728,8 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
return false;
}
} else if (reference instanceof SimpleConfigList) {
if (value instanceof SimpleConfigList) {
// objects may be convertible to lists if they have numeric keys
if (value instanceof SimpleConfigList || value instanceof SimpleConfigObject) {
return true;
} else {
return false;
@ -772,6 +773,25 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
}
}
private static void checkListCompatibility(Path path, SimpleConfigList listRef,
SimpleConfigList listValue, List<ConfigException.ValidationProblem> accumulator) {
if (listRef.isEmpty() || listValue.isEmpty()) {
// can't verify type, leave alone
} else {
AbstractConfigValue refElement = listRef.get(0);
for (ConfigValue elem : listValue) {
AbstractConfigValue e = (AbstractConfigValue) elem;
if (!haveCompatibleTypes(refElement, e)) {
addProblem(accumulator, path, e.origin(), "List at '" + path.render()
+ "' contains wrong value type, expecting list of "
+ getDesc(refElement) + " but got element of type " + getDesc(e));
// don't add a problem for every last array element
break;
}
}
}
}
private static void checkValid(Path path, ConfigValue reference, AbstractConfigValue value,
List<ConfigException.ValidationProblem> accumulator) {
// Unmergeable is supposed to be impossible to encounter in here
@ -784,22 +804,16 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
} else if (reference instanceof SimpleConfigList && value instanceof SimpleConfigList) {
SimpleConfigList listRef = (SimpleConfigList) reference;
SimpleConfigList listValue = (SimpleConfigList) value;
if (listRef.isEmpty() || listValue.isEmpty()) {
// can't verify type, leave alone
} else {
AbstractConfigValue refElement = listRef.get(0);
for (ConfigValue elem : listValue) {
AbstractConfigValue e = (AbstractConfigValue) elem;
if (!haveCompatibleTypes(refElement, e)) {
addProblem(accumulator, path, e.origin(), "List at '" + path.render()
+ "' contains wrong value type, expecting list of "
+ getDesc(refElement) + " but got element of type "
+ getDesc(e));
// don't add a problem for every last array element
break;
}
}
}
checkListCompatibility(path, listRef, listValue, accumulator);
} else if (reference instanceof SimpleConfigList && value instanceof SimpleConfigObject) {
// attempt conversion of indexed object to list
SimpleConfigList listRef = (SimpleConfigList) reference;
AbstractConfigValue listValue = DefaultTransformer.transform(value,
ConfigValueType.LIST);
if (listValue instanceof SimpleConfigList)
checkListCompatibility(path, listRef, (SimpleConfigList) listValue, accumulator);
else
addWrongType(accumulator, reference, value, path);
}
} else {
addWrongType(accumulator, reference, value, path);

View File

@ -15,8 +15,8 @@ class ValidationTest extends TestUtils {
sealed abstract class Problem(path: String, line: Int) {
def check(p: ConfigException.ValidationProblem) {
assertEquals(path, p.path())
assertEquals(line, p.origin().lineNumber())
assertEquals("matching path", path, p.path())
assertEquals("matching line", line, p.origin().lineNumber())
}
protected def assertMessage(p: ConfigException.ValidationProblem, re: String) {
@ -58,7 +58,8 @@ class ValidationTest extends TestUtils {
for ((problem, expected) <- problems zip expecteds) {
expected.check(problem)
}
assertEquals(expecteds.size, problems.size)
assertEquals("found expected validation problems, got '" + problems + "' and expected '" + expecteds + "'",
expecteds.size, problems.size)
}
@Test
@ -118,4 +119,59 @@ class ValidationTest extends TestUtils {
assertTrue("expected different message, got: " + e.getMessage,
e.getMessage.contains("resolve"))
}
@Test
def validationCatchesListOverriddenWithNumber() {
val reference = parseConfig("""{ a : [{},{},{}] }""")
val conf = parseConfig("""{ a : 42 }""")
val e = intercept[ConfigException.ValidationFailed] {
conf.checkValid(reference)
}
val expecteds = Seq(WrongType("a", 1, "list", "number"))
checkException(e, expecteds)
}
@Test
def validationCatchesListOverriddenWithDifferentList() {
val reference = parseConfig("""{ a : [true,false,false] }""")
val conf = parseConfig("""{ a : [42,43] }""")
val e = intercept[ConfigException.ValidationFailed] {
conf.checkValid(reference)
}
val expecteds = Seq(WrongElementType("a", 1, "boolean", "number"))
checkException(e, expecteds)
}
@Test
def validationAllowsListOverriddenWithSameTypeList() {
val reference = parseConfig("""{ a : [1,2,3] }""")
val conf = parseConfig("""{ a : [4,5] }""")
conf.checkValid(reference)
}
@Test
def validationCatchesListOverriddenWithNoIndexesObject() {
val reference = parseConfig("""{ a : [1,2,3] }""")
val conf = parseConfig("""{ a : { notANumber: foo } }""")
val e = intercept[ConfigException.ValidationFailed] {
conf.checkValid(reference)
}
val expecteds = Seq(WrongType("a", 1, "list", "object"))
checkException(e, expecteds)
}
@Test
def validationAllowsListOverriddenWithIndexedObject() {
val reference = parseConfig("""{ a : [a,b,c] }""")
val conf = parseConfig("""{ a : { "0" : x, "1" : y } }""")
conf.checkValid(reference)
assertEquals("got the sequence from overriding list with indexed object",
Seq("x", "y"), conf.getStringList("a").asScala)
}
}