Skip to content

Commit

Permalink
feat: add getJson and outputJson for plugin io util (#25)
Browse files Browse the repository at this point in the history
* feat: add getJson and outputJson for plugin io util

* chore: add example and test

* fix: build.zig updates

* feat: simplify getJson and add more flexible getJsonOpt

* chore: update example to use new functions

* chore: update docs, examples, build script
  • Loading branch information
nilslice authored Jan 5, 2024
1 parent 64feb1c commit 3f59e07
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 19 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,10 @@ jobs:
echo $TEST | grep 'A debug log!'
echo $TEST | grep 'A warning log!'
echo $TEST | grep 'An error log!'
TEST=$(extism call zig-out/bin/basic-example.wasm json_input --input '{"name": "Yoda", "age": 900}')
echo $TEST | grep 'Hello, Yoda. You are 900 years old!'
TEST=$(extism call zig-out/bin/basic-example.wasm json_output)
echo $TEST | grep '["first thing","second thing","third thing"]'
27 changes: 15 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Add the library as a dependency:
.dependencies = .{
.extism_pdk = .{
.url = "https://github.com/extism/zig-pdk/archive/<git-ref-here>.tar.gz",
// .hash = "" (zig build will tell you what to put here)
},
},
Expand All @@ -42,32 +43,34 @@ const builtin = @import("builtin");
pub fn build(b: *std.Build) void {
comptime {
const current_zig = builtin.zig_version;
const min_zig = std.SemanticVersion.parse("0.12.0-dev.64+b835fd90c") catch unreachable; // std.json.ArrayHashMap
const min_zig = std.SemanticVersion.parse("0.12.0-dev.2030+2ac315c24") catch unreachable;
if (current_zig.order(min_zig) == .lt) {
@compileError(std.fmt.comptimePrint("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig }));
}
}
const optimize = b.standardOptimizeOption(.{});
const target = b.standardTargetOptions(.{
// if you're using WASI, change the .os_tag to .wasi
.default_target = .{ .abi = .musl, .os_tag = .freestanding, .cpu_arch = .wasm32 },
// If you need to include WASI, update the `.os_tag` field above to .wasi:
// .default_target = .{ .abi = .musl, .os_tag = .wasi, .cpu_arch = .wasm32 },
});
const pdk_module = b.dependency("extism_pdk", .{ .target = target, .optimize = optimize }).module("extism-pdk");
var plugin = b.addExecutable(.{
.name = "my-plugin",
.root_source_file = .{ .path = "src/main.zig" },
var basic_example = b.addExecutable(.{
.name = "basic-example",
.root_source_file = .{ .path = "examples/basic.zig" },
.target = target,
.optimize = optimize,
});
plugin.addModule("extism-pdk", pdk_module);
plugin.rdynamic = true;
basic_example.rdynamic = true;
basic_example.entry = .disabled; // or add an empty `pub fn main() void {}` to your code
const pdk_module = b.addModule("extism-pdk", .{
.root_source_file = .{ .path = "src/main.zig" },
});
basic_example.root_module.addImport("extism-pdk", pdk_module);
b.installArtifact(plugin);
const plugin_step = b.step("my-plugin", "Build my-plugin");
plugin_step.dependOn(b.getInstallStep());
b.installArtifact(basic_example);
const basic_example_step = b.step("basic_example", "Build basic_example");
basic_example_step.dependOn(b.getInstallStep());
}
```

Expand Down
12 changes: 6 additions & 6 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const builtin = @import("builtin");
pub fn build(b: *std.Build) void {
comptime {
const current_zig = builtin.zig_version;
const min_zig = std.SemanticVersion.parse("0.12.0-dev.64+b835fd90c") catch unreachable; // std.json.ArrayHashMap
const min_zig = std.SemanticVersion.parse("0.12.0-dev.2030+2ac315c24") catch unreachable;
if (current_zig.order(min_zig) == .lt) {
@compileError(std.fmt.comptimePrint("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig }));
}
Expand All @@ -15,18 +15,18 @@ pub fn build(b: *std.Build) void {
.default_target = .{ .abi = .musl, .os_tag = .freestanding, .cpu_arch = .wasm32 },
});

const pdk_module = b.addModule("extism-pdk", .{
.source_file = .{ .path = "src/main.zig" },
});

var basic_example = b.addExecutable(.{
.name = "basic-example",
.root_source_file = .{ .path = "examples/basic.zig" },
.target = target,
.optimize = optimize,
});
basic_example.addModule("extism-pdk", pdk_module);
basic_example.rdynamic = true;
basic_example.entry = .disabled; // or, add an empty `pub fn main() void {}` in your code
const pdk_module = b.addModule("extism-pdk", .{
.root_source_file = .{ .path = "src/main.zig" },
});
basic_example.root_module.addImport("extism-pdk", pdk_module);

b.installArtifact(basic_example);
const basic_example_step = b.step("basic_example", "Build basic_example");
Expand Down
63 changes: 62 additions & 1 deletion examples/basic.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const extism_pdk = @import("extism-pdk");
const Plugin = extism_pdk.Plugin;
const http = extism_pdk.http;

pub fn main() void {}
const allocator = std.heap.wasm_allocator;

// define some type to write as output from the plugin back to the host
Expand Down Expand Up @@ -56,6 +55,68 @@ export fn count_vowels() i32 {
return 0;
}

const Input = struct {
name: []const u8,
age: u16,
};

export fn json_input() i32 {
const plugin = Plugin.init(allocator);
// plugin.getJson() is opinionated about parsing and manages the alloc/free for you
// alternatively, plugin.getJsonOpt() let's you control parse options
const input = plugin.getJson(Input) catch unreachable;
const out = std.fmt.allocPrint(allocator, "Hello, {s}. You are {d} years old!", .{ input.name, input.age }) catch unreachable;

plugin.output(out);
return 0;
}

export fn json_input_opt() i32 {
const plugin = Plugin.init(allocator);
const json = plugin.getJsonOpt(Input, .{ .ignore_unknown_fields = false }) catch |err| {
switch (err) {
error.UnknownField => {
plugin.setError("JSON input contains unknown fields");
return 1;
},
error.DuplicateField => {
plugin.setError("JSON input contains duplicate fields");
return 1;
},
else => {
plugin.setError("some problem parsing JSON");
return 1;
},
}
};
defer json.deinit();

const input: Input = json.value();
const out = std.fmt.allocPrint(allocator, "Hello, {s}. You are {d} years old!\n", .{ input.name, input.age }) catch unreachable;

plugin.output(out);
return 0;
}

const Result = struct {
things: [3][]const u8,
};

export fn json_output() i32 {
const plugin = Plugin.init(allocator);
const data = [_][]const u8{
"first thing",
"second thing",
"third thing",
};

const result = Result{ .things = data };

plugin.outputJson(result, .{}) catch unreachable;

return 0;
}

export fn http_get() i32 {
const plugin = Plugin.init(allocator);
// create an HTTP request via Extism built-in function (doesn't require WASI)
Expand Down
35 changes: 35 additions & 0 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ pub const http = @import("http.zig");

const LogLevel = enum { Info, Debug, Warn, Error };

pub fn Json(comptime T: type) type {
return struct {
parsed: std.json.Parsed(T),
slice: []const u8,

pub fn value(self: @This()) T {
return self.parsed.value;
}

pub fn deinit(self: @This()) void {
self.parsed.deinit();
}
};
}

pub const Plugin = struct {
allocator: std.mem.Allocator,

Expand Down Expand Up @@ -34,6 +49,21 @@ pub const Plugin = struct {
return buf;
}

// IMPORTANT: It's the caller's responsibility to free the returned struct
pub fn getJsonOpt(self: Plugin, comptime T: type, options: std.json.ParseOptions) !Json(T) {
const bytes = try self.getInput();
const out = try std.json.parseFromSlice(T, self.allocator, bytes, options);
const FromJson = Json(T);
const input = FromJson{ .parsed = out, .slice = bytes };
return input;
}

pub fn getJson(self: Plugin, comptime T: type) !T {
const bytes = try self.getInput();
const out = try std.json.parseFromSlice(T, self.allocator, bytes, .{ .allocate = .alloc_always, .ignore_unknown_fields = true });
return out.value;
}

pub fn allocate(self: Plugin, length: usize) Memory {
_ = self; // to make the interface consistent

Expand Down Expand Up @@ -73,6 +103,11 @@ pub const Plugin = struct {
extism.output_set(offset, c_len);
}

pub fn outputJson(self: Plugin, T: anytype, options: std.json.StringifyOptions) !void {
const out = try std.json.stringifyAlloc(self.allocator, T, options);
self.output(out);
}

pub fn setErrorMemory(self: Plugin, mem: Memory) void {
_ = self; // to make the interface consistent
extism.error_set(mem.offset);
Expand Down

0 comments on commit 3f59e07

Please sign in to comment.