From 819d4b07dcc1b9036fa373f7f6eccaad7f3a8b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Mendelski?= Date: Thu, 20 Oct 2022 07:25:50 +0200 Subject: [PATCH] Make profiles config optional --- README.md | 1 + .../config/LoadApplicationConfigSpec.groovy | 32 ++++++++++++----- .../quark/config/AuditableConfig.java | 2 +- .../com/coditory/quark/config/Config.java | 4 +++ .../coditory/quark/config/ConfigLoader.java | 35 ++++++++++++------- 5 files changed, 52 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index b2a197d..ce412ea 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ according to the following order: - profile config - profile based config from classpath, by default: `application-${profile}.{yml,json,properties}` - profiles are enabled in arguments: `--profile=$PROFILE` or `--profile=$PROFILE1,$PROFILE2` + - by default profile configs are optional - external config - config file from system - passed as an argument `--config=$PATH_TO_CONFIG` diff --git a/src/integration/groovy/com/coditory/quark/config/LoadApplicationConfigSpec.groovy b/src/integration/groovy/com/coditory/quark/config/LoadApplicationConfigSpec.groovy index b5dd314..987a057 100644 --- a/src/integration/groovy/com/coditory/quark/config/LoadApplicationConfigSpec.groovy +++ b/src/integration/groovy/com/coditory/quark/config/LoadApplicationConfigSpec.groovy @@ -91,15 +91,16 @@ class LoadApplicationConfigSpec extends Specification implements UsesFiles { config.getString("a") == value where: - profiles | value - ["local"] | "LOCAL" - ["prod"] | "PROD" - ["local", "prod"] | "PROD" - ["prod", "local"] | "LOCAL" + profiles | value + ["local"] | "LOCAL" + ["prod"] | "PROD" + ["local", "prod"] | "PROD" + ["prod", "local"] | "LOCAL" + ["local", "unknown"] | "LOCAL" } @Unroll - def "should use throw error on missing profile config file: #profiles"() { + def "should throw error on missing profile config file: #profiles"() { given: writeClasspathFile("application.yml", "a: BASE") writeClasspathFile("application-local.yml", "a: LOCAL") @@ -107,6 +108,7 @@ class LoadApplicationConfigSpec extends Specification implements UsesFiles { when: stubClassLoader { configLoader() + .withProfileConfigsRequired() .withDefaultProfiles(*profiles) .load() } @@ -226,19 +228,33 @@ class LoadApplicationConfigSpec extends Specification implements UsesFiles { expect: stubClassLoader { configLoader() - .withOptionalProfileConfigs("other") .withArgs("--profile", "other") .load() } and: stubClassLoader { configLoader() - .withOptionalAllProfileConfigs() + .withOptionalProfileConfigs("other") .withArgs("--profile", "other") .load() } } + def "should throw error on missing required profile config"() { + given: + writeClasspathFile("application.yml", "a: A") + when: + stubClassLoader { + configLoader() + .withOptionalProfileConfigs("other") + .withArgs("--profile", "prod") + .load() + } + then: + ConfigLoadException e = thrown(ConfigLoadException) + e.message == "Configuration file not found on classpath: application-prod" + } + def "should make base config optional"() { when: Config config = stubClassLoader { diff --git a/src/main/java/com/coditory/quark/config/AuditableConfig.java b/src/main/java/com/coditory/quark/config/AuditableConfig.java index 6d99b67..82ea4be 100644 --- a/src/main/java/com/coditory/quark/config/AuditableConfig.java +++ b/src/main/java/com/coditory/quark/config/AuditableConfig.java @@ -13,7 +13,7 @@ public class AuditableConfig extends ConfigDecorator { private static final Object USED_MARKER = new Object(); private Config unreadConfig; - public static AuditableConfig of(Config config) { + static AuditableConfig of(Config config) { if (config instanceof AuditableConfig) { return (AuditableConfig) config; } diff --git a/src/main/java/com/coditory/quark/config/Config.java b/src/main/java/com/coditory/quark/config/Config.java index c676fc9..06852c9 100644 --- a/src/main/java/com/coditory/quark/config/Config.java +++ b/src/main/java/com/coditory/quark/config/Config.java @@ -107,6 +107,10 @@ default Config copy() { Config withHiddenSecrets(); + default AuditableConfig auditable() { + return AuditableConfig.of(this); + } + class ConfigBuilder { private MapConfigNode root = MapConfigNode.emptyRoot(); private List valueParsers = new ArrayList<>(DEFAULT_VALUE_PARSERS); diff --git a/src/main/java/com/coditory/quark/config/ConfigLoader.java b/src/main/java/com/coditory/quark/config/ConfigLoader.java index 692c4a2..a0d68c1 100644 --- a/src/main/java/com/coditory/quark/config/ConfigLoader.java +++ b/src/main/java/com/coditory/quark/config/ConfigLoader.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.Function; import java.util.stream.Stream; import static com.coditory.quark.config.Preconditions.expect; @@ -33,7 +34,8 @@ public class ConfigLoader { private boolean optionalBaseConfig = false; private Set allowedProfiles = null; private Set optionalProfileConfigs = null; - private boolean optionalAllProfileConfigs = false; + private boolean profileConfigsRequired = false; + private Function, List> profilesProvider; ConfigLoader() { // deliberately empty @@ -111,6 +113,7 @@ public ConfigLoader withOptionalProfileConfigs(String... optionalProfileConfigs) } public ConfigLoader withOptionalProfileConfigs(Set optionalProfileConfigs) { + withProfileConfigsRequired(); this.optionalProfileConfigs = new HashSet<>(optionalProfileConfigs); return this; } @@ -124,12 +127,12 @@ public ConfigLoader withOptionalBaseConfig(boolean optionalBaseConfig) { return this; } - public ConfigLoader withOptionalAllProfileConfigs() { - return withOptionalAllProfileConfigs(true); + public ConfigLoader withProfileConfigsRequired() { + return withProfileConfigsRequired(true); } - public ConfigLoader withOptionalAllProfileConfigs(boolean optionalAllProfileConfigs) { - this.optionalAllProfileConfigs = optionalAllProfileConfigs; + public ConfigLoader withProfileConfigsRequired(boolean profileConfigsRequired) { + this.profileConfigsRequired = profileConfigsRequired; return this; } @@ -155,13 +158,13 @@ public ConfigLoader withDefaultProfiles(String... defaultProfiles) { return this; } - public ConfigLoader addDefaultProfile(String defaultProfile) { - expectNonBlank(defaultProfile, "defaultProfile"); - List newProfiles = stream(defaultProfile.split(",")) - .map(String::trim) - .filter(s -> !s.isEmpty()) - .toList(); - this.defaultProfiles.addAll(newProfiles); + public ConfigLoader withProfiles(String... profiles) { + this.profilesProvider = (p) -> Arrays.asList(profiles); + return this; + } + + public ConfigLoader withProfiles(Function, List> profilesProvider) { + this.profilesProvider = profilesProvider; return this; } @@ -230,6 +233,12 @@ private List profiles(Config argsConfig) { profiles = profiles.isEmpty() && !defaultProfiles.isEmpty() ? defaultProfiles : profiles; + if (profilesProvider != null) { + profiles = profilesProvider.apply(profiles); + } + profiles = profiles.stream() + .filter(p -> p != null && !p.isBlank() && !p.contains(",")) + .collect(toList()); if (allowedProfiles != null) { Set invalidProfiles = profiles.stream() .filter(p -> !allowedProfiles.contains(p)) @@ -269,7 +278,7 @@ private boolean isConfigOptional(String profile) { if (profile == null) { return optionalBaseConfig; } - return optionalAllProfileConfigs || + return !profileConfigsRequired || (optionalProfileConfigs != null && optionalProfileConfigs.contains(profile)); }