diff --git a/pom.xml b/pom.xml
index 0710a38..5d28d8a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -134,6 +134,11 @@
byte-buddy
1.14.13
+
+ org.codehaus.groovy
+ groovy
+ 3.0.17
+
diff --git a/src/main/java/ru/ewc/checklogic/FileStateFactory.java b/src/main/java/ru/ewc/checklogic/FileStateFactory.java
index 2b62497..05d309d 100644
--- a/src/main/java/ru/ewc/checklogic/FileStateFactory.java
+++ b/src/main/java/ru/ewc/checklogic/FileStateFactory.java
@@ -29,12 +29,14 @@
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.SneakyThrows;
import org.yaml.snakeyaml.Yaml;
+import ru.ewc.checklogic.testing.FunctionsLocator;
import ru.ewc.decisions.api.InMemoryLocator;
import ru.ewc.decisions.api.Locator;
import ru.ewc.state.State;
@@ -65,9 +67,19 @@ public State initialState() {
this.locators.clear();
this.loadLocatorsFromApplicationConfig();
this.loadInMemoryRequestLocator();
+ this.loadFunctionsLocator();
return new State(this.locators);
}
+ private void loadFunctionsLocator() {
+ this.locators.add(
+ new FunctionsLocator(
+ this.config.functionsLocatorName(),
+ Paths.get(this.config.getRoot(), "functions")
+ )
+ );
+ }
+
private void loadInMemoryRequestLocator() {
this.locators.add(
new InMemoryLocator(this.config.requestLocatorName(), new HashMap<>())
diff --git a/src/main/java/ru/ewc/checklogic/ServerConfiguration.java b/src/main/java/ru/ewc/checklogic/ServerConfiguration.java
index e4b352f..addb02b 100644
--- a/src/main/java/ru/ewc/checklogic/ServerConfiguration.java
+++ b/src/main/java/ru/ewc/checklogic/ServerConfiguration.java
@@ -48,7 +48,8 @@ public ServerConfiguration(final String root) {
this.parameters = new HashMap<>(
Map.of(
"request", "request",
- "command", "available"
+ "command", "available",
+ "function", "function"
)
);
}
@@ -88,4 +89,8 @@ public Path applicationConfig() {
public String getRoot() {
return this.root;
}
+
+ public String functionsLocatorName() {
+ return this.parameters.get("function");
+ }
}
diff --git a/src/main/java/ru/ewc/checklogic/testing/FunctionsLocator.java b/src/main/java/ru/ewc/checklogic/testing/FunctionsLocator.java
new file mode 100644
index 0000000..8261047
--- /dev/null
+++ b/src/main/java/ru/ewc/checklogic/testing/FunctionsLocator.java
@@ -0,0 +1,92 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2024 Decision-Driven Development
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package ru.ewc.checklogic.testing;
+
+import groovy.lang.Binding;
+import groovy.util.GroovyScriptEngine;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.HashMap;
+import lombok.SneakyThrows;
+import ru.ewc.decisions.api.ComputationContext;
+import ru.ewc.decisions.api.DecitaException;
+import ru.ewc.decisions.api.InMemoryLocator;
+import ru.ewc.decisions.api.Locator;
+
+/**
+ * I am a special locator that can load functions from Groovy scripts. My main responsibility is to
+ * run the Groovy script and return the result of the script execution.
+ *
+ * @since 0.4.1
+ */
+public final class FunctionsLocator implements Locator {
+ /**
+ * The locator's name.
+ */
+ private final String name;
+
+ /**
+ * The in-memory locator used to store overridden values.
+ */
+ private final InMemoryLocator locator;
+
+ /**
+ * Groovy script engine used to run the Groovy scripts.
+ */
+ private final GroovyScriptEngine engine;
+
+ @SneakyThrows
+ public FunctionsLocator(final String name, final Path path) {
+ this.name = name;
+ this.locator = new InMemoryLocator("locator", new HashMap<>());
+ this.engine = new GroovyScriptEngine(new URL[]{path.toUri().toURL()});
+ }
+
+ @SneakyThrows
+ @Override
+ public String fragmentBy(
+ final String fragment,
+ final ComputationContext context
+ ) throws DecitaException {
+ final String result;
+ if (this.locator.state().containsKey(fragment)) {
+ result = this.locator.fragmentBy(fragment, context);
+ } else {
+ final Binding binding = new Binding();
+ binding.setVariable("context", context);
+ result = this.engine.run("%s.groovy".formatted(fragment), binding).toString();
+ }
+ return result;
+ }
+
+ @Override
+ public void setFragmentValue(final String fragment, final String value) {
+ this.locator.setFragmentValue(fragment, value);
+ }
+
+ @Override
+ public String locatorName() {
+ return this.name;
+ }
+}