Skip to content
This repository was archived by the owner on Apr 9, 2025. It is now read-only.

Commit ed7ae07

Browse files
Merge pull request #6 from lightpanda-io/wrapper
Wrapper
2 parents 2fc7a24 + 2831fbe commit ed7ae07

File tree

9 files changed

+247
-110
lines changed

9 files changed

+247
-110
lines changed

.github/workflows/zig-test.yml

-1
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,3 @@ jobs:
3131
fetch-depth: 0
3232

3333
- run: zig build test
34-
- run: zig build run

build.zig

+6-32
Original file line numberDiff line numberDiff line change
@@ -15,47 +15,21 @@ pub fn build(b: *std.Build) void {
1515
// set a preferred release mode, allowing the user to decide how to optimize.
1616
const optimize = b.standardOptimizeOption(.{});
1717

18-
const exe = b.addExecutable(.{
19-
.name = "zigio",
20-
.root_source_file = b.path("src/main.zig"),
18+
const tests = b.addTest(.{
19+
.root_source_file = b.path("src/tests.zig"),
20+
.test_runner = b.path("src/test_runner.zig"),
2121
.target = target,
2222
.optimize = optimize,
2323
});
2424

25-
// This *creates* a Run step in the build graph, to be executed when another
26-
// step is evaluated that depends on it. The next line below will establish
27-
// such a dependency.
28-
const run_cmd = b.addRunArtifact(exe);
29-
30-
// By making the run step depend on the install step, it will be run from the
31-
// installation directory rather than directly from within the cache directory.
32-
// This is not necessary, however, if the application depends on other installed
33-
// files, this ensures they will be present and in the expected location.
34-
run_cmd.step.dependOn(b.getInstallStep());
35-
36-
// This allows the user to pass arguments to the application in the build
37-
// command itself, like this: `zig build run -- arg1 arg2 etc`
25+
const run_tests = b.addRunArtifact(tests);
3826
if (b.args) |args| {
39-
run_cmd.addArgs(args);
27+
run_tests.addArgs(args);
4028
}
4129

42-
// This creates a build step. It will be visible in the `zig build --help` menu,
43-
// and can be selected like this: `zig build run`
44-
// This will evaluate the `run` step rather than the default, which is "install".
45-
const run_step = b.step("run", "Run the app");
46-
run_step.dependOn(&run_cmd.step);
47-
48-
const exe_unit_tests = b.addTest(.{
49-
.root_source_file = b.path("src/main.zig"),
50-
.target = target,
51-
.optimize = optimize,
52-
});
53-
54-
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
55-
5630
// Similar to creating the run step earlier, this exposes a `test` step to
5731
// the `zig build --help` menu, providing a way for the user to request
5832
// running the unit tests.
5933
const test_step = b.step("test", "Run unit tests");
60-
test_step.dependOn(&run_exe_unit_tests.step);
34+
test_step.dependOn(&run_tests.step);
6135
}

src/Blocking.zig

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
const std = @import("std");
2+
3+
// Blocking is an example implementation of an IO API
4+
// following the zig-async-io model.
5+
// As it name suggests in this implementation all operations are
6+
// in fact blocking, the async API is just faked.
7+
pub const Blocking = @This();
8+
9+
pub const Completion = void;
10+
11+
pub const ConnectError = std.posix.ConnectError;
12+
pub const SendError = std.posix.WriteError;
13+
pub const RecvError = std.posix.ReadError;
14+
15+
pub fn connect(
16+
_: *Blocking,
17+
comptime CtxT: type,
18+
ctx: *CtxT,
19+
_: *Completion,
20+
comptime cbk: fn (ctx: *CtxT, _: *Completion, res: ConnectError!void) void,
21+
socket: std.posix.socket_t,
22+
address: std.net.Address,
23+
) void {
24+
std.posix.connect(socket, &address.any, address.getOsSockLen()) catch |err| {
25+
cbk(ctx, @constCast(&{}), err);
26+
return;
27+
};
28+
cbk(ctx, @constCast(&{}), {});
29+
}
30+
31+
pub fn onConnect(_: *Blocking, _: ConnectError!void) void {}
32+
33+
pub fn send(
34+
_: *Blocking,
35+
comptime CtxT: type,
36+
ctx: *CtxT,
37+
_: *Completion,
38+
comptime cbk: fn (ctx: *CtxT, _: *Completion, res: SendError!usize) void,
39+
socket: std.posix.socket_t,
40+
buf: []const u8,
41+
) void {
42+
const len = std.posix.write(socket, buf) catch |err| {
43+
cbk(ctx, @constCast(&{}), err);
44+
return;
45+
};
46+
cbk(ctx, @constCast(&{}), len);
47+
}
48+
49+
pub fn onSend(_: *Blocking, _: SendError!usize) void {}
50+
51+
pub fn recv(
52+
_: *Blocking,
53+
comptime CtxT: type,
54+
ctx: *CtxT,
55+
_: *Completion,
56+
comptime cbk: fn (ctx: *CtxT, _: *Completion, res: RecvError!usize) void,
57+
socket: std.posix.socket_t,
58+
buf: []u8,
59+
) void {
60+
const len = std.posix.read(socket, buf) catch |err| {
61+
cbk(ctx, @constCast(&{}), err);
62+
return;
63+
};
64+
cbk(ctx, @constCast(&{}), len);
65+
}
66+
67+
pub fn onRecv(_: *Blocking, _: RecvError!usize) void {}

src/io.zig

+140-52
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,147 @@
11
const std = @import("std");
22

3-
pub const Ctx = @import("std/http/Client.zig").Ctx;
4-
pub const Cbk = @import("std/http/Client.zig").Cbk;
5-
6-
pub const Blocking = struct {
7-
pub fn connect(
8-
_: *Blocking,
9-
comptime ctxT: type,
10-
ctx: *ctxT,
11-
comptime cbk: Cbk,
12-
socket: std.posix.socket_t,
13-
address: std.net.Address,
14-
) void {
15-
std.posix.connect(socket, &address.any, address.getOsSockLen()) catch |err| {
16-
std.posix.close(socket);
17-
cbk(ctx, err) catch |e| {
18-
ctx.setErr(e);
19-
};
20-
};
21-
cbk(ctx, {}) catch |e| ctx.setErr(e);
3+
// IO is a type defined via a root declaration.
4+
// It must implements the following methods:
5+
// - connect, onConnect
6+
// - send, onSend
7+
// - recv, onRecv
8+
// It must also define the following types:
9+
// - Completion
10+
// - ConnectError
11+
// - SendError
12+
// - RecvError
13+
// see Blocking.io for an implementation example.
14+
pub const IO = blk: {
15+
const root = @import("root");
16+
if (@hasDecl(root, "IO")) {
17+
break :blk root.IO;
2218
}
19+
@compileError("no IO API defined at root");
20+
};
21+
22+
// Wrapper for a base IO API.
23+
pub fn Wrapper(IO_T: type) type {
24+
return struct {
25+
io: *IO_T,
26+
completion: IO_T.Completion,
27+
28+
const Self = @This();
29+
30+
pub fn init(io: *IO_T) Self {
31+
return .{ .io = io, .completion = undefined };
32+
}
33+
34+
// NOTE: Business methods connect, send, recv expect a Ctx
35+
// who should reference the base IO API in Ctx.io field
36+
37+
// NOTE: Ctx is already known (ie. @import("std/http/Client.zig").Ctx)
38+
// but we require to provide its type (comptime) as argument
39+
// to avoid dependency loop
40+
// ie. Wrapper requiring Ctx and Ctx requiring Wrapper
41+
42+
fn Cbk(comptime Ctx: type) type {
43+
return *const fn (ctx: *Ctx, res: anyerror!void) anyerror!void;
44+
}
2345

24-
pub fn send(
25-
_: *Blocking,
26-
comptime ctxT: type,
27-
ctx: *ctxT,
28-
comptime cbk: Cbk,
29-
socket: std.posix.socket_t,
30-
buf: []const u8,
31-
) void {
32-
const len = std.posix.write(socket, buf) catch |err| {
33-
cbk(ctx, err) catch |e| {
34-
return ctx.setErr(e);
46+
pub fn connect(
47+
self: *Self,
48+
comptime Ctx: type,
49+
ctx: *Ctx,
50+
comptime cbk: Cbk(Ctx),
51+
socket: std.posix.socket_t,
52+
address: std.net.Address,
53+
) void {
54+
self.io.connect(Ctx, ctx, &self.completion, onConnect(Ctx, cbk), socket, address);
55+
}
56+
57+
fn onConnectFn(comptime Ctx: type) type {
58+
return fn (
59+
ctx: *Ctx,
60+
_: *IO_T.Completion,
61+
result: IO_T.ConnectError!void,
62+
) void;
63+
}
64+
fn onConnect(comptime Ctx: type, comptime cbk: Cbk(Ctx)) onConnectFn(Ctx) {
65+
const s = struct {
66+
fn on(
67+
ctx: *Ctx,
68+
_: *IO_T.Completion,
69+
result: IO_T.ConnectError!void,
70+
) void {
71+
ctx.io.io.onConnect(result); // base IO callback
72+
_ = result catch |err| return ctx.setErr(err);
73+
cbk(ctx, {}) catch |err| return ctx.setErr(err);
74+
}
3575
};
36-
return ctx.setErr(err);
37-
};
38-
ctx.setLen(len);
39-
cbk(ctx, {}) catch |e| ctx.setErr(e);
40-
}
76+
return s.on;
77+
}
4178

42-
pub fn recv(
43-
_: *Blocking,
44-
comptime ctxT: type,
45-
ctx: *ctxT,
46-
comptime cbk: Cbk,
47-
socket: std.posix.socket_t,
48-
buf: []u8,
49-
) void {
50-
const len = std.posix.read(socket, buf) catch |err| {
51-
cbk(ctx, err) catch |e| {
52-
return ctx.setErr(e);
79+
pub fn send(
80+
self: *Self,
81+
comptime Ctx: type,
82+
ctx: *Ctx,
83+
comptime cbk: Cbk(Ctx),
84+
socket: std.posix.socket_t,
85+
buf: []const u8,
86+
) void {
87+
self.io.send(Ctx, ctx, &self.completion, onSend(Ctx, cbk), socket, buf);
88+
}
89+
90+
fn onSendFn(comptime Ctx: type) type {
91+
return fn (
92+
ctx: *Ctx,
93+
_: *IO_T.Completion,
94+
result: IO_T.SendError!usize,
95+
) void;
96+
}
97+
fn onSend(comptime Ctx: type, comptime cbk: Cbk(Ctx)) onSendFn(Ctx) {
98+
const s = struct {
99+
fn on(
100+
ctx: *Ctx,
101+
_: *IO_T.Completion,
102+
result: IO_T.SendError!usize,
103+
) void {
104+
ctx.io.io.onSend(result); // base IO callback
105+
const len = result catch |err| return ctx.setErr(err);
106+
ctx.setLen(len);
107+
cbk(ctx, {}) catch |e| ctx.setErr(e);
108+
}
53109
};
54-
return ctx.setErr(err);
55-
};
56-
ctx.setLen(len);
57-
cbk(ctx, {}) catch |e| ctx.setErr(e);
58-
}
59-
};
110+
return s.on;
111+
}
112+
113+
pub fn recv(
114+
self: *Self,
115+
comptime Ctx: type,
116+
ctx: *Ctx,
117+
comptime cbk: Cbk(Ctx),
118+
socket: std.posix.socket_t,
119+
buf: []u8,
120+
) void {
121+
self.io.recv(Ctx, ctx, &self.completion, onRecv(Ctx, cbk), socket, buf);
122+
}
123+
124+
fn onRecvFn(comptime Ctx: type) type {
125+
return fn (
126+
ctx: *Ctx,
127+
_: *IO_T.Completion,
128+
result: IO_T.RecvError!usize,
129+
) void;
130+
}
131+
fn onRecv(comptime Ctx: type, comptime cbk: Cbk(Ctx)) onRecvFn(Ctx) {
132+
const s = struct {
133+
fn do(
134+
ctx: *Ctx,
135+
_: *IO_T.Completion,
136+
result: IO_T.RecvError!usize,
137+
) void {
138+
ctx.io.io.onRecv(result); // base IO callback
139+
const len = result catch |err| return ctx.setErr(err);
140+
ctx.setLen(len);
141+
cbk(ctx, {}) catch |err| return ctx.setErr(err);
142+
}
143+
};
144+
return s.do;
145+
}
146+
};
147+
}

src/lib.zig

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub const Wrapper = @import("io.zig").Wrapper;
2+
pub const Client = @import("std/http/Client.zig");

src/std/http/Client.zig

+4-5
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ const proto = @import("protocol.zig");
2121
const tls23 = @import("../../tls.zig/main.zig");
2222
const VecPut = @import("../../tls.zig/connection.zig").VecPut;
2323
const GenericStack = @import("../../stack.zig").Stack;
24-
const async_io = @import("../../io.zig");
25-
const Loop = async_io.Blocking;
24+
pub const IO = @import("../../io.zig").IO;
2625

2726
const cipher = @import("../../tls.zig/cipher.zig");
2827

@@ -2390,7 +2389,7 @@ pub const Ctx = struct {
23902389

23912390
userData: *anyopaque = undefined,
23922391

2393-
loop: *Loop,
2392+
io: *IO,
23942393
data: Data,
23952394
stack: ?*Stack = null,
23962395
err: ?anyerror = null,
@@ -2419,7 +2418,7 @@ pub const Ctx = struct {
24192418
_tls_write_index: usize = 0,
24202419
_tls_write_buf: [cipher.max_ciphertext_record_len]u8 = undefined,
24212420

2422-
pub fn init(loop: *Loop, req: *Request) !Ctx {
2421+
pub fn init(io: *IO, req: *Request) !Ctx {
24232422
const connection = try req.client.allocator.create(Connection);
24242423
connection.* = .{
24252424
.stream = undefined,
@@ -2430,7 +2429,7 @@ pub const Ctx = struct {
24302429
};
24312430
return .{
24322431
.req = req,
2433-
.loop = loop,
2432+
.io = io,
24342433
.data = .{ .conn = connection },
24352434
};
24362435
}

src/std/net.zig

+3-3
Original file line numberDiff line numberDiff line change
@@ -1908,7 +1908,7 @@ pub const Stream = struct {
19081908
ctx: *Ctx,
19091909
comptime cbk: Cbk,
19101910
) !void {
1911-
return ctx.loop.recv(Ctx, ctx, cbk, self.handle, buffer);
1911+
return ctx.io.recv(Ctx, ctx, cbk, self.handle, buffer);
19121912
}
19131913

19141914
pub fn async_readv(
@@ -1924,7 +1924,7 @@ pub const Stream = struct {
19241924

19251925
// TODO: why not take a buffer here?
19261926
pub fn async_write(self: Stream, buffer: []const u8, ctx: *Ctx, comptime cbk: Cbk) void {
1927-
return ctx.loop.send(Ctx, ctx, cbk, self.handle, buffer);
1927+
return ctx.io.send(Ctx, ctx, cbk, self.handle, buffer);
19281928
}
19291929

19301930
fn onWriteAll(ctx: *Ctx, res: anyerror!void) anyerror!void {
@@ -2033,7 +2033,7 @@ pub fn async_tcpConnectToAddress(address: std.net.Address, ctx: *Ctx, comptime c
20332033
ctx.data.socket = sockfd;
20342034
ctx.push(cbk) catch |e| return ctx.pop(e);
20352035

2036-
ctx.loop.connect(
2036+
ctx.io.connect(
20372037
Ctx,
20382038
ctx,
20392039
setStream,

0 commit comments

Comments
 (0)