make errors more explicit and overall refactor

This commit is contained in:
Yuzu 2024-11-10 11:30:51 -05:00
parent 693b3ec837
commit 79dc5b3a0a
7 changed files with 109 additions and 102 deletions

View File

@ -1,10 +1,9 @@
pub const Discord = @import("types.zig"); pub usingnamespace @import("types.zig");
const Intents = Discord.Intents;
pub const Shard = @import("shard.zig"); pub const Shard = @import("shard.zig");
pub const Internal = @import("internal.zig"); pub const Internal = @import("internal.zig");
const Log = Internal.Log;
const GatewayDispatchEvent = Internal.GatewayDispatchEvent; const GatewayDispatchEvent = Internal.GatewayDispatchEvent;
const Log = Internal.Log;
pub const Sharder = @import("sharder.zig"); pub const Sharder = @import("sharder.zig");
const SessionOptions = Sharder.SessionOptions; const SessionOptions = Sharder.SessionOptions;
@ -19,24 +18,25 @@ const mem = std.mem;
const http = std.http; const http = std.http;
const json = std.json; const json = std.json;
pub const Client = struct { const Self = @This();
allocator: mem.Allocator,
sharder: Sharder,
pub fn init(allocator: mem.Allocator) Client { allocator: mem.Allocator,
sharder: Sharder,
pub fn init(allocator: mem.Allocator) Self {
return .{ return .{
.allocator = allocator, .allocator = allocator,
.sharder = undefined, .sharder = undefined,
}; };
} }
pub fn deinit(self: *Client) void { pub fn deinit(self: *Self) void {
self.sharder.deinit(); self.sharder.deinit();
} }
pub fn start(self: *Client, settings: struct { pub fn start(self: *Self, settings: struct {
token: []const u8, token: []const u8,
intents: Intents, intents: Self.Intents,
options: struct { options: struct {
spawn_shard_delay: u64 = 5300, spawn_shard_delay: u64 = 5300,
total_shards: usize = 1, total_shards: usize = 1,
@ -45,7 +45,7 @@ pub const Client = struct {
}, },
run: GatewayDispatchEvent(*Shard), run: GatewayDispatchEvent(*Shard),
log: Log, log: Log,
}) !void { }) !void {
var req = FetchReq.init(self.allocator, settings.token); var req = FetchReq.init(self.allocator, settings.token);
defer req.deinit(); defer req.deinit();
@ -76,5 +76,4 @@ pub const Client = struct {
}); });
try self.sharder.spawnShards(); try self.sharder.spawnShards();
} }
};

View File

@ -235,10 +235,10 @@ pub fn GatewayDispatchEvent(comptime T: type) type {
// TODO: implement // interaction_create: null = null, // TODO: implement // interaction_create: null = null,
// TODO: implement // invite_create: null = null, // TODO: implement // invite_create: null = null,
// TODO: implement // invite_delete: null = null, // TODO: implement // invite_delete: null = null,
message_create: ?*const fn (save: T, message: Discord.Message) void = undefined, message_create: ?*const fn (save: T, message: Discord.Message) anyerror!void = undefined,
message_update: ?*const fn (save: T, message: Discord.Message) void = undefined, message_update: ?*const fn (save: T, message: Discord.Message) anyerror!void = undefined,
message_delete: ?*const fn (save: T, log: Discord.MessageDelete) void = undefined, message_delete: ?*const fn (save: T, log: Discord.MessageDelete) anyerror!void = undefined,
message_delete_bulk: ?*const fn (save: T, log: Discord.MessageDeleteBulk) void = undefined, message_delete_bulk: ?*const fn (save: T, log: Discord.MessageDeleteBulk) anyerror!void = undefined,
// TODO: implement // message_delete_bulk: null = null, // TODO: implement // message_delete_bulk: null = null,
// TODO: implement // message_reaction_add: null = null, // TODO: implement // message_reaction_add: null = null,
// TODO: implement // message_reaction_remove: null = null, // TODO: implement // message_reaction_remove: null = null,
@ -260,8 +260,8 @@ pub fn GatewayDispatchEvent(comptime T: type) type {
// TODO: implement // message_poll_vote_add: null = null, // TODO: implement // message_poll_vote_add: null = null,
// TODO: implement // message_poll_vote_remove: null = null, // TODO: implement // message_poll_vote_remove: null = null,
ready: ?*const fn (save: T, data: Discord.Ready) void = undefined, ready: ?*const fn (save: T, data: Discord.Ready) anyerror!void = undefined,
// TODO: implement // resumed: null = null, // TODO: implement // resumed: null = null,
any: ?*const fn (save: T, data: []const u8) void = undefined, any: ?*const fn (save: T, data: []const u8) anyerror!void = undefined,
}; };
} }

