Skip to content

Commit 2fa9cd9

Browse files
committed
refactor Serializer to Convert
1 parent 7912d8b commit 2fa9cd9

7 files changed

Lines changed: 58 additions & 66 deletions

File tree

README.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,26 @@ import fromZig from(zig-module.node);
5757

5858
## Type Conversions
5959

60+
Struct types, functions, fields, parameters and return values are all converted by convention.
61+
Unsupported types result in compile errors.
62+
63+
|Native type|Node type|Remarks|
64+
|-|-|-|
65+
|`type`|`Class` or `Function`|Returning or passing a struct `type` to JS, turns it into a class.<br>Returning or passing a `fn`, turns it into a JS-callable, well, function. |
66+
|`i32`,`i64`,`u32`|`number`| |
67+
|`u64`|`BigInt`| |
68+
|`[]const u8`, `[]u8`|`string`|UTF-8|
69+
|`[]const T`, `[]T`|`array`| |
70+
|`*T`|`Object`|Passing struct pointers to JS will wrap & track them.|
71+
|`NodeValue`|`any`|NodeValue can be used to access JS values by reference.|
72+
6073
Function parameters and return types can be
6174
- native Zig types (unsupported types will result in compile time errors)
6275
- one of the NodeValue types to access values by reference.
6376

64-
Native values and NodeValue instance can be converted using the NodeSerializer.
65-
- deserialize(comptime T: type, value: NodeValue, allocator. Allocator) T
66-
- serialize(value: anytype) NodeValue
77+
Native values and NodeValue instance can be converted using `Convert`:
78+
- `nativeFromNode(comptime T: type, value: NodeValue, allocator. Allocator) T`
79+
- `nodeFromNative(value: anytype) NodeValue`
6780

6881
## Define functions
6982

Lines changed: 33 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const NodeFunction = node_values.NodeFunction;
1111
const registry = @import("references.zig").Registry;
1212

