fix: make admin command parsing more robust

There was a bug in how we parsed admin commands, apparently we never
tested if we could parse `!backlog X` or unknown admin commands.

This commit also make updates to the backlog command construction to
make sure that we don't try to access messages in the backlog that
don't exist.
This commit is contained in:
Jacob Jonsson 2026-03-11 00:48:02 +01:00
parent d237ba9e8a
commit 4e11cc9ea1
Signed by: Jassob
GPG key ID: 7E30B9B047F7202E
4 changed files with 100 additions and 40 deletions

View file

@ -9,10 +9,7 @@ pub const Parser = struct {
rest: []const u8,
end_idx: usize,
pub fn seek(self: *const Parser, skip: usize) Parser {
return .{ .original = self.original, .rest = self.rest[skip..], .end_idx = self.end_idx + skip };
}
// Initializes a Parser for s.
pub fn init(s: []const u8) Parser {
return .{
.original = s,
@ -21,6 +18,25 @@ pub const Parser = struct {
};
}
// Seek the parser window of the text forward skip bytes and return a new Parser.
pub fn seek(self: *const Parser, skip: usize) Parser {
return .{ .original = self.original, .rest = self.rest[skip..], .end_idx = self.end_idx + skip };
}
// Attempts to consume at least one whitespace character from the input text.
pub fn consume_space(self: *const Parser) ?Parser {
if (!std.ascii.isWhitespace(self.rest[0])) {
return null;
}
for (self.rest[1..], 1..) |c, idx| {
if (!std.ascii.isWhitespace(c)) {
return self.seek(idx);
}
}
return self.seek(self.rest.len);
}
// Attempts to consume a character c.
pub fn consume_char(self: *const Parser, c: u8) ?Parser {
if (self.rest[0] != c) {
return null;
@ -28,6 +44,7 @@ pub const Parser = struct {
return self.seek(1);
}
// Attempts to consume a string s.
pub fn consume_str(self: *const Parser, s: []const u8) ?Parser {
const len = s.len;
if (self.rest.len < len) {
@ -39,16 +56,32 @@ pub const Parser = struct {
return self.seek(len);
}
// Finds the next occurrence of c (idx) in the current parser
// window and extracts it.
//
// Returns a new parser window that starts after idx and the
// extracted byte slice.
pub fn take_until_char(self: *const Parser, c: u8) struct { Parser, []const u8 } {
const idx = std.mem.indexOfScalar(u8, self.rest, c) orelse unreachable;
return .{ self.seek(idx), self.rest[0..idx] };
}
// Take the current character and advance the parser one step.
pub fn take_char(self: *const Parser) struct { Parser, u8 } {
return .{ self.seek(1), self.rest[0] };
}
// Return the currently accepted text.
pub fn parsed(self: *const Parser) []const u8 {
return self.original[0..self.end_idx];
}
};
test "parser can skip whitespace" {
var parser = init("Hello, World");
parser = parser.consume_str("Hello,").?;
parser = parser.consume_space().?;
parser = parser.consume_str("World").?;
try std.testing.expectEqual("Hello, World", parser.parsed());
}