add proper errors
This commit is contained in:
parent
aa720e7f30
commit
bd07a4d053
31
README.md
31
README.md
@ -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 | ❌ |
|
||||
|
@ -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
17
src/errors.zig
Normal 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);
|
||||
}
|
84
src/http.zig
84
src/http.zig
@ -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(
|
||||
|
73
src/json.zig
73
src/json.zig
@ -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 {
|
||||
|
320
src/shard.zig
320
src/shard.zig
File diff suppressed because it is too large
Load Diff
@ -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 }),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user