lib/pretty/core/src/json/escape.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

 1 const std = @import("std");
 2 
 3 pub fn writeString(writer: anytype, value: []const u8) !void {
 4     try std.json.Stringify.value(value, .{}, writer);
 5 }
 6 
 7 pub fn writeStringParts(writer: anytype, parts: []const []const u8) !void {
 8     for (parts) |part| {
 9         if (!std.unicode.utf8ValidateSlice(part)) return error.InvalidUtf8;
10     }
11     try writer.writeByte('"');
12     for (parts) |part| try writeStringContent(writer, part);
13     try writer.writeByte('"');
14 }
15 
16 pub fn writeByteString(writer: anytype, value: []const u8) !void {
17     const hex = "0123456789abcdef";
18     try writer.writeByte('"');
19     for (value) |byte| {
20         switch (byte) {
21             '"' => try writer.writeAll("\\\""),
22             '\\' => try writer.writeAll("\\\\"),
23             '\x08' => try writer.writeAll("\\b"),
24             '\x0c' => try writer.writeAll("\\f"),
25             '\n' => try writer.writeAll("\\n"),
26             '\r' => try writer.writeAll("\\r"),
27             '\t' => try writer.writeAll("\\t"),
28             0x20...0x21, 0x23...0x5b, 0x5d...0x7e => try writer.writeByte(byte),
29             else => try writer.writeAll(&[_]u8{
30                 '\\',
31                 'u',
32                 '0',
33                 '0',
34                 hex[byte >> 4],
35                 hex[byte & 0x0f],
36             }),
37         }
38     }
39     try writer.writeByte('"');
40 }
41 
42 pub fn stringAlloc(allocator: std.mem.Allocator, value: []const u8) ![]const u8 {
43     return try std.json.Stringify.valueAlloc(allocator, value, .{});
44 }
45 
46 fn writeStringContent(writer: anytype, value: []const u8) !void {
47     for (value) |byte| {
48         switch (byte) {
49             '"' => try writer.writeAll("\\\""),
50             '\\' => try writer.writeAll("\\\\"),
51             '\n' => try writer.writeAll("\\n"),
52             '\r' => try writer.writeAll("\\r"),
53             '\t' => try writer.writeAll("\\t"),
54             else => {
55                 if (byte < 0x20) {
56                     try writer.print("\\u{x:0>4}", .{byte});
57                 } else {
58                     try writer.writeByte(byte);
59                 }
60             },
61         }
62     }
63 }
64 
65 test "writeString escapes JSON string content" {
66     var out = std.Io.Writer.Allocating.init(std.testing.allocator);
67     defer out.deinit();
68     try writeString(&out.writer, "a\n\"b\"");
69     const rendered = try out.toOwnedSlice();
70     defer std.testing.allocator.free(rendered);
71     try std.testing.expectEqualStrings("\"a\\n\\\"b\\\"\"", rendered);
72 }
73 
74 test "writeStringParts escapes composed JSON string content" {
75     var out = std.Io.Writer.Allocating.init(std.testing.allocator);
76     defer out.deinit();
77     try writeStringParts(&out.writer, &.{ "forum:", "post\"id", "#", "reply\nid" });
78     const rendered = try out.toOwnedSlice();
79     defer std.testing.allocator.free(rendered);
80     try std.testing.expectEqualStrings("\"forum:post\\\"id#reply\\nid\"", rendered);
81 }
82 
83 test "writeStringParts rejects invalid utf8 fragments" {
84     var out = std.Io.Writer.Allocating.init(std.testing.allocator);
85     defer out.deinit();
86     try std.testing.expectError(error.InvalidUtf8, writeStringParts(&out.writer, &.{"\xff"}));
87     try std.testing.expectEqualStrings("", out.written());
88 }
89 
90 test "writeByteString escapes arbitrary bytes as lowercase code points" {
91     var out = std.Io.Writer.Allocating.init(std.testing.allocator);
92     defer out.deinit();
93     try writeByteString(&out.writer, "quote\"\n\x01\x7f\x80\xff");
94     try std.testing.expectEqualStrings(
95         "\"quote\\\"\\n\\u0001\\u007f\\u0080\\u00ff\"",
96         out.written(),
97     );
98 }