diff --git a/config/src/main/java/com/typesafe/config/ConfigDocument.java b/config/src/main/java/com/typesafe/config/ConfigDocument.java index d8403122..4524e8e2 100644 --- a/config/src/main/java/com/typesafe/config/ConfigDocument.java +++ b/config/src/main/java/com/typesafe/config/ConfigDocument.java @@ -25,12 +25,29 @@ public interface ConfigDocument { * * @param path the path at which to set the desired value * @param newValue the value to set at the desired path, represented as a string. This - * string will be parsed into a ConfigNode, and the text will be inserted + * string will be parsed into a ConfigNode using the same options used to + * parse the entire document, and the text will be inserted * as-is into the document, with leading and trailing whitespace removed. + * If a concatenation is passed in for newValue but the document was parsed + * with JSON, the first value in the concatenation will be parsed and inserted + * into the ConfigDocument. * @return a copy of the ConfigDocument with the desired value at the desired path */ ConfigDocument setValue(String path, String newValue); + /** + * Returns a new ConfigDocument that is a copy of the current ConfigDocument, + * but with the desired value set at the desired path as with {@link #setValue(String, String)}, + * but takes a ConfigValue instead of a string. + * + * @param path the path at which to set the desired value + * @param newValue the value to set at the desired path, represented as a ConfigValue. + * The rendered text of the ConfigValue will be inserted into the + * ConfigDocument. + * @return a copy of the ConfigDocument with the desired value at the desired path + */ + ConfigDocument setValue(String path, ConfigValue newValue); + /** * The original text of the input, modified if necessary with * any replaced or added values. diff --git a/config/src/main/java/com/typesafe/config/impl/AbstractConfigNode.java b/config/src/main/java/com/typesafe/config/impl/AbstractConfigNode.java index 3214e362..01f9d196 100644 --- a/config/src/main/java/com/typesafe/config/impl/AbstractConfigNode.java +++ b/config/src/main/java/com/typesafe/config/impl/AbstractConfigNode.java @@ -16,4 +16,9 @@ abstract class AbstractConfigNode implements ConfigNode { } return origText.toString(); } + + @Override + final public boolean equals(Object other) { + return other instanceof AbstractConfigNode && render().equals(((AbstractConfigNode)other).render()); + } } diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigDocumentParser.java b/config/src/main/java/com/typesafe/config/impl/ConfigDocumentParser.java index b4c7c395..06339236 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigDocumentParser.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigDocumentParser.java @@ -644,7 +644,16 @@ final class ConfigDocumentParser { throw parseError("Empty value"); } if (flavor == ConfigSyntax.JSON) { - return parseValue(t); + AbstractConfigNodeValue node = parseValue(t); + t = nextToken(); + while (Tokens.isIgnoredWhitespace(t) || Tokens.isNewline(t) || isUnquotedWhitespace(t)) { + t = nextToken(); + } + if (t == Tokens.END) { + return node; + } else { + throw parseError("Tried to parse a concatenation. Concatenations not allowed in valid JSON"); + } } else { putBack(t); ArrayList nodes = new ArrayList(); diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigNodeObject.java b/config/src/main/java/com/typesafe/config/impl/ConfigNodeObject.java index 03bb72c7..ee61734b 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigNodeObject.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigNodeObject.java @@ -33,7 +33,7 @@ final class ConfigNodeObject extends ConfigNodeComplexValue { if (node.value() instanceof ConfigNodeObject) { Path remainingPath = desiredPath.subPath(key.length()); childrenCopy.set(i, node.replaceValue(((ConfigNodeObject)node.value()).changeValueOnPath(remainingPath, valueCopy))); - if (valueCopy != null && node.render() != super.children.get(i).render()) + if (valueCopy != null && !node.equals(super.children.get(i))) valueCopy = null; } } diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfigDocument.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfigDocument.java index 28ef5926..d5f25fcc 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfigDocument.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfigDocument.java @@ -30,6 +30,10 @@ final class SimpleConfigDocument implements ConfigDocument { return new SimpleConfigDocument(((ConfigNodeObject)configNodeTree).setValueOnPath(path, parsedValue, parseOptions.getSyntax()), parseOptions); } + public ConfigDocument setValue(String path, ConfigValue newValue) { + return setValue(path, newValue.render()); + } + public String render() { return configNodeTree.render(); } diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentParserTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentParserTest.scala index 4ee8393a..1f6963a8 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentParserTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentParserTest.scala @@ -1,76 +1,75 @@ package com.typesafe.config.impl -import com.typesafe.config.{ConfigException, ConfigSyntax, ConfigParseOptions} +import com.typesafe.config.{ ConfigException, ConfigSyntax, ConfigParseOptions } import org.junit.Assert._ import org.junit.Test class ConfigDocumentParserTest extends TestUtils { - private def parseTest(origText: String) { - val node = ConfigDocumentParser.parse(tokenize(origText)) - assertEquals(origText, node.render()) - } + private def parseTest(origText: String) { + val node = ConfigDocumentParser.parse(tokenize(origText)) + assertEquals(origText, node.render()) + } - private def parseJSONFailuresTest(origText: String, containsMessage: String) { - var exceptionThrown = false - try { - ConfigDocumentParser.parse(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) - } catch { - case e: Exception => - exceptionThrown = true - assertTrue(e.isInstanceOf[ConfigException]) - assertTrue(e.getMessage.contains(containsMessage)) - } - assertTrue(exceptionThrown) - } + private def parseJSONFailuresTest(origText: String, containsMessage: String) { + var exceptionThrown = false + try { + ConfigDocumentParser.parse(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) + } catch { + case e: Exception => + exceptionThrown = true + assertTrue(e.isInstanceOf[ConfigException]) + assertTrue(e.getMessage.contains(containsMessage)) + } + assertTrue(exceptionThrown) + } - private def parseSimpleValueTest(origText: String, finalText: String = null) { - val expectedRenderedText = if (finalText == null) origText else finalText - val node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults()) - assertEquals(expectedRenderedText, node.render()) - assertTrue(node.isInstanceOf[AbstractConfigNodeValue]) + private def parseSimpleValueTest(origText: String, finalText: String = null) { + val expectedRenderedText = if (finalText == null) origText else finalText + val node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults()) + assertEquals(expectedRenderedText, node.render()) + assertTrue(node.isInstanceOf[AbstractConfigNodeValue]) - val nodeJSON = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) - assertEquals(expectedRenderedText, nodeJSON.render()) - assertTrue(nodeJSON.isInstanceOf[AbstractConfigNodeValue]) - } + val nodeJSON = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) + assertEquals(expectedRenderedText, nodeJSON.render()) + assertTrue(nodeJSON.isInstanceOf[AbstractConfigNodeValue]) + } - private def parseComplexValueTest(origText: String) { - val node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults()) - assertEquals(origText, node.render()) - assertTrue(node.isInstanceOf[AbstractConfigNodeValue]) + private def parseComplexValueTest(origText: String) { + val node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults()) + assertEquals(origText, node.render()) + assertTrue(node.isInstanceOf[AbstractConfigNodeValue]) - val nodeJSON = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) - assertEquals(origText, nodeJSON.render()) - assertTrue(nodeJSON.isInstanceOf[AbstractConfigNodeValue]) - } + val nodeJSON = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) + assertEquals(origText, nodeJSON.render()) + assertTrue(nodeJSON.isInstanceOf[AbstractConfigNodeValue]) + } - private def parseSingleValueInvalidJSONTest(origText: String, containsMessage: String) { - val node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults()) - assertEquals(origText, node.render()) + private def parseSingleValueInvalidJSONTest(origText: String, containsMessage: String) { + val node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults()) + assertEquals(origText, node.render()) - var exceptionThrown = false - try { - ConfigDocumentParser.parse(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) - } catch { - case e: Exception => - exceptionThrown = true - assertTrue(e.isInstanceOf[ConfigException]) - assertTrue(e.getMessage.contains(containsMessage)) - } - assertTrue(exceptionThrown) - } + var exceptionThrown = false + try { + ConfigDocumentParser.parse(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) + } catch { + case e: Exception => + exceptionThrown = true + assertTrue(e.isInstanceOf[ConfigException]) + assertTrue(e.getMessage.contains(containsMessage)) + } + assertTrue(exceptionThrown) + } + @Test + def parseSuccess { + parseTest("foo:bar") + parseTest(" foo : bar ") + parseTest("""include "foo.conf" """) - @Test - def parseSuccess { - parseTest("foo:bar") - parseTest(" foo : bar ") - parseTest("""include "foo.conf" """) - - // Can parse a map with all simple types - parseTest( - """ + // Can parse a map with all simple types + parseTest( + """ aUnquoted : bar aString = "qux" aNum:123 @@ -81,15 +80,15 @@ class ConfigDocumentParserTest extends TestUtils { aSub = ${a.b} include "foo.conf" """) - parseTest("{}") - parseTest("{foo:bar}") - parseTest("{ foo : bar }") - parseTest("{foo:bar} ") - parseTest("""{include "foo.conf"}""") + parseTest("{}") + parseTest("{foo:bar}") + parseTest("{ foo : bar }") + parseTest("{foo:bar} ") + parseTest("""{include "foo.conf"}""") - //Can parse a map with all simple types - parseTest( - """{ + //Can parse a map with all simple types + parseTest( + """{ aUnquoted : bar aString = "qux" aNum:123 @@ -101,9 +100,9 @@ class ConfigDocumentParserTest extends TestUtils { include "foo.conf" }""") - // Test that maps can be nested within other maps - parseTest( - """ + // Test that maps can be nested within other maps + parseTest( + """ foo.bar.baz : { qux : "abcdefg" "abc".def."ghi" : 123 @@ -112,34 +111,34 @@ class ConfigDocumentParserTest extends TestUtils { qux = 123.456 """) - // Test that comments can be parsed in maps - parseTest( - """{ + // Test that comments can be parsed in maps + parseTest( + """{ foo: bar // This is a comment baz:qux // This is another comment }""") - // Basic array tests - parseTest("[]") - parseTest("[foo]") + // Basic array tests + parseTest("[]") + parseTest("[foo]") - // Test trailing comment and whitespace - parseTest("[foo,]") - parseTest("[foo,] ") + // Test trailing comment and whitespace + parseTest("[foo,]") + parseTest("[foo,] ") - // Can parse arrays with all simple types - parseTest("""[foo, bar,"qux", 123,123.456, true,false, null, ${a.b}]""") - parseTest("""[foo, bar,"qux" , 123 , 123.456, true,false, null, ${a.b} ]""") + // Can parse arrays with all simple types + parseTest("""[foo, bar,"qux", 123,123.456, true,false, null, ${a.b}]""") + parseTest("""[foo, bar,"qux" , 123 , 123.456, true,false, null, ${a.b} ]""") - // Basic concatenation tests - parseTest("[foo bar baz qux]") - parseTest("{foo: foo bar baz qux}") - parseTest("[abc 123 123.456 null true false [1, 2, 3] {a:b}, 2]") + // Basic concatenation tests + parseTest("[foo bar baz qux]") + parseTest("{foo: foo bar baz qux}") + parseTest("[abc 123 123.456 null true false [1, 2, 3] {a:b}, 2]") - // Complex node with all types test - parseTest( - """{ + // Complex node with all types test + parseTest( + """{ foo: bar baz qux ernie // The above was a concatenation @@ -172,9 +171,9 @@ class ConfigDocumentParserTest extends TestUtils { // Did I cover everything? }""") - // Can correctly parse a JSON string - val origText = - """{ + // Can correctly parse a JSON string + val origText = + """{ "foo": "bar", "baz": 123, "qux": true, @@ -185,74 +184,82 @@ class ConfigDocumentParserTest extends TestUtils { ] } """ - val node = ConfigDocumentParser.parse(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) - assertEquals(origText, node.render()) - } + val node = ConfigDocumentParser.parse(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) + assertEquals(origText, node.render()) + } - @Test - def parseJSONFailures() { - // JSON does not support concatenations - parseJSONFailuresTest("""{ "foo": 123 456 789 } """, "Expecting close brace } or a comma") + @Test + def parseJSONFailures() { + // JSON does not support concatenations + parseJSONFailuresTest("""{ "foo": 123 456 789 } """, "Expecting close brace } or a comma") - // JSON must begin with { or [ - parseJSONFailuresTest(""""a": 123, "b": 456"""", "Document must have an object or array at root") + // JSON must begin with { or [ + parseJSONFailuresTest(""""a": 123, "b": 456"""", "Document must have an object or array at root") - // JSON does not support unquoted text - parseJSONFailuresTest("""{"foo": unquotedtext}""", "Token not allowed in valid JSON") + // JSON does not support unquoted text + parseJSONFailuresTest("""{"foo": unquotedtext}""", "Token not allowed in valid JSON") - // JSON does not support substitutions - parseJSONFailuresTest("""{"foo": ${"a.b"}}""", "Substitutions (${} syntax) not allowed in JSON") + // JSON does not support substitutions + parseJSONFailuresTest("""{"foo": ${"a.b"}}""", "Substitutions (${} syntax) not allowed in JSON") - // JSON does not support multi-element paths - parseJSONFailuresTest("""{"foo"."bar": 123}""", "Token not allowed in valid JSON") + // JSON does not support multi-element paths + parseJSONFailuresTest("""{"foo"."bar": 123}""", "Token not allowed in valid JSON") - // JSON does not support = - parseJSONFailuresTest("""{"foo"=123}""", """Key '"foo"' may not be followed by token: '='""") + // JSON does not support = + parseJSONFailuresTest("""{"foo"=123}""", """Key '"foo"' may not be followed by token: '='""") - // JSON does not support += - parseJSONFailuresTest("""{"foo" += "bar"}""", """Key '"foo"' may not be followed by token: '+='""") + // JSON does not support += + parseJSONFailuresTest("""{"foo" += "bar"}""", """Key '"foo"' may not be followed by token: '+='""") - // JSON does not support duplicate keys - parseJSONFailuresTest("""{"foo" : 123, "foo": 456}""", "JSON does not allow duplicate fields") + // JSON does not support duplicate keys + parseJSONFailuresTest("""{"foo" : 123, "foo": 456}""", "JSON does not allow duplicate fields") - // JSON does not support trailing commas - parseJSONFailuresTest("""{"foo" : 123,}""", "expecting a field name after a comma, got a close brace } instead") + // JSON does not support trailing commas + parseJSONFailuresTest("""{"foo" : 123,}""", "expecting a field name after a comma, got a close brace } instead") - // JSON does not support empty documents - parseJSONFailuresTest("", "Empty document") + // JSON does not support empty documents + parseJSONFailuresTest("", "Empty document") - } + } - @Test - def parseSingleValues() { - // Parse simple values - parseSimpleValueTest("123") - parseSimpleValueTest("123.456") - parseSimpleValueTest(""""a string"""") - parseSimpleValueTest("true") - parseSimpleValueTest("false") - parseSimpleValueTest("null") + @Test + def parseSingleValues() { + // Parse simple values + parseSimpleValueTest("123") + parseSimpleValueTest("123.456") + parseSimpleValueTest(""""a string"""") + parseSimpleValueTest("true") + parseSimpleValueTest("false") + parseSimpleValueTest("null") - // Parse Simple Value throws out trailing and leading whitespace - parseSimpleValueTest(" 123", "123") - parseSimpleValueTest("123 ", "123") - parseSimpleValueTest(" 123 ", "123") + // Parse Simple Value throws out trailing and leading whitespace + parseSimpleValueTest(" 123", "123") + parseSimpleValueTest("123 ", "123") + parseSimpleValueTest(" 123 ", "123") - // Can parse complex values - parseComplexValueTest("""{"a": "b"}""") - parseComplexValueTest("""["a","b","c"]""") + // Can parse complex values + parseComplexValueTest("""{"a": "b"}""") + parseComplexValueTest("""["a","b","c"]""") - parseSingleValueInvalidJSONTest("unquotedtext", "Token not allowed in valid JSON") - parseSingleValueInvalidJSONTest("${a.b}", "Substitutions (${} syntax) not allowed in JSON") + parseSingleValueInvalidJSONTest("unquotedtext", "Token not allowed in valid JSON") + parseSingleValueInvalidJSONTest("${a.b}", "Substitutions (${} syntax) not allowed in JSON") - // Check that concatenations are handled by CONF parsing - var origText = "123 456 unquotedtext abc" - var node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults()) - assertEquals(origText, node.render()) + // Check that concatenations are handled by CONF parsing + var origText = "123 456 unquotedtext abc" + var node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults()) + assertEquals(origText, node.render()) - // Check that concatenations in JSON will only return the first value passed in - origText = "123 456 789" - node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) - assertEquals("123", node.render()) - } + // Check that concatenations in JSON will throw an error + origText = "123 456 789" + var exceptionThrown = false + try { + node = ConfigDocumentParser.parseValue(tokenize(origText), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) + } catch { + case e: Exception => + exceptionThrown = true + assertTrue(e.isInstanceOf[ConfigException]) + assertTrue(e.getMessage.contains("Tried to parse a concatenation. Concatenations not allowed in valid JSON")) + } + assertTrue(exceptionThrown) + } } diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala index eb25f362..2c819f9a 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala @@ -1,10 +1,10 @@ package com.typesafe.config.impl -import java.io.{BufferedReader, FileReader} +import java.io.{ BufferedReader, FileReader } import java.nio.charset.StandardCharsets -import java.nio.file.{Paths, Files} +import java.nio.file.{ Paths, Files } -import com.typesafe.config.{ConfigException, ConfigSyntax, ConfigParseOptions, ConfigDocumentFactory} +import com.typesafe.config._ import org.junit.Assert._ import org.junit.Test @@ -36,7 +36,7 @@ class ConfigDocumentTest extends TestUtils { // Can handle parsing/replacement with a complicated map var origText = - """{ + """{ "a":123, "b": 123.456, "c": true, @@ -53,7 +53,7 @@ class ConfigDocumentTest extends TestUtils { } }""" var finalText = - """{ + """{ "a":123, "b": 123.456, "c": true, @@ -72,10 +72,9 @@ class ConfigDocumentTest extends TestUtils { configDocumentReplaceConfTest(origText, finalText, """"i am now a string"""", "h.b.a") configDocumentReplaceJsonTest(origText, finalText, """"i am now a string"""", "h.b.a") - // Can handle replacing values with maps finalText = - """{ + """{ "a":123, "b": 123.456, "c": true, @@ -96,7 +95,7 @@ class ConfigDocumentTest extends TestUtils { // Can handle replacing values with arrays finalText = - """{ + """{ "a":123, "b": 123.456, "c": true, @@ -116,7 +115,7 @@ class ConfigDocumentTest extends TestUtils { configDocumentReplaceJsonTest(origText, finalText, "[1,2,3,4,5]", "h.b.a") finalText = - """{ + """{ "a":123, "b": 123.456, "c": true, @@ -133,7 +132,7 @@ class ConfigDocumentTest extends TestUtils { } }""" configDocumentReplaceConfTest(origText, finalText, - "this is a concatenation 123 456 {a:b} [1,2,3] {a: this is another 123 concatenation null true}", "h.b.a") + "this is a concatenation 123 456 {a:b} [1,2,3] {a: this is another 123 concatenation null true}", "h.b.a") } @Test @@ -167,7 +166,20 @@ class ConfigDocumentTest extends TestUtils { } @Test - def configDocumentReplaceFailure { + def configDocumentSetNewConfigValue { + val origText = "{\"a\": \"b\"}" + val finalText = "{\"a\": 12}" + val configDocHOCON = ConfigDocumentFactory.parseString(origText) + val configDocJSON = ConfigDocumentFactory.parseString(origText, ConfigParseOptions.defaults.setSyntax(ConfigSyntax.JSON)) + val newValue = ConfigValueFactory.fromAnyRef(12) + assertEquals(origText, configDocHOCON.render()) + assertEquals(origText, configDocJSON.render()) + assertEquals(finalText, configDocHOCON.setValue("a", newValue).render()) + assertEquals(finalText, configDocJSON.setValue("a", newValue).render()) + } + + @Test + def configDocumentArrayReplaceFailure { // Attempting a replace on a ConfigDocument parsed from an array throws an error val origText = "[1, 2, 3, 4, 5]" val document = ConfigDocumentFactory.parseString(origText) @@ -175,10 +187,46 @@ class ConfigDocumentTest extends TestUtils { try { document.setValue("a", "1") } catch { - case e: Exception => - exceptionThrown = true - assertTrue(e.isInstanceOf[ConfigException]) - assertTrue(e.getMessage.contains("ConfigDocument had an array at the root level")) + case e: Exception => + exceptionThrown = true + assertTrue(e.isInstanceOf[ConfigException]) + assertTrue(e.getMessage.contains("ConfigDocument had an array at the root level")) + } + assertTrue(exceptionThrown) + } + + @Test + def configDocumentJSONReplaceFailure { + // Attempting a replace on a ConfigDocument parsed from JSON with a value using HOCON syntax + // will fail + val origText = "{\"foo\": \"bar\", \"baz\": \"qux\"}" + val document = ConfigDocumentFactory.parseString(origText, ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) + var exceptionThrown = false + try { + document.setValue("foo", "unquoted") + } catch { + case e: Exception => + exceptionThrown = true + assertTrue(e.isInstanceOf[ConfigException]) + assertTrue(e.getMessage.contains("Token not allowed in valid JSON")) + } + assertTrue(exceptionThrown) + } + + @Test + def configDocumentJSONReplaceWithConcatenationFailure { + // Attempting a replace on a ConfigDocument parsed from JSON with a concatenation will + // fail + val origText = "{\"foo\": \"bar\", \"baz\": \"qux\"}" + val document = ConfigDocumentFactory.parseString(origText, ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)) + var exceptionThrown = false + try { + document.setValue("foo", "1 2 3 concatenation") + } catch { + case e: Exception => + exceptionThrown = true + assertTrue(e.isInstanceOf[ConfigException]) + assertTrue(e.getMessage.contains("Tried to parse a concatenation. Concatenations not allowed in valid JSON")) } assertTrue(exceptionThrown) } @@ -205,4 +253,5 @@ class ConfigDocumentTest extends TestUtils { val configDocumentFile = ConfigDocumentFactory.parseFile(resourceFile("/test03.conf")) assertEquals(configDocumentFile.render(), configDocument.render()) } + } diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigNodeTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigNodeTest.scala index fa292cf8..40cb76f1 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigNodeTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigNodeTest.scala @@ -33,8 +33,8 @@ class ConfigNodeTest extends TestUtils { private def topLevelValueReplaceTest(value: AbstractConfigNodeValue, newValue: AbstractConfigNodeValue, key: String = "foo") { val complexNodeChildren = List(nodeOpenBrace, - nodeKeyValuePair(nodeWhitespace(" "), configNodeKey(key),value, nodeWhitespace(" ")), - nodeCloseBrace) + nodeKeyValuePair(nodeWhitespace(" "), configNodeKey(key), value, nodeWhitespace(" ")), + nodeCloseBrace) val complexNode = configNodeObject(complexNodeChildren) val newNode = complexNode.setValueOnPath(key, newValue) val origText = "{ " + key + " : " + value.render() + " }" @@ -151,8 +151,8 @@ class ConfigNodeTest extends TestUtils { // Ensure maps can be replaced val nestedMap = configNodeObject(List(nodeOpenBrace, configNodeKey("abc"), - nodeColon, configNodeSimpleValue(tokenString("a string")), - nodeCloseBrace)) + nodeColon, configNodeSimpleValue(tokenString("a string")), + nodeCloseBrace)) topLevelValueReplaceTest(nestedMap, nodeInt(10)) topLevelValueReplaceTest(nodeInt(10), nestedMap) topLevelValueReplaceTest(array, nestedMap) @@ -168,7 +168,6 @@ class ConfigNodeTest extends TestUtils { topLevelValueReplaceTest(array, concatenation) topLevelValueReplaceTest(concatenation, array) - //Ensure a key with format "a.b" will be properly replaced topLevelValueReplaceTest(nodeInt(10), nestedMap, "foo.bar") } @@ -195,25 +194,25 @@ class ConfigNodeTest extends TestUtils { def replaceNestedNodes() { // Test that all features of node replacement in a map work in a complex map containing nested maps val origText = "foo : bar\nbaz : {\n\t\"abc.def\" : 123\n\t//This is a comment about the below setting\n\n\tabc : {\n\t\t" + - "def : \"this is a string\"\n\t\tghi : ${\"a.b\"}\n\t}\n}\nbaz.abc.ghi : 52\nbaz.abc.ghi : 53\n}" + "def : \"this is a string\"\n\t\tghi : ${\"a.b\"}\n\t}\n}\nbaz.abc.ghi : 52\nbaz.abc.ghi : 53\n}" val lowestLevelMap = configNodeObject(List(nodeOpenBrace, nodeLine(6), - nodeKeyValuePair(nodeWhitespace("\t\t"), configNodeKey("def"), configNodeSimpleValue(tokenString("this is a string")), nodeLine(7)), - nodeKeyValuePair(nodeWhitespace("\t\t"), configNodeKey("ghi"), configNodeSimpleValue(tokenKeySubstitution("a.b")), nodeLine(8)), - nodeWhitespace("\t"), nodeCloseBrace)) + nodeKeyValuePair(nodeWhitespace("\t\t"), configNodeKey("def"), configNodeSimpleValue(tokenString("this is a string")), nodeLine(7)), + nodeKeyValuePair(nodeWhitespace("\t\t"), configNodeKey("ghi"), configNodeSimpleValue(tokenKeySubstitution("a.b")), nodeLine(8)), + nodeWhitespace("\t"), nodeCloseBrace)) val higherLevelMap = configNodeObject(List(nodeOpenBrace, nodeLine(2), - nodeKeyValuePair(nodeWhitespace("\t"), configNodeKey("\"abc.def\""), configNodeSimpleValue(tokenInt(123)), nodeLine(3)), - nodeWhitespace("\t"), configNodeBasic(tokenCommentDoubleSlash("This is a comment about the below setting")), - nodeLine(4), nodeLine(5), - nodeKeyValuePair(nodeWhitespace("\t"), configNodeKey("abc"), lowestLevelMap, nodeLine(9)), nodeWhitespace(""), - nodeCloseBrace)) - val origNode = configNodeObject(List(nodeKeyValuePair(configNodeKey("foo"), configNodeSimpleValue(tokenUnquoted("bar")), nodeLine(1)), - nodeKeyValuePair(configNodeKey("baz"), higherLevelMap, nodeLine(10)), - nodeKeyValuePair(configNodeKey("baz.abc.ghi"), configNodeSimpleValue(tokenInt(52)), nodeLine(11)), - nodeKeyValuePair(configNodeKey("baz.abc.ghi"), configNodeSimpleValue(tokenInt(53)), nodeLine(12)), - nodeCloseBrace)) + nodeKeyValuePair(nodeWhitespace("\t"), configNodeKey("\"abc.def\""), configNodeSimpleValue(tokenInt(123)), nodeLine(3)), + nodeWhitespace("\t"), configNodeBasic(tokenCommentDoubleSlash("This is a comment about the below setting")), + nodeLine(4), nodeLine(5), + nodeKeyValuePair(nodeWhitespace("\t"), configNodeKey("abc"), lowestLevelMap, nodeLine(9)), nodeWhitespace(""), + nodeCloseBrace)) + val origNode = configNodeObject(List(nodeKeyValuePair(configNodeKey("foo"), configNodeSimpleValue(tokenUnquoted("bar")), nodeLine(1)), + nodeKeyValuePair(configNodeKey("baz"), higherLevelMap, nodeLine(10)), + nodeKeyValuePair(configNodeKey("baz.abc.ghi"), configNodeSimpleValue(tokenInt(52)), nodeLine(11)), + nodeKeyValuePair(configNodeKey("baz.abc.ghi"), configNodeSimpleValue(tokenInt(53)), nodeLine(12)), + nodeCloseBrace)) assertEquals(origText, origNode.render()) val finalText = "foo : bar\nbaz : {\n\t\"abc.def\" : true\n\t//This is a comment about the below setting\n\n\tabc : {\n\t\t" + - "def : false\n\t\n\"this.does.not.exist@@@+$#\" : {\nend : doesnotexist\n}\n}\n}\nbaz.abc.ghi : randomunquotedString\n}" + "def : false\n\t\n\"this.does.not.exist@@@+$#\" : {\nend : doesnotexist\n}\n}\n}\nbaz.abc.ghi : randomunquotedString\n}" //Can replace settings in nested maps // Paths with quotes in the name are treated as a single Path, rather than multiple sub-paths