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, buf: []const u8) !ErrorResponse { var res = ErrorResponse{ .severity = "", .code = "", .message = "", .unknown_fields = HMByteString.init(allocator), .buf = buf, }; 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); 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.?); }