refactor(main): remove one layer of indirection

This commit removes the BotAdapter.erased_callback function, which
served as a generic version of BotAdapter.callback that was legible
for zircon.MessageClosure.

However, it is clearer to document how that mechanism works. Therefore
we remove the BotAdapter.erased_callback and instead makes
BotAdapter.callback match the zircon.MessageClosure.callbackFn
signature.
This commit is contained in:
Jacob Jonsson 2026-03-15 15:08:43 +01:00
parent 7d444204ef
commit 2e1416eb21
Signed by: Jassob
GPG key ID: 7E30B9B047F7202E

View file

@ -44,11 +44,6 @@ pub fn main() !void {
};
}
pub const Adapter = struct {
ptr: *anyopaque,
callbackFn: *const fn (*anyopaque, zircon.Message) ?zircon.Message,
};
/// BotAdapter is the closure that we register in zircon as the
/// message callback.
///
@ -82,10 +77,23 @@ pub const BotAdapter = struct {
/// See
/// - https://modern.ircdocs.horse/, for what kinds of messages exists in the IRC protocol documentation,
/// - https://github.com/Jassob/zircon/blob/main/src/message.zig, for zircon documentation.
pub fn callback(self: *BotAdapter, message: zircon.Message) ?zircon.Message {
///
/// NOTE: This function does not have a self-parameter, this is
/// because this function is called as a "generic" function
/// that is parameterized in the pointer argument (otherwise
/// zircon library would not be able to be reused).
///
/// That's why the first thing we do is perform some casting
/// magic to convert our pointer back to a BotAdapter
/// pointer. This is safe as long as we know that we
/// registered a BotAdapter closure and there are no other
/// callbacks registered.
pub fn callback(ptr: *anyopaque, message: zircon.Message) ?zircon.Message {
const self: *@This() = @ptrCast(@alignCast(ptr));
switch (message) {
.PRIVMSG => |msg| {
const nick = if (msg.prefix != null and msg.prefix.?.nick != null) msg.prefix.?.nick.? else "unknown";
const nick = nickFromPrefix(msg.prefix);
// create message
const bot_message = BotMessage.init_owned(
@ -125,19 +133,23 @@ pub const BotAdapter = struct {
return .{ .PRIVMSG = .{ .prefix = null, .targets = "#eru-admin", .text = err_msg } };
}
pub fn erased_callback(self: *anyopaque, message: zircon.Message) ?zircon.Message {
const a: *@This() = @ptrCast(@alignCast(self));
return a.callback(message);
}
pub fn closure(self: *BotAdapter) zircon.MessageClosure {
return .{
.ptr = self,
.callbackFn = BotAdapter.erased_callback,
.callbackFn = BotAdapter.callback,
};
}
};
/// nickFromPrefix returns a nick if it exists in the prefix or
/// "unknown" otherwise.
fn nickFromPrefix(prefix: ?zircon.Prefix) []const u8 {
if (prefix == null or prefix.?.nick == null) {
return "unknown";
}
return prefix.?.nick.?;
}
/// toIRC converts a bot response and converts it to a IRC message.
fn toIRC(response: BotResponse) zircon.Message {
switch (response) {
@ -150,30 +162,27 @@ fn toIRC(response: BotResponse) zircon.Message {
}
}
fn priv_msg(text: []const u8) zircon.Message {
return .{ .PRIVMSG = .{
.prefix = .{ .nick = "jassob", .host = "localhost", .user = "jassob" },
.targets = "#test",
.text = text,
} };
}
test "substitute" {
var bot_adapter = try BotAdapter.init(std.testing.allocator);
defer bot_adapter.deinit();
const prefix = zircon.Prefix{ .nick = "jassob", .user = "jassob", .host = "localhost" };
const msg = zircon.Message{
.PRIVMSG = .{
.prefix = prefix,
.targets = "#test",
.text = "hello world",
},
};
if (bot_adapter.callback(msg)) |_| {
var adapter = try BotAdapter.init(std.testing.allocator);
defer adapter.deinit();
if (BotAdapter.callback(&adapter, priv_msg("hello world"))) |_| {
@panic("unexpected response");
}
const cmd_msg = zircon.Message{
.PRIVMSG = .{
.prefix = prefix,
.targets = "#test",
.text = "s/world/zig/",
},
};
const response = bot_adapter.callback(cmd_msg);
const response = BotAdapter.callback(&adapter, priv_msg("s/world/zig/"));
try std.testing.expect(response != null);
try std.testing.expectEqualStrings("jassob: \"hello zig\"", response.?.PRIVMSG.text);
try std.testing.expectEqualStrings(
"jassob: \"hello zig\"",
response.?.PRIVMSG.text,
);
}
test "get empty backlog message" {
@ -182,5 +191,9 @@ test "get empty backlog message" {
const msg = zircon.Message{
.PRIVMSG = .{ .prefix = null, .targets = "#eru-admin", .text = "!backlog 0" },
};
try std.testing.expectEqualDeep("no matching message", bot_adapter.callback(msg).?.PRIVMSG.text);
try std.testing.expectEqualDeep(
"no matching message",
BotAdapter.callback(&bot_adapter, msg).?.PRIVMSG.text,
);
}