refactor(zig)!: Simplify JSON stringifying (#414)

Uses `std.json.ArrayHashMap` for stringifying.

# Breaking Changes
* Plugin.setConfig's `config` parameter's type changed to
`std.json.ArrayHashMap([]const u8)`
* WasmUrl's `header` field's type changed to
`std.json.ArrayHashMap([]const u8)`
* Manifest's `config` and `allowed_path` fields' types changed to
`std.json.ArrayHashMap([]const u8)`
This commit is contained in:
Doğu Us
2023-08-12 06:10:30 +03:00
committed by GitHub
parent fa909fd53d
commit 889ec39e4d
5 changed files with 24 additions and 176 deletions

View File

@@ -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.11.0-dev.3834+d98147414") catch unreachable; // std.builtin.Version -> std.SemanticVersion
const min_zig = std.SemanticVersion.parse("0.12.0-dev.64+b835fd90c") catch unreachable; // std.json.ArrayHashMap
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 }));
}

View File

@@ -40,9 +40,9 @@ pub fn main() !void {
// var my_plugin = try Plugin.init(allocator, &context, wasm, &[_]Function{f}, true);
defer my_plugin.deinit();
var config = std.StringHashMap([]const u8).init(allocator);
defer config.deinit();
try config.put("thing", "this is a really important thing");
var config = std.json.ArrayHashMap([]const u8){};
defer config.deinit(allocator);
try config.map.put(allocator, "thing", "this is a really important thing");
try my_plugin.setConfig(allocator, config);
const input = "aeiouAEIOU____________________________________&smtms_y?" ** 1182;

View File

@@ -13,16 +13,27 @@ pub const WasmUrl = struct {
hash: ?[]const u8 = null,
name: ?[]const u8 = null,
method: ?[]const u8 = null,
headers: ?std.StringHashMap([]const u8) = null,
headers: ?std.json.ArrayHashMap([]const u8) = null,
};
pub const Wasm = union(enum) { wasm_data: WasmData, wasm_file: WasmFile, wasm_url: WasmUrl };
pub const Wasm = union(enum) {
wasm_data: WasmData,
wasm_file: WasmFile,
wasm_url: WasmUrl,
pub fn jsonStringify(self: @This(), jws: anytype) !void {
switch (self) {
inline else => |value| {
try jws.write(value);
},
}
}
};
pub const Manifest = struct {
wasm: []const Wasm,
memory: ?struct { max_pages: ?u32 } = null,
config: ?std.StringHashMap([]const u8) = null,
config: ?std.json.ArrayHashMap([]const u8) = null,
allowed_hosts: ?[]const []const u8 = null,
allowed_paths: ?std.StringHashMap([]const u8) = null,
allowed_paths: ?std.json.ArrayHashMap([]const u8) = null,
timeout: ?usize = null,
};

View File

@@ -4,7 +4,6 @@ const Manifest = @import("manifest.zig").Manifest;
const Function = @import("function.zig");
const CancelHandle = @import("cancel_handle.zig");
const c = @import("ffi.zig");
const utils = @import("utils.zig");
const Self = @This();
@@ -52,7 +51,7 @@ pub fn init(allocator: std.mem.Allocator, ctx: *Context, data: []const u8, funct
/// Create a new plugin from the given manifest
pub fn initFromManifest(allocator: std.mem.Allocator, ctx: *Context, manifest: Manifest, functions: []const Function, wasi: bool) !Self {
const json = try utils.stringifyAlloc(allocator, manifest);
const json = try std.json.stringifyAlloc(allocator, manifest, .{ .emit_null_optional_fields = false });
defer allocator.free(json);
return init(allocator, ctx, json, functions, wasi);
}
@@ -67,7 +66,7 @@ pub fn create(allocator: std.mem.Allocator, data: []const u8, functions: []const
/// Create a new plugin from the given manifest in its own context
pub fn createFromManifest(allocator: std.mem.Allocator, manifest: Manifest, functions: []const Function, wasi: bool) !Self {
const json = try utils.stringifyAlloc(allocator, manifest);
const json = try std.json.stringifyAlloc(allocator, manifest, .{ .emit_null_optional_fields = false });
defer allocator.free(json);
return create(allocator, json, functions, wasi);
}
@@ -128,15 +127,15 @@ pub fn update(self: *Self, data: []const u8, wasi: bool) !void {
/// Update a plugin with the given manifest
pub fn updateWithManifest(self: *Self, allocator: std.mem.Allocator, manifest: Manifest, wasi: bool) !void {
const json = try utils.stringifyAlloc(allocator, manifest);
const json = try std.json.stringifyAlloc(allocator, manifest, .{ .emit_null_optional_fields = false });
defer allocator.free(json);
return self.update(json, wasi);
}
/// Set configuration values
pub fn setConfig(self: *Self, allocator: std.mem.Allocator, config: std.StringHashMap([]const u8)) !void {
pub fn setConfig(self: *Self, allocator: std.mem.Allocator, config: std.json.ArrayHashMap([]const u8)) !void {
self.ctx.mutex.lock();
defer self.ctx.mutex.unlock();
const config_json = try utils.stringifyAlloc(allocator, config);
const config_json = try std.json.stringifyAlloc(allocator, config, .{ .emit_null_optional_fields = false });
defer allocator.free(config_json);
_ = c.extism_plugin_config(self.ctx.ctx, self.id, config_json.ptr, @as(u64, config_json.len));
}

View File

@@ -1,162 +0,0 @@
const std = @import("std");
const json = std.json;
pub fn stringifyAlloc(allocator: std.mem.Allocator, value: anytype) ![]const u8 {
var list = std.ArrayList(u8).init(allocator);
errdefer list.deinit();
try stringify(value, .{ .emit_null_optional_fields = false }, list.writer());
return list.toOwnedSlice();
}
fn stringify(
value: anytype,
options: json.StringifyOptions,
out_stream: anytype,
) @TypeOf(out_stream).Error!void {
const T = @TypeOf(value);
if (comptime std.mem.count(u8, @typeName(T), "hash_map.HashMap") > 0 and std.mem.count(u8, @typeName(T), "?") == 0) {
var it = value.iterator();
try out_stream.writeByte('{');
var i: usize = 0;
while (it.next()) |entry| {
i += 1;
const name = entry.key_ptr.*;
try json.encodeJsonString(name, options, out_stream);
if (i < value.count()) try out_stream.writeByte(':');
const val = entry.value_ptr.*;
try json.encodeJsonString(val, options, out_stream);
}
try out_stream.writeByte('}');
return;
}
switch (@typeInfo(T)) {
.Float, .ComptimeFloat => {
return std.fmt.formatFloatScientific(value, std.fmt.FormatOptions{}, out_stream);
},
.Int, .ComptimeInt => {
return std.fmt.formatIntValue(value, "", std.fmt.FormatOptions{}, out_stream);
},
.Bool => {
return out_stream.writeAll(if (value) "true" else "false");
},
.Null => {
return out_stream.writeAll("null");
},
.Optional => {
if (value) |payload| {
return try stringify(payload, options, out_stream);
} else {
return try stringify(null, options, out_stream);
}
},
.Enum => {
if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
return value.jsonStringify(options, out_stream);
}
@compileError("Unable to stringify enum '" ++ @typeName(T) ++ "'");
},
.Union => {
if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
return value.jsonStringify(options, out_stream);
}
const info = @typeInfo(T).Union;
if (info.tag_type) |UnionTagType| {
inline for (info.fields) |u_field| {
if (value == @field(UnionTagType, u_field.name)) {
return try stringify(@field(value, u_field.name), options, out_stream);
}
}
} else {
@compileError("Unable to stringify untagged union '" ++ @typeName(T) ++ "'");
}
},
.Struct => |S| {
if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
return value.jsonStringify(options, out_stream);
}
try out_stream.writeByte('{');
var field_output = false;
var child_options = options;
child_options.whitespace = .indent_2;
inline for (S.fields) |Field| {
// don't include void fields
if (Field.type == void) continue;
var emit_field = true;
// don't include optional fields that are null when emit_null_optional_fields is set to false
if (@typeInfo(Field.type) == .Optional) {
if (options.emit_null_optional_fields == false) {
if (@field(value, Field.name) == null) {
emit_field = false;
}
}
}
if (emit_field) {
if (!field_output) {
field_output = true;
} else {
try out_stream.writeByte(',');
}
try json.encodeJsonString(Field.name, options, out_stream);
try out_stream.writeByte(':');
if (child_options.whitespace != .minified) {
try out_stream.writeByte(' ');
}
try stringify(@field(value, Field.name), child_options, out_stream);
}
}
try out_stream.writeByte('}');
return;
},
.ErrorSet => return stringify(@as([]const u8, @errorName(value)), options, out_stream),
.Pointer => |ptr_info| switch (ptr_info.size) {
.One => switch (@typeInfo(ptr_info.child)) {
.Array => {
const Slice = []const std.meta.Elem(ptr_info.child);
return stringify(@as(Slice, value), options, out_stream);
},
else => {
// TODO: avoid loops?
std.debug.print("a: {}", .{std.mem.indexOf(u8, @typeName(T), "HashMap") != null});
// return stringify(value.*, options, out_stream);
},
},
// TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972)
.Slice => {
if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) {
try json.encodeJsonString(value, options, out_stream);
return;
}
try out_stream.writeByte('[');
var child_options = options;
for (value, 0..) |x, i| {
if (i != 0) {
try out_stream.writeByte(',');
}
try stringify(x, child_options, out_stream);
}
try out_stream.writeByte(']');
return;
},
else => {
@compileError("Unable to stringify type '" ++ @typeName(T) ++ "'");
},
},
.Array => return stringify(&value, options, out_stream),
.Vector => |info| {
const array: [info.len]info.child = value;
return stringify(&array, options, out_stream);
},
else => {
@compileError("Unable to stringify type '" ++ @typeName(T) ++ "'");
},
}
unreachable;
}