268 lines
8.9 KiB
Zig
268 lines
8.9 KiB
Zig
//! ISC License
|
|
//!
|
|
//! Copyright (c) 2024-2025 Yuzu
|
|
//!
|
|
//! Permission to use, copy, modify, and/or distribute this software for any
|
|
//! purpose with or without fee is hereby granted, provided that the above
|
|
//! copyright notice and this permission notice appear in all copies.
|
|
//!
|
|
//! THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
//! REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
//! AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
//! INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
//! LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
//! OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
//! PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
const std = @import("std");
|
|
const json = std.json;
|
|
|
|
/// a hashmap for key value pairs
|
|
/// where every key is an int
|
|
///
|
|
/// an example would be this
|
|
///
|
|
/// {
|
|
/// ...
|
|
/// "integration_types_config": {
|
|
/// "0": ...
|
|
/// "1": {
|
|
/// "oauth2_install_params": {
|
|
/// "scopes": ["applications.commands"],
|
|
/// "permissions": "0"
|
|
/// }
|
|
/// }
|
|
/// },
|
|
/// ...
|
|
/// }
|
|
/// this would help us map an enum member 0, 1, etc of type E into V
|
|
/// very useful stuff
|
|
/// internally, an EnumMap
|
|
pub fn AssociativeArray(comptime E: type, comptime V: type) type {
|
|
if (@typeInfo(E) != .@"enum")
|
|
@compileError("may only use enums as keys");
|
|
|
|
return struct {
|
|
map: std.EnumMap(E, V),
|
|
pub fn jsonParse(allocator: std.mem.Allocator, src: anytype, _: json.ParseOptions) !@This() {
|
|
var map: std.EnumMap(E, V) = .{};
|
|
|
|
const value = try std.json.innerParse(std.json.Value, allocator, src, .{
|
|
.ignore_unknown_fields = true,
|
|
.max_value_len = 0x100
|
|
});
|
|
|
|
var iterator = value.object.iterator();
|
|
|
|
while (iterator.next()) |it| {
|
|
const k = it.key_ptr.*;
|
|
const v = it.value_ptr.*;
|
|
|
|
// eg: enum(u8) would be @"enum".tag_type where tag_type is a u8
|
|
const int = std.fmt.parseInt(@typeInfo(E).@"enum".tag_type, k, 10) catch unreachable;
|
|
|
|
const val = try std.json.parseFromValueLeaky(V, allocator, v, .{
|
|
.ignore_unknown_fields = true,
|
|
.max_value_len = 0x100,
|
|
});
|
|
|
|
map.put(@enumFromInt(int), val);
|
|
}
|
|
|
|
return .{.map = map};
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
/// assumes object.value[key] is of type `E` and the result thereof maps to the tagged union value
|
|
/// assumes `key` is a field present in all members of type `U`
|
|
/// eg: `type` is a property of type E and E.User maps to a User struct, whereas E.Admin maps to an Admin struct
|
|
pub fn DiscriminatedUnion(comptime U: type, comptime key: []const u8) type {
|
|
if (@typeInfo(U) != .@"union")
|
|
@compileError("may only cast a union");
|
|
|
|
if (@typeInfo(U).@"union".tag_type == null)
|
|
@compileError("cannot cast untagged union");
|
|
|
|
const E = comptime @typeInfo(U).@"union".tag_type.?;
|
|
|
|
if (@typeInfo(U).@"union".tag_type.? != E)
|
|
@compileError("enum tag type of union(" ++ @typeName(U.@"union".tag_type) ++ ") doesn't match " ++ @typeName(E));
|
|
|
|
return struct {
|
|
t: U,
|
|
pub fn jsonParse(allocator: std.mem.Allocator, src: anytype, _: json.ParseOptions) !@This() {
|
|
// extract next value, which should be an object
|
|
// and should have a key "type" or whichever key might be
|
|
|
|
const value = try std.json.innerParse(std.json.Value, allocator, src, .{
|
|
.ignore_unknown_fields = true,
|
|
.max_value_len = 0x100
|
|
});
|
|
|
|
const discriminator = value.object.get(key) orelse
|
|
@panic("couldn't find property " ++ key ++ "in raw object");
|
|
|
|
var u: U = undefined;
|
|
|
|
const tag: @typeInfo(E).@"enum".tag_type = @intCast(discriminator.integer);
|
|
|
|
inline for (@typeInfo(E).@"enum".fields) |field| {
|
|
if (field.value == tag) {
|
|
const T = comptime std.meta.fields(U)[field.value].type;
|
|
comptime std.debug.assert(@hasField(T, key));
|
|
u = @unionInit(U, field.name, try std.json.innerParse(T, allocator, src, .{
|
|
.ignore_unknown_fields = true,
|
|
.max_value_len = 0x100,
|
|
}));
|
|
}
|
|
}
|
|
|
|
return .{ .t = u };
|
|
}
|
|
};
|
|
}
|
|
|
|
/// a hashmap for key value pairs
|
|
pub fn Record(comptime T: type) type {
|
|
return struct {
|
|
map: std.StringHashMapUnmanaged(T),
|
|
pub fn jsonParse(allocator: std.mem.Allocator, src: anytype, _: json.ParseOptions) !@This() {
|
|
const value = try std.json.innerParse(std.json.Value, allocator, src, .{
|
|
.ignore_unknown_fields = true,
|
|
.max_value_len = 0x100
|
|
});
|
|
|
|
errdefer value.object.deinit();
|
|
var iterator = value.object.iterator();
|
|
|
|
var map: std.StringHashMapUnmanaged(T) = .init;
|
|
|
|
while (iterator.next()) |pair| {
|
|
const k = pair.key_ptr.*;
|
|
const v = pair.value_ptr.*;
|
|
|
|
// might leak because std.json is retarded
|
|
// errdefer allocator.free(k);
|
|
// errdefer v.deinit(allocator);
|
|
try map.put(allocator, k, try std.json.parseFromValue(T, allocator, v, .{
|
|
.ignore_unknown_fields = true,
|
|
.max_value_len = 0x100,
|
|
}));
|
|
}
|
|
|
|
return .{ .map = map };
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Either a b = Left a | Right b
|
|
pub fn Either(comptime L: type, comptime R: type) type {
|
|
return union(enum) {
|
|
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 == .left)
|
|
std.debug.panic("Error: {d}, {s}\n", .{ self.left.code, self.left.message });
|
|
|
|
// for other libraries, it'll do this
|
|
if (self == .left)
|
|
std.debug.panic("Error: {any}\n", .{self.left});
|
|
|
|
return self.right;
|
|
}
|
|
|
|
pub fn is(self: @This(), tag: std.meta.Tag(@This())) bool {
|
|
return self == tag;
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
/// meant to handle a `std.json.Value` and handling the deinitialization thereof
|
|
pub fn Owned(comptime T: type) type {
|
|
return struct {
|
|
arena: *std.heap.ArenaAllocator,
|
|
value: T,
|
|
|
|
pub fn deinit(self: @This()) void {
|
|
const allocator = self.arena.child_allocator;
|
|
self.arena.deinit();
|
|
allocator.destroy(self.arena);
|
|
}
|
|
};
|
|
}
|
|
|
|
/// 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 {
|
|
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);
|
|
}
|
|
};
|
|
}
|
|
|
|
/// same as `std.json.parseFromSlice`
|
|
pub fn parseRight(comptime L: type, comptime R: type, child_allocator: std.mem.Allocator, data: []const u8) json.ParseError(json.Scanner)!OwnedEither(L, R) {
|
|
var owned: OwnedEither(L, R) = .{
|
|
.arena = try child_allocator.create(std.heap.ArenaAllocator),
|
|
.value = undefined,
|
|
};
|
|
owned.arena.* = .init(child_allocator);
|
|
const allocator = owned.arena.allocator();
|
|
const value = try json.parseFromSliceLeaky(json.Value, allocator, data, .{
|
|
.ignore_unknown_fields = true,
|
|
.max_value_len = 0x100,
|
|
});
|
|
|
|
owned.value = .{ .right = try json.parseFromValueLeaky(R, allocator, value, .{
|
|
.ignore_unknown_fields = true,
|
|
.max_value_len = 0x100,
|
|
}) };
|
|
errdefer owned.arena.deinit();
|
|
|
|
return owned;
|
|
}
|
|
|
|
|
|
/// same as `std.json.parseFromSlice`
|
|
pub fn parseLeft(comptime L: type, comptime R: type, child_allocator: std.mem.Allocator, data: []const u8) json.ParseError(json.Scanner)!OwnedEither(L, R) {
|
|
var owned: OwnedEither(L, R) = .{
|
|
.arena = try child_allocator.create(std.heap.ArenaAllocator),
|
|
.value = undefined,
|
|
};
|
|
owned.arena.* = .init(child_allocator);
|
|
const allocator = owned.arena.allocator();
|
|
const value = try json.parseFromSliceLeaky(json.Value, allocator, data, .{
|
|
.ignore_unknown_fields = true,
|
|
.max_value_len = 0x100,
|
|
});
|
|
|
|
owned.value = .{ .left = try json.parseFromValueLeaky(L, allocator, value, .{
|
|
.ignore_unknown_fields = true,
|
|
.max_value_len = 0x100,
|
|
}) };
|
|
errdefer owned.arena.deinit();
|
|
|
|
return owned;
|
|
}
|
|
|
|
|