Skip to content

Commit

Permalink
Add auditable config
Browse files Browse the repository at this point in the history
  • Loading branch information
pmendelski committed Oct 19, 2022
1 parent 676866d commit 6f05d3e
Show file tree
Hide file tree
Showing 21 changed files with 898 additions and 292 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ Config config = ConfigFactory.configLoader()
.load()
```

Loads configuration files:
- from classpath directory: `config`
- with config base name: `application` (it's a default value)
- with default profile: `local` (can be overridden with args `--profile=$PROFILE`)
- overrides configuration values using args `--config-value.$NAME=$VALUE`

```
// Sample config files layout:
src/main/resources/config
application.yml
application-local.yml
application-prod.yml
```

Explore the API, there is much more to configure.

#### Config merging order
Expand Down
92 changes: 92 additions & 0 deletions src/main/java/com/coditory/quark/config/AuditableConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.coditory.quark.config;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import static com.coditory.quark.config.ConfigRemoveOptions.removeEmptyParents;

public class AuditableConfig extends ConfigDecorator {
private static final Object USED_MARKER = new Object();
private Config unreadConfig;

public static AuditableConfig of(Config config) {
if (config instanceof AuditableConfig) {
return (AuditableConfig) config;
}
return new AuditableConfig(config);
}

AuditableConfig(Config config) {
super(config);
this.unreadConfig = config.copy();
}

@Override
public Config getSubConfig(String path) {
markAsRead(path);
return super.getSubConfig(path);
}

@Override
public Config getSubConfigOrEmpty(String path) {
markAsRead(path);
return super.getSubConfigOrEmpty(path);
}

@Override
public Config getSubConfig(String path, Config defaultValue) {
markAsRead(path);
return super.getSubConfig(path, defaultValue);
}

@Override
public Optional<Config> getSubConfigOptional(String path) {
markAsRead(path);
return super.getSubConfigOptional(path);
}

@Override
public <T> Optional<List<T>> getListAsOptional(Class<T> type, String path) {
markAsRead(path);
return super.getListAsOptional(type, path);
}

@Override
public <T> Optional<T> getAsOptional(Class<T> type, String path) {
markAsRead(path);
return super.getAsOptional(type, path);
}

public AuditableConfig markAsRead(String path) {
if (unreadConfig.contains(path)) {
unreadConfig = unreadConfig.withValue(path, USED_MARKER);
}
return this;
}

public Config getUnusedProperties() {
return unreadConfig.filterValues(
(path, value) -> !Objects.equals(value, USED_MARKER),
removeEmptyParents()
);
}

public void throwErrorOnUnusedProperties() {
Map<String, Object> properties = unreadConfig.toFlatMap().entrySet().stream()
.filter(entry -> entry.getValue() != USED_MARKER)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
if (!properties.isEmpty()) {
int limit = 5;
List<String> names = properties.keySet().stream().sorted().toList();
String limitedNames = names.stream().limit(5).collect(Collectors.joining("\n"));
if (names.size() > limit) {
limitedNames = limitedNames + "\n...";
}
throw new ConfigUnusedPropertiesException("Detected unused config properties:\n" + limitedNames);
}
}
}
Loading

0 comments on commit 6f05d3e

Please sign in to comment.