1313
/// Converts a Zig value to a Node-API value. Memory for the node value is allocated by V8.
14-
pub fn serialize(env: c.napi_env, value: anytype) !c.napi_value {
14+
pub fn nodeFromNative(env: c.napi_env, value: anytype) !c.napi_value {
1515
const T = @TypeOf(value);
1616

1717
const node = NodeContext.init(env);
@@ -29,28 +29,28 @@ pub fn serialize(env: c.napi_env, value: anytype) !c.napi_value {
2929
switch (@bitSizeOf(T)) {
3030
0...32 => try s2e(c.napi_create_int32(env, value, &res)),
3131
33...64 => try s2e(c.napi_create_int64(env, value, &res)),
32-
else => |s| @compileError(std.fmt.comptimePrint("Cannot serialize value of type {s}, unsupported bitsize {d}", .{ @typeName(T), s })),
32+
else => |s| @compileError(std.fmt.comptimePrint("Cannot convert value of type '{s}', unsupported bitsize {d}", .{ @typeName(T), s })),
3333
}
3434
} else {
3535
switch (@bitSizeOf(T)) {
3636
0...32 => try s2e(c.napi_create_uint32(env, value, &res)),
3737
33...64 => try s2e(c.napi_create_bigint_uint64(env, value, &res)),
38-
else => |s| @compileError(std.fmt.comptimePrint("Cannot serialize value of type {s}, unsupported bitsize {d}", .{ @typeName(T), s })),
38+
else => |s| @compileError(std.fmt.comptimePrint("Cannot convert value of type '{s}', unsupported bitsize {d}", .{ @typeName(T), s })),
3939
}
4040
}
4141
},
4242
.int => |i| {
4343
if (i.signedness == .signed) {
4444
switch (i.bits) {
45-
0...32 => try s2e(c.napi_create_int32(env, value, &res)),
46-
33...64 => try s2e(c.napi_create_int64(env, value, &res)),
47-
else => |s| @compileError(std.fmt.comptimePrint("Cannot serialize value of type {s}, unsupported bitsize {d}", .{ @typeName(T), s })),
45+
32 => try s2e(c.napi_create_int32(env, value, &res)),
46+
64 => try s2e(c.napi_create_int64(env, value, &res)),
47+
else => |s| @compileError(std.fmt.comptimePrint("Cannot convert value of type '{s}', unsupported bitsize {d}", .{ @typeName(T), s })),
4848
}
4949
} else {
5050
switch (i.bits) {
51-
0...32 => try s2e(c.napi_create_uint32(env, value, &res)),
52-
33...64 => try s2e(c.napi_create_bigint_uint64(env, value, &res)),
53-
else => |s| @compileError(std.fmt.comptimePrint("Cannot serialize value of type {s}, unsupported bitsize {d}", .{ @typeName(T), s })),
51+
32 => try s2e(c.napi_create_uint32(env, value, &res)),
52+
64 => try s2e(c.napi_create_bigint_uint64(env, value, &res)),
53+
else => |s| @compileError(std.fmt.comptimePrint("Cannot convert value of type '{s}', unsupported bitsize {d}", .{ @typeName(T), s })),
5454
}
5555
}
5656
},
@@ -70,7 +70,7 @@ pub fn serialize(env: c.napi_env, value: anytype) !c.napi_value {
7070
},
7171
.optional => {
7272
if (value) |val| {
73-
return serialize(env, val);
73+
return nodeFromNative(env, val);
7474
} else {
7575
try s2e(c.napi_get_null(env, &res));
7676
}
@@ -82,11 +82,7 @@ pub fn serialize(env: c.napi_env, value: anytype) !c.napi_value {
8282
return v;
8383
}
8484

85-
// TODO: could be wrapped value, do we need this?
86-
// how do we know if the instance was already wrapped before?
87-
// hashset?
88-
89-
return serialize(env, value.*);
85+
return nodeFromNative(env, value.*);
9086
},
9187
.slice => {
9288
std.log.debug("serializing {s} of type {s}", .{ value, @typeName(T) });
@@ -96,13 +92,13 @@ pub fn serialize(env: c.napi_env, value: anytype) !c.napi_value {
9692
try s2e(c.napi_create_array_with_length(env, value.len, &res));
9793

9894
for (value, 0..) |item, i| {
99-
const el = try serialize(env, item);
95+
const el = try nodeFromNative(env, item);
10096
try s2e(c.napi_set_element(env, res, i, el));
10197
}
10298
}
10399
},
104100
.many, .c => {
105-
@compileError("Cannot serialize c-style pointer values, they are not supported.");
101+
@compileError("Cannot convert c-style pointer values, they are not supported.");
106102
},
107103
}
108104
},
@@ -114,21 +110,19 @@ pub fn serialize(env: c.napi_env, value: anytype) !c.napi_value {
114110
if (!s.is_tuple) {
115111
try s2e(c.napi_create_object(env, &res));
116112
inline for (s.fields) |field| {
117-
// std.log.debug("tuple field: {any}", .{field});
118113
if (field.type == void) continue;
119114

120115
const field_val = @field(value, field.name);
121116

122-
try s2e(c.napi_set_property(env, res, try serialize(env, field.name), try serialize(env, field_val)));
117+
try s2e(c.napi_set_property(env, res, try nodeFromNative(env, field.name), try nodeFromNative(env, field_val)));
123118
}
124119
} else {
125120
try s2e(c.napi_create_array_with_length(env, s.fields.len, &res));
126121
inline for (s.fields, 0..) |field, i| {
127-
// std.log.debug("field: {any}", .{field});
128122
if (field.type == void) continue;
129123

130124
const field_val = @field(value, field.name);
131-
try s2e(c.napi_set_element(env, res, i, try serialize(env, field_val)));
125+
try s2e(c.napi_set_element(env, res, i, try nodeFromNative(env, field_val)));
132126
}
133127
}
134128
},
@@ -140,7 +134,7 @@ pub fn serialize(env: c.napi_env, value: anytype) !c.napi_value {
140134
try s2e(c.napi_create_array_with_length(env, value.len, &res));
141135

142136
for (value, 0..) |item, i| {
143-
const el = try serialize(env, item);
137+
const el = try nodeFromNative(env, item);
144138
try s2e(c.napi_set_element(env, res, i, el));
145139
}
146140
}
@@ -151,62 +145,47 @@ pub fn serialize(env: c.napi_env, value: anytype) !c.napi_value {
151145

152146
const tag = @as(Tag, value);
153147
const tag_name = @tagName(tag);
154-
try s2e(c.napi_set_property(env, res, try serialize(env, "type"), try serialize(env, tag_name)));
148+
try s2e(c.napi_set_property(env, res, try nodeFromNative(env, "type"), try nodeFromNative(env, tag_name)));
155149

156150
inline for (u.fields) |f| {
157151
if (std.mem.eql(u8, f.name, tag_name)) {
158152
if (f.type == void) break;
159-
try s2e(c.napi_set_property(env, res, try serialize(env, "value"), try serialize(env, @field(value, f.name))));
153+
try s2e(c.napi_set_property(env, res, try nodeFromNative(env, "value"), try nodeFromNative(env, @field(value, f.name))));
160154
break;
161155
}
162156
}
163157
} else {
164-
@compileError(std.fmt.comptimePrint("Cannot serialize value of type {s}, untagged unions are not supported.", .{@typeName(T)}));
158+
@compileError(std.fmt.comptimePrint("Cannot convert value of type '{s}', untagged unions are not supported.", .{@typeName(T)}));
165159
}
166160
},
167161

