validatorsSet) throws GestaltConfigurationException {
+ Objects.requireNonNull(validatorsSet, "Validator should not be null");
+
+ configValidators.addAll(validatorsSet);
+
+ return this;
+ }
+
+ /**
+ * Add a single Validator to the builder.
+ *
+ * @param configValidator add a single Validator
+ * @return GestaltBuilder builder
+ */
+ public GestaltBuilder addValidator(ConfigValidator configValidator) {
+ Objects.requireNonNull(configValidator, "Validator should not be null");
+ this.configValidators.add(configValidator);
+ return this;
+ }
+
/**
* Set the mask to use when replacing a leaf value matching a security rule.
*
@@ -612,6 +681,24 @@ public GestaltBuilder setMetricsManager(MetricsManager metricsManager) {
return this;
}
+ /**
+ * Sets the ValidationManager if you want to provide your own. Otherwise, a default is provided.
+ *
+ * If there are any Validators, it will not add the default Validators.
+ * So you will need to add the defaults manually if needed.
+ *
+ * @param validationManager Validation Manager
+ * @return GestaltBuilder builder
+ */
+ public GestaltBuilder setValidationManager(ValidationManager validationManager) {
+ Objects.requireNonNull(validationManager, "ValidationManager should not be null");
+ this.validationManager = validationManager;
+ validationManager.addValidators(configValidators);
+ return this;
+ }
+
+
+
/**
* Set the decoder service if you want to provide your own. Otherwise a default is provided.
* If there are any decoders set, it will not add the default decoders. So you will need to add the defaults manually if needed.
@@ -794,6 +881,17 @@ public GestaltBuilder setMetricsEnabled(Boolean metricsEnabled) {
return this;
}
+ /**
+ * If we are to enable validation.
+ *
+ * @param validationEnabled If we are to enable validation
+ * @return GestaltBuilder builder
+ */
+ public GestaltBuilder setValidationEnabled(Boolean validationEnabled) {
+ this.validationEnabled = validationEnabled;
+ return this;
+ }
+
/**
* Set a date decoder format. Used to decode date times.
*
@@ -1022,18 +1120,28 @@ public Gestalt build() throws GestaltConfigurationException {
logger.log(TRACE, "No decoders provided, using defaults");
addDefaultDecoders();
}
+ decoders = decoders.stream().filter(Objects::nonNull).collect(Collectors.toList());
// setup the default path mappers, if there are none, add the default ones.
if (pathMappers.isEmpty()) {
logger.log(TRACE, "No path mapper provided, using defaults");
addDefaultPathMappers();
}
+ pathMappers = pathMappers.stream().filter(Objects::nonNull).collect(Collectors.toList());
// setup the default metricsRecorders, if there are none add the default ones.
if (metricsRecorders.isEmpty()) {
logger.log(TRACE, "No metric recorders provided, using defaults");
addDefaultMetricsRecorder();
}
+ metricsRecorders = metricsRecorders.stream().filter(Objects::nonNull).collect(Collectors.toList());
+
+ // setup the default validators, if there are none add the default ones.
+ if (configValidators.isEmpty()) {
+ logger.log(TRACE, "No validators recorders provided, using defaults");
+ addDefaultValidators();
+ }
+ configValidators = configValidators.stream().filter(Objects::nonNull).collect(Collectors.toList());
// if the metricsManager does not exist, create it.
@@ -1044,6 +1152,14 @@ public Gestalt build() throws GestaltConfigurationException {
metricsManager.addMetricsRecorders(metricsRecorders);
}
+ // if the metricsManager does not exist, create it.
+ // Otherwise, get all the recorders from the metricsManager, combine them with the ones in the builder,
+ if (validationManager == null) {
+ validationManager = new ValidationManager(configValidators);
+ } else {
+ validationManager.addValidators(configValidators);
+ }
+
// if the decoderService does not exist, create it.
// Otherwise get all the decoders from the decoderService, combine them with the ones in the builder,
// and update the decoderService
@@ -1060,11 +1176,13 @@ public Gestalt build() throws GestaltConfigurationException {
logger.log(TRACE, "No decoders provided, using defaults");
addDefaultConfigLoaders();
}
+ configLoaders = configLoaders.stream().filter(Objects::nonNull).collect(Collectors.toList());
if (postProcessors.isEmpty()) {
logger.log(TRACE, "No post processors provided, using defaults");
addDefaultPostProcessors();
}
+ postProcessors = postProcessors.stream().filter(Objects::nonNull).collect(Collectors.toList());
// get all the config loaders from the configLoaderRegistry, combine them with the ones in the builder,
// and update the configLoaderRegistry
@@ -1078,11 +1196,13 @@ public Gestalt build() throws GestaltConfigurationException {
configurePostProcessors();
configurePathMappers();
configureMetricsRecorders();
+ configureValidators();
// create a new GestaltCoreReloadStrategy to listen for Gestalt Core Reloads.
CoreReloadListenersContainer coreReloadListenersContainer = new CoreReloadListenersContainer();
final GestaltCore gestaltCore = new GestaltCore(configLoaderService, configSourcePackages, decoderService, sentenceLexer,
- gestaltConfig, configNodeService, coreReloadListenersContainer, postProcessors, secretConcealer, metricsManager, defaultTags);
+ gestaltConfig, configNodeService, coreReloadListenersContainer, postProcessors, secretConcealer, metricsManager,
+ validationManager, defaultTags);
// register gestaltCore with all the source reload strategies.
reloadStrategies.forEach(it -> it.registerListener(gestaltCore));
@@ -1152,6 +1272,9 @@ private GestaltConfig rebuildConfig() {
newConfig.setMetricsEnabled(Objects.requireNonNullElseGet(metricsEnabled,
() -> gestaltConfig.isMetricsEnabled()));
+ newConfig.setValidationEnabled(Objects.requireNonNullElseGet(validationEnabled,
+ () -> gestaltConfig.isValidationEnabled()));
+
return newConfig;
}
}
diff --git a/gestalt-core/src/main/java/org/github/gestalt/config/entity/GestaltConfig.java b/gestalt-core/src/main/java/org/github/gestalt/config/entity/GestaltConfig.java
index 2d51aa238..560b0fc64 100644
--- a/gestalt-core/src/main/java/org/github/gestalt/config/entity/GestaltConfig.java
+++ b/gestalt-core/src/main/java/org/github/gestalt/config/entity/GestaltConfig.java
@@ -48,6 +48,9 @@ public class GestaltConfig {
// if metrics should be enabled
private boolean metricsEnabled = false;
+ // if validation should be enabled.
+ private boolean validationEnabled = false;
+
/**
* Treat all warnings as errors.
*
@@ -333,6 +336,24 @@ public void setMetricsEnabled(boolean metricsEnabled) {
this.metricsEnabled = metricsEnabled;
}
+ /**
+ * Get if validation is enabled.
+ *
+ * @return if validation is enabled
+ */
+ public boolean isValidationEnabled() {
+ return validationEnabled;
+ }
+
+ /**
+ * Set if validation is enabled.
+ *
+ * @param validationEnabled if validation is enabled.
+ */
+ public void setValidationEnabled(boolean validationEnabled) {
+ this.validationEnabled = validationEnabled;
+ }
+
/**
* Register an external module configuration.
*
diff --git a/gestalt-core/src/main/java/org/github/gestalt/config/metrics/MetricsManager.java b/gestalt-core/src/main/java/org/github/gestalt/config/metrics/MetricsManager.java
index 99141663e..bee20b970 100644
--- a/gestalt-core/src/main/java/org/github/gestalt/config/metrics/MetricsManager.java
+++ b/gestalt-core/src/main/java/org/github/gestalt/config/metrics/MetricsManager.java
@@ -19,7 +19,9 @@ public final class MetricsManager {
private final Map metricsRecorders;
public MetricsManager(List recorder) {
- this.metricsRecorders = recorder.stream().collect(Collectors.toMap(MetricsRecorder::recorderId, Function.identity()));
+ this.metricsRecorders = recorder
+ .stream()
+ .collect(Collectors.toMap(MetricsRecorder::recorderId, Function.identity()));
}
public void addMetricsRecorder(MetricsRecorder recorder) {
diff --git a/gestalt-core/src/main/java/org/github/gestalt/config/validation/ConfigValidator.java b/gestalt-core/src/main/java/org/github/gestalt/config/validation/ConfigValidator.java
new file mode 100644
index 000000000..7a7544205
--- /dev/null
+++ b/gestalt-core/src/main/java/org/github/gestalt/config/validation/ConfigValidator.java
@@ -0,0 +1,34 @@
+package org.github.gestalt.config.validation;
+
+import org.github.gestalt.config.entity.GestaltConfig;
+import org.github.gestalt.config.reflect.TypeCapture;
+import org.github.gestalt.config.tag.Tags;
+import org.github.gestalt.config.utils.GResultOf;
+
+/**
+ * Interface for validating objects.
+ *
+ * @author Colin Redmond (c) 2024.
+ */
+public interface ConfigValidator {
+
+ /**
+ * If your Validator needs access to the Gestalt Config.
+ *
+ * @param config Gestalt configuration
+ */
+ default void applyConfig(GestaltConfig config) {}
+
+ /**
+ * Returns the {@link GResultOf} with the validation results. If the object is ok it will return the result with no errors.
+ * If there are validation errors they will be returned.
+ *
+ * @param obj object to validate.
+ * @param path path the object was located at
+ * @param klass the type of object.
+ * @param tags any tags used to retrieve te object
+ * @return The validation results with either errors or a successful obj.
+ * @param Class of the object.
+ */
+ GResultOf validator(T obj, String path, TypeCapture klass, Tags tags);
+}
diff --git a/gestalt-core/src/main/java/org/github/gestalt/config/validation/ValidationManager.java b/gestalt-core/src/main/java/org/github/gestalt/config/validation/ValidationManager.java
new file mode 100644
index 000000000..868c36558
--- /dev/null
+++ b/gestalt-core/src/main/java/org/github/gestalt/config/validation/ValidationManager.java
@@ -0,0 +1,55 @@
+package org.github.gestalt.config.validation;
+
+import org.github.gestalt.config.reflect.TypeCapture;
+import org.github.gestalt.config.tag.Tags;
+import org.github.gestalt.config.utils.GResultOf;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Manages all validations. Gestalt calls the manager who then forwards the calls to all registered validators.
+ * It collates the results and returns either the object or the errors.
+ *
+ * @author Colin Redmond (c) 2024.
+ */
+public final class ValidationManager {
+
+ private final List configValidators;
+
+ public ValidationManager(List configValidators) {
+ this.configValidators = configValidators;
+ }
+
+ /**
+ * Add a list of validators to the manager.
+ *
+ * @param validatorsSet list of validators
+ */
+ public void addValidators(List validatorsSet) {
+ this.configValidators.addAll(validatorsSet);
+ }
+
+ /**
+ * Calls all the validators, and it collates the results and returns either the object or the errors.
+ *
+ * @param obj object to validate.
+ * @param path path the object was located at
+ * @param klass the type of object.
+ * @param tags any tags used to retrieve te object
+ * @param Class of the object.
+ * @return The validation results with either errors or a successful obj.
+ */
+ public GResultOf validator(T obj, String path, TypeCapture klass, Tags tags) {
+ var errors = configValidators.stream()
+ .map(it -> it.validator(obj, path, klass, tags))
+ .flatMap(it -> it.getErrors().stream())
+ .collect(Collectors.toList());
+
+ if (errors.isEmpty()) {
+ return GResultOf.result(obj);
+ } else {
+ return GResultOf.errors(errors);
+ }
+ }
+}
diff --git a/gestalt-core/src/test/java/org/github/gestalt/config/GestaltConfigValidatorTest.java b/gestalt-core/src/test/java/org/github/gestalt/config/GestaltConfigValidatorTest.java
new file mode 100644
index 000000000..84f46125b
--- /dev/null
+++ b/gestalt-core/src/test/java/org/github/gestalt/config/GestaltConfigValidatorTest.java
@@ -0,0 +1,262 @@
+package org.github.gestalt.config;
+
+import org.github.gestalt.config.builder.GestaltBuilder;
+import org.github.gestalt.config.exceptions.GestaltException;
+import org.github.gestalt.config.source.MapConfigSourceBuilder;
+import org.github.gestalt.config.tag.Tags;
+import org.github.gestalt.config.test.classes.DBInfo;
+import org.github.gestalt.config.validation.TestConfigValidator;
+import org.github.gestalt.config.validation.ConfigValidator;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.LogManager;
+
+public class GestaltConfigValidatorTest {
+
+ @BeforeAll
+ public static void beforeAll() {
+ try (InputStream is = GestaltMetricsTest.class.getClassLoader().getResourceAsStream("logging.properties")) {
+ LogManager.getLogManager().readConfiguration(is);
+ } catch (IOException e) {
+ // dont care
+ }
+ }
+
+ @Test
+ public void testValidatorGetOkNoValidator() throws GestaltException {
+
+ Map configs = new HashMap<>();
+ configs.put("db.password", "test");
+ configs.put("db.port", "123");
+ configs.put("db.uri", "my.sql.com");
+
+ Map configs2 = new HashMap<>();
+ configs2.put("db.password", "test2");
+ configs2.put("db.port", "456");
+ configs2.put("db.uri", "my.postgresql.com");
+
+ Gestalt gestalt = new GestaltBuilder()
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs2).setTags(Tags.environment("dev")).build())
+ .setValidationEnabled(true)
+ .build();
+
+ gestalt.loadConfigs();
+
+ Assertions.assertEquals(456, gestalt.getConfig("db.port", Integer.class, Tags.environment("dev")));
+ }
+
+ @Test
+ public void testValidatorGetOkNullValidator() throws GestaltException {
+
+ Map configs = new HashMap<>();
+ configs.put("db.password", "test");
+ configs.put("db.port", "123");
+ configs.put("db.uri", "my.sql.com");
+
+ Map configs2 = new HashMap<>();
+ configs2.put("db.password", "test2");
+ configs2.put("db.port", "456");
+ configs2.put("db.uri", "my.postgresql.com");
+
+ var validatorArray = new ArrayList();
+ validatorArray.add(null);
+
+ Gestalt gestalt = new GestaltBuilder()
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs2).setTags(Tags.environment("dev")).build())
+ .setValidationEnabled(true)
+ .addValidators(validatorArray)
+ .build();
+
+ gestalt.loadConfigs();
+
+ Assertions.assertEquals(456, gestalt.getConfig("db.port", Integer.class, Tags.environment("dev")));
+ }
+
+ @Test
+ public void testValidatorGetOkPrimitive() throws GestaltException {
+
+ Map configs = new HashMap<>();
+ configs.put("db.password", "test");
+ configs.put("db.port", "123");
+ configs.put("db.uri", "my.sql.com");
+
+ Map configs2 = new HashMap<>();
+ configs2.put("db.password", "test2");
+ configs2.put("db.port", "456");
+ configs2.put("db.uri", "my.postgresql.com");
+
+ Gestalt gestalt = new GestaltBuilder()
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs2).setTags(Tags.environment("dev")).build())
+ .setValidationEnabled(true)
+ .addValidator(new TestConfigValidator(true))
+ .build();
+
+ gestalt.loadConfigs();
+
+ Assertions.assertEquals(456, gestalt.getConfig("db.port", Integer.class, Tags.environment("dev")));
+ }
+
+ @Test
+ public void testValidatorGetOkString() throws GestaltException {
+
+ Map configs = new HashMap<>();
+ configs.put("db.password", "test");
+ configs.put("db.port", "123");
+ configs.put("db.uri", "my.sql.com");
+
+ Map configs2 = new HashMap<>();
+ configs2.put("db.password", "test2");
+ configs2.put("db.port", "456");
+ configs2.put("db.uri", "my.postgresql.com");
+
+ Gestalt gestalt = new GestaltBuilder()
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs2).setTags(Tags.environment("dev")).build())
+ .setValidationEnabled(true)
+ .addValidator(new TestConfigValidator(true))
+ .build();
+
+ gestalt.loadConfigs();
+
+ Assertions.assertEquals("test2", gestalt.getConfig("db.password", String.class, Tags.environment("dev")));
+ }
+
+ @Test
+ public void testValidatorGetWouldFailPrimitive() throws GestaltException {
+
+ Map configs = new HashMap<>();
+ configs.put("db.password", "test");
+ configs.put("db.port", "123");
+ configs.put("db.uri", "my.sql.com");
+
+ Map configs2 = new HashMap<>();
+ configs2.put("db.password", "test2");
+ configs2.put("db.port", "456");
+ configs2.put("db.uri", "my.postgresql.com");
+
+ Gestalt gestalt = new GestaltBuilder()
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs2).setTags(Tags.environment("dev")).build())
+ .setValidationEnabled(true)
+ .addValidator(new TestConfigValidator(false))
+ .build();
+
+ gestalt.loadConfigs();
+
+ Assertions.assertEquals(456, gestalt.getConfig("db.port", Integer.class, Tags.environment("dev")));
+ }
+
+ @Test
+ public void testValidatorGetWouldFailString() throws GestaltException {
+
+ Map configs = new HashMap<>();
+ configs.put("db.password", "test");
+ configs.put("db.port", "123");
+ configs.put("db.uri", "my.sql.com");
+
+ Map configs2 = new HashMap<>();
+ configs2.put("db.password", "test2");
+ configs2.put("db.port", "456");
+ configs2.put("db.uri", "my.postgresql.com");
+
+ Gestalt gestalt = new GestaltBuilder()
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs2).setTags(Tags.environment("dev")).build())
+ .setValidationEnabled(true)
+ .addValidator(new TestConfigValidator(false))
+ .build();
+
+ gestalt.loadConfigs();
+
+ Assertions.assertEquals("test2", gestalt.getConfig("db.password", String.class, Tags.environment("dev")));
+ }
+
+ @Test
+ public void testValidatorGetOkClass() throws GestaltException {
+
+ Map configs = new HashMap<>();
+ configs.put("db.password", "test");
+ configs.put("db.port", "123");
+ configs.put("db.uri", "my.sql.com");
+
+ Map configs2 = new HashMap<>();
+ configs2.put("db.password", "test2");
+ configs2.put("db.port", "456");
+ configs2.put("db.uri", "my.postgresql.com");
+
+ Gestalt gestalt = new GestaltBuilder()
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs2).setTags(Tags.environment("dev")).build())
+ .setValidationEnabled(true)
+ .addValidator(new TestConfigValidator(true))
+ .build();
+
+ gestalt.loadConfigs();
+
+ Assertions.assertEquals("test2", gestalt.getConfig("db", DBInfo.class, Tags.environment("dev")).getPassword());
+ }
+
+ @Test
+ public void testValidatorGetFailClass() throws GestaltException {
+
+ Map configs = new HashMap<>();
+ configs.put("db.password", "test");
+ configs.put("db.port", "123");
+ configs.put("db.uri", "my.sql.com");
+
+ Map configs2 = new HashMap<>();
+ configs2.put("db.password", "test2");
+ configs2.put("db.port", "456");
+ configs2.put("db.uri", "my.postgresql.com");
+
+ Gestalt gestalt = new GestaltBuilder()
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs2).setTags(Tags.environment("dev")).build())
+ .setValidationEnabled(true)
+ .addValidator(new TestConfigValidator(false))
+ .build();
+
+ gestalt.loadConfigs();
+
+ var ex = Assertions.assertThrows(GestaltException.class,
+ () -> gestalt.getConfig("db", DBInfo.class, Tags.environment("dev")));
+
+ Assertions.assertEquals("Validation failed for config path: db, and " +
+ "class: org.github.gestalt.config.test.classes.DBInfo\n" +
+ " - level: ERROR, message: something broke", ex.getMessage());
+ }
+
+ @Test
+ public void testValidatorGetFailClassReturnDefault() throws GestaltException {
+
+ Map configs = new HashMap<>();
+ configs.put("db.password", "test");
+ configs.put("db.port", "123");
+ configs.put("db.uri", "my.sql.com");
+
+ Map configs2 = new HashMap<>();
+ configs2.put("db.password", "test2");
+ configs2.put("db.port", "456");
+ configs2.put("db.uri", "my.postgresql.com");
+
+ Gestalt gestalt = new GestaltBuilder()
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs2).setTags(Tags.environment("dev")).build())
+ .setValidationEnabled(true)
+ .addValidator(new TestConfigValidator(false))
+ .build();
+
+ gestalt.loadConfigs();
+ Assertions.assertTrue(gestalt.getConfigOptional("db", DBInfo.class, Tags.environment("dev")).isEmpty());
+ }
+}
diff --git a/gestalt-core/src/test/java/org/github/gestalt/config/GestaltMetricsTest.java b/gestalt-core/src/test/java/org/github/gestalt/config/GestaltMetricsTest.java
index d6de48018..a342cc6a8 100644
--- a/gestalt-core/src/test/java/org/github/gestalt/config/GestaltMetricsTest.java
+++ b/gestalt-core/src/test/java/org/github/gestalt/config/GestaltMetricsTest.java
@@ -2,18 +2,21 @@
import org.github.gestalt.config.builder.GestaltBuilder;
import org.github.gestalt.config.exceptions.GestaltException;
+import org.github.gestalt.config.metrics.MetricsRecorder;
import org.github.gestalt.config.metrics.TestMetricsRecorder;
import org.github.gestalt.config.reload.ManualConfigReloadStrategy;
import org.github.gestalt.config.source.MapConfigSourceBuilder;
import org.github.gestalt.config.tag.Tags;
import org.github.gestalt.config.test.classes.DBInfo;
import org.github.gestalt.config.test.classes.DBInfoOptional;
+import org.github.gestalt.config.validation.TestConfigValidator;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -61,6 +64,58 @@ public void testMetricsGetOk() throws GestaltException {
Assertions.assertEquals(Tags.environment("dev"), metricsRecorder.metrics.get("db.password").tags);
}
+ @Test
+ public void testMetricsGetOkNoMetricRecorder() throws GestaltException {
+
+ Map configs = new HashMap<>();
+ configs.put("db.password", "test");
+ configs.put("db.port", "123");
+ configs.put("db.uri", "my.sql.com");
+
+ Map configs2 = new HashMap<>();
+ configs2.put("db.password", "test2");
+ configs2.put("db.port", "456");
+ configs2.put("db.uri", "my.postgresql.com");
+
+ Gestalt gestalt = new GestaltBuilder()
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs2).setTags(Tags.environment("dev")).build())
+ .setMetricsEnabled(true)
+ .build();
+
+ gestalt.loadConfigs();
+
+ Assertions.assertEquals("test2", gestalt.getConfig("db.password", String.class, Tags.environment("dev")));
+ }
+
+ @Test
+ public void testMetricsGetOkNullMetricRecorder() throws GestaltException {
+
+ Map configs = new HashMap<>();
+ configs.put("db.password", "test");
+ configs.put("db.port", "123");
+ configs.put("db.uri", "my.sql.com");
+
+ Map configs2 = new HashMap<>();
+ configs2.put("db.password", "test2");
+ configs2.put("db.port", "456");
+ configs2.put("db.uri", "my.postgresql.com");
+
+ var metricRecordersArray = new ArrayList();
+ metricRecordersArray.add(null);
+
+ Gestalt gestalt = new GestaltBuilder()
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs2).setTags(Tags.environment("dev")).build())
+ .setMetricsEnabled(true)
+ .addMetricsRecorders(metricRecordersArray)
+ .build();
+
+ gestalt.loadConfigs();
+
+ Assertions.assertEquals("test2", gestalt.getConfig("db.password", String.class, Tags.environment("dev")));
+ }
+
@Test
public void testMetricsGetCache() throws GestaltException {
@@ -307,6 +362,84 @@ public void testMetricsGetWarning() throws GestaltException {
Assertions.assertEquals(Tags.of(), metricsRecorder.metrics.get("get.config.warning").tags);
}
+ @Test
+ public void testMetricsGetFailValidation() throws GestaltException {
+
+ Map configs = new HashMap<>();
+ configs.put("db.password", "test");
+ configs.put("db.port", "123");
+ configs.put("db.uri", "my.sql.com");
+
+ Map configs2 = new HashMap<>();
+ configs2.put("db.password", "test2");
+ configs2.put("db.port", "456");
+ configs2.put("db.uri", "my.postgresql.com");
+
+ var metricsRecorder = new TestMetricsRecorder(0);
+
+ Gestalt gestalt = new GestaltBuilder()
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs2).setTags(Tags.environment("dev")).build())
+ .setMetricsRecorders(List.of(metricsRecorder))
+ .setMetricsEnabled(true)
+ .setValidationEnabled(true)
+ .addValidator(new TestConfigValidator(false))
+ .build();
+
+ gestalt.loadConfigs();
+
+ var ex = Assertions.assertThrows(GestaltException.class,
+ () -> gestalt.getConfig("db", DBInfo.class, Tags.environment("dev")));
+
+
+ Assertions.assertEquals("Validation failed for config path: db, and class: " +
+ "org.github.gestalt.config.test.classes.DBInfo\n" +
+ " - level: ERROR, message: something broke", ex.getMessage());
+ Assertions.assertEquals("db", metricsRecorder.metrics.get("db").path);
+ Assertions.assertEquals(10.0D, metricsRecorder.metrics.get("db").data);
+ Assertions.assertEquals(Tags.of("environment", "dev", "exception", "org.github.gestalt.config.exceptions.GestaltException"),
+ metricsRecorder.metrics.get("db").tags);
+
+ Assertions.assertEquals(1.0, metricsRecorder.metrics.get("get.config.validation.error").data);
+ Assertions.assertEquals(Tags.of(), metricsRecorder.metrics.get("get.config.validation.error").tags);
+ }
+
+ @Test
+ public void testMetricsGetFailValidationOptional() throws GestaltException {
+
+ Map configs = new HashMap<>();
+ configs.put("db.password", "test");
+ configs.put("db.port", "123");
+ configs.put("db.uri", "my.sql.com");
+
+ Map configs2 = new HashMap<>();
+ configs2.put("db.password", "test2");
+ configs2.put("db.port", "456");
+ configs2.put("db.uri", "my.postgresql.com");
+
+ var metricsRecorder = new TestMetricsRecorder(0);
+
+ Gestalt gestalt = new GestaltBuilder()
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs2).setTags(Tags.environment("dev")).build())
+ .setMetricsRecorders(List.of(metricsRecorder))
+ .setMetricsEnabled(true)
+ .setValidationEnabled(true)
+ .addValidator(new TestConfigValidator(false))
+ .build();
+
+ gestalt.loadConfigs();
+
+ Assertions.assertTrue(gestalt.getConfigOptional("db", DBInfo.class, Tags.environment("dev")).isEmpty());
+
+ Assertions.assertEquals("db", metricsRecorder.metrics.get("db").path);
+ Assertions.assertEquals(10.0D, metricsRecorder.metrics.get("db").data);
+ Assertions.assertEquals(Tags.of("environment", "dev", "default", "true"), metricsRecorder.metrics.get("db").tags);
+
+ Assertions.assertEquals(1.0, metricsRecorder.metrics.get("get.config.validation.error").data);
+ Assertions.assertEquals(Tags.of(), metricsRecorder.metrics.get("get.config.validation.error").tags);
+ }
+
@Test
public void testMetricsReload() throws GestaltException {
diff --git a/gestalt-core/src/test/java/org/github/gestalt/config/GestaltTest.java b/gestalt-core/src/test/java/org/github/gestalt/config/GestaltTest.java
index c89e279a3..5dc6a4d09 100644
--- a/gestalt-core/src/test/java/org/github/gestalt/config/GestaltTest.java
+++ b/gestalt-core/src/test/java/org/github/gestalt/config/GestaltTest.java
@@ -516,7 +516,7 @@ public void testPostProcessorNoResults() throws GestaltException {
new DecoderRegistry(List.of(new DoubleDecoder(), new LongDecoder(), new IntegerDecoder(), new StringDecoder()),
configNodeManager, lexer, List.of(new StandardPathMapper())),
lexer, new GestaltConfig(), configNodeManager, null,
- Collections.singletonList(new TestPostProcessor("aaa")), secretConcealer, null, Tags.of());
+ Collections.singletonList(new TestPostProcessor("aaa")), secretConcealer, null, null, Tags.of());
Mockito.when(configNodeManager.postProcess(Mockito.any())).thenReturn(GResultOf.resultOf(null, Collections.emptyList()));
@@ -547,7 +547,7 @@ public void testPostProcessorError() throws GestaltException {
new DecoderRegistry(List.of(new DoubleDecoder(), new LongDecoder(), new IntegerDecoder(), new StringDecoder()),
configNodeManager, lexer, List.of(new StandardPathMapper())),
lexer, new GestaltConfig(), configNodeManager, null,
- Collections.singletonList(new TestPostProcessor("aaa")), secretConcealer, null, Tags.of());
+ Collections.singletonList(new TestPostProcessor("aaa")), secretConcealer, null, null, Tags.of());
Mockito.when(configNodeManager.postProcess(Mockito.any())).thenReturn(
GResultOf.resultOf(true, Collections.singletonList(new ValidationError.ArrayInvalidIndex(-1, "test"))));
@@ -582,7 +582,7 @@ public void testPostProcessorMinorWarnings() throws GestaltException {
new DecoderRegistry(List.of(new DoubleDecoder(), new LongDecoder(), new IntegerDecoder(), new StringDecoder()),
configNodeManager, lexer, List.of(new StandardPathMapper())),
lexer, config, configNodeManager, null,
- Collections.singletonList(new TestPostProcessor("aaa")), secretConcealer, null, Tags.of());
+ Collections.singletonList(new TestPostProcessor("aaa")), secretConcealer, null, null, Tags.of());
Mockito.when(configNodeManager.postProcess(Mockito.any())).thenReturn(
GResultOf.resultOf(true, Collections.singletonList(new ValidationError.ArrayMissingIndex(1, "test"))));
@@ -782,7 +782,8 @@ public void testMergeArraysMissingIndex() throws GestaltException {
MapConfigSourceBuilder.builder().setCustomConfig(configs2).build()),
new DecoderRegistry(List.of(new DoubleDecoder(), new LongDecoder(), new IntegerDecoder(), new StringDecoder(),
new ListDecoder()), configNodeManager, lexer, List.of(new StandardPathMapper())),
- lexer, config, new ConfigNodeManager(), null, Collections.emptyList(), secretConcealer, null, Tags.of());
+ lexer, config, new ConfigNodeManager(), null, Collections.emptyList(), secretConcealer, null, null,
+ Tags.of());
gestalt.loadConfigs();
List errors = gestalt.getLoadErrors();
@@ -842,7 +843,8 @@ public void testNoSources() throws GestaltException {
List.of(),
new DecoderRegistry(List.of(new DoubleDecoder(), new LongDecoder(), new IntegerDecoder(), new StringDecoder()),
configNodeManager, lexer, List.of(new StandardPathMapper())),
- lexer, new GestaltConfig(), new ConfigNodeManager(), null, Collections.emptyList(), secretConcealer, null, Tags.of());
+ lexer, new GestaltConfig(), new ConfigNodeManager(), null, Collections.emptyList(), secretConcealer,
+ null, null, Tags.of());
var ex = Assertions.assertThrows(GestaltException.class, () -> gestalt.loadConfigs());
assertThat(ex).isInstanceOf(GestaltException.class)
@@ -862,7 +864,8 @@ public void testNullSources() throws GestaltException {
null,
new DecoderRegistry(List.of(new DoubleDecoder(), new LongDecoder(), new IntegerDecoder(), new StringDecoder()),
configNodeManager, lexer, List.of(new StandardPathMapper())),
- lexer, new GestaltConfig(), new ConfigNodeManager(), null, Collections.emptyList(), secretConcealer, null, Tags.of());
+ lexer, new GestaltConfig(), new ConfigNodeManager(), null, Collections.emptyList(), secretConcealer,
+ null, null, Tags.of());
var ex = Assertions.assertThrows(GestaltException.class, () -> gestalt.loadConfigs());
assertThat(ex).isInstanceOf(GestaltException.class)
@@ -889,7 +892,8 @@ public void testNoEncoder() throws GestaltException {
List.of(MapConfigSourceBuilder.builder().setCustomConfig(configs).build()),
new DecoderRegistry(Collections.singletonList(new StringDecoder()), configNodeManager, lexer,
List.of(new StandardPathMapper())),
- lexer, new GestaltConfig(), new ConfigNodeManager(), null, Collections.emptyList(), secretConcealer, null, Tags.of());
+ lexer, new GestaltConfig(), new ConfigNodeManager(), null, Collections.emptyList(), secretConcealer,
+ null, null, Tags.of());
gestalt.loadConfigs();
@@ -923,7 +927,8 @@ public void testMultipleEncoder() throws GestaltException {
List.of(MapConfigSourceBuilder.builder().setCustomConfig(configs).build()),
new DecoderRegistry(List.of(new StringDecoder(), new ExceptionDecoder()), configNodeManager, lexer,
List.of(new StandardPathMapper())),
- lexer, new GestaltConfig(), new ConfigNodeManager(), null, Collections.emptyList(), secretConcealer, null, Tags.of());
+ lexer, new GestaltConfig(), new ConfigNodeManager(), null, Collections.emptyList(), secretConcealer,
+ null, null, Tags.of());
gestalt.loadConfigs();
@@ -957,7 +962,8 @@ public void testLoadConfigError() throws GestaltException {
List.of(MapConfigSourceBuilder.builder().setCustomConfig(configs).build()),
new DecoderRegistry(List.of(new DoubleDecoder(), new LongDecoder(), new IntegerDecoder(), new StringDecoder()),
configNodeManager, lexer, List.of(new StandardPathMapper())),
- lexer, new GestaltConfig(), configNodeManager, null, Collections.emptyList(), secretConcealer, null, Tags.of());
+ lexer, new GestaltConfig(), configNodeManager, null, Collections.emptyList(), secretConcealer,
+ null, null, Tags.of());
var ex = Assertions.assertThrows(GestaltException.class, gestalt::loadConfigs);
assertThat(ex).isInstanceOf(GestaltException.class)
@@ -1023,7 +1029,8 @@ public void testNoResultsLoadingNode() throws GestaltException {
List.of(MapConfigSourceBuilder.builder().setCustomConfig(configs).build()),
new DecoderRegistry(List.of(new DoubleDecoder(), new LongDecoder(), new IntegerDecoder(), new StringDecoder()),
configNodeManager, lexer, List.of(new StandardPathMapper())),
- lexer, config, new ConfigNodeManager(), null, Collections.emptyList(), secretConcealer, null, Tags.of());
+ lexer, config, new ConfigNodeManager(), null, Collections.emptyList(), secretConcealer,
+ null, null, Tags.of());
GestaltConfigurationException e = Assertions.assertThrows(GestaltConfigurationException.class, gestalt::loadConfigs);
Assertions.assertEquals("No results found for node", e.getMessage());
@@ -1237,7 +1244,7 @@ public void testReloadNullReloadSource() throws GestaltException {
new DecoderRegistry(List.of(new DoubleDecoder(), new LongDecoder(), new IntegerDecoder(), new StringDecoder()),
configNodeManager, lexer, List.of(new StandardPathMapper())),
lexer, new GestaltConfig(), configNodeManager, coreReloadListenersContainer, Collections.emptyList(), secretConcealer,
- null, Tags.of());
+ null, null, Tags.of());
gestalt.loadConfigs();
List errors = gestalt.getLoadErrors();
@@ -1300,7 +1307,7 @@ public void testReloadNullSource() throws GestaltException {
new DecoderRegistry(List.of(new DoubleDecoder(), new LongDecoder(), new IntegerDecoder(), new StringDecoder()),
configNodeManager, lexer, List.of(new StandardPathMapper())),
lexer, new GestaltConfig(), configNodeManager, coreReloadListenersContainer, Collections.emptyList(), secretConcealer,
- null, Tags.of());
+ null, null, Tags.of());
var ex = Assertions.assertThrows(GestaltException.class, () -> gestalt.reload(sourcePackage));
assertThat(ex).hasMessage("No sources provided, unable to reload any configs");
@@ -1335,7 +1342,7 @@ public void testReloadUnknownSource() throws GestaltException {
new DecoderRegistry(List.of(new DoubleDecoder(), new LongDecoder(), new IntegerDecoder(), new StringDecoder()),
configNodeManager, lexer, List.of(new StandardPathMapper())),
lexer, new GestaltConfig(), configNodeManager, coreReloadListenersContainer, Collections.emptyList(), secretConcealer,
- null, Tags.of());
+ null, null, Tags.of());
gestalt.loadConfigs();
List errors = gestalt.getLoadErrors();
@@ -1408,7 +1415,7 @@ public void testReloadNoResults() throws GestaltException {
new DecoderRegistry(List.of(new DoubleDecoder(), new LongDecoder(), new IntegerDecoder(), new StringDecoder()),
configNodeManager, lexer, List.of(new StandardPathMapper())),
lexer, new GestaltConfig(), configNodeManager, coreReloadListenersContainer, Collections.emptyList(), secretConcealer,
- null, Tags.of());
+ null, null, Tags.of());
gestalt.loadConfigs();
List errors = gestalt.getLoadErrors();
@@ -1473,7 +1480,7 @@ public void testReloadRemoveListener() throws GestaltException {
new DecoderRegistry(List.of(new DoubleDecoder(), new LongDecoder(), new IntegerDecoder(), new StringDecoder()),
configNodeManager, lexer, List.of(new StandardPathMapper())),
lexer, new GestaltConfig(), configNodeManager, coreReloadListenersContainer, Collections.emptyList(), secretConcealer,
- null, Tags.of());
+ null, null, Tags.of());
gestalt.loadConfigs();
List errors = gestalt.getLoadErrors();
@@ -1601,7 +1608,7 @@ public void reloadListener() throws GestaltException {
new OptionalDoubleDecoder(), new OptionalIntDecoder(), new OptionalLongDecoder()),
configNodeManager, lexer, List.of(new StandardPathMapper())),
lexer, new GestaltConfig(), configNodeManager, coreReloadListenersContainer, Collections.emptyList(), secretConcealer,
- null, Tags.of("env", "dev"));
+ null, null, Tags.of("env", "dev"));
gestalt.loadConfigs();
diff --git a/gestalt-core/src/test/java/org/github/gestalt/config/builder/GestaltBuilderTest.java b/gestalt-core/src/test/java/org/github/gestalt/config/builder/GestaltBuilderTest.java
index 54681b27e..0b78f9bb0 100644
--- a/gestalt-core/src/test/java/org/github/gestalt/config/builder/GestaltBuilderTest.java
+++ b/gestalt-core/src/test/java/org/github/gestalt/config/builder/GestaltBuilderTest.java
@@ -35,6 +35,8 @@
import org.github.gestalt.config.test.classes.DBInfo;
import org.github.gestalt.config.token.Token;
import org.github.gestalt.config.utils.GResultOf;
+import org.github.gestalt.config.validation.TestConfigValidator;
+import org.github.gestalt.config.validation.ValidationManager;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -116,6 +118,10 @@ public void build() throws GestaltException {
.addMetricsRecorders(List.of(new TestMetricsRecorder(1)))
.setMetricsRecorders(List.of(new TestMetricsRecorder(0), new TestMetricsRecorder(1)))
.setMetricsManager(new MetricsManager(List.of()))
+ .addValidator(new TestConfigValidator(true))
+ .addValidators(List.of(new TestConfigValidator(true)))
+ .setValidators(List.of(new TestConfigValidator(true)))
+ .setValidationManager(new ValidationManager(new ArrayList<>()))
.setSecurityMaskingRule(new HashSet<>())
.addSecurityMaskingRule("secret")
.setSecurityMask("&&&&")
@@ -334,7 +340,8 @@ public void testNullResultsForNodeIgnored() throws GestaltException {
new DecoderRegistry(List.of(new DoubleDecoder(), new LongDecoder(), new IntegerDecoder(),
new StringDecoder(), new ObjectDecoder()),
configNodeManager, lexer, List.of(new StandardPathMapper())),
- lexer, config, new ConfigNodeManager(), null, Collections.emptyList(), secretConcealer, null, Tags.of());
+ lexer, config, new ConfigNodeManager(), null, Collections.emptyList(), secretConcealer,
+ null, null, Tags.of());
gestalt.loadConfigs();
diff --git a/gestalt-core/src/test/java/org/github/gestalt/config/validation/TestConfigValidator.java b/gestalt-core/src/test/java/org/github/gestalt/config/validation/TestConfigValidator.java
new file mode 100644
index 000000000..347b3a811
--- /dev/null
+++ b/gestalt-core/src/test/java/org/github/gestalt/config/validation/TestConfigValidator.java
@@ -0,0 +1,30 @@
+package org.github.gestalt.config.validation;
+
+import org.github.gestalt.config.entity.ValidationError;
+import org.github.gestalt.config.entity.ValidationLevel;
+import org.github.gestalt.config.reflect.TypeCapture;
+import org.github.gestalt.config.tag.Tags;
+import org.github.gestalt.config.utils.GResultOf;
+
+public class TestConfigValidator implements ConfigValidator {
+
+ public boolean isOk = true;
+
+ public TestConfigValidator(boolean isOk) {
+ this.isOk = isOk;
+ }
+
+ @Override
+ public GResultOf validator(T obj, String path, TypeCapture klass, Tags tags) {
+ if (isOk) {
+ return GResultOf.result(obj);
+ } else {
+ return GResultOf.errors(new ValidationError(ValidationLevel.ERROR) {
+ @Override
+ public String description() {
+ return "something broke";
+ }
+ });
+ }
+ }
+}
diff --git a/gestalt-core/src/test/java/org/github/gestalt/config/validation/ValidationManagerTest.java b/gestalt-core/src/test/java/org/github/gestalt/config/validation/ValidationManagerTest.java
new file mode 100644
index 000000000..0fcd66742
--- /dev/null
+++ b/gestalt-core/src/test/java/org/github/gestalt/config/validation/ValidationManagerTest.java
@@ -0,0 +1,83 @@
+package org.github.gestalt.config.validation;
+
+import org.github.gestalt.config.entity.ValidationLevel;
+import org.github.gestalt.config.reflect.TypeCapture;
+import org.github.gestalt.config.tag.Tags;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+class ValidationManagerTest {
+
+ @Test
+ public void testValidatorOk() {
+ var validationManager = new ValidationManager(List.of(new TestConfigValidator(true)));
+
+ var results = validationManager.validator("test", "my.path", TypeCapture.of(String.class), Tags.of());
+
+ Assertions.assertTrue(results.hasResults());
+ Assertions.assertFalse(results.hasErrors());
+
+ Assertions.assertEquals("test", results.results());
+ }
+
+ @Test
+ public void testTwoValidatorOk() {
+ var validationManager = new ValidationManager(List.of(new TestConfigValidator(true), new TestConfigValidator(true)));
+
+ var results = validationManager.validator("test", "my.path", TypeCapture.of(String.class), Tags.of());
+
+ Assertions.assertTrue(results.hasResults());
+ Assertions.assertFalse(results.hasErrors());
+
+ Assertions.assertEquals("test", results.results());
+ }
+
+ @Test
+ public void testValidatorOneError() {
+ var validationManager = new ValidationManager(List.of(new TestConfigValidator(false)));
+
+ var results = validationManager.validator("test", "my.path", TypeCapture.of(String.class), Tags.of());
+
+ Assertions.assertFalse(results.hasResults());
+ Assertions.assertTrue(results.hasErrors());
+
+ Assertions.assertEquals(1, results.getErrors().size());
+ Assertions.assertEquals(ValidationLevel.ERROR, results.getErrors().get(0).level());
+ Assertions.assertEquals("something broke", results.getErrors().get(0).description());
+ }
+
+ @Test
+ public void testTwoValidatorOneError() {
+ var validationManager = new ValidationManager(List.of(new TestConfigValidator(true), new TestConfigValidator(false)));
+
+ var results = validationManager.validator("test", "my.path", TypeCapture.of(String.class), Tags.of());
+
+ Assertions.assertFalse(results.hasResults());
+ Assertions.assertTrue(results.hasErrors());
+
+ Assertions.assertEquals(1, results.getErrors().size());
+ Assertions.assertEquals(ValidationLevel.ERROR, results.getErrors().get(0).level());
+ Assertions.assertEquals("something broke", results.getErrors().get(0).description());
+ }
+
+ @Test
+ public void testTwoValidatorTwoError() {
+ var validationManager = new ValidationManager(List.of(new TestConfigValidator(false), new TestConfigValidator(false)));
+
+ var results = validationManager.validator("test", "my.path", TypeCapture.of(String.class), Tags.of());
+
+ Assertions.assertFalse(results.hasResults());
+ Assertions.assertTrue(results.hasErrors());
+
+ Assertions.assertEquals(2, results.getErrors().size());
+
+ Assertions.assertEquals(ValidationLevel.ERROR, results.getErrors().get(0).level());
+ Assertions.assertEquals("something broke", results.getErrors().get(0).description());
+
+ Assertions.assertEquals(ValidationLevel.ERROR, results.getErrors().get(1).level());
+ Assertions.assertEquals("something broke", results.getErrors().get(1).description());
+ }
+
+}
diff --git a/gestalt-examples/gestalt-sample-module/build.gradle.kts b/gestalt-examples/gestalt-sample-module/build.gradle.kts
index a18c3a10b..9f9bb56c9 100644
--- a/gestalt-examples/gestalt-sample-module/build.gradle.kts
+++ b/gestalt-examples/gestalt-sample-module/build.gradle.kts
@@ -18,14 +18,25 @@ dependencies {
implementation(libs.gestalt.guice)
implementation(libs.gestalt.hocon)
implementation(libs.gestalt.kotlin)
+ implementation(libs.gestalt.micrometer)
implementation(libs.gestalt.json)
implementation(libs.gestalt.toml)
+ implementation(libs.gestalt.hibernate)
implementation(libs.gestalt.vault)
implementation(libs.gestalt.yaml)
implementation(libs.junitAPI)
+ implementation(libs.assertJ)
implementation(libs.aws.mock)
implementation(libs.guice)
+
+ implementation(libs.micrometer)
+
+ implementation(libs.hibernate.validator)
+ implementation(libs.expressly)
+
+ testImplementation(libs.junitAPI)
+ testImplementation(libs.assertJ)
}
java {
diff --git a/gestalt-examples/gestalt-sample-module/src/main/java/module-info.java b/gestalt-examples/gestalt-sample-module/src/main/java/module-info.java
index 55b2e6d60..a71c57693 100644
--- a/gestalt-examples/gestalt-sample-module/src/main/java/module-info.java
+++ b/gestalt-examples/gestalt-sample-module/src/main/java/module-info.java
@@ -2,29 +2,43 @@
* Module info definition for gestalt yaml integration
*/
module org.github.gestalt.config.integration {
+
+ requires org.github.gestalt.aws;
requires org.github.gestalt.core;
+ requires org.github.gestalt.git;
+ requires org.github.gestalt.google;
requires org.github.gestalt.guice;
requires org.github.gestalt.hocon;
requires org.github.gestalt.json;
- requires org.github.gestalt.yaml;
requires org.github.gestalt.config.kotlin;
+ requires org.github.gestalt.micrometer;
+ requires org.github.gestalt.toml;
+ requires org.github.gestalt.validation.hibernate;
+ requires org.github.gestalt.vault;
+ requires org.github.gestalt.yaml;
requires com.google.guice;
requires org.junit.jupiter.api;
requires kotlin.stdlib;
requires software.amazon.awssdk.http;
requires software.amazon.awssdk.services.s3;
- requires org.github.gestalt.git;
requires jakarta.inject;
- requires org.github.gestalt.google;
requires software.amazon.awssdk.auth;
requires software.amazon.awssdk.regions;
requires software.amazon.awssdk.http.urlconnection;
- requires org.github.gestalt.aws;
- requires org.github.gestalt.vault;
+
+ requires jakarta.validation;
+ requires jakarta.el;
+ requires org.hibernate.validator;
+ requires com.fasterxml.classmate;
+ requires net.bytebuddy;
+
+ requires vault.java.driver;
+ requires org.assertj.core;
exports org.github.gestalt.config.integration;
//you need to export any data classes you want to use the Object decoder on (since it uses reflection).
- opens org.github.gestalt.config.integration to org.github.gestalt.core, org.github.gestalt.guice, org.github.gestalt.config.kotlin;
+ opens org.github.gestalt.config.integration to org.github.gestalt.core, org.github.gestalt.guice,
+ org.github.gestalt.config.kotlin, org.hibernate.validator;
}
diff --git a/gestalt-examples/gestalt-sample-module/src/main/java/org/github/gestalt/config/integration/GestaltConfigTest.java b/gestalt-examples/gestalt-sample-module/src/main/java/org/github/gestalt/config/integration/GestaltConfigTest.java
index c454b85d3..ed47443a8 100644
--- a/gestalt-examples/gestalt-sample-module/src/main/java/org/github/gestalt/config/integration/GestaltConfigTest.java
+++ b/gestalt-examples/gestalt-sample-module/src/main/java/org/github/gestalt/config/integration/GestaltConfigTest.java
@@ -1,10 +1,19 @@
package org.github.gestalt.config.integration;
+import com.github.gestalt.config.validation.hibernate.builder.HibernateModuleBuilder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import io.github.jopenlibs.vault.Vault;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import jakarta.validation.ValidatorFactory;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
import org.github.gestalt.config.Gestalt;
import org.github.gestalt.config.annotations.Config;
import org.github.gestalt.config.annotations.ConfigPrefix;
@@ -16,6 +25,7 @@
import org.github.gestalt.config.google.storage.GCSConfigSourceBuilder;
import org.github.gestalt.config.guice.GestaltModule;
import org.github.gestalt.config.guice.InjectConfig;
+import org.github.gestalt.config.micrometer.builder.MicrometerModuleConfigBuilder;
import org.github.gestalt.config.post.process.transform.RandomTransformer;
import org.github.gestalt.config.post.process.transform.SystemPropertiesTransformer;
import org.github.gestalt.config.post.process.transform.TransformerPostProcessor;
@@ -26,6 +36,7 @@
import org.github.gestalt.config.vault.config.VaultBuilder;
import org.github.gestalt.config.vault.config.VaultModuleConfig;
import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
import software.amazon.awssdk.regions.Region;
@@ -50,10 +61,10 @@ public void integrationTest() throws GestaltException {
// The later ones layer on and over write any values in the previous
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("default.properties").build())
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.properties").build())
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .build();
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("default.properties").build())
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.properties").build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .build();
// Load the configurations, this will thow exceptions if there are any errors.
gestalt.loadConfigs();
@@ -73,11 +84,11 @@ public void integrationTestNoCache() throws GestaltException {
// The later ones layer on and over write any values in the previous
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("default.properties").build())
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.properties").build())
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .useCacheDecorator(false)
- .build();
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("default.properties").build())
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.properties").build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .useCacheDecorator(false)
+ .build();
// Load the configurations, this will thow exceptions if there are any errors.
gestalt.loadConfigs();
@@ -95,14 +106,14 @@ public void integrationTestTags() throws GestaltException {
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(URLConfigSourceBuilder.builder().setSourceURL(fileURL).build())
- .addSource(ClassPathConfigSourceBuilder.builder()
- .setResource("dev.properties")
- .setTags(Tags.of("toy", "ball"))
- .build())
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .addSource(StringConfigSourceBuilder.builder().setConfig("db.idleTimeout=123").setFormat("properties").build())
- .build();
+ .addSource(URLConfigSourceBuilder.builder().setSourceURL(fileURL).build())
+ .addSource(ClassPathConfigSourceBuilder.builder()
+ .setResource("dev.properties")
+ .setTags(Tags.of("toy", "ball"))
+ .build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addSource(StringConfigSourceBuilder.builder().setConfig("db.idleTimeout=123").setFormat("properties").build())
+ .build();
gestalt.loadConfigs();
@@ -159,11 +170,11 @@ public void integrationTestEnvVars() throws GestaltException {
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(URLConfigSourceBuilder.builder().setSourceURL(urlFile).build())
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.properties").build())
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .addSource(EnvironmentConfigSourceBuilder.builder().build())
- .build();
+ .addSource(URLConfigSourceBuilder.builder().setSourceURL(urlFile).build())
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.properties").build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addSource(EnvironmentConfigSourceBuilder.builder().build())
+ .build();
gestalt.loadConfigs();
@@ -185,10 +196,10 @@ public void integrationTestJson() throws GestaltException {
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("default.json").build())
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.json").build())
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .build();
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("default.json").build())
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.json").build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .build();
gestalt.loadConfigs();
@@ -204,10 +215,10 @@ public void integrationTestYaml() throws GestaltException {
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("default.yml").build())
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.yml").build())
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .build();
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("default.yml").build())
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.yml").build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .build();
gestalt.loadConfigs();
@@ -223,10 +234,10 @@ public void integrationTestJsonAndYaml() throws GestaltException {
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("default.json").build())
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.yml").build())
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .build();
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("default.json").build())
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.yml").build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .build();
gestalt.loadConfigs();
@@ -242,10 +253,10 @@ public void integrationTestHocon() throws GestaltException {
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("default.conf").build())
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.yml").build())
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .build();
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("default.conf").build())
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.yml").build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .build();
gestalt.loadConfigs();
@@ -261,10 +272,10 @@ public void integrationTestToml() throws GestaltException {
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("default.conf").build())
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.toml").build())
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .build();
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("default.conf").build())
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.toml").build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .build();
gestalt.loadConfigs();
@@ -283,17 +294,17 @@ public void integrationGitTest() throws GestaltException, IOException {
GitConfigSourceBuilder gitBuilder = GitConfigSourceBuilder.builder()
- .setRepoURI("https://github.com/gestalt-config/gestalt.git")
- .setConfigFilePath("gestalt-examples/gestalt-sample/src/test/resources/default.properties")
- .setLocalRepoDirectory(configDirectory);
+ .setRepoURI("https://github.com/gestalt-config/gestalt.git")
+ .setConfigFilePath("gestalt-examples/gestalt-sample/src/test/resources/default.properties")
+ .setLocalRepoDirectory(configDirectory);
ConfigSourcePackage source = gitBuilder.build();
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(source)
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.properties").build())
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .build();
+ .addSource(source)
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.properties").build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .build();
gestalt.loadConfigs();
@@ -336,10 +347,10 @@ public void integrationTestGoogleCloud() throws GestaltException {
// The later ones layer on and over write any values in the previous
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("/default.properties").build())
- .addSource(GCSConfigSourceBuilder.builder().setBucketName("gestalt-test").setObjectName("dev.properties").build())
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .build();
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("/default.properties").build())
+ .addSource(GCSConfigSourceBuilder.builder().setBucketName("gestalt-test").setObjectName("dev.properties").build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .build();
// Load the configurations, this will thow exceptions if there are any errors.
gestalt.loadConfigs();
@@ -385,25 +396,25 @@ public void integrationTestAws() throws GestaltException {
admin.overrideEnabled=true
*/
S3Client s3Client = S3Client.builder()
- .credentialsProvider(DefaultCredentialsProvider.create())
- .region(Region.US_EAST_1)
- .httpClient(UrlConnectionHttpClient.builder().build())
- .build();
+ .credentialsProvider(DefaultCredentialsProvider.create())
+ .region(Region.US_EAST_1)
+ .httpClient(UrlConnectionHttpClient.builder().build())
+ .build();
// using the builder to layer on the configuration files.
// The later ones layer on and over write any values in the previous
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("/default.properties").build())
- .addSource(S3ConfigSourceBuilder.builder()
- .setS3(s3Client)
- .setBucketName("gestalt-test")
- .setKeyName("dev.properties")
- .build())
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .addModuleConfig(AWSBuilder.builder().setRegion("us-east-1").build())
- .build();
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("/default.properties").build())
+ .addSource(S3ConfigSourceBuilder.builder()
+ .setS3(s3Client)
+ .setBucketName("gestalt-test")
+ .setKeyName("dev.properties")
+ .build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addModuleConfig(AWSBuilder.builder().setRegion("us-east-1").build())
+ .build();
// Load the configurations, this will thow exceptions if there are any errors.
gestalt.loadConfigs();
@@ -423,9 +434,9 @@ public void integrationTestPostProcessorVault() throws GestaltException, VaultEx
final VaultConfig config = new VaultConfig()
- .address("http://127.0.0.1:8080")
- .token(VAULT_TOKEN)
- .build();
+ .address("http://127.0.0.1:8080")
+ .token(VAULT_TOKEN)
+ .build();
Vault vault = Vault.create(config);
@@ -438,11 +449,11 @@ public void integrationTestPostProcessorVault() throws GestaltException, VaultEx
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("/defaultPPVault.properties").build())
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("/integration.properties").build())
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .addModuleConfig(vaultModuleConfig)
- .build();
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("/defaultPPVault.properties").build())
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("/integration.properties").build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addModuleConfig(vaultModuleConfig)
+ .build();
gestalt.loadConfigs();
@@ -484,7 +495,7 @@ private void validateResults(Gestalt gestalt) throws GestaltException {
Assertions.assertEquals(600, gestalt.getConfig("DB.connectionTimeout", Integer.class));
Assertions.assertEquals(123, db.idleTimeout);
Assertions.assertEquals(60000.0F, db.maxLifetime);
- Assertions.assertNull(db.isEnabled);
+ Assertions.assertTrue(db.isEnabled);
Assertions.assertTrue(gestalt.getConfig("db.isEnabled", true, Boolean.class));
Assertions.assertEquals(3, db.hosts.size());
@@ -508,7 +519,7 @@ private void validateResults(Gestalt gestalt) throws GestaltException {
Assertions.assertEquals(600, dbPrefix.connectionTimeout);
Assertions.assertEquals(123, dbPrefix.idleTimeout);
Assertions.assertEquals(60000.0F, dbPrefix.maxLifetime);
- Assertions.assertNull(dbPrefix.isEnabled);
+ Assertions.assertTrue(dbPrefix.isEnabled);
Assertions.assertEquals(3, dbPrefix.hosts.size());
Assertions.assertEquals("credmond", dbPrefix.hosts.getFirst().getUser());
@@ -635,7 +646,7 @@ private void validateResults(Gestalt gestalt) throws GestaltException {
Assertions.assertEquals(600, gestalt.getConfig("DB.connectionTimeout", Integer.class));
Assertions.assertEquals(123, db.idleTimeout);
Assertions.assertEquals(60000.0F, db.maxLifetime);
- Assertions.assertNull(db.isEnabled);
+ Assertions.assertTrue(db.isEnabled);
Assertions.assertTrue(gestalt.getConfig("db.isEnabled", true, Boolean.class));
Assertions.assertEquals(3, db.hosts.size());
@@ -673,11 +684,11 @@ public void integrationTestPostProcessorEnvironment() throws GestaltException {
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("defaultPPEnv.properties").build())
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("integration.properties").build())
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .addDefaultPostProcessors()
- .build();
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("defaultPPEnv.properties").build())
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("integration.properties").build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addDefaultPostProcessors()
+ .build();
gestalt.loadConfigs();
@@ -712,11 +723,11 @@ public void integrationTestPostProcessorSystem() throws GestaltException {
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("defaultPPSys.properties").build())
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("integration.properties").build())
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .addPostProcessor(new TransformerPostProcessor(List.of(new SystemPropertiesTransformer(), new RandomTransformer())))
- .build();
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("defaultPPSys.properties").build())
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("integration.properties").build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .addPostProcessor(new TransformerPostProcessor(List.of(new SystemPropertiesTransformer(), new RandomTransformer())))
+ .build();
gestalt.loadConfigs();
@@ -729,7 +740,7 @@ public void integrationTestPostProcessorSystem() throws GestaltException {
Assertions.assertEquals("booking", booking.getService().getPath());
Assertions.assertNotNull(gestalt.getConfig("appUUID", UUID.class));
Assertions.assertTrue(gestalt.getConfig("appId", Integer.class) == 20 ||
- gestalt.getConfig("appId", Integer.class) == 21);
+ gestalt.getConfig("appId", Integer.class) == 21);
}
public void integrationTestPostProcessorNode() throws GestaltException {
@@ -742,10 +753,10 @@ public void integrationTestPostProcessorNode() throws GestaltException {
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("defaultPPNode.properties").build())
- .addSource(ClassPathConfigSourceBuilder.builder().setResource("integration.properties").build())
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .build();
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("defaultPPNode.properties").build())
+ .addSource(ClassPathConfigSourceBuilder.builder().setResource("integration.properties").build())
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .build();
gestalt.loadConfigs();
@@ -769,8 +780,8 @@ public void integrationTestCamelCase() throws GestaltException {
GestaltBuilder builder = new GestaltBuilder();
Gestalt gestalt = builder
- .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
- .build();
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .build();
gestalt.loadConfigs();
@@ -781,6 +792,161 @@ public void integrationTestCamelCase() throws GestaltException {
}
+ @Test
+ public void testSubstitution() throws GestaltException {
+ Map customMap = new HashMap<>();
+ customMap.put("place", "world");
+ customMap.put("weather", "sunny");
+ customMap.put("message", "hello ${place} it is ${weather} today");
+
+ GestaltBuilder builder = new GestaltBuilder();
+ Gestalt gestalt = builder
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(customMap).build())
+ .build();
+
+ gestalt.loadConfigs();
+
+ String message = gestalt.getConfig("message", TypeCapture.of(String.class));
+
+ Assertions.assertEquals("hello world it is sunny today", message);
+ }
+
+ @Test
+ public void testNestedSubstitution() throws GestaltException {
+ Map customMap = new HashMap<>();
+ customMap.put("variable", "place");
+ customMap.put("place", "world");
+ customMap.put("weather", "sunny");
+ customMap.put("message", "hello ${${variable}} it is ${weather} today");
+
+ GestaltBuilder builder = new GestaltBuilder();
+ Gestalt gestalt = builder
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(customMap).build())
+ .build();
+
+ gestalt.loadConfigs();
+
+ String message = gestalt.getConfig("message", TypeCapture.of(String.class));
+
+ Assertions.assertEquals("hello world it is sunny today", message);
+ }
+
+ @Test
+ public void testEscapedSubstitution() throws GestaltException {
+ Map customMap = new HashMap<>();
+ customMap.put("place", "world");
+ customMap.put("weather", "sunny");
+ customMap.put("message", "hello \\${place} it is ${weather} today");
+
+ GestaltBuilder builder = new GestaltBuilder();
+ Gestalt gestalt = builder
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(customMap).build())
+ .build();
+
+ gestalt.loadConfigs();
+
+ String message = gestalt.getConfig("message", TypeCapture.of(String.class));
+
+ Assertions.assertEquals("hello ${place} it is sunny today", message);
+ }
+
+ @Test
+ public void testMetrics() throws GestaltException {
+ Map configs = new HashMap<>();
+ configs.put("db.password", "test");
+ configs.put("db.port", "123");
+ configs.put("db.uri", "my.sql.com");
+
+ SimpleMeterRegistry registry = new SimpleMeterRegistry();
+
+ GestaltBuilder builder = new GestaltBuilder();
+ Gestalt gestalt = builder
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .setMetricsEnabled(true)
+ .addModuleConfig(MicrometerModuleConfigBuilder.builder()
+ .setMeterRegistry(registry)
+ .setPrefix("myApp")
+ .build())
+ .build();
+
+ gestalt.loadConfigs();
+
+ Assertions.assertEquals("test", gestalt.getConfig("db.password", String.class));
+
+ org.assertj.core.api.Assertions.assertThat(registry.getMetersAsString())
+ .startsWith("myApp.config.get(TIMER)[]; count=1.0, total_time=");
+
+ Assertions.assertEquals("test", gestalt.getConfig("db.password", String.class));
+
+ org.assertj.core.api.Assertions.assertThat(registry.getMetersAsString())
+ .contains("myApp.config.get(TIMER)[]; count=1.0, total_time=")
+ .contains("myApp.cache.hit(COUNTER)[]; count=1.0");
+ }
+
+ @Test
+ public void testValidationOk() throws GestaltException {
+ Map configs = new HashMap<>();
+ configs.put("db.password", "test");
+ configs.put("db.port", "123");
+ configs.put("db.uri", "my.sql.com");
+
+ ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+ Validator validator = factory.getValidator();
+
+ GestaltBuilder builder = new GestaltBuilder();
+ Gestalt gestalt = builder
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .setValidationEnabled(true)
+ .addModuleConfig(HibernateModuleBuilder.builder()
+ .setValidator(validator)
+ .build())
+ .build();
+
+ gestalt.loadConfigs();
+
+ Assertions.assertEquals("test", gestalt.getConfig("db.password", String.class));
+ Assertions.assertEquals("test", gestalt.getConfig("db", DBInfo.class).password);
+ Assertions.assertEquals(123, gestalt.getConfig("db", DBInfo.class).port);
+
+ Assertions.assertEquals("test", gestalt.getConfig("db", DBInfoValid.class).password);
+ Assertions.assertEquals(123, gestalt.getConfig("db", DBInfoValid.class).port);
+ }
+
+ @Test
+ public void testValidationError() throws GestaltException {
+ Map configs = new HashMap<>();
+ configs.put("db.password", "12345678901234567890");
+ configs.put("db.port", "0");
+ configs.put("db.uri", "my.sql.com");
+
+ ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+ Validator validator = factory.getValidator();
+
+ GestaltBuilder builder = new GestaltBuilder();
+ Gestalt gestalt = builder
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .setValidationEnabled(true)
+ .addModuleConfig(HibernateModuleBuilder.builder()
+ .setValidator(validator)
+ .build())
+ .build();
+
+ gestalt.loadConfigs();
+
+ Assertions.assertEquals("12345678901234567890", gestalt.getConfig("db.password", String.class));
+ Assertions.assertEquals("12345678901234567890", gestalt.getConfig("db", DBInfo.class).password);
+ Assertions.assertEquals(0, gestalt.getConfig("db", DBInfo.class).port);
+
+ var ex = Assertions.assertThrows(GestaltException.class, () -> gestalt.getConfig("db", DBInfoValid.class));
+
+ org.assertj.core.api.Assertions.assertThat(ex.getMessage())
+ .startsWith("Validation failed for config path: db, and " +
+ "class: org.github.gestalt.config.integration.GestaltConfigTest$DBInfoValid")
+ .contains("- level: ERROR, message: Hibernate Validator, on path: db, error: size must be between 2 and 14")
+ .contains("- level: ERROR, message: Hibernate Validator, on path: db, error: port should not be less than 10");
+ }
+
+
public enum Role {
LEVEL0, LEVEL1
}
@@ -924,7 +1090,7 @@ public static class DataBase {
public int connectionTimeout;
public Integer idleTimeout;
public float maxLifetime;
- public Boolean isEnabled;
+ public Boolean isEnabled = true;
public DataBase() {
@@ -937,7 +1103,7 @@ public static class DataBasePrefix {
public int connectionTimeout;
public Integer idleTimeout;
public float maxLifetime;
- public Boolean isEnabled;
+ public Boolean isEnabled = true;
public DataBasePrefix() {
}
@@ -1042,4 +1208,97 @@ public void setDataBase(DataBase dataBase) {
this.dataBase = dataBase;
}
}
+
+
+ public static class DBInfo {
+ private Integer port;
+ private String uri;
+ private String password;
+ @Config(defaultVal = "200")
+ private Integer connections;
+
+ public DBInfo() {
+ }
+
+ public Integer getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public void setUri(String uri) {
+ this.uri = uri;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public Integer getConnections() {
+ return connections;
+ }
+
+ public void setConnections(Integer connections) {
+ this.connections = connections;
+ }
+ }
+
+ public static class DBInfoValid {
+
+ @Min(value = 10, message = "port should not be less than 10")
+ @Max(value = 200, message = "port should not be greater than 200")
+ private Integer port;
+ private String uri;
+ @NotNull
+ @Size(min = 2, max = 14)
+ private String password;
+ @NotNull
+ @Config(defaultVal = "200")
+ private Integer connections;
+
+ public DBInfoValid() {
+ }
+
+ public Integer getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public void setUri(String uri) {
+ this.uri = uri;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public Integer getConnections() {
+ return connections;
+ }
+
+ public void setConnections(Integer connections) {
+ this.connections = connections;
+ }
+ }
}
diff --git a/gestalt-examples/gestalt-sample-module/src/main/java/org/github/gestalt/config/integration/MainClass.java b/gestalt-examples/gestalt-sample-module/src/main/java/org/github/gestalt/config/integration/MainClass.java
index 03328d56a..2ebfb3b5f 100644
--- a/gestalt-examples/gestalt-sample-module/src/main/java/org/github/gestalt/config/integration/MainClass.java
+++ b/gestalt-examples/gestalt-sample-module/src/main/java/org/github/gestalt/config/integration/MainClass.java
@@ -26,6 +26,10 @@ public static void main(String[] args) throws GestaltException, IOException, Vau
configTest.integrationTestPostProcessorSystem();
configTest.integrationTestPostProcessorNode();
configTest.integrationTestCamelCase();
+ configTest.testEscapedSubstitution();
+ configTest.testMetrics();
+ configTest.testValidationOk();
+ configTest.testValidationError();
if( Boolean.parseBoolean(System.getenv("LOCAL_TEST"))) {
configTest.integrationTestGoogleCloud();
diff --git a/gestalt-examples/gestalt-sample-module/src/main/kotlin/org/github/gestalt/config/integration/GestaltKotlinTest.kt b/gestalt-examples/gestalt-sample-module/src/main/kotlin/org/github/gestalt/config/integration/GestaltKotlinTest.kt
index aed4f5739..cd99ed256 100644
--- a/gestalt-examples/gestalt-sample-module/src/main/kotlin/org/github/gestalt/config/integration/GestaltKotlinTest.kt
+++ b/gestalt-examples/gestalt-sample-module/src/main/kotlin/org/github/gestalt/config/integration/GestaltKotlinTest.kt
@@ -26,6 +26,7 @@ class GestaltKotlinTest {
.addSource(ClassPathConfigSourceBuilder.builder().setResource("default.properties").build())
.addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.properties").build())
.addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .setTreatMissingValuesAsErrors(false)
.build()
gestalt.loadConfigs()
testValidation(gestalt)
@@ -43,6 +44,7 @@ class GestaltKotlinTest {
.addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.properties").build())
.addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
.addSource(EnvironmentConfigSourceBuilder.builder().build())
+ .setTreatMissingValuesAsErrors(false)
.build()
gestalt.loadConfigs()
testValidation(gestalt)
@@ -67,6 +69,7 @@ class GestaltKotlinTest {
.addSource(ClassPathConfigSourceBuilder.builder().setResource("default.properties").build())
.addSource(ClassPathConfigSourceBuilder.builder().setResource("dev.properties").build())
.addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .setTreatMissingValuesAsErrors(false)
.build()
gestalt.loadConfigs()
val pool: HttpPool = gestalt.getConfig("http.pool", KTypeCapture.of(typeOf())) as HttpPool
@@ -206,7 +209,7 @@ class GestaltKotlinTest {
var connectionTimeout = 0
var idleTimeout: Int? = null
var maxLifetime = 0f
- var isEnabled: Boolean? = null
+ var isEnabled: Boolean? = null;
}
class User {
diff --git a/gestalt-examples/gestalt-sample-module/src/test/java/org/github/gestalt/config/integration/GestaltSampleTest.java b/gestalt-examples/gestalt-sample-module/src/test/java/org/github/gestalt/config/integration/GestaltSampleTest.java
new file mode 100644
index 000000000..e08ac4f83
--- /dev/null
+++ b/gestalt-examples/gestalt-sample-module/src/test/java/org/github/gestalt/config/integration/GestaltSampleTest.java
@@ -0,0 +1,47 @@
+package org.github.gestalt.config.integration;
+
+import io.github.jopenlibs.vault.VaultException;
+import org.github.gestalt.config.exceptions.GestaltException;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class GestaltSampleTest {
+
+ @Test
+ public void testIntegration() throws GestaltException, IOException, VaultException {
+ GestaltConfigTest configTest = new GestaltConfigTest();
+ configTest.integrationTest();
+ configTest.integrationTestNoCache();
+ configTest.integrationTestTags();
+ configTest.integrationTestEnvVars();
+ configTest.integrationTestJson();
+ configTest.integrationTestYaml();
+ configTest.integrationTestJsonAndYaml();
+ configTest.integrationTestHocon();
+ configTest.integrationTestToml();
+ configTest.integrationGitTest();
+ configTest.integrationTestPostProcessorEnvironment();
+ configTest.integrationTestPostProcessorSystem();
+ configTest.integrationTestPostProcessorNode();
+ configTest.integrationTestCamelCase();
+ configTest.testEscapedSubstitution();
+ configTest.testMetrics();
+ configTest.testValidationOk();
+ configTest.testValidationError();
+
+ if( Boolean.parseBoolean(System.getenv("LOCAL_TEST"))) {
+ configTest.integrationTestGoogleCloud();
+ configTest.integrationTestAws();
+ }
+
+ if( Boolean.parseBoolean(System.getenv("GESTALT_VAULT_TEST"))) {
+ configTest.integrationTestPostProcessorVault();
+ }
+
+ var kotlinTests = new GestaltKotlinTest();
+ kotlinTests.integrationTest();
+ kotlinTests.integrationTestEnvVars();
+ kotlinTests.integrationTestWithTypeOf();
+ }
+}
diff --git a/gestalt-examples/gestalt-sample/build.gradle.kts b/gestalt-examples/gestalt-sample/build.gradle.kts
index e917f3867..ab5db41ec 100644
--- a/gestalt-examples/gestalt-sample/build.gradle.kts
+++ b/gestalt-examples/gestalt-sample/build.gradle.kts
@@ -41,6 +41,7 @@ testing {
implementation(project(":gestalt-micrometer"))
implementation(project(":gestalt-json"))
implementation(project(":gestalt-toml"))
+ implementation(project(":gestalt-validator-hibernate"))
implementation(project(":gestalt-vault"))
implementation(project(":gestalt-yaml"))
@@ -54,6 +55,10 @@ testing {
implementation(project(":gestalt-guice"))
implementation(libs.testcontainers.junit5)
implementation(libs.micrometer)
+
+ implementation(libs.hibernate.validator)
+
+ implementation(libs.expressly)
}
}
}
diff --git a/gestalt-examples/gestalt-sample/src/test/java/org/github/gestalt/config/integration/GestaltSample.java b/gestalt-examples/gestalt-sample/src/test/java/org/github/gestalt/config/integration/GestaltSample.java
index ba0386811..ecbf36156 100644
--- a/gestalt-examples/gestalt-sample/src/test/java/org/github/gestalt/config/integration/GestaltSample.java
+++ b/gestalt-examples/gestalt-sample/src/test/java/org/github/gestalt/config/integration/GestaltSample.java
@@ -2,6 +2,7 @@
import com.adobe.testing.s3mock.testcontainers.S3MockContainer;
+import com.github.gestalt.config.validation.hibernate.builder.HibernateModuleBuilder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import io.github.jopenlibs.vault.Vault;
@@ -9,6 +10,13 @@
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.response.LogicalResponse;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import jakarta.validation.Validation;
+import jakarta.validation.Validator;
+import jakarta.validation.ValidatorFactory;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
import org.github.gestalt.config.Gestalt;
import org.github.gestalt.config.annotations.Config;
import org.github.gestalt.config.annotations.ConfigPrefix;
@@ -1391,6 +1399,67 @@ public void testMetrics() throws GestaltException {
.contains("myApp.cache.hit(COUNTER)[]; count=1.0");
}
+ @Test
+ public void testValidationOk() throws GestaltException {
+ Map configs = new HashMap<>();
+ configs.put("db.password", "test");
+ configs.put("db.port", "123");
+ configs.put("db.uri", "my.sql.com");
+
+ ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+ Validator validator = factory.getValidator();
+
+ GestaltBuilder builder = new GestaltBuilder();
+ Gestalt gestalt = builder
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .setValidationEnabled(true)
+ .addModuleConfig(HibernateModuleBuilder.builder()
+ .setValidator(validator)
+ .build())
+ .build();
+
+ gestalt.loadConfigs();
+
+ Assertions.assertEquals("test", gestalt.getConfig("db.password", String.class));
+ Assertions.assertEquals("test", gestalt.getConfig("db", DBInfo.class).password);
+ Assertions.assertEquals(123, gestalt.getConfig("db", DBInfo.class).port);
+
+ Assertions.assertEquals("test", gestalt.getConfig("db", DBInfoValid.class).password);
+ Assertions.assertEquals(123, gestalt.getConfig("db", DBInfoValid.class).port);
+ }
+
+ @Test
+ public void testValidationError() throws GestaltException {
+ Map configs = new HashMap<>();
+ configs.put("db.password", "12345678901234567890");
+ configs.put("db.port", "0");
+ configs.put("db.uri", "my.sql.com");
+
+ ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+ Validator validator = factory.getValidator();
+
+ Gestalt gestalt = new GestaltBuilder()
+ .addSource(MapConfigSourceBuilder.builder().setCustomConfig(configs).build())
+ .setValidationEnabled(true)
+ .addModuleConfig(HibernateModuleBuilder.builder()
+ .setValidator(validator)
+ .build())
+ .build();
+
+ gestalt.loadConfigs();
+
+ Assertions.assertEquals("12345678901234567890", gestalt.getConfig("db.password", String.class));
+ Assertions.assertEquals("12345678901234567890", gestalt.getConfig("db", DBInfo.class).password);
+ Assertions.assertEquals(0, gestalt.getConfig("db", DBInfo.class).port);
+
+ var ex = Assertions.assertThrows(GestaltException.class, () -> gestalt.getConfig("db", DBInfoValid.class));
+ org.assertj.core.api.Assertions.assertThat(ex.getMessage())
+ .startsWith("Validation failed for config path: db, and " +
+ "class: org.github.gestalt.config.integration.GestaltSample$DBInfoValid")
+ .contains("- level: ERROR, message: Hibernate Validator, on path: db, error: size must be between 2 and 14")
+ .contains("- level: ERROR, message: Hibernate Validator, on path: db, error: port should not be less than 10");
+
+ }
public enum Role {
LEVEL0, LEVEL1
@@ -1726,4 +1795,100 @@ public void setMaxTotal(Integer port) {
this.maxTotal = port;
}
}
+
+ public static class DBInfo {
+ private Integer port;
+ private String uri;
+ private String password;
+
+ public DBInfo() {
+ }
+
+ @Config(defaultVal = "200")
+ private Integer connections;
+
+ public Integer getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public void setUri(String uri) {
+ this.uri = uri;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public Integer getConnections() {
+ return connections;
+ }
+
+ public void setConnections(Integer connections) {
+ this.connections = connections;
+ }
+ }
+
+ public static class DBInfoValid {
+
+ public DBInfoValid() {
+ }
+
+ @Min(value = 10, message = "port should not be less than 10")
+ @Max(value = 200, message = "port should not be greater than 200")
+ private Integer port;
+
+ private String uri;
+
+ @NotNull
+ @Size(min = 2, max = 14)
+ private String password;
+
+ @NotNull
+ @Config(defaultVal = "200")
+ private Integer connections;
+
+ public Integer getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public void setUri(String uri) {
+ this.uri = uri;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public Integer getConnections() {
+ return connections;
+ }
+
+ public void setConnections(Integer connections) {
+ this.connections = connections;
+ }
+ }
}
diff --git a/gestalt-micrometer/src/main/java/module-info.java b/gestalt-micrometer/src/main/java/module-info.java
index d7bbf976c..2c4fe3d20 100644
--- a/gestalt-micrometer/src/main/java/module-info.java
+++ b/gestalt-micrometer/src/main/java/module-info.java
@@ -2,7 +2,7 @@
* Module info definition for gestalt yaml integration
*/
@SuppressWarnings({ "requires-transitive-automatic" })
-module org.github.gestalt.metrics {
+module org.github.gestalt.micrometer {
requires org.github.gestalt.core;
requires transitive micrometer.core;
diff --git a/gestalt-validator-hibernate/build.gradle.kts b/gestalt-validator-hibernate/build.gradle.kts
new file mode 100644
index 000000000..d1f71e368
--- /dev/null
+++ b/gestalt-validator-hibernate/build.gradle.kts
@@ -0,0 +1,15 @@
+plugins {
+ id("gestalt.java-library-conventions")
+ id("gestalt.java-test-conventions")
+ id("gestalt.java-code-quality-conventions")
+ id("gestalt.java-publish-conventions")
+}
+
+dependencies {
+ implementation(project(":gestalt-core"))
+ api(libs.hibernate.validator)
+
+ testImplementation(libs.expressly)
+}
+
+
diff --git a/gestalt-validator-hibernate/src/main/java/com/github/gestalt/config/validation/hibernate/HibernateConfigValidator.java b/gestalt-validator-hibernate/src/main/java/com/github/gestalt/config/validation/hibernate/HibernateConfigValidator.java
new file mode 100644
index 000000000..0da594003
--- /dev/null
+++ b/gestalt-validator-hibernate/src/main/java/com/github/gestalt/config/validation/hibernate/HibernateConfigValidator.java
@@ -0,0 +1,80 @@
+package com.github.gestalt.config.validation.hibernate;
+
+import com.github.gestalt.config.validation.hibernate.config.HibernateModuleConfig;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.Validation;
+import jakarta.validation.ValidatorFactory;
+import org.github.gestalt.config.entity.GestaltConfig;
+import org.github.gestalt.config.entity.ValidationError;
+import org.github.gestalt.config.entity.ValidationLevel;
+import org.github.gestalt.config.reflect.TypeCapture;
+import org.github.gestalt.config.tag.Tags;
+import org.github.gestalt.config.utils.GResultOf;
+import org.github.gestalt.config.validation.ConfigValidator;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Implementation of the Gestalt validator for Hibernate. Used to validate configurations.
+ *
+ * @author Colin Redmond (c) 2024.
+ */
+public final class HibernateConfigValidator implements ConfigValidator {
+
+ private static final System.Logger logger = System.getLogger(HibernateConfigValidator.class.getName());
+
+ private jakarta.validation.Validator validator;
+
+ public HibernateConfigValidator() {
+ try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
+ validator = factory.getValidator();
+ }
+ }
+
+ @Override
+ public void applyConfig(GestaltConfig config) {
+ HibernateModuleConfig moduleConfig = config.getModuleConfig(HibernateModuleConfig.class);
+ if (moduleConfig == null ) {
+ logger.log(System.Logger.Level.WARNING, "Hibernate Validator will be set to the defaults and use a " +
+ "Validation.buildDefaultValidatorFactory() to build a Validator, please register a HibernateModuleConfig with " +
+ "gestalt builder to customize the validation.");
+
+ ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+ validator = factory.getValidator();
+ } else {
+
+ validator = moduleConfig.getValidator();
+ }
+ }
+
+ @Override
+ public GResultOf validator(T obj, String path, TypeCapture klass, Tags tags) {
+ Set> constraintViolations = validator.validate(obj);
+
+ if (constraintViolations.isEmpty()) {
+ return GResultOf.result(obj);
+ } else {
+ return GResultOf.errors(constraintViolations.stream()
+ .map(ConstraintViolation::getMessage)
+ .map(it -> new HibernateValidatorError(path, it))
+ .collect(Collectors.toList()));
+ }
+ }
+
+ public static class HibernateValidatorError extends ValidationError {
+ private final String path;
+ private final String error;
+
+ public HibernateValidatorError(String path, String error) {
+ super(ValidationLevel.ERROR);
+ this.path = path;
+ this.error = error;
+ }
+
+ @Override
+ public String description() {
+ return "Hibernate Validator, on path: " + path + ", error: " + error;
+ }
+ }
+}
diff --git a/gestalt-validator-hibernate/src/main/java/com/github/gestalt/config/validation/hibernate/builder/HibernateModuleBuilder.java b/gestalt-validator-hibernate/src/main/java/com/github/gestalt/config/validation/hibernate/builder/HibernateModuleBuilder.java
new file mode 100644
index 000000000..eed9623a9
--- /dev/null
+++ b/gestalt-validator-hibernate/src/main/java/com/github/gestalt/config/validation/hibernate/builder/HibernateModuleBuilder.java
@@ -0,0 +1,54 @@
+package com.github.gestalt.config.validation.hibernate.builder;
+
+import com.github.gestalt.config.validation.hibernate.config.HibernateModuleConfig;
+import jakarta.validation.Validator;
+
+/**
+ * Builder for creating Vault specific configuration.
+ * You can either provide the HibernateModuleConfig and the builder will create the client,
+ * or you can provide a Vault client yourself.
+ *
+ *
+ * @author Colin Redmond (c) 2024.
+ */
+public final class HibernateModuleBuilder {
+
+ private Validator validator;
+
+ private HibernateModuleBuilder() {
+
+ }
+
+ public static HibernateModuleBuilder builder() {
+ return new HibernateModuleBuilder();
+ }
+
+ /**
+ * Get the jakarta validator.
+ *
+ * @return the jakarta validator
+ */
+ public Validator getValidator() {
+ return validator;
+ }
+
+ /**
+ * Set the jakarta validator.
+ *
+ * @param validator the jakarta validator
+ * @return builder
+ */
+ public HibernateModuleBuilder setValidator(Validator validator) {
+ this.validator = validator;
+ return this;
+ }
+
+ /**
+ * Build the HibernateModuleConfig.
+ *
+ * @return HibernateModuleConfig
+ */
+ public HibernateModuleConfig build() {
+ return new HibernateModuleConfig(validator);
+ }
+}
diff --git a/gestalt-validator-hibernate/src/main/java/com/github/gestalt/config/validation/hibernate/config/HibernateModuleConfig.java b/gestalt-validator-hibernate/src/main/java/com/github/gestalt/config/validation/hibernate/config/HibernateModuleConfig.java
new file mode 100644
index 000000000..8f475884d
--- /dev/null
+++ b/gestalt-validator-hibernate/src/main/java/com/github/gestalt/config/validation/hibernate/config/HibernateModuleConfig.java
@@ -0,0 +1,38 @@
+package com.github.gestalt.config.validation.hibernate.config;
+
+import jakarta.validation.Validator;
+import org.github.gestalt.config.entity.GestaltModuleConfig;
+
+
+/**
+ * Module config for Hibernate. Provides a jakarta Validator to validate objects.
+ *
+ * @author Colin Redmond (c) 2024.
+ */
+public final class HibernateModuleConfig implements GestaltModuleConfig {
+
+ private final Validator validator;
+
+ /**
+ * Create the HibernateModuleConfig.
+ *
+ * @param validator jakarta Validator
+ */
+ public HibernateModuleConfig(Validator validator) {
+ this.validator = validator;
+ }
+
+ @Override
+ public String name() {
+ return "hibernate-validator";
+ }
+
+ /**
+ * Get the jakarta Validator.
+ *
+ * @return jakarta Validator
+ */
+ public Validator getValidator() {
+ return validator;
+ }
+}
diff --git a/gestalt-validator-hibernate/src/main/java/module-info.java b/gestalt-validator-hibernate/src/main/java/module-info.java
new file mode 100644
index 000000000..0855c8310
--- /dev/null
+++ b/gestalt-validator-hibernate/src/main/java/module-info.java
@@ -0,0 +1,16 @@
+/*
+ * Module info definition for gestalt yaml integration
+ */
+@SuppressWarnings({ "requires-transitive-automatic" })
+module org.github.gestalt.validation.hibernate {
+ requires org.github.gestalt.core;
+ requires transitive jakarta.validation;
+ requires transitive org.hibernate.validator;
+
+ exports com.github.gestalt.config.validation.hibernate;
+ exports com.github.gestalt.config.validation.hibernate.config;
+ exports com.github.gestalt.config.validation.hibernate.builder;
+
+ provides org.github.gestalt.config.validation.ConfigValidator with
+ com.github.gestalt.config.validation.hibernate.HibernateConfigValidator;
+}
diff --git a/gestalt-validator-hibernate/src/main/resources/META-INF/services/org.github.gestalt.config.validation.ConfigValidator b/gestalt-validator-hibernate/src/main/resources/META-INF/services/org.github.gestalt.config.validation.ConfigValidator
new file mode 100644
index 000000000..db4bf2ae2
--- /dev/null
+++ b/gestalt-validator-hibernate/src/main/resources/META-INF/services/org.github.gestalt.config.validation.ConfigValidator
@@ -0,0 +1 @@
+com.github.gestalt.config.validation.hibernate.HibernateConfigValidator
diff --git a/gestalt-validator-hibernate/src/test/java/com/github/gestalt/config/validation/hibernate/HibernateConfigValidatorTest.java b/gestalt-validator-hibernate/src/test/java/com/github/gestalt/config/validation/hibernate/HibernateConfigValidatorTest.java
new file mode 100644
index 000000000..09cadef9c
--- /dev/null
+++ b/gestalt-validator-hibernate/src/test/java/com/github/gestalt/config/validation/hibernate/HibernateConfigValidatorTest.java
@@ -0,0 +1,128 @@
+package com.github.gestalt.config.validation.hibernate;
+
+import com.github.gestalt.config.validation.hibernate.builder.HibernateModuleBuilder;
+import jakarta.validation.Validation;
+import jakarta.validation.ValidatorFactory;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import org.github.gestalt.config.entity.GestaltConfig;
+import org.github.gestalt.config.entity.ValidationLevel;
+import org.github.gestalt.config.reflect.TypeCapture;
+import org.github.gestalt.config.tag.Tags;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class HibernateConfigValidatorTest {
+
+ @Test
+ public void testHibernateValidatorOk() {
+
+ ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+ var validator = factory.getValidator();
+ HibernateModuleBuilder builder = HibernateModuleBuilder.builder().setValidator(validator);
+ GestaltConfig gestaltConfig = new GestaltConfig();
+ gestaltConfig.registerModuleConfig(builder.build());
+
+ HibernateConfigValidator hibernateValidator = new HibernateConfigValidator();
+
+ hibernateValidator.applyConfig(gestaltConfig);
+
+ Car car = new Car("Morris", "DD-AB-123", 2);
+
+ var results = hibernateValidator.validator(car, "car", TypeCapture.of(Car.class), Tags.of());
+
+ Assertions.assertTrue(results.hasResults());
+ Assertions.assertFalse(results.hasErrors());
+ }
+
+ @Test
+ public void testHibernateValidatorErrors() {
+
+ ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+ var validator = factory.getValidator();
+ HibernateModuleBuilder builder = HibernateModuleBuilder.builder().setValidator(validator);
+ GestaltConfig gestaltConfig = new GestaltConfig();
+ gestaltConfig.registerModuleConfig(builder.build());
+
+ HibernateConfigValidator hibernateValidator = new HibernateConfigValidator();
+
+ hibernateValidator.applyConfig(gestaltConfig);
+
+ Car car = new Car("Morris", "DD-AB-123", 1);
+
+ var results = hibernateValidator.validator(car, "car", TypeCapture.of(Car.class), Tags.of());
+
+ Assertions.assertFalse(results.hasResults());
+ Assertions.assertTrue(results.hasErrors());
+ Assertions.assertEquals(1, results.getErrors().size());
+ Assertions.assertEquals(ValidationLevel.ERROR, results.getErrors().get(0).level());
+ Assertions.assertEquals("Hibernate Validator, on path: car, error: must be greater than or equal to 2",
+ results.getErrors().get(0).description());
+ }
+
+ @Test
+ public void testHibernateValidatorMultipleErrors() {
+
+ ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+ var validator = factory.getValidator();
+ HibernateModuleBuilder builder = HibernateModuleBuilder.builder().setValidator(validator);
+ GestaltConfig gestaltConfig = new GestaltConfig();
+ gestaltConfig.registerModuleConfig(builder.build());
+
+ HibernateConfigValidator hibernateValidator = new HibernateConfigValidator();
+
+ hibernateValidator.applyConfig(gestaltConfig);
+
+ Car car = new Car("Morris", "A", 1);
+
+ var results = hibernateValidator.validator(car, "car", TypeCapture.of(Car.class), Tags.of());
+
+ Assertions.assertFalse(results.hasResults());
+ Assertions.assertTrue(results.hasErrors());
+ Assertions.assertEquals(2, results.getErrors().size());
+
+ Assertions.assertEquals(ValidationLevel.ERROR, results.getErrors().get(0).level());
+
+ org.assertj.core.api.Assertions.assertThat(results.getErrors().get(0).description()).containsAnyOf(
+ "Hibernate Validator, on path: car, error: size must be between 2 and 14",
+ "Hibernate Validator, on path: car, error: must be greater than or equal to 2");
+
+ Assertions.assertEquals(ValidationLevel.ERROR, results.getErrors().get(1).level());
+ org.assertj.core.api.Assertions.assertThat(results.getErrors().get(1).description()).containsAnyOf(
+ "Hibernate Validator, on path: car, error: size must be between 2 and 14",
+ "Hibernate Validator, on path: car, error: must be greater than or equal to 2");
+ }
+
+ public static class Car {
+
+ @NotNull
+ private final String manufacturer;
+
+ @NotNull
+ @Size(min = 2, max = 14)
+ private final String licensePlate;
+
+ @Min(2)
+ private final int seatCount;
+
+ public Car(String manufacturer, String licencePlate, int seatCount) {
+ this.manufacturer = manufacturer;
+ this.licensePlate = licencePlate;
+ this.seatCount = seatCount;
+ }
+
+ public String getManufacturer() {
+ return manufacturer;
+ }
+
+ public String getLicensePlate() {
+ return licensePlate;
+ }
+
+ public int getSeatCount() {
+ return seatCount;
+ }
+ }
+}
+
diff --git a/gestalt-validator-hibernate/src/test/java/com/github/gestalt/config/validation/hibernate/builder/HibernateModuleBuilderTest.java b/gestalt-validator-hibernate/src/test/java/com/github/gestalt/config/validation/hibernate/builder/HibernateModuleBuilderTest.java
new file mode 100644
index 000000000..664ef062e
--- /dev/null
+++ b/gestalt-validator-hibernate/src/test/java/com/github/gestalt/config/validation/hibernate/builder/HibernateModuleBuilderTest.java
@@ -0,0 +1,23 @@
+package com.github.gestalt.config.validation.hibernate.builder;
+
+import jakarta.validation.Validation;
+import jakarta.validation.ValidatorFactory;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class HibernateModuleBuilderTest {
+
+ @Test
+ public void builderTest() {
+ ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+ var validator = factory.getValidator();
+ HibernateModuleBuilder builder = HibernateModuleBuilder.builder().setValidator(validator);
+
+ Assertions.assertEquals(validator, builder.getValidator());
+
+ var config = builder.build();
+ Assertions.assertEquals(validator, config.getValidator());
+ Assertions.assertEquals("hibernate-validator", config.name());
+ }
+
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ff4837b52..1af6cf621 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -7,7 +7,7 @@ kotlin = "1.9.23"
kotlinDokka = "1.9.20"
# Kotlin DI
kodeinDI = "7.21.2"
-koinDI = "3.5.3"
+koinDI = "3.5.4"
# Java DI
guice = "7.0.0"
# @pin
@@ -20,8 +20,8 @@ weldCore = "4.0.3.Final"
jackson = "2.17.0"
hocon = "1.4.3"
# Cloud
-awsBom = "2.25.23"
-gcpLibraries = "26.35.0"
+awsBom = "2.25.26"
+gcpLibraries = "26.37.0"
# vault
vault = "6.2.0"
# Git support
@@ -29,6 +29,9 @@ jgit = "6.9.0.202403050737-r"
eddsa = "0.3.0"
# metrics
micrometer = "1.12.4"
+# validation
+hibernateValidator = "8.0.1.Final"
+expressly = "5.0.0"
# testing
junit5 = "5.10.2"
assertJ = "3.25.3"
@@ -47,7 +50,7 @@ checkStyle = "10.10.0"
jmh = "1.37"
gradleJmh = "0.7.2"
# @pin gestalt for integration tests.
-gestalt = "0.25.3"
+gestalt = "0.27.0"
# Gradle utility
gradleVersions = "0.51.0"
gitVersions = "3.0.0"
@@ -93,6 +96,9 @@ jgit-apache-SSH = { module = "org.eclipse.jgit:org.eclipse.jgit.ssh.apache", ver
eddsa = { module = "net.i2p.crypto:eddsa", version.ref = "eddsa" }
# metrics
micrometer = { module = "io.micrometer:micrometer-core", version.ref = "micrometer" }
+# configValidators
+hibernate-validator = { module = "org.hibernate.validator:hibernate-validator", version.ref = "hibernateValidator" }
+expressly = { module = "org.glassfish.expressly:expressly", version.ref = "expressly" }
# Testing
junitAPI = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit5" }
junitEngine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit5" }
@@ -127,7 +133,9 @@ gestalt-json = { module = "com.github.gestalt-config:gestalt-json", version.ref
gestalt-kodein-di = { module = "com.github.gestalt-config:gestalt-kodein-di", version.ref = "gestalt" }
gestalt-koin-di = { module = "com.github.gestalt-config:gestalt-koin-di", version.ref = "gestalt" }
gestalt-kotlin = { module = "com.github.gestalt-config:gestalt-kotlin", version.ref = "gestalt" }
+gestalt-micrometer = { module = "com.github.gestalt-config:gestalt-micrometer", version.ref = "gestalt" }
gestalt-toml = { module = "com.github.gestalt-config:gestalt-toml", version.ref = "gestalt" }
+gestalt-hibernate = { module = "com.github.gestalt-config:gestalt-validator-hibernate", version.ref = "gestalt" }
gestalt-vault = { module = "com.github.gestalt-config:gestalt-vault", version.ref = "gestalt" }
gestalt-yaml = { module = "com.github.gestalt-config:gestalt-yaml", version.ref = "gestalt" }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 1cc873349..111d10536 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -2,7 +2,7 @@ rootProject.name = "gestalt"
include(
"gestalt-aws", "gestalt-cdi", "gestalt-core", "gestalt-hocon", "gestalt-json", "gestalt-git",
"gestalt-google-cloud", "gestalt-guice", "gestalt-kotlin", "gestalt-micrometer", "gestalt-kodein-di",
- "gestalt-koin-di", "gestalt-toml", "gestalt-vault", "gestalt-yaml"
+ "gestalt-koin-di", "gestalt-toml", "gestalt-validator-hibernate", "gestalt-vault", "gestalt-yaml"
)
// testing utility projects