add proper errors

This commit is contained in:
Yuzu 2024-12-12 00:31:55 -05:00
parent aa720e7f30
commit bd07a4d053
7 changed files with 326 additions and 220 deletions

View File

@ -5,34 +5,42 @@ A high-performance bleeding edge Discord library in Zig, featuring full API cove
* 100% API Coverage & Fully Typed: Offers complete access to Discord's API with strict typing for reliable and safe code. * 100% API Coverage & Fully Typed: Offers complete access to Discord's API with strict typing for reliable and safe code.
* High Performance: Faster than whichever library you can name (WIP) * High Performance: Faster than whichever library you can name (WIP)
* Flexible Payload Parsing: Supports payload parsing through both zlib and zstd*. * Flexible Payload Parsing: Supports payload parsing through both zlib and zstd*.
* Proper error handling
```zig ```zig
const Client = @import("discord.zig").Client;
const Shard = @import("discord.zig").Shard;
const Discord = @import("discord.zig");
const Intents = Discord.Intents;
const std = @import("std"); const std = @import("std");
const Discord = @import("discord.zig");
const Shard = Discord.Shard;
const Intents = Discord.Intents;
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(_: *Shard, message: Discord.Message) void { fn message_create(session: *Shard, message: Discord.Message) !void {
std.debug.print("captured: {?s}\n", .{ message.content }); if (message.content) |mc| if (std.ascii.eqlIgnoreCase(mc, "!hi")) {
var result = try session.sendMessage(message.channel_id, .{ .content = "discord.zig best lib" });
defer result.deinit();
switch (result.value) {
.left => |e| std.debug.panic("Error: {d}\r{s}\n", .{ e.code, e.message }), // or you may tell the end user the error
.right => |m| std.debug.print("Sent: {?s} sent by {s}\n", .{ m.content, m.author.username }),
}
};
} }
pub fn main() !void { pub fn main() !void {
var handler = Client.init(allocator); var gpa = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 9999 }){};
var handler = Discord.init(gpa.allocator());
try handler.start(.{ try handler.start(.{
.token = std.posix.getenv("TOKEN") orelse unreachable, .token = std.posix.getenv("TOKEN").?, // or your token
.intents = Intents.fromRaw(37379), .intents = Intents.fromRaw(53608447), // all intents
.run = .{ .message_create = &message_create, .ready = &ready }, .run = .{ .message_create = &message_create, .ready = &ready },
.log = .yes, .log = .yes,
.options = .{}, .options = .{},
}); });
errdefer handler.deinit(); errdefer handler.deinit();
} }
``` ```
## Installation ## Installation
```zig ```zig
@ -60,6 +68,7 @@ Contributions are welcome! Please open an issue or pull request if you'd like to
|-------------------------------------------------------------|--------| |-------------------------------------------------------------|--------|
| stablish good sharding support with buckets | ✅ | | stablish good sharding support with buckets | ✅ |
| finish the event coverage roadmap | ✅ | | finish the event coverage roadmap | ✅ |
| proper error handling | ✅ |
| use the priority queues for handling ratelimits (half done) | ❌ | | use the priority queues for handling ratelimits (half done) | ❌ |
| make the library scalable with a gateway proxy | ❌ | | make the library scalable with a gateway proxy | ❌ |
| get a cool logo | ❌ | | get a cool logo | ❌ |

View File

