╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │264 │ │265 pub fn slice(self: *const Array(max_len)) []const u8 { │ │266 return self.bytes[0..self.len]; │ │267 + } │ │268 + │ │269 + // round-trip as a json string instead of an array of numbers. │ │270 + pub fn jsonStringify(self: *const Array(max_len), jw: anytype) !void { │ │271 + try jw.write(self.slice()); │ │272 + } │ │273 + │ │274 + pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) !Array(max_len) {│ │275 + const s = try std.json.innerParse([]const u8, allocator, source, options); │ │276 + // ValueTooLong is std.json's error for an over-long field. │ │277 + return from(s) orelse error.ValueTooLong; │ │278 } │ │279 }; │ │280 } │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │539 else => false, │ │540 }; │ │541 } │ │531 - │ │532 - // serialize as { "kind": <tag>, "name"?: <name>, "after"?: <n> }. the default │ │533 - // union(enum) codec doesn't round-trip here: Stringify emits a void │ │534 - // variant as a bare tag string, but the parser expects an object — so the │ │535 - // server->wasm Snapshot JSON would fail to parse. │ │536 - pub fn jsonStringify(self: RoutablePage, jw: anytype) !void { │ │537 - try jw.beginObject(); │ │538 - try jw.objectField("kind"); │ │539 - try jw.write(@tagName(self)); │ │540 - switch (self) { │ │541 - .user_repos => |u| { │ │542 - try jw.objectField("name"); │ │543 - try jw.write(u.name.slice()); │ │544 - try jw.objectField("after"); │ │545 - try jw.write(u.after); │ │546 - }, │ │547 - .user_settings, .user_auth => |name| { │ │548 - try jw.objectField("name"); │ │549 - try jw.write(name.slice()); │ │550 - }, │ │551 - .repo_settings, .repo_auth => |name| { │ │552 - try jw.objectField("name"); │ │553 - try jw.write(name.slice()); │ │554 - }, │ │555 - .repo_issues => |i| { │ │556 - try jw.objectField("name"); │ │557 - try jw.write(i.name.slice()); │ │558 - try jw.objectField("tag"); │ │559 - try jw.write(i.tag.slice()); │ │560 - try jw.objectField("selected"); │ │561 - try jw.write(i.selected.slice()); │ │562 - try jw.objectField("view"); │ │563 - try jw.write(@tagName(i.view)); │ │564 - }, │ │565 - .repo_files => |f| { │ │566 - try jw.objectField("name"); │ │567 - try jw.write(f.name.slice()); │ │568 - try jw.objectField("after"); │ │569 - try jw.write(f.after); │ │570 - }, │ │571 - .repo_commits => |c| { │ │572 - try jw.objectField("name"); │ │573 - try jw.write(c.name.slice()); │ │574 - try jw.objectField("after"); │ │575 - try jw.write(c.after); │ │576 - }, │ │577 - .repo_refs => |r| { │ │578 - try jw.objectField("name"); │ │579 - try jw.write(r.name.slice()); │ │580 - // "kind" already holds the union tag, so the paginated column │ │581 - // travels under a distinct field. │ │582 - try jw.objectField("ref_kind"); │ │583 - try jw.write(@tagName(r.kind)); │ │584 - try jw.objectField("after"); │ │585 - try jw.write(r.after); │ │586 - }, │ │587 - .home_users, .home_repos => |after| { │ │588 - try jw.objectField("after"); │ │589 - try jw.write(after); │ │590 - }, │ │591 - else => {}, │ │592 - } │ │593 - try jw.endObject(); │ │594 - } │ │595 - │ │596 - pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) !RoutablePage { │ │597 - const Helper = struct { kind: std.meta.Tag(RoutablePage), name: ?[]const u8 = null, after: usize = 0, ref_kind: ?[]const u8 = null, tag: ?[]const u8 = null, selected: ?[]const u8 = null, view: ?[]const u8 = null };│ │598 - const helper = try std.json.innerParse(Helper, allocator, source, options); │ │599 - const parseName = struct { │ │600 - // errors must stay within std.json's ParseError set; ValueTooLong │ │601 - // is its member for an over-long field. │ │602 - fn f(comptime max_len: usize, maybe: ?[]const u8) !Array(max_len) { │ │603 - return Array(max_len).from(maybe orelse return error.MissingField) orelse error.ValueTooLong; │ │604 - } │ │605 - }.f; │ │606 - return switch (helper.kind) { │ │607 - .home_users => .{ .home_users = helper.after }, │ │608 - .home_repos => .{ .home_repos = helper.after }, │ │609 - .home_settings => .home_settings, │ │610 - .home_auth => .home_auth, │ │611 - .user_repos => .{ .user_repos = .{ .name = try parseName(evt.User.name_max_len, helper.name), .after = helper.after } }, │ │612 - .user_settings => .{ .user_settings = try parseName(evt.User.name_max_len, helper.name) }, │ │613 - .user_auth => .{ .user_auth = try parseName(evt.User.name_max_len, helper.name) }, │ │614 - .repo_files => .{ .repo_files = .{ .name = try parseName(repo_route_max_len, helper.name), .after = helper.after } }, │ │615 - .repo_commits => .{ .repo_commits = .{ .name = try parseName(repo_route_max_len, helper.name), .after = helper.after } }, │ │616 - .repo_refs => .{ .repo_refs = .{ │ │617 - .name = try parseName(repo_route_max_len, helper.name), │ │618 - .kind = if (helper.ref_kind) |k| (if (std.mem.eql(u8, k, "tag")) .tag else .branch) else .branch, │ │619 - .after = helper.after, │ │620 - } }, │ │621 - .repo_issues => .{ .repo_issues = .{ │ │622 - .name = try parseName(repo_route_max_len, helper.name), │ │623 - .tag = Array(issue_tag_route_max_len).from(helper.tag orelse "") orelse return error.ValueTooLong, │ │624 - .selected = Array(evt.event_id_size * 2).from(helper.selected orelse "") orelse return error.ValueTooLong, │ │625 - .view = if (helper.view) |v| (if (std.mem.eql(u8, v, "tags")) .tags else .results) else .results, │ │626 - } }, │ │627 - .repo_settings => .{ .repo_settings = try parseName(repo_route_max_len, helper.name) }, │ │628 - .repo_auth => .{ .repo_auth = try parseName(repo_route_max_len, helper.name) }, │ │629 - }; │ │630 - } │ │542 }; │ │543 │ │544 // a `RefOrOid` resolved against an on-disk repo to the commit oid it points at │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