lib/zen/src/diagram/document/plan.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 const model = @import("model.zig");
4
5 const diagram = @import("../root.zig");
6 const diagram_limits = diagram.limits;
7
8 pub const Limits = struct {
9 max_line_scratch_bytes: usize,
10 max_document_bytes: usize,
11 };
12
13 pub const Plan = struct {
14 source_bytes: usize,
15 source_hash: u64,
16 line_scratch_bytes: usize,
17 document_bytes: usize,
18 surveyed: diagram_limits.Limits,
19
20 pub fn inspect(line_scratch: []u8, jsonl: []const u8) model.Error!Plan {
21 const line_scratch_bytes = try requiredLineScratchBytes(jsonl);
22 if (line_scratch_bytes > line_scratch.len) {
23 return error.DiagramLineScratchCapacityExceeded;
24 }
25 const surveyed = try diagram_limits.survey(
26 line_scratch[0..line_scratch_bytes],
27 jsonl,
28 );
29 const derived = try diagram_limits.Capacity.derive(surveyed);
30 return .{
31 .source_bytes = jsonl.len,
32 .source_hash = std.hash.Wyhash.hash(0, jsonl),
33 .line_scratch_bytes = line_scratch_bytes,
34 .document_bytes = try regionLength(derived.document_bytes),
35 .surveyed = surveyed,
36 };
37 }
38
39 pub fn requiredLineScratchBytes(jsonl: []const u8) error{DiagramTooLarge}!usize {
40 const measured = diagram_limits.measure(jsonl);
41 return regionLength(try diagram_limits.lineScratchBytes(measured.max_line_bytes));
42 }
43
44 pub fn exactLimits(self: Plan) Limits {
45 return .{
46 .max_line_scratch_bytes = self.line_scratch_bytes,
47 .max_document_bytes = self.document_bytes,
48 };
49 }
50
51 pub fn require(self: Plan, limits: Limits) model.Exhaustion!void {
52 if (self.line_scratch_bytes > limits.max_line_scratch_bytes) {
53 return error.DiagramLineScratchCapacityExceeded;
54 }
55 if (self.document_bytes > limits.max_document_bytes) {
56 return error.DiagramDocumentCapacityExceeded;
57 }
58 }
59
60 pub fn matches(self: Plan, jsonl: []const u8) bool {
61 return self.source_bytes == jsonl.len and
62 self.source_hash == std.hash.Wyhash.hash(0, jsonl);
63 }
64 };
65
66 fn regionLength(bytes: u64) error{DiagramTooLarge}!usize {
67 return std.math.cast(usize, bytes) orelse error.DiagramTooLarge;
68 }
69
70 test "diagram Document plan exposes exact surveyed regions" {
71 const jsonl =
72 \\{"kind":"frame","title":"Plan"}
73 \\{"kind":"bar","x":"a","y":1}
74 ;
75 const line_scratch_bytes = try Plan.requiredLineScratchBytes(jsonl);
76 const line_scratch = try std.testing.allocator.alloc(u8, line_scratch_bytes);
77 defer std.testing.allocator.free(line_scratch);
78 const plan = try Plan.inspect(line_scratch, jsonl);
79 try std.testing.expectEqual(jsonl.len, plan.source_bytes);
80 try std.testing.expectEqual(line_scratch_bytes, plan.line_scratch_bytes);
81 try std.testing.expect(plan.document_bytes > 0);
82 try std.testing.expectEqual(@as(u64, 2), plan.surveyed.record_count);
83 try std.testing.expect(plan.matches(jsonl));
84 try std.testing.expectEqual(plan.exactLimits(), Limits{
85 .max_line_scratch_bytes = plan.line_scratch_bytes,
86 .max_document_bytes = plan.document_bytes,
87 });
88 }
89
90 test "diagram Document plan rejects every resource limit" {
91 const jsonl = "{\"kind\":\"point\",\"x\":1,\"y\":2}";
92 const line_scratch_bytes = try Plan.requiredLineScratchBytes(jsonl);
93 const line_scratch = try std.testing.allocator.alloc(u8, line_scratch_bytes);
94 defer std.testing.allocator.free(line_scratch);
95 const plan = try Plan.inspect(line_scratch, jsonl);
96 try plan.require(plan.exactLimits());
97 try std.testing.expectError(
98 error.DiagramLineScratchCapacityExceeded,
99 Plan.inspect(line_scratch[0 .. line_scratch.len - 1], jsonl),
100 );
101 try std.testing.expectError(error.DiagramLineScratchCapacityExceeded, plan.require(.{
102 .max_line_scratch_bytes = plan.line_scratch_bytes - 1,
103 .max_document_bytes = plan.document_bytes,
104 }));
105 try std.testing.expectError(error.DiagramDocumentCapacityExceeded, plan.require(.{
106 .max_line_scratch_bytes = plan.line_scratch_bytes,
107 .max_document_bytes = plan.document_bytes - 1,
108 }));
109 }