Skip to content

Commit 08b6cbc

Browse files
committed
Return the whole key when a section key contains the separator
A child section handed out by getKeysSections carries its key in the escaped path form, but getSectionKey and getParentSection split on a raw lastIndexOf of the separator. A key that legitimately holds the separator (a server id like "eu.west") then came back as its trailing half, and the parent was cut inside the escape, leaving a dangling backslash. Route both through the dotted-path grammar (leaf/parent), which already splits on the last UNescaped separator and unescapes the key. Resolution was never affected; only these two decomposition accessors were.
1 parent 08aae0b commit 08b6cbc

2 files changed

Lines changed: 85 additions & 7 deletions

File tree

‎src/main/java/br/com/finalcraft/everyconfig/config/section/ConfigSection.java‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import br.com.finalcraft.everyconfig.config.Config;
66
import br.com.finalcraft.everyconfig.config.MigrationResult;
77
import br.com.finalcraft.everyconfig.core.comment.CommentType;
8+
import br.com.finalcraft.everyconfig.core.tree.DPath;
89
import com.fasterxml.jackson.databind.JsonNode;
910

1011
import java.util.List;
@@ -42,17 +43,17 @@ public String getPath() {
4243
return path;
4344
}
4445

45-
/** The leaf key of this section's path (its last segment). */
46+
/**
47+
* The leaf key of this section's path, as the LITERAL key: a separator inside it was escaped on the
48+
* way in, so it is unescaped on the way out and the key comes back whole.
49+
*/
4650
public String getSectionKey() {
47-
final char sep = config.pathSeparator();
48-
final int i = path.lastIndexOf(sep);
49-
return i < 0 ? path : path.substring(i + 1);
51+
return DPath.leaf(path);
5052
}
5153

54+
/** This section's parent, cutting at the last UNescaped separator so an escaped one stays a key. */
5255
public ConfigSection getParentSection() {
53-
final char sep = config.pathSeparator();
54-
final int i = path.lastIndexOf(sep);
55-
return new ConfigSection(config, i < 0 ? "" : path.substring(0, i));
56+
return new ConfigSection(config, DPath.parent(path));
5657
}
5758

5859
/** Absolute path for a sub-path within this section. */
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package br.com.finalcraft.everyconfig.config.section;
2+
3+
import br.com.finalcraft.everyconfig.config.Config;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.util.Set;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
10+
/**
11+
* A key that legitimately holds the path separator ({@code "eu.west"}) travels the path grammar escaped
12+
* ({@code servers.eu\.west}). These pin the round-trip: whoever hands out a child section must be able to
13+
* ask it back for its own key and its parent, and get the literal key and the real parent - not the two
14+
* halves the escape was there to prevent splitting.
15+
*/
16+
class ConfigSectionPathGrammarTest {
17+
18+
/** The escaped path form of the single key {@code "eu.west"} nested under {@code "servers"}. */
19+
private static final String ESCAPED_PATH = "servers.eu\\.west";
20+
21+
private static Config configWithADottedKey() {
22+
final Config cfg = Config.inMemory();
23+
cfg.setValue(ESCAPED_PATH + ".port", 25565);
24+
return cfg;
25+
}
26+
27+
private static ConfigSection onlyChildOfServers(final Config cfg) {
28+
final Set<ConfigSection> children = cfg.getKeysSections("servers");
29+
assertEquals(1, children.size(), "the dotted key must be ONE child, not two");
30+
return children.iterator().next();
31+
}
32+
33+
@Test
34+
void aChildHandedOutByGetKeysSectionsKnowsItsOwnLiteralKey() {
35+
final ConfigSection child = onlyChildOfServers(configWithADottedKey());
36+
37+
assertEquals(ESCAPED_PATH, child.getPath(), "the path stays in escaped form");
38+
assertEquals("eu.west", child.getSectionKey(),
39+
"the key must come back whole - splitting it is what the escape prevents");
40+
}
41+
42+
@Test
43+
void theParentOfADottedKeyIsNotCutInsideTheEscape() {
44+
final ConfigSection child = onlyChildOfServers(configWithADottedKey());
45+
46+
assertEquals("servers", child.getParentSection().getPath(),
47+
"cutting at the escaped separator would leave a dangling backslash");
48+
}
49+
50+
@Test
51+
void aChildKeyIsUsableToReadBackThroughItsOwnSection() {
52+
final ConfigSection child = onlyChildOfServers(configWithADottedKey());
53+
54+
assertEquals(25565, child.getInt("port"), "the section must resolve against the whole key");
55+
}
56+
57+
@Test
58+
void anOrdinaryKeyIsUnaffected() {
59+
final Config cfg = Config.inMemory();
60+
cfg.setValue("servers.lobby.port", 25565);
61+
62+
final ConfigSection child = onlyChildOfServers(cfg);
63+
64+
assertEquals("lobby", child.getSectionKey());
65+
assertEquals("servers", child.getParentSection().getPath());
66+
}
67+
68+
@Test
69+
void aRootSectionHasNoKeyAndNoParentPath() {
70+
final Config cfg = Config.inMemory();
71+
cfg.setValue("top", 1);
72+
73+
final ConfigSection top = cfg.getConfigSection("top");
74+
assertEquals("top", top.getSectionKey());
75+
assertEquals("", top.getParentSection().getPath());
76+
}
77+
}

0 commit comments

Comments
 (0)