aboutsummaryrefslogtreecommitdiff
path: root/src/proto/error_response.zig
blob: dc750531db46b6857b7c6f0a261fc5692a060f3d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
const std = @import("std");
const HMByteString = std.AutoHashMap(u8, []const u8);
const ByteArrayList = std.ArrayList(u8);
const ProtocolError = @import("../main.zig").ProtocolError;

const ErrorResponse = @This();
pub const Tag: u8 = 'E';

buf: ?[]const u8 = null, // owned
severity: []const u8,
severity_unlocalized: ?[]const u8 = null,
code: []const u8,
message: []const u8,
detail: ?[]const u8 = null,
hint: ?[]const u8 = null,
position: ?u32 = null,
internal_position: ?u32 = null,
internal_query: ?[]const u8 = null,
where: ?[]const u8 = null,
schema_name: ?[]const u8 = null,
table_name: ?[]const u8 = null,
column_name: ?[]const u8 = null,
data_type_name: ?[]const u8 = null,
constraint_name: ?[]const u8 = null,
file_name: ?[]const u8 = null,
line: ?u32 = null,
routine: ?[]const u8 = null,
unknown_fields: HMByteString,

pub fn read(allocator: std.mem.Allocator, b: []const u8) !ErrorResponse {
    var res = ErrorResponse{
        .severity = "",
        .code = "",
        .message = "",
        .unknown_fields = HMByteString.init(allocator),
        .buf = try allocator.dupe(u8, b),
    };
    errdefer res.deinit(allocator);
    var it = std.mem.splitScalar(u8, res.buf.?, 0);
    var setSev = false;
    var setCode = false;
    var setMsg = false;
    while (it.next()) |next| {
        if (next.len < 1) break;
        switch (next[0]) {
            0 => break,
            'S' => {
                res.severity = next[1..];
                setSev = true;
            },
            'V' => {
                res.severity_unlocalized = next[1..];
            },
            'C' => {
                res.code = next[1..];
                setCode = true;
            },
            'M' => {
                res.message = next[1..];
                setMsg = true;
            },
            'D' => {
                res.detail = next[1..];
            },
            'H' => {
                res.hint = next[1..];
            },
            'P' => {
                res.position = try std.fmt.parseInt(u32, next[1..], 10);
            },
            'p' => {
                res.internal_position = try std.fmt.parseInt(u32, next[1..], 10);
            },
            'q' => {
                res.internal_query = next[1..];
            },
            'W' => {
                res.where = next[1..];
            },
            's' => {
                res.schema_name = next[1..];
            },
            't' => {
                res.table_name = next[1..];
            },
            'c' => {
                res.column_name = next[1..];
            },
            'd' => {
                res.data_type_name = next[1..];
            },
            'n' => {
                res.constraint_name = next[1..];
            },
            'F' => {
                res.file_name = next[1..];
            },
            'L' => {
                res.line = try std.fmt.parseInt(u32, next[1..], 10);
            },
            'R' => {
                res.routine = next[1..];
            },
            else => {
                try res.unknown_fields.put(next[0], next[1..]);
            },
        }
    }
    if (!(setSev and setCode and setMsg)) return ProtocolError.MissingField;
    return res;
}
pub fn write(self: ErrorResponse, allocator: std.mem.Allocator, stream_writer: anytype) !void {
    try stream_writer.writeByte(Tag);
    var al = ByteArrayList.init(allocator);
    defer al.deinit();
    var cw = std.io.countingWriter(al.writer());
    var writer = cw.writer();
    try writer.writeIntBig(u32, 0); // Length placeholder.

    try write_field_nt('S', self, "severity", writer);
    if (self.severity_unlocalized) |severity_unlocalized| try write_nt('V', severity_unlocalized, writer);
    try write_field_nt('C', self, "code", writer);
    try write_field_nt('M', self, "message", writer);
    if (self.detail) |detail| try write_nt('D', detail, writer);
    // TODO rest of the fields

    // replace the length and write it to the actual stream
    std.mem.writeIntBig(u32, al.items[0..4], @as(u32, @intCast(cw.bytes_written)));
    try stream_writer.writeAll(al.items);
}
fn write_field_nt(comptime tag: u8, self: ErrorResponse, comptime field: []const u8, writer: anytype) !void {
    try write_nt(tag, @field(self, field), writer);
}
fn write_nt(comptime tag: u8, value: []const u8, writer: anytype) !void {
    try writer.writeByte(tag);
    try writer.writeAll(value);
    try writer.writeByte(0);
}

pub fn deinit(self: *ErrorResponse, allocator: std.mem.Allocator) void {
    self.unknown_fields.deinit();
    if (self.buf != null) allocator.free(self.buf.?);
}

pub fn format(self: ErrorResponse, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
    _ = options;
    _ = fmt;
    try writer.writeAll("ErrorResponse severity [");
    try writer.writeAll(self.severity);
    try writer.writeAll("] ");
    try writer.writeAll("code [");
    try writer.writeAll(self.code);
    try writer.writeAll("] ");
    try writer.writeAll("message [");
    try writer.writeAll(self.message);
    try writer.writeAll("]");
}

test "round trip" {
    const allocator = std.testing.allocator;
    var sm = ErrorResponse{
        .severity = "foo",
        .severity_unlocalized = "foo_unlocal",
        .code = "bar",
        .message = "baz",
        .detail = "bang, and that's the end",
        .unknown_fields = HMByteString.init(allocator),
    };
    defer sm.deinit(allocator);

    var bal = ByteArrayList.init(allocator);
    defer bal.deinit();
    try sm.write(allocator, bal.writer());

    var fbs = std.io.fixedBufferStream(bal.items);
    var reader = fbs.reader();
    const tag = try reader.readByte();
    try std.testing.expectEqual(Tag, tag);
    const len = try reader.readIntBig(u32);
    const buf = try allocator.alloc(u8, len - 4);
    defer allocator.free(buf);
    try reader.readNoEof(buf);
    var sm2 = try ErrorResponse.read(allocator, buf);
    defer sm2.deinit(allocator);

    try std.testing.expectEqualStrings("foo", sm2.severity);
    try std.testing.expectEqualStrings("foo_unlocal", sm2.severity_unlocalized.?);
    try std.testing.expectEqualStrings("bar", sm2.code);
    try std.testing.expectEqualStrings("baz", sm2.message);
    try std.testing.expectEqualStrings("bang, and that's the end", sm2.detail.?);
}