Zig query string encoder and pair splitter

Zig
const std = @import("std");

fn encodeQuery(a: std.mem.Allocator, pairs: []const [2][]const u8) ![]u8 {
    var out = std.ArrayList(u8).init(a);
    const w = out.writer();
    for (pairs, 0..) |p, i| {
        if (i > 0) try w.writeByte('&');
        try w.writeAll(p[0]); try w.writeByte('=');
        for (p[1]) |c| if (std.ascii.isAlphanumeric(c) or c == '-') try w.writeByte(c) else try w.print("%{X:0>2}", .{c});
    }
    return out.toOwnedSlice();
}

fn parseQuery(a: std.mem.Allocator, q: []const u8) !std.StringHashMap([]const u8) {
    var map = std.StringHashMap([]const u8).init(a);
    var it = std.mem.splitScalar(u8, q, '&');
    while (it.next()) |part| {
        const eq = std.mem.indexOfScalar(u8, part, '=') orelse continue;
        try map.put(try a.dupe(u8, part[0..eq]), try a.dupe(u8, part[eq + 1 ..]));
    }
    return map;
}