View File

@ -4,7 +4,7 @@ const std = @import("std");
const mem = std.mem; const mem = std.mem;
const Snowflake = @import("shared.zig").Snowflake; const Snowflake = @import("shared.zig").Snowflake;
pub fn parseUser(_: mem.Allocator, obj: *zmpl.Data.Object) !Discord.User { pub fn parseUser(_: mem.Allocator, obj: *zmpl.Data.Object) std.fmt.ParseIntError!Discord.User {
const avatar_decoration_data_obj = obj.getT(.object, "avatar_decoration_data"); const avatar_decoration_data_obj = obj.getT(.object, "avatar_decoration_data");
const user = Discord.User{ const user = Discord.User{
.clan = null, .clan = null,
@ -35,7 +35,7 @@ pub fn parseUser(_: mem.Allocator, obj: *zmpl.Data.Object) !Discord.User {
return user; return user;
} }
pub fn parseMember(_: mem.Allocator, obj: *zmpl.Data.Object) !Discord.Member { pub fn parseMember(_: mem.Allocator, obj: *zmpl.Data.Object) std.fmt.ParseIntError!Discord.Member {
const avatar_decoration_data_member_obj = obj.getT(.object, "avatar_decoration_data"); const avatar_decoration_data_member_obj = obj.getT(.object, "avatar_decoration_data");
const member = Discord.Member{ const member = Discord.Member{
.deaf = obj.getT(.boolean, "deaf"), .deaf = obj.getT(.boolean, "deaf"),
@ -59,7 +59,7 @@ pub fn parseMember(_: mem.Allocator, obj: *zmpl.Data.Object) !Discord.Member {
} }
/// caller must free the received referenced_message if any /// caller must free the received referenced_message if any
pub fn parseMessage(allocator: mem.Allocator, obj: *zmpl.Data.Object) !Discord.Message { pub fn parseMessage(allocator: mem.Allocator, obj: *zmpl.Data.Object) (mem.Allocator.Error || std.fmt.ParseIntError)!Discord.Message {
// parse mentions // parse mentions
const mentions_obj = obj.getT(.array, "mentions").?; const mentions_obj = obj.getT(.array, "mentions").?;

View File

@ -34,9 +34,7 @@ const GatewayDispatchEvent = Internal.GatewayDispatchEvent;
const Bucket = Internal.Bucket; const Bucket = Internal.Bucket;
const default_identify_properties = Internal.default_identify_properties; const default_identify_properties = Internal.default_identify_properties;
const FetchRequest = @import("http.zig").FetchReq; pub const ShardSocketCloseCodes = enum(u16) {
const ShardSocketCloseCodes = enum(u16) {
Shutdown = 3000, Shutdown = 3000,
ZombiedConnection = 3010, ZombiedConnection = 3010,
}; };
@ -83,8 +81,10 @@ ws_mutex: std.Thread.Mutex = .{},
rw_mutex: std.Thread.RwLock = .{}, rw_mutex: std.Thread.RwLock = .{},
log: Log = .no, log: Log = .no,
pub const JsonResolutionError = std.fmt.ParseIntError || std.fmt.ParseFloatError || json.ParseFromValueError || json.ParseError(json.Scanner);
/// caller must free the data /// caller must free the data
fn parseJson(self: *Self, raw: []const u8) !zmpl.Data { fn parseJson(self: *Self, raw: []const u8) JsonResolutionError!zmpl.Data {
var data = zmpl.Data.init(self.allocator); var data = zmpl.Data.init(self.allocator);
try data.fromJson(raw); try data.fromJson(raw);
return data; return data;
@ -96,7 +96,7 @@ pub fn resumable(self: *Self) bool {
self.sequence.load(.monotonic) > 0; self.sequence.load(.monotonic) > 0;
} }
pub fn resume_(self: *Self) !void { pub fn resume_(self: *Self) SendError!void {
const data = .{ .op = @intFromEnum(Opcode.Resume), .d = .{ const data = .{ .op = @intFromEnum(Opcode.Resume), .d = .{
.token = self.details.token, .token = self.details.token,
.session_id = self.session_id, .session_id = self.session_id,
@ -111,7 +111,7 @@ inline fn gatewayUrl(self: ?*Self) []const u8 {
} }
/// identifies in order to connect to Discord and get the online status, this shall be done on hello perhaps /// identifies in order to connect to Discord and get the online status, this shall be done on hello perhaps
fn identify(self: *Self, properties: ?IdentifyProperties) !void { pub fn identify(self: *Self, properties: ?IdentifyProperties) SendError!void {
self.logif("intents: {d}", .{self.details.intents.toRaw()}); self.logif("intents: {d}", .{self.details.intents.toRaw()});
if (self.details.intents.toRaw() != 0) { if (self.details.intents.toRaw() != 0) {
@ -205,6 +205,8 @@ pub fn deinit(self: *Self) void {
self.logif("killing the whole bot", .{}); self.logif("killing the whole bot", .{});
} }
const ReadMessageError = mem.Allocator.Error || zlib.Error || json.ParseError(json.Scanner) || json.ParseFromValueError;
/// listens for messages /// listens for messages
fn readMessage(self: *Self, _: anytype) !void { fn readMessage(self: *Self, _: anytype) !void {
try self.client.readTimeout(0); try self.client.readTimeout(0);
@ -313,7 +315,9 @@ fn readMessage(self: *Self, _: anytype) !void {
} }
} }
pub fn heartbeat(self: *Self, initial_jitter: f64) !void { pub const SendHeartbeatError = CloseError || SendError;
pub fn heartbeat(self: *Self, initial_jitter: f64) SendHeartbeatError!void {
var jitter = initial_jitter; var jitter = initial_jitter;
while (true) { while (true) {
@ -341,7 +345,7 @@ pub fn heartbeat(self: *Self, initial_jitter: f64) !void {
self.ws_mutex.unlock(); self.ws_mutex.unlock();
if ((std.time.milliTimestamp() - last) > (5000 * self.heart.heartbeatInterval)) { if ((std.time.milliTimestamp() - last) > (5000 * self.heart.heartbeatInterval)) {
self.close(ShardSocketCloseCodes.ZombiedConnection, "Zombied connection") catch unreachable; try self.close(ShardSocketCloseCodes.ZombiedConnection, "Zombied connection");
@panic("zombied conn\n"); @panic("zombied conn\n");
} }
@ -349,13 +353,18 @@ pub fn heartbeat(self: *Self, initial_jitter: f64) !void {
} }
} }
pub inline fn reconnect(self: *Self) !void { pub const ReconnectError = ConnectError || CloseError;
pub fn reconnect(self: *Self) ReconnectError!void {
try self.disconnect(); try self.disconnect();
try self.connect(); try self.connect();
} }
pub const ConnectError = pub const ConnectError =
std.net.TcpConnectToAddressError || std.crypto.tls.Client.InitError(std.net.Stream) || std.net.Stream.ReadError || std.net.IPParseError || std.crypto.Certificate.Bundle.RescanError || std.net.TcpConnectToHostError || std.fmt.BufPrintError || mem.Allocator.Error; net.TcpConnectToAddressError || crypto.tls.Client.InitError(net.Stream) ||
net.Stream.ReadError || net.IPParseError ||
crypto.Certificate.Bundle.RescanError || net.TcpConnectToHostError ||
std.fmt.BufPrintError || mem.Allocator.Error;
pub fn connect(self: *Self) ConnectError!void { pub fn connect(self: *Self) ConnectError!void {
//std.time.sleep(std.time.ms_per_s * 5); //std.time.sleep(std.time.ms_per_s * 5);
@ -366,7 +375,7 @@ pub fn connect(self: *Self) ConnectError!void {
self.readMessage(null) catch unreachable; self.readMessage(null) catch unreachable;
} }
pub fn disconnect(self: *Self) !void { pub fn disconnect(self: *Self) CloseError!void {
try self.close(ShardSocketCloseCodes.Shutdown, "Shard down request"); try self.close(ShardSocketCloseCodes.Shutdown, "Shard down request");
} }
@ -456,7 +465,7 @@ pub fn handleEvent(self: *Self, name: []const u8, payload: []const u8) !void {
else => unreachable, else => unreachable,
}; };
} }
if (self.handler.ready) |event| event(self, ready); if (self.handler.ready) |event| try event(self, ready);
} }
if (std.ascii.eqlIgnoreCase(name, "message_delete")) { if (std.ascii.eqlIgnoreCase(name, "message_delete")) {
@ -470,7 +479,7 @@ pub fn handleEvent(self: *Self, name: []const u8, payload: []const u8) !void {
.guild_id = try Shared.Snowflake.fromMaybe(obj.getT(.string, "guild_id")), .guild_id = try Shared.Snowflake.fromMaybe(obj.getT(.string, "guild_id")),
}; };
if (self.handler.message_delete) |event| event(self, data); if (self.handler.message_delete) |event| try event(self, data);
} }
if (std.ascii.eqlIgnoreCase(name, "message_delete_bulk")) { if (std.ascii.eqlIgnoreCase(name, "message_delete_bulk")) {
@ -491,7 +500,7 @@ pub fn handleEvent(self: *Self, name: []const u8, payload: []const u8) !void {
.guild_id = try Shared.Snowflake.fromMaybe(obj.getT(.string, "guild_id")), .guild_id = try Shared.Snowflake.fromMaybe(obj.getT(.string, "guild_id")),
}; };
if (self.handler.message_delete_bulk) |event| event(self, data); if (self.handler.message_delete_bulk) |event| try event(self, data);
} }
if (std.ascii.eqlIgnoreCase(name, "message_update")) { if (std.ascii.eqlIgnoreCase(name, "message_update")) {
@ -502,7 +511,7 @@ pub fn handleEvent(self: *Self, name: []const u8, payload: []const u8) !void {
const message = try Parser.parseMessage(self.allocator, obj); const message = try Parser.parseMessage(self.allocator, obj);
//defer if (message.referenced_message) |mptr| self.allocator.destroy(mptr); //defer if (message.referenced_message) |mptr| self.allocator.destroy(mptr);
if (self.handler.message_update) |event| event(self, message); if (self.handler.message_update) |event| try event(self, message);
} }
if (std.ascii.eqlIgnoreCase(name, "message_create")) { if (std.ascii.eqlIgnoreCase(name, "message_create")) {
@ -515,9 +524,9 @@ pub fn handleEvent(self: *Self, name: []const u8, payload: []const u8) !void {
//defer if (message.referenced_message) |mptr| self.allocator.destroy(mptr); //defer if (message.referenced_message) |mptr| self.allocator.destroy(mptr);
self.logif("it worked {s} {?s}", .{ name, message.content }); self.logif("it worked {s} {?s}", .{ name, message.content });
if (self.handler.message_create) |event| event(self, message); if (self.handler.message_create) |event| try event(self, message);
} else { } else {
if (self.handler.any) |anyEvent| anyEvent(self, payload); if (self.handler.any) |anyEvent| try anyEvent(self, payload);
} }
} }

View File

@ -15,8 +15,8 @@ const debug = Internal.debug;
pub const discord_epoch = 1420070400000; pub const discord_epoch = 1420070400000;
/// Calculate and return the shard ID for a given guild ID /// Calculate and return the shard ID for a given guild ID
pub inline fn calculateShardId(guildId: u64, shards: ?usize) u64 { pub inline fn calculateShardId(guild_id: u64, shards: ?usize) u64 {
return (guildId >> 22) % shards orelse 1; return (guild_id >> 22) % shards orelse 1;
} }
/// Convert a timestamp to a snowflake. /// Convert a timestamp to a snowflake.
@ -106,11 +106,11 @@ pub fn forceIdentify(self: *Self, shard_id: usize) !void {
return shard.identify(null); return shard.identify(null);
} }
pub fn disconnect(self: *Self, shard_id: usize) !void { pub fn disconnect(self: *Self, shard_id: usize) Shard.CloseError!void {
return if (self.shards.get(shard_id)) |shard| shard.disconnect(); return if (self.shards.get(shard_id)) |shard| shard.disconnect();
} }
pub fn disconnectAll(self: *Self) !void { pub fn disconnectAll(self: *Self) Shard.CloseError!void {
while (self.shards.iterator().next()) |shard| shard.value_ptr.disconnect(); while (self.shards.iterator().next()) |shard| shard.value_ptr.disconnect();
} }
@ -199,10 +199,8 @@ pub fn spawnShards(self: *Self) !void {
//self.startResharder(); //self.startResharder();
} }
pub fn send(self: *Self, shard_id: usize, data: anytype) !void { pub fn send(self: *Self, shard_id: usize, data: anytype) Shard.SendError!void {
if (self.shards.get(shard_id)) |shard| { if (self.shards.get(shard_id)) |shard| try shard.send(data);
try shard.send(data);
}
} }
// SPEC OF THE RESHARDER: // SPEC OF THE RESHARDER:

View File

@ -70,7 +70,7 @@ pub const ShardDetails = struct {
pub const Snowflake = struct { pub const Snowflake = struct {
id: u64, id: u64,
pub fn fromMaybe(raw: ?[]const u8) !?Snowflake { pub fn fromMaybe(raw: ?[]const u8) std.fmt.ParseIntError!?Snowflake {
if (raw) |id| { if (raw) |id| {
return .{ return .{
.id = try std.fmt.parseInt(u64, id, 10), .id = try std.fmt.parseInt(u64, id, 10),
@ -78,7 +78,7 @@ pub const Snowflake = struct {
} else return null; } else return null;
} }
pub fn fromRaw(raw: []const u8) !Snowflake { pub fn fromRaw(raw: []const u8) std.fmt.ParseIntError!Snowflake {
return .{ return .{
.id = try std.fmt.parseInt(u64, raw, 10), .id = try std.fmt.parseInt(u64, raw, 10),
}; };

View File

@ -1,17 +1,18 @@
const Client = @import("discord.zig").Client; const Discord = @import("discord.zig");
const Shard = @import("discord.zig").Shard;
const Discord = @import("discord.zig").Discord; const Shard = Discord.Shard;
const Internal = @import("discord.zig").Internal; const Internal = Discord.Internal;
const FetchReq = @import("discord.zig").FetchReq; const FetchReq = Discord.FetchReq;
const Intents = Discord.Intents; const Intents = Discord.Intents;
const Thread = std.Thread; const Thread = std.Thread;
const std = @import("std"); const std = @import("std");
const fmt = std.fmt;
fn ready(_: *Shard, payload: Discord.Ready) void { fn ready(_: *Shard, payload: Discord.Ready) !void {
std.debug.print("logged in as {s}\n", .{payload.user.username}); std.debug.print("logged in as {s}\n", .{payload.user.username});
} }
fn message_create(session: *Shard, message: Discord.Message) void { fn message_create(session: *Shard, message: Discord.Message) fmt.AllocPrintError!void {
std.debug.print("captured: {?s} send by {s}\n", .{ message.content, message.author.username }); std.debug.print("captured: {?s} send by {s}\n", .{ message.content, message.author.username });
if (message.content) |mc| if (std.ascii.eqlIgnoreCase(mc, "!hi")) { if (message.content) |mc| if (std.ascii.eqlIgnoreCase(mc, "!hi")) {
@ -21,7 +22,7 @@ fn message_create(session: *Shard, message: Discord.Message) void {
const payload: Discord.Partial(Discord.CreateMessage) = .{ .content = "Hi, I'm hang man, your personal assistant" }; const payload: Discord.Partial(Discord.CreateMessage) = .{ .content = "Hi, I'm hang man, your personal assistant" };
const json = std.json.stringifyAlloc(session.allocator, payload, .{}) catch unreachable; const json = std.json.stringifyAlloc(session.allocator, payload, .{}) catch unreachable;
defer session.allocator.free(json); defer session.allocator.free(json);
const path = std.fmt.allocPrint(session.allocator, "/channels/{d}/messages", .{message.channel_id.value()}) catch unreachable; const path = try fmt.allocPrint(session.allocator, "/channels/{d}/messages", .{message.channel_id.value()});
_ = req.makeRequest(.POST, path, json) catch unreachable; _ = req.makeRequest(.POST, path, json) catch unreachable;
}; };
@ -30,7 +31,7 @@ fn message_create(session: *Shard, message: Discord.Message) void {
pub fn main() !void { pub fn main() !void {
var tsa = std.heap.ThreadSafeAllocator{ .child_allocator = std.heap.c_allocator }; var tsa = std.heap.ThreadSafeAllocator{ .child_allocator = std.heap.c_allocator };
var handler = Client.init(tsa.allocator()); var handler = Discord.init(tsa.allocator());
try handler.start(.{ try handler.start(.{
.token = std.posix.getenv("TOKEN") orelse unreachable, .token = std.posix.getenv("TOKEN") orelse unreachable,
.intents = Intents.fromRaw(37379), .intents = Intents.fromRaw(37379),