diff --git a/pom.xml b/pom.xml
index e2e717e..19e8889 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,16 +51,19 @@
UTF-8
- 5.12.1
- 2.10
+ 5.13.0
+ 2.10.1
- 5.9.1
- 3.23.1
+ 5.9.3
+ 3.24.2
- 3.10.1
- 2.22.2
+ 3.11.0
+ 3.1.2
+ 3.5.0
+ 3.2.1
+ 1.8.0
true
@@ -74,7 +77,7 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 3.4.1
+ ${maven-javadoc-plugin.version}
-Xdoclint:none
@@ -90,7 +93,7 @@
org.apache.maven.plugins
maven-source-plugin
- 3.2.1
+ ${maven-source-plugin.version}
attach-source
@@ -103,7 +106,7 @@
org.jreleaser
jreleaser-maven-plugin
- 1.3.1
+ ${jreleaser-maven-plugin.version}
@@ -183,12 +186,5 @@
${assertj-core.version}
test
-
-
- uk.org.webcompere
- model-assert
- 1.0.0
- test
-
diff --git a/src/main/java/org/extism/sdk/CancelHandle.java b/src/main/java/org/extism/sdk/CancelHandle.java
index b88e3b2..563e03c 100644
--- a/src/main/java/org/extism/sdk/CancelHandle.java
+++ b/src/main/java/org/extism/sdk/CancelHandle.java
@@ -6,7 +6,8 @@
* CancelHandle is used to cancel a running Plugin
*/
public class CancelHandle {
- private Pointer handle;
+
+ private final Pointer handle;
public CancelHandle(Pointer handle) {
this.handle = handle;
@@ -15,7 +16,7 @@ public CancelHandle(Pointer handle) {
/**
* Cancel execution of the Plugin associated with the CancelHandle
*/
- boolean cancel() {
+ public boolean cancel() {
return LibExtism.INSTANCE.extism_plugin_cancel(this.handle);
}
}
diff --git a/src/main/java/org/extism/sdk/Extism.java b/src/main/java/org/extism/sdk/Extism.java
index 8e106b3..458de6a 100644
--- a/src/main/java/org/extism/sdk/Extism.java
+++ b/src/main/java/org/extism/sdk/Extism.java
@@ -1,7 +1,9 @@
package org.extism.sdk;
import org.extism.sdk.manifest.Manifest;
+import org.extism.sdk.wasm.WasmSourceResolver;
+import java.net.URI;
import java.nio.file.Path;
import java.util.Objects;
@@ -10,25 +12,38 @@
*/
public class Extism {
+ private static final WasmSourceResolver DEFAULT_RESOLVER = new WasmSourceResolver();
+
/**
- * Configure a log file with the given {@link Path} and configure the given {@link LogLevel}.
- *
+ * Creates a {@link Manifest} from the given {@code path}.
* @param path
- * @param level
- *
- * @deprecated will be replaced with better logging API.
+ * @return
*/
- @Deprecated(forRemoval = true)
- public static void setLogFile(Path path, LogLevel level) {
-
+ public static Manifest manifestFromPath(Path path) {
Objects.requireNonNull(path, "path");
- Objects.requireNonNull(level, "level");
+ return new Manifest(DEFAULT_RESOLVER.resolve(path));
+ }
- var result = LibExtism.INSTANCE.extism_log_file(path.toString(), level.getLevel());
- if (!result) {
- var error = String.format("Could not set extism logger to %s with level %s", path, level);
- throw new ExtismException(error);
- }
+ /**
+ * Creates a {@link Manifest} from the given {@code name} and {@code bytes}.
+ * @param name
+ * @param bytes
+ * @return
+ */
+ public static Manifest manifestFromBytes(String name, byte[] bytes) {
+ Objects.requireNonNull(name, "name");
+ Objects.requireNonNull(bytes, "bytes");
+ return new Manifest(DEFAULT_RESOLVER.resolve(name, bytes));
+ }
+
+ /**
+ * Creates a {@link Manifest} from the given {@code url}.
+ * @param url
+ * @return
+ */
+ public static Manifest manifestFromUrl(String url) {
+ Objects.requireNonNull(url, "url");
+ return new Manifest(DEFAULT_RESOLVER.resolve(URI.create(url)));
}
/**
@@ -46,30 +61,4 @@ public static String invokeFunction(Manifest manifest, String function, String i
return plugin.call(function, input);
}
}
-
- /**
- * Error levels for the Extism logging facility.
- *
- * @see Extism#setLogFile(Path, LogLevel)
- */
- public enum LogLevel {
-
- INFO("info"), //
-
- DEBUG("debug"), //
-
- WARN("warn"), //
-
- TRACE("trace");
-
- private final String level;
-
- LogLevel(String level) {
- this.level = level;
- }
-
- public String getLevel() {
- return level;
- }
- }
}
diff --git a/src/main/java/org/extism/sdk/ExtismCurrentPlugin.java b/src/main/java/org/extism/sdk/ExtismCurrentPlugin.java
index 8e6a327..b675c13 100644
--- a/src/main/java/org/extism/sdk/ExtismCurrentPlugin.java
+++ b/src/main/java/org/extism/sdk/ExtismCurrentPlugin.java
@@ -5,7 +5,8 @@
import java.nio.charset.StandardCharsets;
public class ExtismCurrentPlugin {
- public Pointer pointer;
+
+ private final Pointer pointer;
public ExtismCurrentPlugin(Pointer pointer) {
this.pointer = pointer;
diff --git a/src/main/java/org/extism/sdk/HostFunction.java b/src/main/java/org/extism/sdk/HostFunction.java
index 7eebd30..a8bc2ae 100644
--- a/src/main/java/org/extism/sdk/HostFunction.java
+++ b/src/main/java/org/extism/sdk/HostFunction.java
@@ -1,82 +1,86 @@
package org.extism.sdk;
import com.sun.jna.Pointer;
-import com.sun.jna.PointerType;
+import org.extism.sdk.LibExtism.ExtismVal;
+import org.extism.sdk.LibExtism.ExtismValType;
+import org.extism.sdk.LibExtism.InternalExtismFunction;
import java.util.Arrays;
import java.util.Optional;
public class HostFunction {
- private final LibExtism.InternalExtismFunction callback;
+ private final Pointer pointer;
- public final Pointer pointer;
+ private final String name;
- public final String name;
+ private final ExtismValType[] params;
- public final LibExtism.ExtismValType[] params;
+ private final ExtismValType[] returns;
- public final LibExtism.ExtismValType[] returns;
+ private final T userData;
- public final Optional userData;
+ public HostFunction(String name, ExtismValType[] params, ExtismValType[] returns, ExtismFunction func) {
+ this(name, params, returns, func, null);
+ }
- public HostFunction(String name, LibExtism.ExtismValType[] params, LibExtism.ExtismValType[] returns, ExtismFunction f, Optional userData) {
+ public HostFunction(String name, ExtismValType[] params, ExtismValType[] returns, ExtismFunction func, T userData) {
this.name = name;
this.params = params;
this.returns = returns;
this.userData = userData;
- this.callback = (Pointer currentPlugin,
- LibExtism.ExtismVal inputs,
- int nInputs,
- LibExtism.ExtismVal outs,
- int nOutputs,
- Pointer data) -> {
-
- LibExtism.ExtismVal[] outputs = (LibExtism.ExtismVal []) outs.toArray(nOutputs);
-
- f.invoke(
- new ExtismCurrentPlugin(currentPlugin),
- (LibExtism.ExtismVal []) inputs.toArray(nInputs),
- outputs,
- userData
- );
-
- for (LibExtism.ExtismVal output : outputs) {
- convertOutput(output, output);
- }
- };
- this.pointer = LibExtism.INSTANCE.extism_function_new(
- this.name,
- Arrays.stream(this.params).mapToInt(r -> r.v).toArray(),
- this.params.length,
- Arrays.stream(this.returns).mapToInt(r -> r.v).toArray(),
- this.returns.length,
- this.callback,
- userData.map(PointerType::getPointer).orElse(null),
- null
+ int[] inputTypeValues = Arrays.stream(this.params).mapToInt(r -> r.v).toArray();
+ int[] outputTypeValues = Arrays.stream(this.returns).mapToInt(r -> r.v).toArray();
+ InternalExtismFunction callback = createCallbackFunction(func, userData);
+ Pointer userDataPointer = userData != null ? userData.getPointer() : null;
+
+ this.pointer = LibExtism.INSTANCE.extism_function_new( //
+ name, //
+ inputTypeValues, //
+ inputTypeValues.length, //
+ outputTypeValues, //
+ outputTypeValues.length, //
+ callback, //
+ userDataPointer, //
+ null //
);
}
- void convertOutput(LibExtism.ExtismVal original, LibExtism.ExtismVal fromHostFunction) {
- if (fromHostFunction.t != original.t)
- throw new ExtismException(String.format("Output type mismatch, got %d but expected %d", fromHostFunction.t, original.t));
-
- if (fromHostFunction.t == LibExtism.ExtismValType.I32.v) {
- original.v.setType(Integer.TYPE);
- original.v.i32 = fromHostFunction.v.i32;
- } else if (fromHostFunction.t == LibExtism.ExtismValType.I64.v) {
- original.v.setType(Long.TYPE);
- original.v.i64 = fromHostFunction.v.i64;
- } else if (fromHostFunction.t == LibExtism.ExtismValType.F32.v) {
- original.v.setType(Float.TYPE);
- original.v.f32 = fromHostFunction.v.f32;
- } else if (fromHostFunction.t == LibExtism.ExtismValType.F64.v) {
- original.v.setType(Double.TYPE);
- original.v.f64 = fromHostFunction.v.f64;
- } else
- throw new ExtismException(String.format("Unsupported return type: %s", original.t));
+ private InternalExtismFunction createCallbackFunction(ExtismFunction func, T userData) {
+ return (Pointer pluginPointer, ExtismVal inputs, int nInputs, ExtismVal outputs, int nOutputs, Pointer data) -> {
+
+ var outputValues = (ExtismVal[]) outputs.toArray(nOutputs);
+ var inputValues = (ExtismVal[]) inputs.toArray(nInputs);
+ var currentPlugin = new ExtismCurrentPlugin(pluginPointer);
+
+ func.invoke(currentPlugin, inputValues, outputValues, Optional.ofNullable(userData));
+
+ for (ExtismVal output : outputValues) {
+ coerceType(output);
+ }
+ };
+ }
+
+ void coerceType(ExtismVal value) {
+
+ switch (value.t) {
+ case ExtismValType.I32_KEY:
+ value.v.setType(Integer.TYPE);
+ break;
+ case ExtismValType.I64_KEY:
+ value.v.setType(Long.TYPE);
+ break;
+ case ExtismValType.F32_KEY:
+ value.v.setType(Float.TYPE);
+ break;
+ case ExtismValType.F64_KEY:
+ value.v.setType(Double.TYPE);
+ break;
+ default:
+ throw new ExtismException(String.format("Unsupported return type: %s", value.t));
+ }
}
public void setNamespace(String name) {
@@ -85,8 +89,28 @@ public void setNamespace(String name) {
}
}
- HostFunction withNamespace(String name) {
+ public HostFunction withNamespace(String name) {
this.setNamespace(name);
return this;
}
+
+ public Optional params() {
+ return Optional.ofNullable(params).map(ExtismValType[]::clone);
+ }
+
+ public Optional returns() {
+ return Optional.ofNullable(returns).map(ExtismValType[]::clone);
+ }
+
+ public Optional userData() {
+ return Optional.ofNullable(userData);
+ }
+
+ public String name() {
+ return name;
+ }
+
+ /* package scoped */ Pointer getPointer() {
+ return pointer;
+ }
}
diff --git a/src/main/java/org/extism/sdk/HostUserData.java b/src/main/java/org/extism/sdk/HostUserData.java
index bbd31f4..f0ab773 100644
--- a/src/main/java/org/extism/sdk/HostUserData.java
+++ b/src/main/java/org/extism/sdk/HostUserData.java
@@ -3,5 +3,4 @@
import com.sun.jna.PointerType;
public class HostUserData extends PointerType {
-
}
\ No newline at end of file
diff --git a/src/main/java/org/extism/sdk/LibExtism.java b/src/main/java/org/extism/sdk/LibExtism.java
index b05fa98..f7f3810 100644
--- a/src/main/java/org/extism/sdk/LibExtism.java
+++ b/src/main/java/org/extism/sdk/LibExtism.java
@@ -1,6 +1,11 @@
package org.extism.sdk;
-import com.sun.jna.*;
+import com.sun.jna.Callback;
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+import com.sun.jna.Pointer;
+import com.sun.jna.Structure;
+import com.sun.jna.Union;
/**
* Wrapper around the Extism library.
@@ -13,23 +18,36 @@ public interface LibExtism extends Library {
*/
LibExtism INSTANCE = Native.load("extism", LibExtism.class);
+ /**
+ * Host function callback
+ */
interface InternalExtismFunction extends Callback {
- void invoke(
- Pointer currentPlugin,
- ExtismVal inputs,
- int nInputs,
- ExtismVal outputs,
- int nOutputs,
- Pointer data
- );
+
+ /**
+ * Host function implementation.
+ *
+ * @param plugin
+ * @param inputs
+ * @param nInputs
+ * @param outputs
+ * @param nOutputs
+ * @param data
+ */
+ void invoke(Pointer plugin, ExtismVal inputs, int nInputs, ExtismVal outputs, int nOutputs, Pointer data);
}
+ /**
+ * `Holds the type and value of a function argument/return.
+ */
@Structure.FieldOrder({"t", "v"})
class ExtismVal extends Structure {
public int t;
public ExtismValUnion v;
}
+ /**
+ * A union type for host function argument/return values.
+ */
class ExtismValUnion extends Union {
public int i32;
public long i64;
@@ -37,22 +55,81 @@ class ExtismValUnion extends Union {
public double f64;
}
+ /**
+ * An enumeration of all possible value types in WebAssembly.
+ */
enum ExtismValType {
- I32(0),
- I64(1),
- F32(2),
- F64(3),
- V128(4),
- FuncRef(5),
- ExternRef(6);
+
+ /**
+ * Signed 32 bit integer.
+ */
+ I32(ExtismValType.I32_KEY),
+
+ /**
+ * Signed 64 bit integer.
+ */
+ I64(ExtismValType.I64_KEY),
+
+ /**
+ * Floating point 32 bit.
+ */
+ F32(ExtismValType.F32_KEY),
+
+ /**
+ * Floating point 64 bit.
+ */
+ F64(ExtismValType.F64_KEY),
+
+ /**
+ * A 128 bit number
+ */
+ V128(ExtismValType.V128_KEY),
+
+ /**
+ * A reference to a Wasm function.
+ */
+ FuncRef(ExtismValType.FUNC_REF_KEY),
+
+ /**
+ * A reference to opaque data in the Wasm instance.
+ */
+ ExternRef(ExtismValType.FUNC_EXTERN_REF_KEY);
public final int v;
+ public static final int I32_KEY = 0;
+
+ public static final int I64_KEY = 1;
+
+ public static final int F32_KEY = 2;
+
+ public static final int F64_KEY = 3;
+
+ public static final int V128_KEY = 4;
+
+ public static final int FUNC_REF_KEY = 6;
+
+ public static final int FUNC_EXTERN_REF_KEY = 6;
+
+
ExtismValType(int value) {
this.v = value;
}
}
+ /**
+ * Create a new host function.
+ *
+ * @param name function name
+ * @param inputs argument types
+ * @param nInputs number of argument types
+ * @param outputs return types
+ * @param nOutputs number of return types
+ * @param func the function to call
+ * @param userData a pointer that will be passed to the function when it's called
+ * @param freeUserData a callback to release the `user_data` value when the resulting ExtismFunction is freed.
+ * @return Returns a pointer to a new ExtismFunction or {@literal null} if the {@code name} argument is invalid.
+ */
Pointer extism_function_new(String name,
int[] inputs,
int nInputs,
@@ -162,8 +239,32 @@ Pointer extism_function_new(String name,
* @return {@literal true} if update was successful
*/
boolean extism_plugin_config(Pointer pluginPointer, byte[] json, int jsonLength);
+
+ /**
+ * Get a handle for plugin cancellation
+ * @param pluginPointer
+ * @return a Pointer to a cancellation handle
+ */
Pointer extism_plugin_cancel_handle(Pointer pluginPointer);
+
+ /**
+ * Cancel a running plugin.
+ * @param cancelHandle
+ * @return {@literal true} if cancellation was successful
+ */
boolean extism_plugin_cancel(Pointer cancelHandle);
- void extism_function_set_namespace(Pointer p, String name);
- int strlen(Pointer s);
+
+ /**
+ * Set the namespace of an `ExtismFunction`
+ * @param pluginPointer
+ * @param namespace
+ */
+ void extism_function_set_namespace(Pointer pluginPointer, String namespace);
+
+ /**
+ * Helper function to get the length of a string represented by the pointer s.
+ * @param stringPointer
+ * @return
+ */
+ int strlen(Pointer stringPointer);
}
diff --git a/src/main/java/org/extism/sdk/Plugin.java b/src/main/java/org/extism/sdk/Plugin.java
index 7a5500c..6997ae0 100644
--- a/src/main/java/org/extism/sdk/Plugin.java
+++ b/src/main/java/org/extism/sdk/Plugin.java
@@ -22,23 +22,27 @@ public class Plugin implements AutoCloseable {
* @param functions The Host functions for th eplugin
* @param withWASI Set to true to enable WASI
*/
- public Plugin(byte[] manifestBytes, boolean withWASI, HostFunction[] functions) {
+ public Plugin(byte[] manifestBytes, boolean withWASI, HostFunction>[] functions) {
Objects.requireNonNull(manifestBytes, "manifestBytes");
- Pointer[] ptrArr = new Pointer[functions == null ? 0 : functions.length];
-
- if (functions != null)
+ int funcCount = functions == null ? 0 : functions.length;
+ Pointer[] ptrArr = new Pointer[funcCount];
+ if (funcCount > 0) {
for (int i = 0; i < functions.length; i++) {
- ptrArr[i] = functions[i].pointer;
+ ptrArr[i] = functions[i].getPointer();
}
+ }
Pointer[] errormsg = new Pointer[1];
- Pointer p = LibExtism.INSTANCE.extism_plugin_new(manifestBytes, manifestBytes.length,
+ Pointer p = LibExtism.INSTANCE.extism_plugin_new(
+ manifestBytes,
+ manifestBytes.length,
ptrArr,
- functions == null ? 0 : functions.length,
+ funcCount,
withWASI,
errormsg);
+
if (p == null) {
int errlen = LibExtism.INSTANCE.strlen(errormsg[0]);
byte[] msg = new byte[errlen];
@@ -50,10 +54,18 @@ public Plugin(byte[] manifestBytes, boolean withWASI, HostFunction[] functions)
this.pluginPointer = p;
}
- public Plugin(Manifest manifest, boolean withWASI, HostFunction[] functions) {
+ public Plugin(Manifest manifest, boolean withWASI, HostFunction>[] functions) {
this(serialize(manifest), withWASI, functions);
}
+ public Plugin(Manifest manifest, boolean withWASI) {
+ this(manifest, withWASI, null);
+ }
+
+ public Plugin(Manifest manifest) {
+ this(manifest, false);
+ }
+
private static byte[] serialize(Manifest manifest) {
Objects.requireNonNull(manifest, "manifest");
@@ -120,8 +132,8 @@ public void free() {
/**
* Update plugin config values, this will merge with the existing values.
*
- * @param json
- * @return
+ * @param json json string of the config
+ * @return true if the config could be applied
*/
public boolean updateConfig(String json) {
Objects.requireNonNull(json, "json");
@@ -131,7 +143,7 @@ public boolean updateConfig(String json) {
/**
* Update plugin config values, this will merge with the existing values.
*
- * @param jsonBytes
+ * @param jsonBytes bytes of the config json
* @return {@literal true} if update was successful
*/
public boolean updateConfig(byte[] jsonBytes) {
@@ -151,7 +163,6 @@ public void close() {
* Return a new `CancelHandle`, which can be used to cancel a running Plugin
*/
public CancelHandle cancelHandle() {
- Pointer handle = LibExtism.INSTANCE.extism_plugin_cancel_handle(this.pluginPointer);
- return new CancelHandle(handle);
+ return new CancelHandle(LibExtism.INSTANCE.extism_plugin_cancel_handle(this.pluginPointer));
}
}
diff --git a/src/main/java/org/extism/sdk/manifest/Manifest.java b/src/main/java/org/extism/sdk/manifest/Manifest.java
index 37f4c61..87ec53d 100644
--- a/src/main/java/org/extism/sdk/manifest/Manifest.java
+++ b/src/main/java/org/extism/sdk/manifest/Manifest.java
@@ -8,24 +8,47 @@
import java.util.List;
import java.util.Map;
+/**
+ * The `Manifest` type is used to configure the runtime and specify how to load modules.
+ */
public class Manifest {
+ /**
+ * Holds the WebAssembly modules, the `main` module should be named `main` or listed last.
+ */
@SerializedName("wasm")
private final List sources;
+ /**
+ * Holds the memory options
+ */
@SerializedName("memory")
private final MemoryOptions memoryOptions;
+ /**
+ * Specifies which hosts may be accessed via HTTP, if this is empty then no hosts may be accessed. Wildcards may be used.
+ */
// FIXME remove this and related stuff if not supported in java-sdk
@SerializedName("allowed_hosts")
private final List allowedHosts;
+ /**
+ * Specifies which paths should be made available on disk when using WASI.
+ * This is a mapping from the path on disk to the path it should be available inside the plugin.
+ * For example, `".": "/tmp"` would mount the current directory as `/tmp` inside the module
+ */
@SerializedName("allowed_paths")
private final Map allowedPaths;
+ /**
+ * Config values are made accessible using the PDK `extism_config_get` function
+ */
@SerializedName("config")
private final Map config;
+ /**
+ * Creates a new Manifest with an empty list of wasm sources.
+ */
public Manifest() {
this(new ArrayList<>(), null, null, null, null);
}
@@ -58,8 +81,63 @@ public Manifest(List sources, MemoryOptions memoryOptions, Map(this.sources);
+ sources.add(source);
+
+ return new Manifest(sources, memoryOptions, config, allowedHosts, allowedPaths);
+ }
+
+ /**
+ * Returns a new {@link Manifest} with the given {@link MemoryOptions}.
+ * @param memoryOptions
+ * @return
+ */
+ public Manifest withMemoryOptions(MemoryOptions memoryOptions) {
+ return new Manifest(sources, memoryOptions, config, allowedHosts, allowedPaths);
+ }
+
+ /**
+ * Returns a new {@link Manifest} with the given config.
+ * @param config
+ * @return
+ */
+ public Manifest withConfig(Map config) {
+ return new Manifest(sources, memoryOptions, config, allowedHosts, allowedPaths);
+ }
+
+ /**
+ * Returns a new {@link Manifest} with the given allowed hosts.
+ * @param allowedHosts
+ * @return
+ */
+ public Manifest withAllowedHosts(List allowedHosts) {
+ return new Manifest(sources, memoryOptions, config, allowedHosts, allowedPaths);
+ }
+
+ /**
+ * Returns a new {@link Manifest} with the given allowed paths.
+ *
+ * @param allowedPaths
+ * @return
+ */
+ public Manifest withAllowedPaths(Map allowedPaths) {
+ return new Manifest(sources, memoryOptions, config, allowedHosts, allowedPaths);
}
public List getSources() {
diff --git a/src/main/java/org/extism/sdk/manifest/MemoryOptions.java b/src/main/java/org/extism/sdk/manifest/MemoryOptions.java
index f350893..4702b61 100644
--- a/src/main/java/org/extism/sdk/manifest/MemoryOptions.java
+++ b/src/main/java/org/extism/sdk/manifest/MemoryOptions.java
@@ -5,10 +5,12 @@
/**
* Configures memory for the Wasm runtime.
* Memory is described in units of pages (64KB) and represent contiguous chunks of addressable memory.
- *
- * @param max Max number of pages.
*/
public class MemoryOptions {
+
+ /**
+ * The max number of WebAssembly pages that should be allocated for the module.
+ */
@SerializedName("max")
private final Integer max;
diff --git a/src/main/java/org/extism/sdk/support/Hashing.java b/src/main/java/org/extism/sdk/support/Hashing.java
index 57c72b6..dd49acc 100644
--- a/src/main/java/org/extism/sdk/support/Hashing.java
+++ b/src/main/java/org/extism/sdk/support/Hashing.java
@@ -2,7 +2,11 @@
import java.security.MessageDigest;
-public class Hashing {
+public final class Hashing {
+
+ private Hashing() {
+ // prevent instantiation
+ }
public static String sha256HexDigest(byte[] input) {
try {
diff --git a/src/main/java/org/extism/sdk/support/JsonSerde.java b/src/main/java/org/extism/sdk/support/JsonSerde.java
index 7669572..b0e7704 100644
--- a/src/main/java/org/extism/sdk/support/JsonSerde.java
+++ b/src/main/java/org/extism/sdk/support/JsonSerde.java
@@ -1,17 +1,23 @@
package org.extism.sdk.support;
-import com.google.gson.*;
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParseException;
+import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import org.extism.sdk.manifest.Manifest;
import java.io.IOException;
-import java.lang.reflect.Type;
-import java.nio.charset.StandardCharsets;
import java.util.Base64;
-public class JsonSerde {
+public final class JsonSerde {
+
+ private JsonSerde() {
+ // prevent instantiation
+ }
private static final Gson GSON;
diff --git a/src/main/java/org/extism/sdk/support/Logging.java b/src/main/java/org/extism/sdk/support/Logging.java
new file mode 100644
index 0000000..24b8220
--- /dev/null
+++ b/src/main/java/org/extism/sdk/support/Logging.java
@@ -0,0 +1,66 @@
+package org.extism.sdk.support;
+
+import org.extism.sdk.ExtismException;
+import org.extism.sdk.LibExtism;
+
+import java.nio.file.Path;
+import java.util.Objects;
+
+public final class Logging {
+
+ private Logging() {
+ // prevent instantiation
+ }
+
+ /**
+ * Configure a log file with the given {@link Path} and configure the given {@link LogLevel}.
+ *
+ * @param path
+ * @param level
+ *
+ * @deprecated will be replaced with better logging API.
+ */
+ @Deprecated(forRemoval = true)
+ public static void setLogFile(Path path, LogLevel level) {
+
+ Objects.requireNonNull(path, "path");
+ Objects.requireNonNull(level, "level");
+
+ var result = LibExtism.INSTANCE.extism_log_file(path.toString(), level.getLevel());
+ if (!result) {
+ var error = String.format("Could not set extism logger to %s with level %s", path, level);
+ throw new ExtismException(error);
+ }
+ }
+
+ /**
+ * Error levels for the Extism logging facility.
+ *
+ * @see Logging#setLogFile(Path, LogLevel)
+ */
+ public enum LogLevel {
+
+ INFO("info"), //
+
+ DEBUG("debug"), //
+
+ WARN("warn"), //
+
+ TRACE("trace"), //
+
+ ERROR("error"), //
+
+ OFF("off"), //
+ ;
+
+ private final String level;
+
+ LogLevel(String level) {
+ this.level = level;
+ }
+
+ public String getLevel() {
+ return level;
+ }
+ }
+}
diff --git a/src/main/java/org/extism/sdk/wasm/UrlWasmSource.java b/src/main/java/org/extism/sdk/wasm/UrlWasmSource.java
index 9398fdc..5422700 100644
--- a/src/main/java/org/extism/sdk/wasm/UrlWasmSource.java
+++ b/src/main/java/org/extism/sdk/wasm/UrlWasmSource.java
@@ -1,5 +1,7 @@
package org.extism.sdk.wasm;
+import java.net.URI;
+
/**
* WASM Source represented by a url.
*/
@@ -21,6 +23,10 @@ public static UrlWasmSource fromUrl(String url) {
return new UrlWasmSource(null, url, null);
}
+ public static UrlWasmSource fromUrl(URI uri) {
+ return fromUrl(uri.toString());
+ }
+
/**
* Constructor
* @param name
diff --git a/src/main/java/org/extism/sdk/wasm/WasmSourceResolver.java b/src/main/java/org/extism/sdk/wasm/WasmSourceResolver.java
index 8b6ab82..54db429 100644
--- a/src/main/java/org/extism/sdk/wasm/WasmSourceResolver.java
+++ b/src/main/java/org/extism/sdk/wasm/WasmSourceResolver.java
@@ -4,9 +4,10 @@
import org.extism.sdk.support.Hashing;
import java.io.IOException;
-import java.net.URL;
+import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Arrays;
import java.util.Objects;
/**
@@ -32,6 +33,14 @@ public ByteArrayWasmSource resolve(String name, byte[] bytes) {
return new ByteArrayWasmSource(name, bytes, hash(bytes));
}
+ public ByteArrayWasmSource resolve(byte[] bytes) {
+ return resolve("wasm@" + Arrays.hashCode(bytes), bytes);
+ }
+
+ public UrlWasmSource resolve(URI uri) {
+ return UrlWasmSource.fromUrl(uri);
+ }
+
protected String hash(Path wasmFile) {
try {
return hash(Files.readAllBytes(wasmFile));
diff --git a/src/test/java/org/extism/sdk/ManifestTests.java b/src/test/java/org/extism/sdk/ManifestTests.java
index 3c24c4f..df2ce41 100644
--- a/src/test/java/org/extism/sdk/ManifestTests.java
+++ b/src/test/java/org/extism/sdk/ManifestTests.java
@@ -1,44 +1,48 @@
package org.extism.sdk;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
import org.extism.sdk.manifest.Manifest;
import org.extism.sdk.manifest.MemoryOptions;
import org.extism.sdk.support.JsonSerde;
import org.junit.jupiter.api.Test;
import java.util.List;
-import java.util.HashMap;
+import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.extism.sdk.TestWasmSources.CODE;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static uk.org.webcompere.modelassert.json.JsonAssertions.assertJson;
public class ManifestTests {
@Test
public void shouldSerializeManifestWithWasmSourceToJson() {
- var paths = new HashMap();
- paths.put("/tmp/foo", "/tmp/extism-plugins/foo");
+ var externalPath = "/tmp/foo";
+ var internalPath = "/tmp/extism-plugins/foo";
+ var paths = Map.of(externalPath, internalPath);
var manifest = new Manifest(List.of(CODE.pathWasmSource()), null, null, null, paths);
- var json = JsonSerde.toJson(manifest);
- assertNotNull(json);
-
- assertJson(json).at("/wasm").isArray();
- assertJson(json).at("/wasm").hasSize(1);
- assertJson(json).at("/allowed_paths").isObject();
- assertJson(json).at("/allowed_paths").hasSize(1);
+ var jsonString = JsonSerde.toJson(manifest);
+ assertNotNull(jsonString);
+
+ var json = parseAsJsonObject(jsonString);
+ assertThat(json.get("wasm").isJsonArray()).isTrue();
+ assertThat(json.get("wasm").getAsJsonArray()).hasSize(1);
+ assertThat(json.get("allowed_paths").isJsonObject()).isTrue();
+ assertThat(json.get("allowed_paths").getAsJsonObject().get(externalPath).getAsString()).isEqualTo(internalPath);
}
@Test
public void shouldSerializeManifestWithWasmSourceAndMemoryOptionsToJson() {
var manifest = new Manifest(List.of(CODE.pathWasmSource()), new MemoryOptions(4));
- var json = JsonSerde.toJson(manifest);
- assertNotNull(json);
+ var jsonString = JsonSerde.toJson(manifest);
+ assertNotNull(jsonString);
- assertJson(json).at("/wasm").isArray();
- assertJson(json).at("/wasm").hasSize(1);
- assertJson(json).at("/memory/max").isEqualTo(4);
+ var json = parseAsJsonObject(jsonString);
+ assertThat(json.get("wasm").isJsonArray()).isTrue();
+ assertThat(json.get("wasm").getAsJsonArray()).hasSize(1);
+ assertThat(json.get("memory").getAsJsonObject().get("max").getAsInt()).isEqualTo(4);
}
@Test
@@ -49,4 +53,8 @@ public void codeWasmFromFileAndBytesShouldProduceTheSameHash() {
assertThat(byteHash).isEqualTo(fileHash);
}
+
+ private static JsonObject parseAsJsonObject(String json) {
+ return JsonParser.parseString(json).getAsJsonObject();
+ }
}
diff --git a/src/test/java/org/extism/sdk/PluginTests.java b/src/test/java/org/extism/sdk/PluginTests.java
index 6ace3fe..cf1d704 100644
--- a/src/test/java/org/extism/sdk/PluginTests.java
+++ b/src/test/java/org/extism/sdk/PluginTests.java
@@ -1,13 +1,14 @@
package org.extism.sdk;
-import com.sun.jna.Pointer;
import org.extism.sdk.manifest.Manifest;
import org.extism.sdk.manifest.MemoryOptions;
import org.extism.sdk.wasm.UrlWasmSource;
import org.extism.sdk.wasm.WasmSourceResolver;
import org.junit.jupiter.api.Test;
-import java.util.*;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.extism.sdk.TestWasmSources.CODE;
@@ -21,7 +22,7 @@ public class PluginTests {
@Test
public void shouldInvokeFunctionWithMemoryOptions() {
- var manifest = new Manifest(List.of(CODE.pathWasmSource()), new MemoryOptions(0));
+ var manifest = new Manifest(CODE.pathWasmSource()).withMemoryOptions(new MemoryOptions(0));
assertThrows(ExtismException.class, () -> {
Extism.invokeFunction(manifest, "count_vowels", "Hello World");
});
@@ -31,7 +32,7 @@ public void shouldInvokeFunctionWithMemoryOptions() {
public void shouldInvokeFunctionWithConfig() {
//FIXME check if config options are available in wasm call
var config = Map.of("key1", "value1");
- var manifest = new Manifest(List.of(CODE.pathWasmSource()), null, config);
+ var manifest = new Manifest(CODE.pathWasmSource()).withConfig(config);
var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World");
assertThat(output).isEqualTo("{\"count\": 3}");
}
@@ -47,10 +48,13 @@ public void shouldInvokeFunctionFromFileWasmSource() {
public void shouldInvokeFunctionFromUrlWasmSource() {
var url = "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm";
var config = Map.of("vowels", "aeiouyAEIOUY");
- var manifest = new Manifest(List.of(UrlWasmSource.fromUrl(url)), null, config);
- var plugin = new Plugin(manifest, false, null);
- var output = plugin.call("count_vowels", "Yellow, World!");
- assertThat(output).isEqualTo("{\"count\":4,\"total\":4,\"vowels\":\"aeiouyAEIOUY\"}");
+ var manifest = Extism.manifestFromUrl(url).withConfig(config);
+
+ try (var plugin = new Plugin(manifest)) {
+ String output = plugin.call("count_vowels", "Yellow, World!");
+ assertThat(output).isEqualTo("{\"count\":4,\"total\":4,\"vowels\":\"aeiouyAEIOUY\"}");
+ }
+
}
// @Test
@@ -105,12 +109,12 @@ public void shouldInvokeFunctionFromUrlWasmSource() {
// var output = plugin.call("count_vowels", "Hello, World!");
// }
- @Test
- public void shouldInvokeFunctionFromByteArrayWasmSource() {
- var manifest = new Manifest(CODE.byteArrayWasmSource());
- var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World");
- assertThat(output).isEqualTo("{\"count\": 3}");
- }
+ @Test
+ public void shouldInvokeFunctionFromByteArrayWasmSource() {
+ var manifest = new Manifest(CODE.byteArrayWasmSource());
+ var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World");
+ assertThat(output).isEqualTo("{\"count\": 3}");
+ }
@Test
public void shouldFailToInvokeUnknownFunction() {
@@ -123,13 +127,12 @@ public void shouldFailToInvokeUnknownFunction() {
@Test
public void shouldAllowInvokeFunctionFromFileWasmSourceApiUsageExample() {
- var wasmSourceResolver = new WasmSourceResolver();
- var manifest = new Manifest(wasmSourceResolver.resolve(CODE.getWasmFilePath()));
+ var manifest = Extism.manifestFromPath(CODE.getWasmFilePath());
var functionName = "count_vowels";
var input = "Hello World";
- try (var plugin = new Plugin(manifest, false, null)) {
+ try (var plugin = new Plugin(manifest)) {
var output = plugin.call(functionName, input);
assertThat(output).isEqualTo("{\"count\": 3}");
}
@@ -141,7 +144,7 @@ public void shouldAllowInvokeFunctionFromFileWasmSourceMultipleTimes() {
var functionName = "count_vowels";
var input = "Hello World";
- try (var plugin = new Plugin(manifest, false, null)) {
+ try (var plugin = new Plugin(manifest)) {
var output = plugin.call(functionName, input);
assertThat(output).isEqualTo("{\"count\": 3}");
@@ -152,108 +155,101 @@ public void shouldAllowInvokeFunctionFromFileWasmSourceMultipleTimes() {
@Test
public void shouldAllowInvokeHostFunctionFromPDK() {
- var parametersTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64};
- var resultsTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64};
class MyUserData extends HostUserData {
- private String data1;
- private int data2;
+
+ private final String data1;
+
+ private final int data2;
public MyUserData(String data1, int data2) {
- super();
this.data1 = data1;
this.data2 = data2;
}
}
- ExtismFunction helloWorldFunction = (ExtismFunction) (plugin, params, returns, data) -> {
+ ExtismFunction func = (plugin, params, returns, data) -> {
System.out.println("Hello from Java Host Function!");
- System.out.println(String.format("Input string received from plugin, %s", plugin.inputString(params[0])));
+ System.out.printf("Input string received from plugin, %s%n", plugin.inputString(params[0]));
- int offs = plugin.alloc(4);
- Pointer mem = plugin.memory();
- mem.write(offs, "test".getBytes(), 0, 4);
- returns[0].v.i64 = offs;
+ data.ifPresent(d -> {
+ System.out.printf("Host user data, %s, %d%n", d.data1, d.data2);
- data.ifPresent(d -> System.out.println(String.format("Host user data, %s, %d", d.data1, d.data2)));
+ plugin.returnString(returns[0], d.data1.toUpperCase(Locale.US));
+ });
};
- HostFunction helloWorld = new HostFunction<>(
+ var parametersTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64};
+ var resultsTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64};
+
+ var hostFunc = new HostFunction(
"hello_world",
parametersTypes,
resultsTypes,
- helloWorldFunction,
- Optional.of(new MyUserData("test", 2))
+ func,
+ new MyUserData("test", 2)
);
- HostFunction[] functions = {helloWorld};
+ var functions = new HostFunction[]{hostFunc};
- Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource()));
+ Manifest manifest = new Manifest(CODE.pathWasmFunctionsSource());
String functionName = "count_vowels";
try (var plugin = new Plugin(manifest, true, functions)) {
var output = plugin.call(functionName, "this is a test");
- assertThat(output).isEqualTo("test");
+ assertThat(output).isEqualTo("TEST");
}
}
@Test
public void shouldAllowInvokeHostFunctionWithoutUserData() {
- var parametersTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64};
- var resultsTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64};
-
- ExtismFunction helloWorldFunction = (plugin, params, returns, data) -> {
+ ExtismFunction> func = (plugin, params, returns, data) -> {
System.out.println("Hello from Java Host Function!");
- System.out.println(String.format("Input string received from plugin, %s", plugin.inputString(params[0])));
+ System.out.printf("Input string received from plugin, %s%n", plugin.inputString(params[0]));
- int offs = plugin.alloc(4);
- Pointer mem = plugin.memory();
- mem.write(offs, "test".getBytes(), 0, 4);
- returns[0].v.i64 = offs;
+ plugin.returnString(returns[0], "fromHostFunction");
- assertThat(data.isEmpty());
+ assertThat(data.isEmpty()).isTrue();
};
- HostFunction f = new HostFunction<>(
+ var parametersTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64};
+ var resultsTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64};
+
+ var hostFuncEnv = new HostFunction<>(
"hello_world",
parametersTypes,
resultsTypes,
- helloWorldFunction,
- Optional.empty()
- )
- .withNamespace("env");
+ func
+ ).withNamespace("env");
- HostFunction g = new HostFunction<>(
+ var hostFuncTest = new HostFunction<>(
"hello_world",
parametersTypes,
resultsTypes,
- helloWorldFunction,
- Optional.empty()
- )
- .withNamespace("test");
+ func
+ ).withNamespace("test");
- HostFunction[] functions = {f,g};
+ var functions = new HostFunction[]{hostFuncEnv, hostFuncTest};
- Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource()));
+ Manifest manifest = new Manifest(CODE.pathWasmFunctionsSource());
String functionName = "count_vowels";
try (var plugin = new Plugin(manifest, true, functions)) {
var output = plugin.call(functionName, "this is a test");
- assertThat(output).isEqualTo("test");
+ assertThat(output).isEqualTo("fromHostFunction");
}
}
@Test
public void shouldFailToInvokeUnknownHostFunction() {
- Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource()));
+ Manifest manifest = new Manifest(CODE.pathWasmFunctionsSource());
String functionName = "count_vowels";
- try {
- var plugin = new Plugin(manifest, true, null);
+ try (var plugin = new Plugin(manifest, true)) {
plugin.call(functionName, "this is a test");
- } catch (ExtismException e) {
+ } catch (ExtismException e) {
assertThat(e.getMessage()).contains("unknown import: `env::hello_world` has not been defined");
}
}
diff --git a/src/test/java/org/extism/sdk/TestWasmSources.java b/src/test/java/org/extism/sdk/TestWasmSources.java
index 20e0f16..acc82e0 100644
--- a/src/test/java/org/extism/sdk/TestWasmSources.java
+++ b/src/test/java/org/extism/sdk/TestWasmSources.java
@@ -38,7 +38,7 @@ public PathWasmSource pathWasmFunctionsSource() {
public ByteArrayWasmSource byteArrayWasmSource() {
try {
byte[] wasmBytes = Files.readAllBytes(getWasmFilePath());
- return new WasmSourceResolver().resolve("wasm@" + Arrays.hashCode(wasmBytes), wasmBytes);
+ return new WasmSourceResolver().resolve(wasmBytes);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}