Skip to content

Commit

Permalink
feat: add Zig wws module (Fixes vmware-labs#218)
Browse files Browse the repository at this point in the history
This commit introduces `kits/zig/wws`, with the intention of replacing `kits/zig/worker`.
The design of the new SDK improves upon the previous one by exposing utilities for parsing wws requests and serializing wws responses, to give the user more control over memory while still keeping the SDK easy to use.
The name was changed to make it more ergonomic within the Zig ecosystem (the developer will pull in a `wws` dependency to interface with wws, rather than a `worker` dependency).
  • Loading branch information
sea-grass committed Feb 23, 2024
1 parent 6564f1c commit 97ef4ea
Show file tree
Hide file tree
Showing 6 changed files with 514 additions and 0 deletions.
34 changes: 34 additions & 0 deletions examples/zig-module/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const std = @import("std");
const wws = @import("wws");

pub fn build(b: *std.Build) !void {
const target = wws.getTarget(b);
const optimize = b.standardOptimizeOption(.{});

const wws_dep = b.dependency("wws", .{});

const exe = b.addExecutable(.{
.name = "example",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
exe.wasi_exec_model = .reactor;
exe.root_module.addImport("wws", wws_dep.module("wws"));

b.installArtifact(exe);

const config =
\\name = "example"
\\version = "1"
\\[data]
\\[data.kv]
\\namespace = "example"
;
const wf = b.addWriteFiles();
const config_path = wf.add("example.toml", config);

const install_config = b.addInstallBinFile(config_path, "example.toml");

b.getInstallStep().dependOn(&install_config.step);
}
65 changes: 65 additions & 0 deletions examples/zig-module/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
.{
.name = "zig-build",
// This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication.
.version = "0.0.0",

// This field is optional.
// This is currently advisory only; Zig does not yet do anything
// with this value.
//.minimum_zig_version = "0.11.0",

// This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`.
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
// Once all dependencies are fetched, `zig build` no longer requires
// internet connectivity.
.dependencies = .{
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
//.example = .{
// // When updating this field to a new URL, be sure to delete the corresponding
// // `hash`, otherwise you are communicating that you expect to find the old hash at
// // the new URL.
// .url = "https://example.com/foo.tar.gz",
//
// // This is computed from the file contents of the directory of files that is
// // obtained after fetching `url` and applying the inclusion rules given by
// // `paths`.
// //
// // This field is the source of truth; packages do not come from a `url`; they
// // come from a `hash`. `url` is just one of many possible mirrors for how to
// // obtain a package matching this `hash`.
// //
// // Uses the [multihash](https://multiformats.io/multihash/) format.
// .hash = "...",
//
// // When this is provided, the package is found in a directory relative to the
// // build root. In this case the package's hash is irrelevant and therefore not
// // computed. This field and `url` are mutually exclusive.
// .path = "foo",
//},
.wws = .{
.path = "../../kits/zig/wws",
},
},

// Specifies the set of files and directories that are included in this package.
// Only files and directories listed here are included in the `hash` that
// is computed for this package.
// Paths are relative to the build root. Use the empty string (`""`) to refer to
// the build root itself.
// A directory listed here means that all files within, recursively, are included.
.paths = .{
// This makes *all* files, recursively, included in this package. It is generally
// better to explicitly list the files and directories instead, to insure that
// fetching from tarballs, file system paths, and version control all result
// in the same contents hash.
"",
// For example...
//"build.zig",
//"build.zig.zon",
//"src",
//"LICENSE",
//"README.md",
},
}
67 changes: 67 additions & 0 deletions examples/zig-module/src/main.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const std = @import("std");
const wws = @import("wws");

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
switch (gpa.deinit()) {
.ok => {},
.leak => {},
}
}
const allocator = gpa.allocator();

// Parse request from stdin
var event = try wws.parseStream(allocator, .{});
defer event.destroy();
const wws_request = event.request;

// Prepare response
var body = std.ArrayList(u8).init(allocator);
defer body.deinit();

try body.writer().print("{any} {s}\n", .{ wws_request.method, wws_request.url });

{
var it = wws_request.storage.iterator();
while (it.next()) |entry| {
try body.writer().print("kv.{s}: {s}\n", .{ entry.key_ptr.*, entry.value_ptr.* });
}
}

var headers = wws.Headers.init(allocator);
defer headers.deinit();
try headers.append("Content-Type", "text/plain");

var storage = std.StringHashMap([]const u8).init(allocator);
defer storage.deinit();
defer {
var it = storage.iterator();
while (it.next()) |entry| {
allocator.free(entry.value_ptr.*);
}
}

var counter: usize = if (wws_request.storage.get("counter")) |v| try std.fmt.parseInt(usize, v, 10) else 0;

// Increment counter, save the result to storage
counter += 1;

{
var buf = std.ArrayList(u8).init(allocator);
defer buf.deinit();
try buf.writer().print("{d}", .{counter});
try storage.put("counter", try buf.toOwnedSlice());
}

const response = try wws.formatResponse(allocator, .{
.data = body.items,
.status = 200,
.headers = &headers,
.storage = &storage,
});
defer allocator.free(response);

const stdout = std.io.getStdOut();
try stdout.writer().print("{s}", .{response});
}
44 changes: 44 additions & 0 deletions kits/zig/wws/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const std = @import("std");

pub fn build(b: *std.Build) void {
_ = b.standardTargetOptions(.{});
_ = b.standardOptimizeOption(.{});

const module = b.addModule("wws", .{
.root_source_file = .{ .path = "src/wws.zig" },
.target = getTarget(b),
});

_ = module;
}

pub inline fn getTarget(b: *std.Build) std.Build.ResolvedTarget {
return b.resolveTargetQuery(.{
.cpu_arch = .wasm32,
.os_tag = .wasi,
});
}

pub const WwsLibOptions = struct {
name: []const u8,
root_source_file: std.Build.LazyPath,
optimize: std.builtin.OptimizeMode,
imports: []const std.Build.Module.Import = &.{},
};

pub fn addExecutable(b: *std.Build, options: WwsLibOptions) *std.Build.Step.Compile {
const exe = b.addExecutable(.{
.name = options.name,
.root_source_file = options.root_source_file,
.target = getTarget(b),
.optimize = options.optimize,
});

exe.wasi_exec_model = .reactor;

for (options.imports) |import| {
exe.root_module.addImport(import.name, import.module);
}

return exe;
}
62 changes: 62 additions & 0 deletions kits/zig/wws/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
.{
.name = "wws",
// This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication.
.version = "0.0.0",

// This field is optional.
// This is currently advisory only; Zig does not yet do anything
// with this value.
//.minimum_zig_version = "0.11.0",

// This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`.
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
// Once all dependencies are fetched, `zig build` no longer requires
// internet connectivity.
.dependencies = .{
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
//.example = .{
// // When updating this field to a new URL, be sure to delete the corresponding
// // `hash`, otherwise you are communicating that you expect to find the old hash at
// // the new URL.
// .url = "https://example.com/foo.tar.gz",
//
// // This is computed from the file contents of the directory of files that is
// // obtained after fetching `url` and applying the inclusion rules given by
// // `paths`.
// //
// // This field is the source of truth; packages do not come from a `url`; they
// // come from a `hash`. `url` is just one of many possible mirrors for how to
// // obtain a package matching this `hash`.
// //
// // Uses the [multihash](https://multiformats.io/multihash/) format.
// .hash = "...",
//
// // When this is provided, the package is found in a directory relative to the
// // build root. In this case the package's hash is irrelevant and therefore not
// // computed. This field and `url` are mutually exclusive.
// .path = "foo",
//},
},

// Specifies the set of files and directories that are included in this package.
// Only files and directories listed here are included in the `hash` that
// is computed for this package.
// Paths are relative to the build root. Use the empty string (`""`) to refer to
// the build root itself.
// A directory listed here means that all files within, recursively, are included.
.paths = .{
// This makes *all* files, recursively, included in this package. It is generally
// better to explicitly list the files and directories instead, to insure that
// fetching from tarballs, file system paths, and version control all result
// in the same contents hash.
"",
// For example...
//"build.zig",
//"build.zig.zon",
//"src",
//"LICENSE",
//"README.md",
},
}
Loading

0 comments on commit 97ef4ea

Please sign in to comment.