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.
* High Performance: Faster than whichever library you can name (WIP)
* Flexible Payload Parsing: Supports payload parsing through both zlib and zstd*.
* Proper error handling
```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 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});
}
fn message_create(_: *Shard, message: Discord.Message) void {
std.debug.print("captured: {?s}\n", .{ message.content });
fn message_create(session: *Shard, message: Discord.Message) !void {
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 {
var handler = Client.init(allocator);
var gpa = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = 9999 }){};
var handler = Discord.init(gpa.allocator());
try handler.start(.{
.token = std.posix.getenv("TOKEN") orelse unreachable,
.intents = Intents.fromRaw(37379),
.token = std.posix.getenv("TOKEN").?, // or your token
.intents = Intents.fromRaw(53608447), // all intents
.run = .{ .message_create = &message_create, .ready = &ready },
.log = .yes,
.options = .{},
});
errdefer handler.deinit();
}
```
## Installation
```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 | ✅ |
| finish the event coverage roadmap | ✅ |
| proper error handling | ✅ |
| use the priority queues for handling ratelimits (half done) | ❌ |
| make the library scalable with a gateway proxy | ❌ |
| get a cool logo | ❌ |

View File

@ -32,7 +32,7 @@ pub fn build(b: *std.Build) void {
const marin = b.addExecutable(.{
.name = "marin",
.root_source_file = b.path("src/test.zig"),
.root_source_file = b.path("test/test.zig"),
.target = target,
.optimize = optimize,
.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 json = std.json;
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";
@ -95,22 +97,24 @@ pub const FetchReq = struct {
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);
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;
}
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);
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 fba = std.heap.FixedBufferAllocator.init(&buf);
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());
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 {
@ -135,10 +139,12 @@ pub const FetchReq = struct {
const result = try self.makeRequest(.PATCH, path, try string.toOwnedSlice());
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 fba = std.heap.FixedBufferAllocator.init(&buf);
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());
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 fba = std.heap.FixedBufferAllocator.init(&buf);
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());
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);
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);
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 fba = std.heap.FixedBufferAllocator.init(&buf);
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());
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 fba = std.heap.FixedBufferAllocator.init(&buf);
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());
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);
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(
@ -227,7 +237,7 @@ pub const FetchReq = struct {
path: []const u8,
object: anytype,
files: []const FileData,
) !zjson.Owned(T) {
) !Result(T) {
var buf: [4096]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buf);
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);
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 fba = std.heap.FixedBufferAllocator.init(&buf);
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());
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);
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(

View File

@ -76,10 +76,23 @@ pub fn ParseResult(comptime T: type) type {
}
/// 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) {
left: T,
right: U,
left: L,
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 {
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 `{...}`
/// casts the value into `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;
}
/// 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
pub fn Record(comptime T: type) type {
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
//! PERFORMANCE OF THIS SOFTWARE.
const std = @import("std");
const Discord = @import("discord.zig");
const Shard = Discord.Shard;
const Internal = Discord.Internal;
const FetchReq = Discord.FetchReq;
const Intents = Discord.Intents;
const Thread = std.Thread;
const std = @import("std");
const fmt = std.fmt;
const INTENTS = 53608447;
@ -30,13 +26,14 @@ fn ready(_: *Shard, payload: Discord.Ready) !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")) {
const msg = try session.sendMessage(message.channel_id, .{
.content = "discord.zig best library",
});
defer msg.deinit();
var result = try session.sendMessage(message.channel_id, .{ .content = "hi :)" });
defer result.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 }),
}
};
}