@ -32,7 +32,7 @@ pub fn build(b: *std.Build) void {
const marin = b.addExecutable(.{ const marin = b.addExecutable(.{
.name = "marin", .name = "marin",
.root_source_file = b.path("src/test.zig"), .root_source_file = b.path("test/test.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.link_libc = true, .link_libc = true,

17
src/errors.zig Normal file
View File

@ -0,0 +1,17 @@
/// an error ought to be matched by `code` for providing the end user with sensible errors
pub const DiscordErrorPayload = struct {
/// cryptic error code, eg: `MISSING_PERMISSIONS`
code: []const u8,
/// human readable error message
message: []const u8,
};
pub const DiscordError = struct {
code: usize,
message: []const u8,
errors: ?struct { _errors: []DiscordErrorPayload },
};
pub fn Result(comptime T: type) type {
return @import("json.zig").OwnedEither(DiscordError, T);
}

View File

@ -19,6 +19,8 @@ const mem = std.mem;
const http = std.http; const http = std.http;
const json = std.json; const json = std.json;
const zjson = @import("json.zig"); const zjson = @import("json.zig");
pub const Result = @import("errors.zig").Result;
pub const DiscordError = @import("errors.zig").DiscordError;
pub const BASE_URL = "https://discord.com/api/v10"; pub const BASE_URL = "https://discord.com/api/v10";
@ -95,22 +97,24 @@ pub const FetchReq = struct {
return query.toOwnedSlice(self.allocator); return query.toOwnedSlice(self.allocator);
} }
pub fn get(self: *FetchReq, comptime T: type, path: []const u8) !zjson.Owned(T) { pub fn get(self: *FetchReq, comptime T: type, path: []const u8) !Result(T) {
const result = try self.makeRequest(.GET, path, null); const result = try self.makeRequest(.GET, path, null);
if (result.status != .ok) if (result.status != .ok)
return error.FailedRequest; return try zjson.parseLeft(DiscordError, T, self.allocator, try self.body.toOwnedSlice());
const output = try zjson.parse(T, self.allocator, try self.body.toOwnedSlice()); const output = try zjson.parseRight(DiscordError, T, self.allocator, try self.body.toOwnedSlice());
return output; return output;
} }
pub fn delete(self: *FetchReq, path: []const u8) !void { pub fn delete(self: *FetchReq, path: []const u8) !Result(void) {
const result = try self.makeRequest(.DELETE, path, null); const result = try self.makeRequest(.DELETE, path, null);
if (result.status != .no_content) if (result.status != .no_content)
return error.FailedRequest; return try zjson.tryParse(DiscordError, void, self.allocator, try self.body.toOwnedSlice());
return .ok({});
} }
pub fn patch(self: *FetchReq, comptime T: type, path: []const u8, object: anytype) !zjson.Owned(T) { pub fn patch(self: *FetchReq, comptime T: type, path: []const u8, object: anytype) !Result(T) {
var buf: [4096]u8 = undefined; var buf: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf); var fba = std.heap.FixedBufferAllocator.init(&buf);
var string = std.ArrayList(u8).init(fba.allocator()); var string = std.ArrayList(u8).init(fba.allocator());
@ -120,9 +124,9 @@ pub const FetchReq = struct {
const result = try self.makeRequest(.PATCH, path, try string.toOwnedSlice()); const result = try self.makeRequest(.PATCH, path, try string.toOwnedSlice());
if (result.status != .ok) if (result.status != .ok)
return error.FailedRequest; return try zjson.parseLeft(DiscordError, T, self.allocator, try self.body.toOwnedSlice());
return try zjson.parse(T, self.allocator, try self.body.toOwnedSlice()); return try zjson.parseRight(DiscordError, T, self.allocator, try self.body.toOwnedSlice());
} }
pub fn patch2(self: *FetchReq, path: []const u8, object: anytype) !void { pub fn patch2(self: *FetchReq, path: []const u8, object: anytype) !void {
@ -135,10 +139,12 @@ pub const FetchReq = struct {
const result = try self.makeRequest(.PATCH, path, try string.toOwnedSlice()); const result = try self.makeRequest(.PATCH, path, try string.toOwnedSlice());
if (result.status != .no_content) if (result.status != .no_content)
return error.FailedRequest; return try zjson.parseLeft(DiscordError, void, self.allocator, try self.body.toOwnedSlice());
return .ok({});
} }
pub fn put(self: *FetchReq, comptime T: type, path: []const u8, object: anytype) !zjson.Owned(T) { pub fn put(self: *FetchReq, comptime T: type, path: []const u8, object: anytype) !Result(T) {
var buf: [4096]u8 = undefined; var buf: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf); var fba = std.heap.FixedBufferAllocator.init(&buf);
var string = std.ArrayList(u8).init(fba.allocator()); var string = std.ArrayList(u8).init(fba.allocator());
@ -148,12 +154,12 @@ pub const FetchReq = struct {
const result = try self.makeRequest(.PUT, path, try string.toOwnedSlice()); const result = try self.makeRequest(.PUT, path, try string.toOwnedSlice());
if (result.status != .ok) if (result.status != .ok)
return error.FailedRequest; return try zjson.parseLeft(DiscordError, T, self.allocator, try self.body.toOwnedSlice());
return try zjson.parse(T, self.allocator, try self.body.toOwnedSlice()); return try zjson.parseRight(DiscordError, T, self.allocator, try self.body.toOwnedSlice());
} }
pub fn put2(self: *FetchReq, comptime T: type, path: []const u8, object: anytype) !?zjson.Owned(T) { pub fn put2(self: *FetchReq, comptime T: type, path: []const u8, object: anytype) !Result(T) {
var buf: [4096]u8 = undefined; var buf: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf); var fba = std.heap.FixedBufferAllocator.init(&buf);
var string = std.ArrayList(u8).init(fba.allocator()); var string = std.ArrayList(u8).init(fba.allocator());
@ -163,28 +169,30 @@ pub const FetchReq = struct {
const result = try self.makeRequest(.PUT, path, try string.toOwnedSlice()); const result = try self.makeRequest(.PUT, path, try string.toOwnedSlice());
if (result.status == .no_content) if (result.status == .no_content)
return null; return try zjson.parseLeft(DiscordError, T, self.allocator, try self.body.toOwnedSlice());
return try zjson.parse(T, self.allocator, try self.body.toOwnedSlice()); return try zjson.parseRight(DiscordError, T, self.allocator, try self.body.toOwnedSlice());
} }
pub fn put3(self: *FetchReq, path: []const u8) !void { pub fn put3(self: *FetchReq, path: []const u8) !Result(void) {
const result = try self.makeRequest(.PUT, path, null); const result = try self.makeRequest(.PUT, path, null);
if (result.status != .no_content) if (result.status != .no_content)
return error.FailedRequest; return try zjson.parseLeft(DiscordError, void, self.allocator, try self.body.toOwnedSlice());
return .ok({});
} }
pub fn put4(self: *FetchReq, comptime T: type, path: []const u8) !zjson.Owned(T) { pub fn put4(self: *FetchReq, comptime T: type, path: []const u8) !Result(T) {
const result = try self.makeRequest(.PUT, path, null); const result = try self.makeRequest(.PUT, path, null);
if (result.status != .ok) if (result.status != .ok)
return error.FailedRequest; return try zjson.parseLeft(DiscordError, T, self.allocator, try self.body.toOwnedSlice());
return try zjson.parse(T, self.allocator, try self.body.toOwnedSlice()); return try zjson.parseRight(DiscordError, T, self.allocator, try self.body.toOwnedSlice());
} }
pub fn put5(self: *FetchReq, path: []const u8, object: anytype) !void { pub fn put5(self: *FetchReq, path: []const u8, object: anytype) !Result(void) {
var buf: [4096]u8 = undefined; var buf: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf); var fba = std.heap.FixedBufferAllocator.init(&buf);
var string = std.ArrayList(u8).init(fba.allocator()); var string = std.ArrayList(u8).init(fba.allocator());
@ -194,10 +202,12 @@ pub const FetchReq = struct {
const result = try self.makeRequest(.PUT, path, try self.body.toOwnedSlice()); const result = try self.makeRequest(.PUT, path, try self.body.toOwnedSlice());
if (result.status != .no_content) if (result.status != .no_content)
return error.FailedRequest; return try zjson.parseLeft(DiscordError, void, self.allocator, try self.body.toOwnedSlice());
return .ok({});
} }
pub fn post(self: *FetchReq, comptime T: type, path: []const u8, object: anytype) !zjson.Owned(T) { pub fn post(self: *FetchReq, comptime T: type, path: []const u8, object: anytype) !Result(T) {
var buf: [4096]u8 = undefined; var buf: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf); var fba = std.heap.FixedBufferAllocator.init(&buf);
var string = std.ArrayList(u8).init(fba.allocator()); var string = std.ArrayList(u8).init(fba.allocator());
@ -207,18 +217,18 @@ pub const FetchReq = struct {
const result = try self.makeRequest(.POST, path, try string.toOwnedSlice()); const result = try self.makeRequest(.POST, path, try string.toOwnedSlice());
if (result.status != .ok) if (result.status != .ok)
return error.FailedRequest; return try zjson.parseLeft(DiscordError, T, self.allocator, try self.body.toOwnedSlice());
return try zjson.parse(T, self.allocator, try self.body.toOwnedSlice()); return try zjson.parseRight(DiscordError, T, self.allocator, try self.body.toOwnedSlice());
} }
pub fn post2(self: *FetchReq, comptime T: type, path: []const u8) !zjson.Owned(T) { pub fn post2(self: *FetchReq, comptime T: type, path: []const u8) !Result(T) {
const result = try self.makeRequest(.POST, path, null); const result = try self.makeRequest(.POST, path, null);
if (result.status != .ok) if (result.status != .ok)
return error.FailedRequest; return try zjson.parseLeft(DiscordError, T, self.allocator, try self.body.toOwnedSlice());
return try zjson.parse(T, self.allocator, try self.body.toOwnedSlice()); return try zjson.parseRight(DiscordError, T, self.allocator, try self.body.toOwnedSlice());
} }
pub fn post3( pub fn post3(
@ -227,7 +237,7 @@ pub const FetchReq = struct {
path: []const u8, path: []const u8,
object: anytype, object: anytype,
files: []const FileData, files: []const FileData,
) !zjson.Owned(T) { ) !Result(T) {
var buf: [4096]u8 = undefined; var buf: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf); var fba = std.heap.FixedBufferAllocator.init(&buf);
var string = std.ArrayList(u8).init(fba.allocator()); var string = std.ArrayList(u8).init(fba.allocator());
@ -237,12 +247,12 @@ pub const FetchReq = struct {
const result = try self.makeRequestWithFiles(.POST, path, try string.toOwnedSlice(), files); const result = try self.makeRequestWithFiles(.POST, path, try string.toOwnedSlice(), files);
if (result.status != .ok) if (result.status != .ok)
return error.FailedRequest; return try zjson.parseLeft(DiscordError, T, self.allocator, try self.body.toOwnedSlice());
return try zjson.parse(T, self.allocator, try self.body.toOwnedSlice()); return try zjson.parseRight(DiscordError, T, self.allocator, try self.body.toOwnedSlice());
} }
pub fn post4(self: *FetchReq, path: []const u8, object: anytype) !void { pub fn post4(self: *FetchReq, path: []const u8, object: anytype) !Result(void) {
var buf: [4096]u8 = undefined; var buf: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf); var fba = std.heap.FixedBufferAllocator.init(&buf);
var string = std.ArrayList(u8).init(fba.allocator()); var string = std.ArrayList(u8).init(fba.allocator());
@ -252,14 +262,18 @@ pub const FetchReq = struct {
const result = try self.makeRequest(.POST, path, try string.toOwnedSlice()); const result = try self.makeRequest(.POST, path, try string.toOwnedSlice());
if (result.status != .no_content) if (result.status != .no_content)
return error.FailedRequest; return try zjson.parseLeft(DiscordError, void, self.allocator, try self.body.toOwnedSlice());
return .ok({});
} }
pub fn post5(self: *FetchReq, path: []const u8) !void { pub fn post5(self: *FetchReq, path: []const u8) !Result(void) {
const result = try self.makeRequest(.POST, path, null); const result = try self.makeRequest(.POST, path, null);
if (result.status != .no_content) if (result.status != .no_content)
return error.FailedRequest; return try zjson.parseLeft(DiscordError, void, self.allocator, try self.body.toOwnedSlice());
return .ok({});
} }
pub fn makeRequest( pub fn makeRequest(

View File

@ -76,10 +76,23 @@ pub fn ParseResult(comptime T: type) type {
} }
/// Either a b = Left a | Right b /// Either a b = Left a | Right b
pub fn Either(comptime T: type, comptime U: type) type { pub fn Either(comptime L: type, comptime R: type) type {
return union(enum) { return union(enum) {
left: T, left: L,
right: U, right: R,
/// always returns .right
pub fn unwrap(self: @This()) R {
// discord.zig specifics
if (@hasField(L, "code") and @hasField(L, "message") and self.value == .left)
std.debug.panic("Error: {d}, {s}\n", .{ self.value.left.code, self.value.left.message });
// for other libraries, it'll do this
if (self.value == .left)
std.debug.panic("Error: {any}\n", .{self.value.left});
return self.value.right;
}
pub fn is(self: @This(), tag: std.meta.Tag(@This())) bool { pub fn is(self: @This(), tag: std.meta.Tag(@This())) bool {
return self == tag; return self == tag;
@ -1141,6 +1154,30 @@ pub fn Owned(comptime T: type) type {
}; };
} }
/// same as `Owned` but instead it handles 2 different values, generally `.right` is the correct one and `left` the error type
pub fn OwnedEither(comptime L: type, comptime R: type) type {
// if (@typeInfo(Struct) != .@"struct") @compileError("expected a `struct` type");
return struct {
value: Either(L, R),
arena: *std.heap.ArenaAllocator,
pub fn ok(ok_value: R) @This() {
return .{ .value = ok_value };
}
pub fn err(err_value: L) @This() {
return .{ .value = err_value };
}
pub fn deinit(self: @This()) void {
const allocator = self.arena.child_allocator;
self.arena.deinit();
allocator.destroy(self.arena);
}
};
}
/// parse any string containing a JSON object root `{...}` /// parse any string containing a JSON object root `{...}`
/// casts the value into `T` /// casts the value into `T`
pub fn parse(comptime T: type, child_allocator: mem.Allocator, data: []const u8) ParserError!Owned(T) { pub fn parse(comptime T: type, child_allocator: mem.Allocator, data: []const u8) ParserError!Owned(T) {
@ -1157,6 +1194,36 @@ pub fn parse(comptime T: type, child_allocator: mem.Allocator, data: []const u8)
return owned; return owned;
} }
/// same as `parse`
pub fn parseLeft(comptime L: type, comptime R: type, child_allocator: mem.Allocator, data: []const u8) ParserError!OwnedEither(L, R) {
var owned: OwnedEither(L, R) = .{
.arena = try child_allocator.create(std.heap.ArenaAllocator),
.value = undefined,
};
owned.arena.* = std.heap.ArenaAllocator.init(child_allocator);
const allocator = owned.arena.allocator();
const value = try ultimateParserAssert(data, allocator);
owned.value = .{ .left = try parseInto(L, allocator, value) };
errdefer owned.arena.deinit();
return owned;
}
/// same as `parse`
pub fn parseRight(comptime L: type, comptime R: type, child_allocator: mem.Allocator, data: []const u8) ParserError!OwnedEither(L, R) {
var owned: OwnedEither(L, R) = .{
.arena = try child_allocator.create(std.heap.ArenaAllocator),
.value = undefined,
};
owned.arena.* = std.heap.ArenaAllocator.init(child_allocator);
const allocator = owned.arena.allocator();
const value = try ultimateParserAssert(data, allocator);
owned.value = .{ .right = try parseInto(R, allocator, value) };
errdefer owned.arena.deinit();
return owned;
}
/// a hashmap for key value pairs /// a hashmap for key value pairs
pub fn Record(comptime T: type) type { pub fn Record(comptime T: type) type {
return struct { return struct {

File diff suppressed because it is too large Load Diff

View File

@ -14,14 +14,10 @@
//! OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR //! OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
//! PERFORMANCE OF THIS SOFTWARE. //! PERFORMANCE OF THIS SOFTWARE.
const std = @import("std");
const Discord = @import("discord.zig"); const Discord = @import("discord.zig");
const Shard = Discord.Shard; const Shard = Discord.Shard;
const Internal = Discord.Internal;
const FetchReq = Discord.FetchReq;
const Intents = Discord.Intents; const Intents = Discord.Intents;
const Thread = std.Thread;
const std = @import("std");
const fmt = std.fmt;
const INTENTS = 53608447; const INTENTS = 53608447;
@ -30,13 +26,14 @@ fn ready(_: *Shard, payload: Discord.Ready) !void {
} }
fn message_create(session: *Shard, message: Discord.Message) !void { fn message_create(session: *Shard, message: Discord.Message) !void {
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")) {
const msg = try session.sendMessage(message.channel_id, .{ var result = try session.sendMessage(message.channel_id, .{ .content = "hi :)" });
.content = "discord.zig best library", defer result.deinit();
});
defer msg.deinit(); switch (result.value) {
.left => |e| std.debug.panic("Error: {d}\r{s}\n", .{ e.code, e.message }),
.right => |m| std.debug.print("Sent: {?s} sent by {s}\n", .{ m.content, m.author.username }),
}
}; };
} }