lib/closure/src/receipt/json.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const pretty_json = @import("pretty").json;
3
4 pub const Error = error{ReceiptCapacityExceeded};
5
6 const measurement_buffer_bytes: usize = 256;
7
8 pub fn measure(
9 comptime write: anytype,
10 args: anytype,
11 ) Error!usize {
12 var buffer: [measurement_buffer_bytes]u8 = undefined;
13 var discarding = std.Io.Writer.Discarding.init(&buffer);
14 @call(.auto, write, .{&discarding.writer} ++ args) catch
15 return error.ReceiptCapacityExceeded;
16 return std.math.cast(usize, discarding.fullCount()) orelse
17 error.ReceiptCapacityExceeded;
18 }
19
20 pub fn render(
21 output: []u8,
22 expected: usize,
23 comptime write: anytype,
24 args: anytype,
25 ) Error![]const u8 {
26 if (expected > output.len) return error.ReceiptCapacityExceeded;
27 var writer = std.Io.Writer.fixed(output);
28 @call(.auto, write, .{&writer} ++ args) catch
29 return error.ReceiptCapacityExceeded;
30 if (writer.end != expected) return error.ReceiptCapacityExceeded;
31 return output[0..writer.end];
32 }
33
34 fn writeFixture(writer: *std.Io.Writer, value: []const u8) std.Io.Writer.Error!void {
35 var json = pretty_json.Writer.init(writer, .minified);
36 const object = try json.object();
37 try object.byteString("value", value);
38 try object.endLine();
39 }
40
41 test "bounded JSON measures and renders arbitrary byte strings exactly" {
42 const expected = "{\"value\":\"quote\\\"\\n\\u0080\\u00ff\"}\n";
43 const length = try measure(writeFixture, .{"quote\"\n\x80\xff"});
44 try std.testing.expectEqual(expected.len, length);
45 var output: [expected.len]u8 = undefined;
46 const rendered = try render(
47 &output,
48 length,
49 writeFixture,
50 .{"quote\"\n\x80\xff"},
51 );
52 try std.testing.expectEqualStrings(expected, rendered);
53 }
54
55 test "bounded JSON rejects short output without mutation" {
56 const length = try measure(writeFixture, .{"value"});
57 var output: [1]u8 = .{0xa5};
58 try std.testing.expectError(
59 error.ReceiptCapacityExceeded,
60 render(&output, length, writeFixture, .{"value"}),
61 );
62 try std.testing.expectEqual(@as(u8, 0xa5), output[0]);
63 }