Skip to content

Commit b296ac7

Browse files
committed
Fix autditable subconfigs
1 parent b2fad31 commit b296ac7

File tree

6 files changed

+144
-19
lines changed

6 files changed

+144
-19
lines changed

src/main/java/com/coditory/quark/config/AuditableConfig.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.coditory.quark.config;
22

33
import org.jetbrains.annotations.NotNull;
4+
import org.jetbrains.annotations.Nullable;
45

56
import java.util.Arrays;
67
import java.util.List;
@@ -35,6 +36,13 @@ public Config getSubConfig(@NotNull String path) {
3536
return super.getSubConfig(path);
3637
}
3738

39+
@Nullable
40+
@Override
41+
public Config getSubConfigOrNull(@NotNull String path) {
42+
markAsUsedProperty(path);
43+
return super.getSubConfigOrNull(path);
44+
}
45+
3846
@NotNull
3947
@Override
4048
public Config getSubConfigOrEmpty(@NotNull String path) {
@@ -98,7 +106,11 @@ public void failOnUnusedProperties() {
98106
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
99107
if (!properties.isEmpty()) {
100108
int limit = 5;
101-
List<String> names = properties.keySet().stream().sorted().toList();
109+
Path configPath = Path.parse(super.getPath());
110+
List<String> names = properties.keySet().stream()
111+
.map(p -> configPath.add(p).toString())
112+
.sorted()
113+
.toList();
102114
String limitedNames = names.stream().limit(5).collect(Collectors.joining("\n"));
103115
if (names.size() > limit) {
104116
limitedNames = limitedNames + "\n...";

src/main/java/com/coditory/quark/config/Config.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import org.jetbrains.annotations.Nullable;
55

66
import java.util.LinkedHashMap;
7-
import java.util.List;
87
import java.util.Map;
98
import java.util.Objects;
109
import java.util.Optional;
@@ -76,6 +75,9 @@ static Config of(@NotNull Map<String, ?> entries) {
7675
@NotNull
7776
Optional<Config> getSubConfigAsOptional(@NotNull String path);
7877

78+
@NotNull
79+
String getPath();
80+
7981
@NotNull
8082
MapConfigNode getRootNode();
8183

@@ -90,13 +92,18 @@ default AuditableConfig auditable() {
9092
}
9193

9294
default <T> T mapAuditableSubConfigOrNull(@NotNull String path, @NotNull Function<AuditableConfig, T> configMapper) {
93-
Config subconfig = this.getSubConfigOrNull(path);
95+
Config subconfig = this.getSubConfigOrNull(path);
9496
if (subconfig == null) {
9597
return null;
9698
}
9799
return subconfig.mapAuditable(configMapper);
98100
}
99101

102+
default <T> T mapAuditableSubConfigOrEmpty(@NotNull String path, @NotNull Function<AuditableConfig, T> configMapper) {
103+
Config subconfig = this.getSubConfigOrEmpty(path);
104+
return subconfig.mapAuditable(configMapper);
105+
}
106+
100107
default <T> T mapAuditableSubConfig(@NotNull String path, @NotNull Function<AuditableConfig, T> configMapper) {
101108
return this.getSubConfig(path).mapAuditable(configMapper);
102109
}

src/main/java/com/coditory/quark/config/ConfigDecorator.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ public Optional<Config> getSubConfigAsOptional(@NotNull String path) {
7070
return config.getSubConfigAsOptional(path);
7171
}
7272

73+
@NotNull
74+
@Override
75+
public String getPath() {
76+
return config.getPath();
77+
}
78+
7379
@NotNull
7480
@Override
7581
public MapConfigNode getRootNode() {

src/main/java/com/coditory/quark/config/Path.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ static Path parse(String path) {
4949
}
5050

5151
private static Path parseOrThrow(String path) {
52+
if (path.isEmpty()) return Path.root();
5253
String[] chunks = path.split("\\.");
5354
List<PathElement> result = new ArrayList<>(chunks.length);
5455
for (String chunk : chunks) {
@@ -143,7 +144,7 @@ Path subPath(int index) {
143144
}
144145

145146
PathElement getFirstElement() {
146-
return elements.get(0);
147+
return elements.getFirst();
147148
}
148149

149150
List<String> getPropertyNames() {
@@ -260,7 +261,7 @@ public Integer getIndex() {
260261

261262
@Override
262263
public void append(StringBuilder builder) {
263-
if (builder.length() > 0) {
264+
if (!builder.isEmpty()) {
264265
builder.append(".");
265266
}
266267
builder.append(name);

src/main/java/com/coditory/quark/config/ResolvableConfig.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public static ResolvableConfig empty() {
3030
return EMPTY;
3131
}
3232

33+
private final Path path;
3334
private final ConfigValueParser valueParser;
3435
private final MapConfigNode root;
3536
private final ConfigEntryMapper secretHidingValueMapper;
@@ -39,11 +40,27 @@ public static ResolvableConfig empty() {
3940
ConfigValueParser valueParser,
4041
ConfigEntryMapper secretHidingValueMapper
4142
) {
43+
this(Path.root(), root, valueParser, secretHidingValueMapper);
44+
}
45+
46+
ResolvableConfig(
47+
Path path,
48+
MapConfigNode root,
49+
ConfigValueParser valueParser,
50+
ConfigEntryMapper secretHidingValueMapper
51+
) {
52+
this.path = expectNonNull(path);
4253
this.root = expectNonNull(root);
4354
this.valueParser = expectNonNull(valueParser);
4455
this.secretHidingValueMapper = expectNonNull(secretHidingValueMapper);
4556
}
4657

58+
@NotNull
59+
@Override
60+
public String getPath() {
61+
return this.path.toString();
62+
}
63+
4764
@NotNull
4865
@Override
4966
public Map<String, Object> toMap() {
@@ -67,24 +84,25 @@ public MapConfigNode getRootNode() {
6784
@Override
6885
public Config withHiddenSecrets() {
6986
MapConfigNode mapped = root.mapLeaves(Path.root(), secretHidingValueMapper);
70-
return new ResolvableConfig(mapped, valueParser, secretHidingValueMapper);
87+
return new ResolvableConfig(path, mapped, valueParser, secretHidingValueMapper);
7188
}
7289

73-
private ResolvableConfig withRoot(MapConfigNode root) {
90+
private ResolvableConfig withRoot(Path path, MapConfigNode root) {
7491
return Objects.equals(this.root, root)
7592
? this
76-
: new ResolvableConfig(root, valueParser, secretHidingValueMapper);
93+
: new ResolvableConfig(this.path.add(path), root, valueParser, secretHidingValueMapper);
7794
}
7895

7996
@NotNull
8097
private Optional<List<Config>> extractSubConfigListAsOptional(@NotNull String path) {
8198
expectNonBlank(path, "path");
82-
return root.getOptionalNode(Path.parse(path))
99+
Path parsedPath = Path.parse(path);
100+
return root.getOptionalNode(parsedPath)
83101
.filter(node -> node instanceof ListConfigNode)
84102
.map(node -> (ListConfigNode) node)
85103
.map(node -> node.children().stream()
86104
.filter(child -> child instanceof MapConfigNode)
87-
.map(child -> withRoot((MapConfigNode)child))
105+
.map(child -> withRoot(parsedPath, (MapConfigNode) child))
88106
.collect(Collectors.toList()));
89107
}
90108

@@ -100,7 +118,7 @@ public Config getSubConfig(@NotNull String path) {
100118
@Override
101119
public Config getSubConfigOrEmpty(@NotNull String path) {
102120
expectNonBlank(path, "path");
103-
return getSubConfig(path, withRoot(MapConfigNode.emptyRoot()));
121+
return getSubConfig(path, withRoot(Path.parse(path), MapConfigNode.emptyRoot()));
104122
}
105123

106124
@Nullable
@@ -123,9 +141,10 @@ public Config getSubConfig(@NotNull String path, @NotNull Config defaultValue) {
123141
@Override
124142
public Optional<Config> getSubConfigAsOptional(@NotNull String path) {
125143
expectNonBlank(path, "path");
126-
return root.getOptionalNode(Path.parse(path))
144+
Path parsedPath = Path.parse(path);
145+
return root.getOptionalNode(parsedPath)
127146
.filter(node -> node instanceof MapConfigNode)
128-
.map(node -> withRoot((MapConfigNode) node));
147+
.map(node -> withRoot(parsedPath, (MapConfigNode) node));
129148
}
130149

131150
@SuppressWarnings("unchecked")

src/test/groovy/com/coditory/quark/config/AuditableConfigSpec.groovy

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,7 @@ class AuditableConfigSpec extends Specification {
100100

101101
def "should detected all unused config properties in passed config consumer"() {
102102
given:
103-
Config config = Config.builder()
104-
.putAll(a: "A", b: "B")
105-
.build()
103+
Config config = Config.of(a: "A", b: "B")
106104
when:
107105
config.consumeAuditable {
108106
it.getString("a")
@@ -122,9 +120,7 @@ class AuditableConfigSpec extends Specification {
122120

123121
def "should detected all unused config properties in passed config mapper"() {
124122
given:
125-
Config config = Config.builder()
126-
.putAll(a: "A", b: "B")
127-
.build()
123+
Config config = Config.of(a: "A", b: "B")
128124
when:
129125
String result = config.mapAuditable {
130126
it.getString("a")
@@ -142,4 +138,88 @@ class AuditableConfigSpec extends Specification {
142138
noExceptionThrown()
143139
result == "AB"
144140
}
141+
142+
def "should detected all unused config properties from subconfig"() {
143+
given:
144+
Config config = Config.of(
145+
a: "A",
146+
b: [
147+
c: "C",
148+
d: "D",
149+
e: [
150+
f: "F",
151+
g: "G"
152+
]
153+
])
154+
when:
155+
config.mapAuditableSubConfig("b") {
156+
it.getString("c")
157+
it.getString("e.g")
158+
"Test"
159+
}
160+
then:
161+
ConfigUnusedPropertiesException e = thrown(ConfigUnusedPropertiesException)
162+
e.message == "Detected unused config properties:\nb.d\nb.e.f"
163+
164+
when:
165+
config.mapAuditableSubConfig("b") {
166+
it.getString("c")
167+
it.getString("d")
168+
it.getString("e.f")
169+
it.getString("e.g")
170+
}
171+
then:
172+
noExceptionThrown()
173+
}
174+
175+
def "should reason two auditable subconfigs"() {
176+
given:
177+
Config config = Config.of(
178+
a: "A",
179+
b: [
180+
e: [
181+
f: "F",
182+
g: "G"
183+
]
184+
])
185+
when:
186+
config.mapAuditableSubConfig("b") {
187+
it.mapAuditableSubConfig("e") {
188+
it.getString("g")
189+
}
190+
}
191+
then:
192+
ConfigUnusedPropertiesException e = thrown(ConfigUnusedPropertiesException)
193+
e.message == "Detected unused config properties:\nb.e.f"
194+
195+
when:
196+
config.mapAuditableSubConfig("b") {
197+
it.mapAuditableSubConfig("e") {
198+
it.getString("g")
199+
it.getString("f")
200+
}
201+
}
202+
then:
203+
noExceptionThrown()
204+
205+
when:
206+
config.mapAuditableSubConfig("b") {
207+
it.mapAuditableSubConfigOrNull("e") {
208+
it.getString("g")
209+
it.getString("f")
210+
}
211+
}
212+
then:
213+
noExceptionThrown()
214+
215+
when:
216+
config.mapAuditableSubConfig("b") {
217+
it.mapAuditableSubConfigOrEmpty("e") {
218+
it.getString("g")
219+
it.getString("f")
220+
}
221+
}
222+
then:
223+
noExceptionThrown()
224+
}
145225
}

0 commit comments

Comments
 (0)