168-
else => @compileError(std.fmt.comptimePrint("Cannot serialize value of type {s}", .{@typeName(T)})),
162+
else => @compileError(std.fmt.comptimePrint("Cannot convert value of type '{s}'", .{@typeName(T)})),
169163
}
170164

171165
return res;
172166
}
173167

174168
/// Converts a Node-API value to a native Zig value.
175-
pub fn deserialize(env: c.napi_env, comptime T: type, js_value: c.napi_value, allocator: std.mem.Allocator) !T {
169+
pub fn nativeFromNode(env: c.napi_env, comptime T: type, js_value: c.napi_value, allocator: std.mem.Allocator) !T {
176170
var res: T = undefined;
177171

178172
switch (@typeInfo(T)) {
179173
.bool => try s2e(c.napi_get_value_bool(env, js_value, &res)),
180174
.int => |i| {
181175
if (i.signedness == .signed) {
182176
switch (i.bits) {
183-
32 => {
184-
var tmp: i32 = undefined;
185-
try s2e(c.napi_get_value_int32(env, js_value, &tmp));
186-
// TODO: handle failing int casts
187-
return @intCast(tmp);
188-
},
189-
64 => {
190-
var tmp: i64 = undefined;
191-
try s2e(c.napi_get_value_int64(env, js_value, &tmp));
192-
return @intCast(tmp);
193-
},
194-
else => @compileError(std.fmt.comptimePrint("Cannot deserialize value of type {s}", .{@typeName(T)})),
177+
32 => try s2e(c.napi_get_value_int32(env, js_value, &res)),
178+
64 => try s2e(c.napi_get_value_int64(env, js_value, &res)),
179+
else => @compileError(std.fmt.comptimePrint("Cannot convert node value to '{s}'", .{@typeName(T)})),
195180
}
196181
} else {
197182
switch (i.bits) {
198-
0...32 => {
199-
var tmp: u32 = undefined;
200-
try s2e(c.napi_get_value_uint32(env, js_value, &tmp));
201-
return @intCast(tmp);
202-
},
203-
33...64 => {
204-
var tmp: u64 = undefined;
183+
32 => try s2e(c.napi_get_value_uint32(env, js_value, &res)),
184+
64 => {
205185
var b: bool = undefined;
206-
try s2e(c.napi_get_value_bigint_uint64(env, js_value, &tmp, &b));
207-
return @intCast(tmp);
186+
try s2e(c.napi_get_value_bigint_uint64(env, js_value, &res, &b));
208187
},
209-
else => @compileError(std.fmt.comptimePrint("Cannot deserialize value of type {s}", .{@typeName(T)})),
188+
else => @compileError(std.fmt.comptimePrint("Cannot convert node value to '{s}'", .{@typeName(T)})),
210189
}
211190
}
212191
},
@@ -234,7 +213,7 @@ pub fn deserialize(env: c.napi_env, comptime T: type, js_value: c.napi_value, al
234213
try s2e(c.napi_typeof(env, js_value, &t));
235214
switch (t) {
236215
c.napi_undefined, c.napi_null => return null,
237-
else => return try deserialize(env, o.child, js_value, allocator),
216+
else => return try nativeFromNode(env, o.child, js_value, allocator),
238217
}
239218
},
240219
.pointer => |p| {
@@ -261,7 +240,7 @@ pub fn deserialize(env: c.napi_env, comptime T: type, js_value: c.napi_value, al
261240
for (0..len) |i| {
262241
var elem: c.napi_value = undefined;
263242
try s2e(c.napi_get_element(env, js_value, i, &elem));
264-
buf[i] = try deserialize(env, p.child, elem, allocator);
243+
buf[i] = try nativeFromNode(env, p.child, elem, allocator);
265244
}
266245

267246
return buf;
@@ -272,7 +251,7 @@ pub fn deserialize(env: c.napi_env, comptime T: type, js_value: c.napi_value, al
272251
}
273252
},
274253
else => {
275-
@compileError("Cannot serialize c-style pointer values, they are not supported.");
254+
@compileError("Cannot convert c-style pointer values, they are not supported.");
276255
},
277256
}
278257
},
@@ -288,13 +267,13 @@ pub fn deserialize(env: c.napi_env, comptime T: type, js_value: c.napi_value, al
288267
inline for (s.fields) |field| {
289268
var v: c.napi_value = undefined;
290269
try s2e(c.napi_get_named_property(env, js_value, field.name.ptr, &v));
291-
@field(instance, field.name) = try deserialize(env, field.type, v, allocator);
270+
@field(instance, field.name) = try nativeFromNode(env, field.type, v, allocator);
292271
}
293272

294273
return instance;
295274
},
296275

297-
else => @compileError(std.fmt.comptimePrint("Cannot deserialize value of type {s}", .{@typeName(T)})),
276+
else => @compileError(std.fmt.comptimePrint("Cannot convert node value to '{s}'", .{@typeName(T)})),
298277
}
299278

300279
return res;

src/Node.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const c = lib.c;
55
const NodeApiError = lib.NodeApiError;
66
const s2e = lib.statusToError;
77

8-
const Serializer = @import("Serializer.zig");
8+
const Convert = @import("Convert.zig");
99

1010
const NodeValues = @import("node_values.zig");
1111
const NodeValue = NodeValues.NodeValue;
@@ -504,12 +504,12 @@ pub const NodeContext = struct {
504504
pub fn serialize(self: Self, value: anytype) !NodeValue {
505505
return .{
506506
.napi_env = self.napi_env,
507-
.napi_value = try Serializer.serialize(self.napi_env, value),
507+
.napi_value = try Convert.nodeFromNative(self.napi_env, value),
508508
};
509509
}
510510

511511
pub fn deserialize(self: Self, comptime T: type, value: NodeValue) !T {
512-
return try Serializer.deserialize(self.napi_env, T, value.napi_value, std.heap.c_allocator);
512+
return try Convert.nativeFromNode(self.napi_env, T, value.napi_value, std.heap.c_allocator);
513513
}
514514

515515
pub fn handleError(self: Self, err: anyerror) void {

src/node_values.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const lib = @import("c.zig");
33
const c = lib.c;
44
const s2e = lib.statusToError;
55

6-
const Serializer = @import("Serializer.zig");
6+
const Convert = @import("Convert.zig");
77

88
// https://nodejs.org/api/n-api.html#napi_valuetype
99
pub const NodeValueType = enum {
@@ -179,7 +179,7 @@ pub fn NodeFunction(comptime F: anytype) type {
179179
pub fn call(self: Self, args: TupleTypeOf(f.params)) !f.return_type.? {
180180
var js_args: [f.params.len]c.napi_value = undefined;
181181
inline for (0..args.len) |i| {
182-
js_args[i] = try Serializer.serialize(self.napi_env, args[i]);
182+
js_args[i] = try Convert.nodeFromNative(self.napi_env, args[i]);
183183
}
184184
var res: c.napi_value = undefined;
185185

@@ -191,7 +191,7 @@ pub fn NodeFunction(comptime F: anytype) type {
191191
var arena = std.heap.ArenaAllocator.init(std.heap.c_allocator);
192192
defer arena.deinit();
193193

194-
return try Serializer.deserialize(self.napi_env, f.return_type.?, res, arena.allocator());
194+
return try Convert.nativeFromNode(self.napi_env, f.return_type.?, res, arena.allocator());
195195
}
196196
};
197197
}

src/root.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const std = @import("std");
22

33
const c = @import("c.zig").c;
4-
const Serializer = @import("Serializer.zig");
4+
const Convert = @import("Convert.zig");
55
const NodeValues = @import("node_values.zig");
66
/// Represents a Node VM Context.
77
pub const NodeContext = @import("Node.zig").NodeContext;

tests/serialization.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import addon from "node-api-test-module";
22
import { describe, it, expect } from "bun:test";
33

4-
describe("Serializer", () => {
4+
describe("Convert", () => {
55
describe("serialize", () => {
66
it("utf8 string", () => {
77
expect(addon.serialization.serializeString()).toEqual("foo");

tests/zig/test-module.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ fn init(node: node_api.NodeContext) !?node_api.NodeValue {
3838
});
3939
}
4040

41-
fn getInt() i16 {
41+
fn getInt() i32 {
4242
return -456 + 45;
4343
}
4444
fn getUInt() u16 {

0 commit comments

Comments
 (0)