From 37021a266be7abe9e051c65bedc0f91f36f96540 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 22 Mar 2025 21:42:24 +0100 Subject: [PATCH 01/16] feat: add support for encoder members --- src/cbor.zig | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/cbor.zig b/src/cbor.zig index 2d791c8..e3cdb6f 100644 --- a/src/cbor.zig +++ b/src/cbor.zig @@ -215,6 +215,9 @@ pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void { .error_union => return if (value) |v| writeValue(writer, v) else |err| writeValue(writer, err), .error_set => return writeErrorset(writer, value), .@"union" => |info| { + if (std.meta.hasFn(T, "cborEncode")) { + return value.cborEncode(writer); + } if (info.tag_type) |TagType| { comptime var v = void; inline for (info.fields) |u_field| { @@ -231,6 +234,9 @@ pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void { } }, .@"struct" => |info| { + if (std.meta.hasFn(T, "cborEncode")) { + return value.cborEncode(writer); + } if (info.is_tuple) { if (info.fields.len == 0) return writeNull(writer); try writeArrayHeader(writer, info.fields.len); @@ -277,6 +283,12 @@ pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void { 64 => try writeF64(writer, value), else => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"), }, + .@"enum" => { + if (std.meta.hasFn(T, "cborEncode")) { + return value.cborEncode(writer); + } + return writeString(writer, @tagName(value)); + }, else => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"), } } From 32505ed465fb860c5cf77334163e8bcb32e4be76 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 22 Mar 2025 21:43:58 +0100 Subject: [PATCH 02/16] feat: more explicit error handling --- src/cbor.zig | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/cbor.zig b/src/cbor.zig index e3cdb6f..92b9606 100644 --- a/src/cbor.zig +++ b/src/cbor.zig @@ -16,6 +16,10 @@ pub const Error = error{ InvalidType, TooShort, OutOfMemory, + InvalidFloatType, + InvalidArrayType, + InvalidPIntType, + JsonIncompatibleType, }; pub const JsonEncodeError = (Error || error{ @@ -310,7 +314,7 @@ pub fn decodeType(iter: *[]const u8) error{TooShort}!CborType { return .{ .type = type_, .minor = bits.minor, .major = bits.major }; } -fn decodeUIntLengthRecurse(iter: *[]const u8, length: usize, acc: u64) !u64 { +fn decodeUIntLengthRecurse(iter: *[]const u8, length: usize, acc: u64) error{TooShort}!u64 { if (iter.len < 1) return error.TooShort; const v: u8 = iter.*[0]; @@ -327,14 +331,14 @@ fn decodeUIntLength(iter: *[]const u8, length: usize) !u64 { return decodeUIntLengthRecurse(iter, length, 0); } -fn decodePInt(iter: *[]const u8, minor: u5) !u64 { +fn decodePInt(iter: *[]const u8, minor: u5) error{ TooShort, InvalidPIntType }!u64 { if (minor < 24) return minor; return switch (minor) { 24 => decodeUIntLength(iter, 1), // 1 byte 25 => decodeUIntLength(iter, 2), // 2 byte 26 => decodeUIntLength(iter, 4), // 4 byte 27 => decodeUIntLength(iter, 8), // 8 byte - else => error.InvalidType, + else => error.InvalidPIntType, }; } @@ -347,16 +351,16 @@ pub fn decodeMapHeader(iter: *[]const u8) Error!usize { if (t.type == cbor_magic_null) return 0; if (t.major != 5) - return error.InvalidType; + return error.InvalidMapType; return @intCast(try decodePInt(iter, t.minor)); } -pub fn decodeArrayHeader(iter: *[]const u8) Error!usize { +pub fn decodeArrayHeader(iter: *[]const u8) error{ TooShort, InvalidArrayType, InvalidPIntType }!usize { const t = try decodeType(iter); if (t.type == cbor_magic_null) return 0; if (t.major != 4) - return error.InvalidType; + return error.InvalidArrayType; return @intCast(try decodePInt(iter, t.minor)); } @@ -461,7 +465,7 @@ fn decodeFloat(comptime T: type, iter_: *[]const u8, t: CborType) Error!T { v = @floatCast(f); iter = iter[8..]; }, - else => return error.InvalidType, + else => return error.InvalidFloatType, } iter_.* = iter; return v; @@ -786,7 +790,7 @@ fn matchJsonObject(iter_: *[]const u8, obj: *json.ObjectMap) !bool { if (t.type == cbor_magic_null) return true; if (t.major != 5) - return error.InvalidType; + return error.NotAnObject; const ret = try decodeJsonObject(&iter, t.minor, obj); if (ret) iter_.* = iter; return ret; @@ -972,7 +976,7 @@ pub fn JsonStreamWriter(comptime Writer: type) type { 3 => w.write(try decodeString(iter, t.minor)), // string 4 => jsonWriteArray(w, iter, t.minor), // array 5 => jsonWriteMap(w, iter, t.minor), // map - else => error.InvalidType, + else => error.JsonIncompatibleType, }; } }; From 183248d7f2b51190b2a99a9776ab2fc65009ba47 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 22 Mar 2025 21:45:58 +0100 Subject: [PATCH 03/16] fix: build of writeArray with zig-0.14 --- src/cbor.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cbor.zig b/src/cbor.zig index 92b9606..923ac29 100644 --- a/src/cbor.zig +++ b/src/cbor.zig @@ -126,8 +126,8 @@ pub fn writeMapHeader(writer: anytype, sz: usize) @TypeOf(writer).Error!void { pub fn writeArray(writer: anytype, args: anytype) @TypeOf(writer).Error!void { const args_type_info = @typeInfo(@TypeOf(args)); - if (args_type_info != .Struct) @compileError("expected tuple or struct argument"); - const fields_info = args_type_info.Struct.fields; + if (args_type_info != .@"struct") @compileError("expected tuple or struct argument"); + const fields_info = args_type_info.@"struct".fields; try writeArrayHeader(writer, fields_info.len); inline for (fields_info) |field_info| try writeValue(writer, @field(args, field_info.name)); From b32ffa3a5220dac7ddf73481bb0e71136d868a3c Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 22 Mar 2025 21:46:55 +0100 Subject: [PATCH 04/16] fix: matchArray should not return an error on match failure --- src/cbor.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cbor.zig b/src/cbor.zig index 923ac29..cfedaf8 100644 --- a/src/cbor.zig +++ b/src/cbor.zig @@ -759,7 +759,11 @@ fn matchArrayMore(iter_: *[]const u8, n_: u64) Error!bool { fn matchArray(iter_: *[]const u8, arr: anytype, info: anytype) Error!bool { var iter = iter_.*; - var n = try decodeArrayHeader(&iter); + var n = decodeArrayHeader(&iter) catch |e| switch (e) { + error.InvalidArrayType => return false, + error.InvalidPIntType => return e, + error.TooShort => return e, + }; inline for (info.fields) |f| { const value = @field(arr, f.name); if (isMore(value)) From 285f64ede634100f561c11e14aa185f15dccd620 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 22 Mar 2025 21:48:50 +0100 Subject: [PATCH 05/16] feat: add support for extractor members --- src/cbor.zig | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/cbor.zig b/src/cbor.zig index cfedaf8..3a550a4 100644 --- a/src/cbor.zig +++ b/src/cbor.zig @@ -788,6 +788,18 @@ fn matchArray(iter_: *[]const u8, arr: anytype, info: anytype) Error!bool { return n == 0; } +fn matchArrayScalar(iter: *[]const u8, arr: anytype) Error!bool { + var i: usize = 0; + var n = try decodeArrayHeader(iter); + if (n != arr.len) return false; + while (n > 0) : (n -= 1) { + if (!(matchValue(iter, extract(&arr[i])) catch return false)) + return false; + i += 1; + } + return true; +} + fn matchJsonObject(iter_: *[]const u8, obj: *json.ObjectMap) !bool { var iter = iter_.*; const t = try decodeType(&iter); @@ -892,7 +904,8 @@ fn Extractor(comptime T: type) type { }, .float => return matchFloat(T, iter, self.dest), .@"enum" => return matchEnum(T, iter, self.dest), - else => extractError(T), + .array => return matchArrayScalar(iter, self.dest), + else => return self.dest.cborExtract(iter), } } }; From 769bff078fc6432bd214e4d8894ad75a367dbad0 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 22 Mar 2025 21:49:19 +0100 Subject: [PATCH 06/16] feat: add support for allocating extractors --- src/cbor.zig | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/src/cbor.zig b/src/cbor.zig index 3a550a4..efadaea 100644 --- a/src/cbor.zig +++ b/src/cbor.zig @@ -818,7 +818,11 @@ pub fn match(buf: []const u8, pattern: anytype) Error!bool { } fn extractError(comptime T: type) noreturn { - @compileError("cannot extract type '" ++ @typeName(T) ++ "' from cbor stream"); + @compileError("cannot extract type '" ++ @typeName(T) ++ "' from a cbor stream"); +} + +fn extractErrorAlloc(comptime T: type) noreturn { + @compileError("extracting type '" ++ @typeName(T) ++ "' from a cbor stream requires an allocating extractor, use extractAlloc"); } fn hasExtractorTag(info: anytype) bool { @@ -837,6 +841,79 @@ fn isExtractor(comptime T: type) bool { }; } +fn ExtractDef(comptime T: type) type { + return fn (*T, *[]const u8) Error!bool; +} + +fn hasExtractMethod(T: type, info: anytype) bool { + const result = blk: { + if (info.is_tuple) break :blk false; + for (info.decls) |decl| { + if (std.mem.eql(u8, decl.name, "cborExtract") and @TypeOf(@field(T, decl.name)) == ExtractDef(T)) + break :blk true; + } + break :blk false; + }; + // @compileLog("hasExtractMethod", @typeName(T), result); + return result; +} + +pub fn isExtractable(comptime T: type) bool { + return comptime switch (@typeInfo(T)) { + .@"struct" => |info| hasExtractMethod(T, info), + .@"enum" => |info| hasExtractMethod(T, info), + .@"union" => |info| hasExtractMethod(T, info), + else => false, + }; +} + +fn ExtractAllocDef(comptime T: type) type { + return fn (*T, *[]const u8, std.mem.Allocator) Error!bool; +} + +fn hasExtractMethodAlloc(T: type, info: anytype) bool { + const result = blk: { + if (@hasField(@TypeOf(info), "is_tuple") and info.is_tuple) break :blk false; + for (info.decls) |decl| { + if (std.mem.eql(u8, decl.name, "cborExtract") and @TypeOf(@field(T, decl.name)) == ExtractAllocDef(T)) + break :blk true; + } + break :blk false; + }; + // @compileLog("hasExtractMethodAlloc", @typeName(T), result); + return result; +} + +pub fn isExtractableAlloc(comptime T: type) bool { + return comptime switch (@typeInfo(T)) { + .@"struct" => |info| hasExtractMethodAlloc(T, info), + .@"enum" => |info| hasExtractMethodAlloc(T, info), + .@"union" => |info| hasExtractMethodAlloc(T, info), + else => false, + }; +} + +fn GenericExtractorAlloc(T: type) type { + return struct { + dest: *T, + allocator: std.mem.Allocator, + const Self = @This(); + pub const EXTRACTOR_TAG = struct {}; + + pub fn init(dest: *T, allocator: std.mem.Allocator) Self { + return .{ .dest = dest, .allocator = allocator }; + } + + pub fn extract(self: Self, iter: *[]const u8) Error!bool { + if (comptime isExtractableAlloc(T)) { + return self.dest.cborExtract(iter, self.allocator); + } else { + return self.dest.cborExtract(iter); + } + } + }; +} + const JsonValueExtractor = struct { dest: *T, const Self = @This(); @@ -914,7 +991,10 @@ fn Extractor(comptime T: type) type { fn ExtractorType(comptime T: type) type { const T_type_info = @typeInfo(T); if (T_type_info != .pointer) @compileError("extract requires a pointer argument"); - return Extractor(T_type_info.pointer.child); + return if (isExtractableAlloc(T_type_info.pointer.child)) + extractErrorAlloc(T_type_info.pointer.child) + else + Extractor(T_type_info.pointer.child); } pub fn extract(dest: anytype) ExtractorType(@TypeOf(dest)) { @@ -925,6 +1005,21 @@ pub fn extract(dest: anytype) ExtractorType(@TypeOf(dest)) { return ExtractorType(@TypeOf(dest)).init(dest); } +fn ExtractorTypeAlloc(comptime T: type) type { + const T_type_info = @typeInfo(T); + if (T_type_info != .pointer) @compileError("extractAlloc requires a pointer argument"); + // @compileLog("ExtractorTypeAlloc", @typeName(T), isExtractableAlloc(T_type_info.pointer.child)); + return GenericExtractorAlloc(T_type_info.pointer.child); +} + +pub fn extractAlloc(dest: anytype, allocator: std.mem.Allocator) ExtractorTypeAlloc(@TypeOf(dest)) { + comptime { + if (!isExtractor(ExtractorTypeAlloc(@TypeOf(dest)))) + @compileError("isExtractor self check failed for " ++ @typeName(ExtractorTypeAlloc(@TypeOf(dest)))); + } + return ExtractorTypeAlloc(@TypeOf(dest)).init(dest, allocator); +} + const CborExtractor = struct { dest: *[]const u8, const Self = @This(); From 18f64388650453bed6504391dec99156598cd260 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Sat, 22 Mar 2025 21:51:08 +0100 Subject: [PATCH 07/16] fix: add missing NotAnObject error --- src/cbor.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cbor.zig b/src/cbor.zig index efadaea..9e2b4cd 100644 --- a/src/cbor.zig +++ b/src/cbor.zig @@ -20,6 +20,7 @@ pub const Error = error{ InvalidArrayType, InvalidPIntType, JsonIncompatibleType, + NotAnObject, }; pub const JsonEncodeError = (Error || error{ From 1fccb83c70cd84e1dff57cc53f7db8fb99909a94 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 24 Mar 2025 10:10:38 +0100 Subject: [PATCH 08/16] fix: error return type in decodeMapHeader --- src/cbor.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cbor.zig b/src/cbor.zig index 9e2b4cd..d6e2bc5 100644 --- a/src/cbor.zig +++ b/src/cbor.zig @@ -347,7 +347,7 @@ fn decodeNInt(iter: *[]const u8, minor: u5) Error!i64 { return -@as(i64, @intCast(try decodePInt(iter, minor) + 1)); } -pub fn decodeMapHeader(iter: *[]const u8) Error!usize { +pub fn decodeMapHeader(iter: *[]const u8) error{ TooShort, InvalidMapType, InvalidPIntType }!usize { const t = try decodeType(iter); if (t.type == cbor_magic_null) return 0; From 8d184c5981b71655972d2f08509cf87de63d2f8f Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Wed, 28 May 2025 11:17:49 +0200 Subject: [PATCH 09/16] docs: add deepwiki badge to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 21a64d0..e719d8f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # Zig CBOR A Fast & flexible cbor encoding, decoding and matching library for Zig + +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/neurocyte/cbor) From 5ea4b7319146f29bb1aa9acf65982feaba9edc3d Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Mon, 14 Jul 2025 13:07:37 +0200 Subject: [PATCH 10/16] feat: add support for allocating extraction of arrays --- src/cbor.zig | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/cbor.zig b/src/cbor.zig index d6e2bc5..ac2b2f0 100644 --- a/src/cbor.zig +++ b/src/cbor.zig @@ -21,6 +21,7 @@ pub const Error = error{ InvalidPIntType, JsonIncompatibleType, NotAnObject, + BadArrayAllocExtract, }; pub const JsonEncodeError = (Error || error{ @@ -801,6 +802,21 @@ fn matchArrayScalar(iter: *[]const u8, arr: anytype) Error!bool { return true; } +fn matchArrayAlloc(iter: *[]const u8, element_type: type, arr: anytype, allocator: std.mem.Allocator) Error!bool { + var arr_: std.ArrayListUnmanaged(element_type) = .empty; + errdefer arr_.deinit(allocator); + var n = try decodeArrayHeader(iter); + while (n > 0) : (n -= 1) { + var element: element_type = undefined; + const extractor = GenericExtractorAlloc(element_type).init(&element, allocator); + if (try extractor.extract(iter)) { + (try arr_.addOne(allocator)).* = element; + } else return error.BadArrayAllocExtract; + } + arr.* = try arr_.toOwnedSlice(allocator); + return true; +} + fn matchJsonObject(iter_: *[]const u8, obj: *json.ObjectMap) !bool { var iter = iter_.*; const t = try decodeType(&iter); @@ -909,7 +925,32 @@ fn GenericExtractorAlloc(T: type) type { if (comptime isExtractableAlloc(T)) { return self.dest.cborExtract(iter, self.allocator); } else { - return self.dest.cborExtract(iter); + switch (comptime @typeInfo(T)) { + .int, .comptime_int => return matchInt(T, iter, self.dest), + .bool => return matchBool(iter, self.dest), + .pointer => |ptr_info| switch (ptr_info.size) { + .slice => { + if (ptr_info.child == u8) + return matchString(iter, self.dest) + else + return matchArrayAlloc(iter, ptr_info.child, self.dest, self.allocator); + }, + else => extractError(T), + }, + .optional => |opt_info| { + var nested: opt_info.child = undefined; + const extractor = GenericExtractorAlloc(opt_info.child).init(&nested, self.allocator); + if (try extractor.extract(iter)) { + self.dest.* = nested; + return true; + } + return false; + }, + .float => return matchFloat(T, iter, self.dest), + .@"enum" => return matchEnum(T, iter, self.dest), + .array => return matchArrayScalar(iter, self.dest), + else => return self.dest.cborExtract(iter), + } } } }; From 3461e8d8081545e0150cd30ce946d64eef517dc1 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 15 Jul 2025 16:43:13 +0200 Subject: [PATCH 11/16] build: fix build for 0.15.0-dev.1034+bd97b6618 --- build.zig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/build.zig b/build.zig index aab561e..12cf970 100644 --- a/build.zig +++ b/build.zig @@ -9,9 +9,11 @@ pub fn build(b: *std.Build) void { }); const tests = b.addTest(.{ - .root_source_file = b.path("test/tests.zig"), - .target = target, - .optimize = optimize, + .root_module = b.createModule(.{ + .root_source_file = b.path("test/tests.zig"), + .target = target, + .optimize = optimize, + }), }); tests.root_module.addImport("cbor", cbor_mod); From 6eccce0b984296e7d05c20d83933cb31530e4fac Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Tue, 15 Jul 2025 16:43:31 +0200 Subject: [PATCH 12/16] fix: bit rotten json in cbor.extract_cbor test case --- test/tests.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/tests.zig b/test/tests.zig index 1d27410..6b0f307 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -4,6 +4,7 @@ const cbor_mod = @import("cbor"); const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const expectEqualDeep = std.testing.expectEqualDeep; +const expectEqualStrings = std.testing.expectEqualStrings; const expectError = std.testing.expectError; const fmt = cbor_mod.fmt; @@ -519,5 +520,5 @@ test "cbor.extract_cbor f64" { const json2 = try toJsonAlloc(std.testing.allocator, sub); defer std.testing.allocator.free(json2); - try expectEqualDeep("[9.689138531684875e-1]", json2); + try expectEqualStrings("[0.9689138531684875]", json2); } From 3bdf25183ec3644445ef6a8457c4a3f5f73b622b Mon Sep 17 00:00:00 2001 From: Lumor Sunil Date: Sun, 20 Jul 2025 22:40:08 +0200 Subject: [PATCH 13/16] Added struct and union cases in extract and extractAlloc and match (except for structs). Added tests for enums, unions and structs. --- src/cbor.zig | 278 +++++++++++++++++++++++++++++++++++++++++++++---- test/tests.zig | 181 +++++++++++++++++++++++++++++++- 2 files changed, 437 insertions(+), 22 deletions(-) diff --git a/src/cbor.zig b/src/cbor.zig index ac2b2f0..a4d53e2 100644 --- a/src/cbor.zig +++ b/src/cbor.zig @@ -18,7 +18,9 @@ pub const Error = error{ OutOfMemory, InvalidFloatType, InvalidArrayType, + InvalidMapType, InvalidPIntType, + InvalidUnion, JsonIncompatibleType, NotAnObject, BadArrayAllocExtract, @@ -212,6 +214,44 @@ fn writeErrorset(writer: anytype, err: anyerror) @TypeOf(writer).Error!void { return writeString(writer, stream.getWritten()); } +fn writeEnum(writer: anytype, value: anytype) @TypeOf(writer).Error!void { + const T = @TypeOf(value); + + if (std.meta.hasFn(T, "cborEncode")) { + return value.cborEncode(writer); + } + + return writeString(writer, @tagName(value)); +} + +fn writeUnion(writer: anytype, value: anytype, info: std.builtin.Type.Union) @TypeOf(writer).Error!void { + const T = @TypeOf(value); + + if (std.meta.hasFn(T, "cborEncode")) { + return value.cborEncode(writer); + } + if (info.tag_type) |TagType| { + inline for (info.fields) |u_field| { + const t = @field(TagType, u_field.name); + if (value == t) { + const Payload = std.meta.TagPayload(T, t); + if (Payload != void) { + try writeArrayHeader(writer, 2); + try writeEnum(writer, value); + return try writeValue(writer, @field(value, u_field.name)); + } else { + try writeArrayHeader(writer, 1); + try writeEnum(writer, value); + } + + return; + } + } else unreachable; + } else { + @compileError("cannot write untagged union '" ++ @typeName(T) ++ "' to cbor stream"); + } +} + pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void { const T = @TypeOf(value); switch (@typeInfo(T)) { @@ -220,25 +260,7 @@ pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void { .optional => return if (value) |v| writeValue(writer, v) else writeNull(writer), .error_union => return if (value) |v| writeValue(writer, v) else |err| writeValue(writer, err), .error_set => return writeErrorset(writer, value), - .@"union" => |info| { - if (std.meta.hasFn(T, "cborEncode")) { - return value.cborEncode(writer); - } - if (info.tag_type) |TagType| { - comptime var v = void; - inline for (info.fields) |u_field| { - if (value == @field(TagType, u_field.name)) - v = @field(value, u_field.name); - } - try writeArray(writer, .{ - @typeName(T), - @tagName(@as(TagType, value)), - v, - }); - } else { - try writeArray(writer, .{@typeName(T)}); - } - }, + .@"union" => |info| return writeUnion(writer, value, info), .@"struct" => |info| { if (std.meta.hasFn(T, "cborEncode")) { return value.cborEncode(writer); @@ -501,6 +523,17 @@ pub fn matchIntValue(comptime T: type, iter: *[]const u8, val: T) Error!bool { return if (try matchInt(T, iter, &v)) v == val else false; } +pub fn matchNull(iter_: *[]const u8) Error!bool { + var iter = iter_.*; + + if (iter.len > 0 and iter[0] == cbor_magic_null) { + iter_.* = iter[1..]; + return true; + } + + return false; +} + pub fn matchBool(iter_: *[]const u8, v: *bool) Error!bool { var iter = iter_.*; const t = try decodeType(&iter); @@ -555,6 +588,191 @@ fn matchEnumValue(comptime T: type, iter: *[]const u8, val: T) Error!bool { return matchStringValue(iter, @tagName(val)); } +fn matchUnionScalar(comptime T: type, iter_: *[]const u8, val_: *T) Error!bool { + var iter = iter_.*; + + const n = decodeArrayHeader(&iter) catch |e| switch (e) { + error.InvalidArrayType => return false, + error.InvalidPIntType => return e, + error.TooShort => return e, + }; + if (n == 0) return false; + + const TagType = std.meta.Tag(T); + var unionTag: TagType = undefined; + if (!try matchEnum(TagType, &iter, &unionTag)) return false; + + inline for (comptime std.meta.tags(TagType)) |t_| { + if (t_ == unionTag) { + const Payload = std.meta.TagPayload(T, t_); + + if (Payload == void) { + if (n != 1) return false; + val_.* = t_; + iter_.* = iter; + return true; + } else { + if (n != 2) return false; + var val: Payload = undefined; + if (try matchValue(&iter, extract(&val))) { + val_.* = @unionInit(T, @tagName(t_), val); + iter_.* = iter; + return true; + } + } + } + } + + return false; +} + +fn matchUnionAlloc(comptime T: type, iter_: *[]const u8, val_: *T, allocator: std.mem.Allocator) Error!bool { + var iter = iter_.*; + + const n = decodeArrayHeader(&iter) catch |e| switch (e) { + error.InvalidArrayType => return false, + error.InvalidPIntType => return e, + error.TooShort => return e, + }; + if (n == 0) return false; + + const TagType = std.meta.Tag(T); + var unionTag: TagType = undefined; + if (!try matchEnum(TagType, &iter, &unionTag)) return false; + + inline for (comptime std.meta.tags(TagType)) |t_| { + if (t_ == unionTag) { + const Payload = std.meta.TagPayload(T, t_); + + if (Payload == void) { + if (n != 1) return false; + val_.* = t_; + iter_.* = iter; + return true; + } else { + if (n != 2) return false; + var val: Payload = undefined; + if (try matchValue(&iter, extractAlloc(&val, allocator))) { + val_.* = @unionInit(T, @tagName(t_), val); + iter_.* = iter; + return true; + } + } + } + } + + return false; +} + +fn matchUnionValue(comptime T: type, iter_: *[]const u8, val: T) Error!bool { + switch (val) { + inline else => |v, t| { + var iter = iter_.*; + + const n = decodeArrayHeader(&iter) catch |e| switch (e) { + error.InvalidArrayType => return false, + error.InvalidPIntType => return e, + error.TooShort => return e, + }; + if (n == 0) return false; + + if (!try matchEnumValue(std.meta.Tag(T), &iter, t)) return false; + + if (std.meta.TagPayload(T, t) != void) { + if (n != 2) return false; + if (!try matchValue(&iter, v)) return false; + } else { + if (n != 1) return false; + } + + iter_.* = iter; + return true; + }, + } +} + +fn matchStructScalar(comptime T: type, iter_: *[]const u8, val_: *T) Error!bool { + var iter = iter_.*; + const info = @typeInfo(T).@"struct"; + + const len = decodeMapHeader(&iter) catch |err| switch (err) { + error.TooShort => return false, + error.InvalidMapType => return err, + error.InvalidPIntType => return err, + }; + + if (len != info.fields.len) return false; + + if (info.fields.len == 0) { + iter_.* = iter; + val_.* = .{}; + return true; + } + + var val: T = undefined; + + fields: for (0..info.fields.len) |_| { + var fieldName: []const u8 = undefined; + if (!try matchString(&iter, &fieldName)) return false; + + inline for (info.fields) |f| { + if (std.mem.eql(u8, f.name, fieldName)) { + var fieldVal: @FieldType(T, f.name) = undefined; + if (!try matchValue(&iter, extract(&fieldVal))) return false; + @field(val, f.name) = fieldVal; + continue :fields; + } + } + + return false; + } + + val_.* = val; + iter_.* = iter; + + return true; +} + +fn matchStructAlloc(comptime T: type, iter_: *[]const u8, val_: *T, allocator: std.mem.Allocator) Error!bool { + var iter = iter_.*; + const info = @typeInfo(T).@"struct"; + + const len = decodeMapHeader(&iter) catch |err| switch (err) { + error.TooShort => return false, + error.InvalidMapType => return err, + error.InvalidPIntType => return err, + }; + + if (len != info.fields.len) return false; + + if (info.fields.len == 0) { + iter_.* = iter; + val_.* = .{}; + return true; + } + + var val: T = undefined; + + for (0..info.fields.len) |_| { + var fieldName: []const u8 = undefined; + if (!try matchString(&iter, &fieldName)) return false; + + inline for (info.fields) |f| { + if (std.mem.eql(u8, f.name, fieldName)) { + var fieldVal: @FieldType(T, f.name) = undefined; + if (!try matchValue(&iter, extractAlloc(&fieldVal, allocator))) return false; + @field(val, f.name) = fieldVal; + break; + } + } else return false; + } + + val_.* = val; + iter_.* = iter; + + return true; +} + fn skipString(iter: *[]const u8, minor: u5) Error!void { const len: usize = @intCast(try decodePInt(iter, minor)); if (iter.len < len) @@ -683,14 +901,20 @@ pub fn matchValue(iter: *[]const u8, value: anytype) Error!bool { .many, .c => matchError(T), .slice => if (info.child == u8) matchStringValue(iter, value) else matchArray(iter, value, info), }, + .optional => if (value) |v| matchValue(iter, v) else matchNull(iter), .@"struct" => |info| if (info.is_tuple) matchArray(iter, value, info) + // TODO: Add case for matching struct here else matchError(T), .array => |info| if (info.child == u8) matchStringValue(iter, &value) else matchArray(iter, value, info), .float => matchFloatValue(T, iter, value), .comptime_float => matchFloatValue(f64, iter, value), .@"enum" => matchEnumValue(T, iter, value), + .@"union" => |info| if (info.tag_type) |_| + matchUnionValue(T, iter, value) + else + @compileError("cannot match value type '" ++ @typeName(T) ++ "' to cbor stream"), else => @compileError("cannot match value type '" ++ @typeName(T) ++ "' to cbor stream"), }; } @@ -949,7 +1173,13 @@ fn GenericExtractorAlloc(T: type) type { .float => return matchFloat(T, iter, self.dest), .@"enum" => return matchEnum(T, iter, self.dest), .array => return matchArrayScalar(iter, self.dest), - else => return self.dest.cborExtract(iter), + else => if (@hasDecl(T, "cborExtract")) { + return self.dest.cborExtract(iter); + } else switch (comptime @typeInfo(T)) { + .@"union" => return matchUnionAlloc(T, iter, self.dest, self.allocator), + .@"struct" => return matchStructAlloc(T, iter, self.dest, self.allocator), + else => @compileError(@typeName(T) ++ " (" ++ @tagName(@typeInfo(T)) ++ ") is and unsupported or invalid type for cbor extract, or implement cborExtract function"), + }, } } } @@ -1024,7 +1254,13 @@ fn Extractor(comptime T: type) type { .float => return matchFloat(T, iter, self.dest), .@"enum" => return matchEnum(T, iter, self.dest), .array => return matchArrayScalar(iter, self.dest), - else => return self.dest.cborExtract(iter), + else => if (@hasDecl(T, "cborExtract")) { + return self.dest.cborExtract(iter); + } else switch (comptime @typeInfo(T)) { + .@"union" => return matchUnionScalar(T, iter, self.dest), + .@"struct" => return matchStructScalar(T, iter, self.dest), + else => @compileError("cannot extract type " ++ @typeName(T)), + }, } } }; diff --git a/test/tests.zig b/test/tests.zig index 6b0f307..6572639 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -24,6 +24,7 @@ const writeArrayHeader = cbor_mod.writeArrayHeader; const writeMapHeader = cbor_mod.writeMapHeader; const writeValue = cbor_mod.writeValue; const extract = cbor_mod.extract; +const extractAlloc = cbor_mod.extractAlloc; const extract_cbor = cbor_mod.extract_cbor; const more = cbor_mod.more; @@ -520,5 +521,183 @@ test "cbor.extract_cbor f64" { const json2 = try toJsonAlloc(std.testing.allocator, sub); defer std.testing.allocator.free(json2); - try expectEqualStrings("[0.9689138531684875]", json2); + try expectEqualStrings("[9.689138531684875e-1]", json2); +} + +test "cbor.writeValue enum" { + const TestEnum = enum { a, b }; + var buf: [128]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + const writer = stream.writer(); + try writeValue(writer, TestEnum.a); + const expected = @tagName(TestEnum.a); + var m = std.json.Value{ .null = {} }; + try expect(try match(stream.getWritten(), extract(&m))); + try expectEqualStrings(expected, m.string); +} + +test "cbor.extract enum" { + const TestEnum = enum { a, b }; + var buf: [128]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + const writer = stream.writer(); + try writeValue(writer, TestEnum.a); + var m: TestEnum = undefined; + try expect(try match(stream.getWritten(), extract(&m))); + try expectEqualStrings(@tagName(m), @tagName(TestEnum.a)); + try expect(m == .a); +} + +test "cbor.writeValue tagged union" { + const TestUnion = union(enum) { a: f32, b: []const u8 }; + var buf: [128]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + const writer = stream.writer(); + try writeValue(writer, TestUnion{ .b = "should work" }); + var tagName = std.json.Value{ .null = {} }; + var value = std.json.Value{ .null = {} }; + var iter: []const u8 = stream.getWritten(); + try expect(try matchValue(&iter, .{ extract(&tagName), extract(&value) })); + try expectEqualStrings(@tagName(TestUnion.b), tagName.string); + try expectEqualStrings("should work", value.string); +} + +test "cbor.writeValue tagged union no payload types" { + const TestUnion = union(enum) { a, b }; + var buf: [128]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + const writer = stream.writer(); + try writeValue(writer, TestUnion.b); + + try expect(try match(stream.getWritten(), @tagName(TestUnion.b))); + var tagName = std.json.Value{ .null = {} }; + try expect(try match(stream.getWritten(), extract(&tagName))); + try expectEqualStrings(@tagName(TestUnion.b), tagName.string); +} + +test "cbor.writeValue nested union json" { + const TestUnion = union(enum) { + a: f32, + b: []const u8, + c: []const union(enum) { + d: f32, + e: i32, + f, + }, + }; + var buf: [256]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + const writer = stream.writer(); + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + try writeValue(writer, TestUnion{ .c = &.{ .{ .d = 1.5 }, .{ .e = 5 }, .f } }); + var json_buf: [128]u8 = undefined; + const json = try toJson(stream.getWritten(), &json_buf); + try expectEqualStrings(json, + \\["c",[["d",1.5e0],["e",5],["f"]]] + ); +} + +test "cbor.extract tagged union" { + const TestUnion = union(enum) { a: f32, b: []const u8 }; + var buf: [128]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + const writer = stream.writer(); + try writeValue(writer, TestUnion{ .b = "should work" }); + var m: TestUnion = undefined; + try expect(try match(stream.getWritten(), extract(&m))); + try expectEqualDeep(TestUnion{ .b = "should work" }, m); +} + +test "cbor.extract nested union" { + const TestUnion = union(enum) { + a: f32, + b: []const u8, + c: []const union(enum) { + d: f32, + e: i32, + f, + }, + }; + var buf: [256]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + const writer = stream.writer(); + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + const allocator = arena.allocator(); + defer arena.deinit(); + try writeValue(writer, TestUnion{ .c = &.{ .{ .d = 1.5 }, .{ .e = 5 }, .f } }); + var m: TestUnion = undefined; + try expect(try match(stream.getWritten(), extractAlloc(&m, allocator))); + try expectEqualDeep(TestUnion{ .c = &.{ .{ .d = 1.5 }, .{ .e = 5 }, .f } }, m); +} + +test "cbor.extract struct no fields" { + const TestStruct = struct {}; + var buf: [128]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + const writer = stream.writer(); + try writeValue(writer, TestStruct{}); + var m: TestStruct = undefined; + try expect(try match(stream.getWritten(), extract(&m))); + try expectEqualDeep(TestStruct{}, m); +} + +test "cbor.extract_cbor struct" { + const TestStruct = struct { + a: f32, + b: []const u8, + }; + var buf: [128]u8 = undefined; + const v = TestStruct{ .a = 1.5, .b = "hello" }; + const m = fmt(&buf, v); + var map_cbor: []const u8 = undefined; + try expect(try match(m, extract_cbor(&map_cbor))); + var json_buf: [256]u8 = undefined; + const json = try toJson(map_cbor, &json_buf); + try expectEqualStrings(json, + \\{"a":1.5e0,"b":"hello"} + ); +} + +test "cbor.extract struct" { + const TestStruct = struct { + a: f32, + b: []const u8, + }; + var buf: [128]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + const writer = stream.writer(); + const v = TestStruct{ .a = 1.5, .b = "hello" }; + try writeValue(writer, v); + var obj: TestStruct = undefined; + var json_buf: [256]u8 = undefined; + var iter: []const u8 = stream.getWritten(); + const t = try decodeType(&iter); + try expectEqual(5, t.major); + const json = try toJson(stream.getWritten(), &json_buf); + try expectEqualStrings(json, + \\{"a":1.5e0,"b":"hello"} + ); + try expect(try match(stream.getWritten(), extract(&obj))); + try expectEqual(1.5, obj.a); + try expectEqualStrings("hello", obj.b); +} + +test "cbor.extractAlloc struct" { + const TestStruct = struct { + a: f32, + b: []const u8, + }; + var buf: [128]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + const allocator = arena.allocator(); + defer arena.deinit(); + const writer = stream.writer(); + const v = TestStruct{ .a = 1.5, .b = "hello" }; + try writeValue(writer, v); + var obj: TestStruct = undefined; + try expect(try match(stream.getWritten(), extractAlloc(&obj, allocator))); + try expectEqual(1.5, obj.a); + try expectEqualStrings("hello", obj.b); } From 451bc20ddeb97baab3c558d8d693be8ae48cbc8a Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Fri, 8 Aug 2025 12:45:21 +0200 Subject: [PATCH 14/16] refactor: add test cases for optional values --- test/tests.zig | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/test/tests.zig b/test/tests.zig index 6572639..c8672a0 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -701,3 +701,84 @@ test "cbor.extractAlloc struct" { try expectEqual(1.5, obj.a); try expectEqualStrings("hello", obj.b); } + +fn test_value_write_and_extract(T: type, value: T) !void { + const test_value: T = value; + var buf: [1024]u8 = undefined; + var stream = std.io.fixedBufferStream(&buf); + const writer = stream.writer(); + try writeValue(writer, test_value); + + var result_value: T = undefined; + + var iter: []const u8 = stream.getWritten(); + try expect(try matchValue(&iter, extract(&result_value))); + + switch (comptime @typeInfo(T)) { + .pointer => |ptr_info| switch (ptr_info.size) { + .slice => if (ptr_info.child == u8) return expectEqualStrings(test_value, result_value), + else => {}, + }, + .optional => |opt_info| switch (comptime @typeInfo(opt_info.child)) { + .pointer => |ptr_info| switch (ptr_info.size) { + .slice => if (ptr_info.child == u8) { + if (test_value == null and result_value == null) return; + if (test_value == null or result_value == null) return error.TestExpectedEqual; + return expectEqualStrings(test_value.?, result_value.?); + }, + else => {}, + }, + else => {}, + }, + else => {}, + } + return expectEqual(test_value, result_value); +} + +test "read/write optional bool (null)" { + try test_value_write_and_extract(?bool, null); +} + +test "read/write optional bool (true)" { + try test_value_write_and_extract(?bool, true); +} + +test "read/write optional bool (false)" { + try test_value_write_and_extract(?bool, false); +} + +test "read/write optional string (null)" { + try test_value_write_and_extract(?[]const u8, null); +} + +test "read/write optional string (non-null)" { + try test_value_write_and_extract(?[]const u8, "TESTME"); +} + +test "read/write optional struct (null)" { + try test_value_write_and_extract(?struct { field: usize }, null); +} + +test "read/write optional struct (non-null)" { + try test_value_write_and_extract(?struct { field: usize }, .{ .field = 42 }); +} + +test "read/write struct optional field (null)" { + try test_value_write_and_extract(struct { field: ?usize }, .{ .field = null }); +} + +test "read/write struct optional field (non-null)" { + try test_value_write_and_extract(struct { field: ?usize }, .{ .field = 42 }); +} + +test "read/write optional struct optional field (null) (na)" { + try test_value_write_and_extract(?struct { field: ?usize }, null); +} + +test "read/write optional struct optional field (non-null) (null)" { + try test_value_write_and_extract(?struct { field: ?usize }, .{ .field = null }); +} + +test "read/write optional struct optional field (non-null) (non=null)" { + try test_value_write_and_extract(?struct { field: ?usize }, .{ .field = 42 }); +} From ba2955fe3aa08456ed61a0e087be1e2ec46927e0 Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Fri, 8 Aug 2025 12:45:58 +0200 Subject: [PATCH 15/16] fix: extraction of null optional values --- src/cbor.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cbor.zig b/src/cbor.zig index a4d53e2..549212b 100644 --- a/src/cbor.zig +++ b/src/cbor.zig @@ -1244,6 +1244,10 @@ fn Extractor(comptime T: type) type { }, .optional => |opt_info| { var nested: opt_info.child = undefined; + if (try matchNull(iter)) { + self.dest.* = null; + return true; + } const extractor = Extractor(opt_info.child).init(&nested); if (try extractor.extract(iter)) { self.dest.* = nested; From 0708420594f5af0a8289e6e1bae0ae03f011731f Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Tue, 19 Aug 2025 15:11:54 +0200 Subject: [PATCH 16/16] update to zig 0.15 --- src/cbor.zig | 249 ++++++++++++++++++++++++------------------------- test/tests.zig | 132 ++++++++++++-------------- 2 files changed, 183 insertions(+), 198 deletions(-) diff --git a/src/cbor.zig b/src/cbor.zig index 549212b..0760cec 100644 --- a/src/cbor.zig +++ b/src/cbor.zig @@ -1,10 +1,10 @@ const std = @import("std"); const builtin = @import("builtin"); +const Io = std.Io; const native_endian = builtin.cpu.arch.endian(); const eql = std.mem.eql; const bufPrint = std.fmt.bufPrint; -const fixedBufferStream = std.io.fixedBufferStream; const maxInt = std.math.maxInt; const minInt = std.math.minInt; const json = std.json; @@ -15,6 +15,7 @@ pub const Error = error{ IntegerTooSmall, InvalidType, TooShort, + WriteFailed, OutOfMemory, InvalidFloatType, InvalidArrayType, @@ -86,11 +87,11 @@ fn isMore(value: anytype) bool { return if (comptime @TypeOf(value) == value_type) value == value_type.more else false; } -fn write(writer: anytype, value: u8) @TypeOf(writer).Error!void { +fn write(writer: *Io.Writer, value: u8) Io.Writer.Error!void { _ = try writer.write(&[_]u8{value}); } -fn writeTypedVal(writer: anytype, type_: u8, value: u64) @TypeOf(writer).Error!void { +fn writeTypedVal(writer: *Io.Writer, type_: u8, value: u64) Io.Writer.Error!void { const t: u8 = type_ << 5; if (value < 24) { try write(writer, t | @as(u8, @truncate(value))); @@ -120,15 +121,15 @@ fn writeTypedVal(writer: anytype, type_: u8, value: u64) @TypeOf(writer).Error!v } } -pub fn writeArrayHeader(writer: anytype, sz: usize) @TypeOf(writer).Error!void { +pub fn writeArrayHeader(writer: *Io.Writer, sz: usize) Io.Writer.Error!void { return writeTypedVal(writer, cbor_magic_type_array, sz); } -pub fn writeMapHeader(writer: anytype, sz: usize) @TypeOf(writer).Error!void { +pub fn writeMapHeader(writer: *Io.Writer, sz: usize) Io.Writer.Error!void { return writeTypedVal(writer, cbor_magic_type_map, sz); } -pub fn writeArray(writer: anytype, args: anytype) @TypeOf(writer).Error!void { +pub fn writeArray(writer: *Io.Writer, args: anytype) Io.Writer.Error!void { const args_type_info = @typeInfo(@TypeOf(args)); if (args_type_info != .@"struct") @compileError("expected tuple or struct argument"); const fields_info = args_type_info.@"struct".fields; @@ -137,18 +138,18 @@ pub fn writeArray(writer: anytype, args: anytype) @TypeOf(writer).Error!void { try writeValue(writer, @field(args, field_info.name)); } -fn writeI64(writer: anytype, value: i64) @TypeOf(writer).Error!void { +fn writeI64(writer: *Io.Writer, value: i64) Io.Writer.Error!void { return if (value < 0) writeTypedVal(writer, 1, @as(u64, @bitCast(-(value + 1)))) else writeTypedVal(writer, 0, @as(u64, @bitCast(value))); } -fn writeU64(writer: anytype, value: u64) @TypeOf(writer).Error!void { +fn writeU64(writer: *Io.Writer, value: u64) Io.Writer.Error!void { return writeTypedVal(writer, 0, value); } -fn writeF16(writer: anytype, value: f16) @TypeOf(writer).Error!void { +fn writeF16(writer: *Io.Writer, value: f16) Io.Writer.Error!void { try write(writer, cbor_magic_float16); const value_bytes = std.mem.asBytes(&value); switch (native_endian) { @@ -160,7 +161,7 @@ fn writeF16(writer: anytype, value: f16) @TypeOf(writer).Error!void { } } -fn writeF32(writer: anytype, value: f32) @TypeOf(writer).Error!void { +fn writeF32(writer: *Io.Writer, value: f32) Io.Writer.Error!void { try write(writer, cbor_magic_float32); const value_bytes = std.mem.asBytes(&value); switch (native_endian) { @@ -174,7 +175,7 @@ fn writeF32(writer: anytype, value: f32) @TypeOf(writer).Error!void { } } -fn writeF64(writer: anytype, value: f64) @TypeOf(writer).Error!void { +fn writeF64(writer: *Io.Writer, value: f64) Io.Writer.Error!void { try write(writer, cbor_magic_float64); const value_bytes = std.mem.asBytes(&value); switch (native_endian) { @@ -192,29 +193,28 @@ fn writeF64(writer: anytype, value: f64) @TypeOf(writer).Error!void { } } -fn writeString(writer: anytype, s: []const u8) @TypeOf(writer).Error!void { +fn writeString(writer: *Io.Writer, s: []const u8) Io.Writer.Error!void { try writeTypedVal(writer, 3, s.len); _ = try writer.write(s); } -fn writeBool(writer: anytype, value: bool) @TypeOf(writer).Error!void { +fn writeBool(writer: *Io.Writer, value: bool) Io.Writer.Error!void { return write(writer, if (value) cbor_magic_true else cbor_magic_false); } -fn writeNull(writer: anytype) @TypeOf(writer).Error!void { +fn writeNull(writer: *Io.Writer) Io.Writer.Error!void { return write(writer, cbor_magic_null); } -fn writeErrorset(writer: anytype, err: anyerror) @TypeOf(writer).Error!void { +fn writeErrorset(writer: *Io.Writer, err: anyerror) Io.Writer.Error!void { var buf: [256]u8 = undefined; - var stream = fixedBufferStream(&buf); - const writer_ = stream.writer(); - _ = writer_.write("error.") catch @panic("cbor.writeErrorset failed!"); - _ = writer_.write(@errorName(err)) catch @panic("cbor.writeErrorset failed!"); - return writeString(writer, stream.getWritten()); + var fixed_writer: Io.Writer = .fixed(&buf); + _ = fixed_writer.write("error.") catch @panic("cbor.writeErrorset failed!"); + _ = fixed_writer.write(@errorName(err)) catch @panic("cbor.writeErrorset failed!"); + return writeString(writer, fixed_writer.buffered()); } -fn writeEnum(writer: anytype, value: anytype) @TypeOf(writer).Error!void { +fn writeEnum(writer: *Io.Writer, value: anytype) Io.Writer.Error!void { const T = @TypeOf(value); if (std.meta.hasFn(T, "cborEncode")) { @@ -224,7 +224,7 @@ fn writeEnum(writer: anytype, value: anytype) @TypeOf(writer).Error!void { return writeString(writer, @tagName(value)); } -fn writeUnion(writer: anytype, value: anytype, info: std.builtin.Type.Union) @TypeOf(writer).Error!void { +fn writeUnion(writer: *Io.Writer, value: anytype, info: std.builtin.Type.Union) Io.Writer.Error!void { const T = @TypeOf(value); if (std.meta.hasFn(T, "cborEncode")) { @@ -252,7 +252,7 @@ fn writeUnion(writer: anytype, value: anytype, info: std.builtin.Type.Union) @Ty } } -pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void { +pub fn writeValue(writer: *Io.Writer, value: anytype) Io.Writer.Error!void { const T = @TypeOf(value); switch (@typeInfo(T)) { .int, .comptime_int => return if (T == u64) writeU64(writer, value) else writeI64(writer, @intCast(value)), @@ -322,9 +322,9 @@ pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void { } pub fn fmt(buf: []u8, value: anytype) []const u8 { - var stream = fixedBufferStream(buf); - writeValue(stream.writer(), value) catch unreachable; - return stream.getWritten(); + var writer: Io.Writer = .fixed(buf); + writeValue(&writer, value) catch unreachable; + return writer.buffered(); } const CborType = struct { type: u8, minor: u5, major: u3 }; @@ -1323,109 +1323,107 @@ pub fn extract_cbor(dest: *[]const u8) CborExtractor { return CborExtractor.init(dest); } -pub fn JsonStream(comptime T: type) type { - return JsonStreamWriter(T.Writer); -} - -pub fn JsonStreamWriter(comptime Writer: type) type { - return struct { - const JsonWriter = json.WriteStream(Writer, .{ .checked_to_fixed_depth = 256 }); - - fn jsonWriteArray(w: *JsonWriter, iter: *[]const u8, minor: u5) !void { - var count = try decodePInt(iter, minor); - try w.beginArray(); - while (count > 0) : (count -= 1) { - try jsonWriteValue(w, iter); - } - try w.endArray(); +pub const JsonWriter = struct { + fn jsonWriteArray(w: *json.Stringify, iter: *[]const u8, minor: u5) !void { + var count = try decodePInt(iter, minor); + try w.beginArray(); + while (count > 0) : (count -= 1) { + try jsonWriteValue(w, iter); } + try w.endArray(); + } - fn jsonWriteMap(w: *JsonWriter, iter: *[]const u8, minor: u5) !void { - var count = try decodePInt(iter, minor); - try w.beginObject(); - while (count > 0) : (count -= 1) { - const t = try decodeType(iter); - if (t.major != 3) return error.InvalidType; - try w.objectField(try decodeString(iter, t.minor)); - try jsonWriteValue(w, iter); - } - try w.endObject(); - } - - pub fn jsonWriteValue(w: *JsonWriter, iter: *[]const u8) (JsonEncodeError || Writer.Error)!void { + fn jsonWriteMap(w: *json.Stringify, iter: *[]const u8, minor: u5) !void { + var count = try decodePInt(iter, minor); + try w.beginObject(); + while (count > 0) : (count -= 1) { const t = try decodeType(iter); - switch (t.type) { - cbor_magic_false => return w.write(false), - cbor_magic_true => return w.write(true), - cbor_magic_null => return w.write(null), - cbor_magic_float16 => return w.write(try decodeFloat(f16, iter, t)), - cbor_magic_float32 => return w.write(try decodeFloat(f32, iter, t)), - cbor_magic_float64 => return w.write(try decodeFloat(f64, iter, t)), - else => {}, - } - return switch (t.major) { - 0 => w.write(try decodePInt(iter, t.minor)), // positive integer - 1 => w.write(try decodeNInt(iter, t.minor)), // negative integer - 2 => error.UnsupportedType, // bytes - 3 => w.write(try decodeString(iter, t.minor)), // string - 4 => jsonWriteArray(w, iter, t.minor), // array - 5 => jsonWriteMap(w, iter, t.minor), // map - else => error.JsonIncompatibleType, - }; + if (t.major != 3) return error.InvalidType; + try w.objectField(try decodeString(iter, t.minor)); + try jsonWriteValue(w, iter); } + try w.endObject(); + } + + pub fn jsonWriteValue(w: *json.Stringify, iter: *[]const u8) (JsonEncodeError || Io.Writer.Error)!void { + const t = try decodeType(iter); + switch (t.type) { + cbor_magic_false => return w.write(false), + cbor_magic_true => return w.write(true), + cbor_magic_null => return w.write(null), + cbor_magic_float16 => return w.write(try decodeFloat(f16, iter, t)), + cbor_magic_float32 => return w.write(try decodeFloat(f32, iter, t)), + cbor_magic_float64 => return w.write(try decodeFloat(f64, iter, t)), + else => {}, + } + return switch (t.major) { + 0 => w.write(try decodePInt(iter, t.minor)), // positive integer + 1 => w.write(try decodeNInt(iter, t.minor)), // negative integer + 2 => error.UnsupportedType, // bytes + 3 => w.write(try decodeString(iter, t.minor)), // string + 4 => jsonWriteArray(w, iter, t.minor), // array + 5 => jsonWriteMap(w, iter, t.minor), // map + else => error.JsonIncompatibleType, + }; + } +}; + +pub fn toJson(cbor_buf: []const u8, json_buf: []u8) (JsonEncodeError || Io.Writer.Error)![]const u8 { + var writer: Io.Writer = .fixed(json_buf); + var s: json.Stringify = .{ .writer = &writer }; + var iter: []const u8 = cbor_buf; + try JsonWriter.jsonWriteValue(&s, &iter); + return writer.buffered(); +} + +pub fn toJsonWriter(cbor_buf: []const u8, writer: *Io.Writer, options: std.json.StringifyOptions) !void { + var s: json.Stringify = .{ .writer = writer, .options = options }; + var iter: []const u8 = cbor_buf; + try JsonWriter.jsonWriteValue(&s, &iter); +} + +pub fn toJsonAlloc(a: std.mem.Allocator, cbor_buf: []const u8) (JsonEncodeError || Io.Writer.Error)![]const u8 { + var w = Io.Writer.Allocating.init(a); + defer w.deinit(); + var s: json.Stringify = .{ .writer = &w.writer }; + var iter: []const u8 = cbor_buf; + try JsonWriter.jsonWriteValue(&s, &iter); + return w.toOwnedSlice(); +} + +pub fn toJsonPretty(cbor_buf: []const u8, json_buf: []u8) (JsonEncodeError || Io.Writer.Error)![]const u8 { + var writer: Io.Writer = .fixed(json_buf); + var s: json.Stringify = .{ + .writer = &writer, + .options = .{ .whitespace = .indent_1 }, }; -} - -pub fn toJson(cbor_buf: []const u8, json_buf: []u8) (JsonEncodeError || error{NoSpaceLeft})![]const u8 { - var fbs = fixedBufferStream(json_buf); - var s = json.writeStream(fbs.writer(), .{}); var iter: []const u8 = cbor_buf; - try JsonStream(@TypeOf(fbs)).jsonWriteValue(&s, &iter); - return fbs.getWritten(); + try JsonWriter.jsonWriteValue(&s, &iter); + return writer.buffered(); } -pub fn toJsonWriter(cbor_buf: []const u8, writer: anytype, options: std.json.StringifyOptions) !void { - var s = json.writeStream(writer, options); - var iter: []const u8 = cbor_buf; - try JsonStreamWriter(@TypeOf(writer)).jsonWriteValue(&s, &iter); -} - -pub fn toJsonAlloc(a: std.mem.Allocator, cbor_buf: []const u8) (JsonEncodeError)![]const u8 { - var buf = std.ArrayList(u8).init(a); +pub fn toJsonPrettyAlloc(a: std.mem.Allocator, cbor_buf: []const u8) (JsonEncodeError || Io.Writer.Error)![]const u8 { + var buf = Io.Writer.Allocating.init(a); defer buf.deinit(); - var s = json.writeStream(buf.writer(), .{}); + var s: json.Stringify = .{ + .writer = &buf.writer, + .options = .{ .whitespace = .indent_1 }, + }; var iter: []const u8 = cbor_buf; - try JsonStream(@TypeOf(buf)).jsonWriteValue(&s, &iter); - return buf.toOwnedSlice(); -} - -pub fn toJsonPretty(cbor_buf: []const u8, json_buf: []u8) (JsonEncodeError || error{NoSpaceLeft})![]const u8 { - var fbs = fixedBufferStream(json_buf); - var s = json.writeStream(fbs.writer(), .{ .whitespace = .indent_1 }); - var iter: []const u8 = cbor_buf; - try JsonStream(@TypeOf(fbs)).jsonWriteValue(&s, &iter); - return fbs.getWritten(); -} - -pub fn toJsonPrettyAlloc(a: std.mem.Allocator, cbor_buf: []const u8) JsonEncodeError![]const u8 { - var buf = std.ArrayList(u8).init(a); - defer buf.deinit(); - var s = json.writeStream(buf.writer(), .{ .whitespace = .indent_1 }); - var iter: []const u8 = cbor_buf; - try JsonStream(@TypeOf(buf)).jsonWriteValue(&s, &iter); + try JsonWriter.jsonWriteValue(&s, &iter); return buf.toOwnedSlice(); } pub fn toJsonOptsAlloc(a: std.mem.Allocator, cbor_buf: []const u8, opts: std.json.StringifyOptions) JsonEncodeError![]const u8 { - var buf = std.ArrayList(u8).init(a); + var buf = Io.Writer.Allocating.init(a); defer buf.deinit(); - var s = json.writeStream(buf.writer(), opts); + var s: json.Stringify = .{ .writer = &buf.writer, .options = opts }; var iter: []const u8 = cbor_buf; - try JsonStream(@TypeOf(buf)).jsonWriteValue(&s, &iter); + try JsonWriter.jsonWriteValue(&s, &iter); return buf.toOwnedSlice(); } -pub fn writeJsonValue(writer: anytype, value: json.Value) !void { +pub fn writeJsonValue(writer: *Io.Writer, value: json.Value) !void { try switch (value) { .array => |_| unreachable, .object => |_| unreachable, @@ -1434,11 +1432,11 @@ pub fn writeJsonValue(writer: anytype, value: json.Value) !void { }; } -fn jsonScanUntil(writer: anytype, scanner: *json.Scanner, end_token: anytype) (JsonDecodeError || @TypeOf(writer).Error)!usize { +fn jsonScanUntil(writer: *Io.Writer, scanner: *json.Scanner, end_token: anytype) (JsonDecodeError || Io.Writer.Error)!usize { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); var sfa = std.heap.stackFallback(1024, arena.allocator()); - var partial = std.ArrayList(u8).init(sfa.get()); + var partial = std.array_list.Managed(u8).init(sfa.get()); var count: usize = 0; var token = try scanner.next(); @@ -1494,44 +1492,43 @@ fn jsonScanUntil(writer: anytype, scanner: *json.Scanner, end_token: anytype) (J return count; } -fn writeJsonArray(writer_: anytype, scanner: *json.Scanner) (JsonDecodeError || @TypeOf(writer_).Error)!void { +fn writeJsonArray(writer_: *Io.Writer, scanner: *json.Scanner) (JsonDecodeError || Io.Writer.Error)!void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); var sfa = std.heap.stackFallback(1024, arena.allocator()); - var buf = std.ArrayList(u8).init(sfa.get()); - const writer = buf.writer(); + var buf = Io.Writer.Allocating.init(sfa.get()); + const writer = &buf.writer; const count = try jsonScanUntil(writer, scanner, .array_end); try writeArrayHeader(writer_, count); - try writer_.writeAll(buf.items); + try writer_.writeAll(buf.written()); } -fn writeJsonObject(writer_: anytype, scanner: *json.Scanner) (JsonDecodeError || @TypeOf(writer_).Error)!void { +fn writeJsonObject(writer_: *Io.Writer, scanner: *json.Scanner) (JsonDecodeError || Io.Writer.Error)!void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); var sfa = std.heap.stackFallback(1024, arena.allocator()); - var buf = std.ArrayList(u8).init(sfa.get()); - const writer = buf.writer(); + var buf = Io.Writer.Allocating.init(sfa.get()); + const writer = &buf.writer; const count = try jsonScanUntil(writer, scanner, .object_end); try writeMapHeader(writer_, count / 2); - try writer_.writeAll(buf.items); + try writer_.writeAll(buf.written()); } -pub fn fromJson(json_buf: []const u8, cbor_buf: []u8) (JsonDecodeError || error{NoSpaceLeft})![]const u8 { +pub fn fromJson(json_buf: []const u8, cbor_buf: []u8) (JsonDecodeError || Io.Writer.Error)![]const u8 { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); var sfa = std.heap.stackFallback(1024, arena.allocator()); - var stream = fixedBufferStream(cbor_buf); - const writer = stream.writer(); + var writer: Io.Writer = .fixed(cbor_buf); var scanner = json.Scanner.initCompleteInput(sfa.get(), json_buf); defer scanner.deinit(); - _ = try jsonScanUntil(writer, &scanner, .end_of_document); - return stream.getWritten(); + _ = try jsonScanUntil(&writer, &scanner, .end_of_document); + return writer.buffered(); } pub fn fromJsonAlloc(a: std.mem.Allocator, json_buf: []const u8) JsonDecodeError![]const u8 { - var stream = std.ArrayList(u8).init(a); + var stream = std.array_list.Managed(u8).init(a); defer stream.deinit(); const writer = stream.writer(); diff --git a/test/tests.zig b/test/tests.zig index c8672a0..653d9cb 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1,6 +1,7 @@ const std = @import("std"); const cbor_mod = @import("cbor"); +const Io = std.Io; const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const expectEqualDeep = std.testing.expectEqualDeep; @@ -206,31 +207,29 @@ test "cbor.nulls" { test "cbor.stream_writer" { var buf: [128]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - const writer = stream.writer(); - try writeArrayHeader(writer, 5); - try writeValue(writer, "five"); - try writeValue(writer, 5); - try writeValue(writer, "four"); - try writeValue(writer, 4); - try writeArrayHeader(writer, 2); - try writeValue(writer, "three"); - try writeValue(writer, 3); - try expect(try match(stream.getWritten(), .{ "five", 5, "four", 4, .{ "three", 3 } })); + var writer: Io.Writer = .fixed(&buf); + try writeArrayHeader(&writer, 5); + try writeValue(&writer, "five"); + try writeValue(&writer, 5); + try writeValue(&writer, "four"); + try writeValue(&writer, 4); + try writeArrayHeader(&writer, 2); + try writeValue(&writer, "three"); + try writeValue(&writer, 3); + try expect(try match(writer.buffered(), .{ "five", 5, "four", 4, .{ "three", 3 } })); } test "cbor.stream_object_writer" { var buf: [128]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - const writer = stream.writer(); - try writeMapHeader(writer, 3); - try writeValue(writer, "five"); - try writeValue(writer, 5); - try writeValue(writer, "four"); - try writeValue(writer, 4); - try writeValue(writer, "three"); - try writeValue(writer, 3); - const obj = stream.getWritten(); + var writer: Io.Writer = .fixed(&buf); + try writeMapHeader(&writer, 3); + try writeValue(&writer, "five"); + try writeValue(&writer, 5); + try writeValue(&writer, "four"); + try writeValue(&writer, 4); + try writeValue(&writer, "three"); + try writeValue(&writer, 3); + const obj = writer.buffered(); var json_buf: [128]u8 = undefined; const json = try toJson(obj, &json_buf); try expectEqualDeep(json, @@ -521,29 +520,27 @@ test "cbor.extract_cbor f64" { const json2 = try toJsonAlloc(std.testing.allocator, sub); defer std.testing.allocator.free(json2); - try expectEqualStrings("[9.689138531684875e-1]", json2); + try expectEqualStrings("[0.9689138531684875]", json2); } test "cbor.writeValue enum" { const TestEnum = enum { a, b }; var buf: [128]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - const writer = stream.writer(); - try writeValue(writer, TestEnum.a); + var writer: Io.Writer = .fixed(&buf); + try writeValue(&writer, TestEnum.a); const expected = @tagName(TestEnum.a); var m = std.json.Value{ .null = {} }; - try expect(try match(stream.getWritten(), extract(&m))); + try expect(try match(writer.buffered(), extract(&m))); try expectEqualStrings(expected, m.string); } test "cbor.extract enum" { const TestEnum = enum { a, b }; var buf: [128]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - const writer = stream.writer(); - try writeValue(writer, TestEnum.a); + var writer: Io.Writer = .fixed(&buf); + try writeValue(&writer, TestEnum.a); var m: TestEnum = undefined; - try expect(try match(stream.getWritten(), extract(&m))); + try expect(try match(writer.buffered(), extract(&m))); try expectEqualStrings(@tagName(m), @tagName(TestEnum.a)); try expect(m == .a); } @@ -551,12 +548,11 @@ test "cbor.extract enum" { test "cbor.writeValue tagged union" { const TestUnion = union(enum) { a: f32, b: []const u8 }; var buf: [128]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - const writer = stream.writer(); - try writeValue(writer, TestUnion{ .b = "should work" }); + var writer: Io.Writer = .fixed(&buf); + try writeValue(&writer, TestUnion{ .b = "should work" }); var tagName = std.json.Value{ .null = {} }; var value = std.json.Value{ .null = {} }; - var iter: []const u8 = stream.getWritten(); + var iter: []const u8 = writer.buffered(); try expect(try matchValue(&iter, .{ extract(&tagName), extract(&value) })); try expectEqualStrings(@tagName(TestUnion.b), tagName.string); try expectEqualStrings("should work", value.string); @@ -565,13 +561,12 @@ test "cbor.writeValue tagged union" { test "cbor.writeValue tagged union no payload types" { const TestUnion = union(enum) { a, b }; var buf: [128]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - const writer = stream.writer(); - try writeValue(writer, TestUnion.b); + var writer: Io.Writer = .fixed(&buf); + try writeValue(&writer, TestUnion.b); - try expect(try match(stream.getWritten(), @tagName(TestUnion.b))); + try expect(try match(writer.buffered(), @tagName(TestUnion.b))); var tagName = std.json.Value{ .null = {} }; - try expect(try match(stream.getWritten(), extract(&tagName))); + try expect(try match(writer.buffered(), extract(&tagName))); try expectEqualStrings(@tagName(TestUnion.b), tagName.string); } @@ -586,26 +581,24 @@ test "cbor.writeValue nested union json" { }, }; var buf: [256]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - const writer = stream.writer(); + var writer: Io.Writer = .fixed(&buf); var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); - try writeValue(writer, TestUnion{ .c = &.{ .{ .d = 1.5 }, .{ .e = 5 }, .f } }); + try writeValue(&writer, TestUnion{ .c = &.{ .{ .d = 1.5 }, .{ .e = 5 }, .f } }); var json_buf: [128]u8 = undefined; - const json = try toJson(stream.getWritten(), &json_buf); + const json = try toJson(writer.buffered(), &json_buf); try expectEqualStrings(json, - \\["c",[["d",1.5e0],["e",5],["f"]]] + \\["c",[["d",1.5],["e",5],["f"]]] ); } test "cbor.extract tagged union" { const TestUnion = union(enum) { a: f32, b: []const u8 }; var buf: [128]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - const writer = stream.writer(); - try writeValue(writer, TestUnion{ .b = "should work" }); + var writer: Io.Writer = .fixed(&buf); + try writeValue(&writer, TestUnion{ .b = "should work" }); var m: TestUnion = undefined; - try expect(try match(stream.getWritten(), extract(&m))); + try expect(try match(writer.buffered(), extract(&m))); try expectEqualDeep(TestUnion{ .b = "should work" }, m); } @@ -620,25 +613,23 @@ test "cbor.extract nested union" { }, }; var buf: [256]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - const writer = stream.writer(); + var writer: Io.Writer = .fixed(&buf); var arena = std.heap.ArenaAllocator.init(std.testing.allocator); const allocator = arena.allocator(); defer arena.deinit(); - try writeValue(writer, TestUnion{ .c = &.{ .{ .d = 1.5 }, .{ .e = 5 }, .f } }); + try writeValue(&writer, TestUnion{ .c = &.{ .{ .d = 1.5 }, .{ .e = 5 }, .f } }); var m: TestUnion = undefined; - try expect(try match(stream.getWritten(), extractAlloc(&m, allocator))); + try expect(try match(writer.buffered(), extractAlloc(&m, allocator))); try expectEqualDeep(TestUnion{ .c = &.{ .{ .d = 1.5 }, .{ .e = 5 }, .f } }, m); } test "cbor.extract struct no fields" { const TestStruct = struct {}; var buf: [128]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - const writer = stream.writer(); - try writeValue(writer, TestStruct{}); + var writer: Io.Writer = .fixed(&buf); + try writeValue(&writer, TestStruct{}); var m: TestStruct = undefined; - try expect(try match(stream.getWritten(), extract(&m))); + try expect(try match(writer.buffered(), extract(&m))); try expectEqualDeep(TestStruct{}, m); } @@ -655,7 +646,7 @@ test "cbor.extract_cbor struct" { var json_buf: [256]u8 = undefined; const json = try toJson(map_cbor, &json_buf); try expectEqualStrings(json, - \\{"a":1.5e0,"b":"hello"} + \\{"a":1.5,"b":"hello"} ); } @@ -665,20 +656,19 @@ test "cbor.extract struct" { b: []const u8, }; var buf: [128]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - const writer = stream.writer(); + var writer: Io.Writer = .fixed(&buf); const v = TestStruct{ .a = 1.5, .b = "hello" }; - try writeValue(writer, v); + try writeValue(&writer, v); var obj: TestStruct = undefined; var json_buf: [256]u8 = undefined; - var iter: []const u8 = stream.getWritten(); + var iter: []const u8 = writer.buffered(); const t = try decodeType(&iter); try expectEqual(5, t.major); - const json = try toJson(stream.getWritten(), &json_buf); + const json = try toJson(writer.buffered(), &json_buf); try expectEqualStrings(json, - \\{"a":1.5e0,"b":"hello"} + \\{"a":1.5,"b":"hello"} ); - try expect(try match(stream.getWritten(), extract(&obj))); + try expect(try match(writer.buffered(), extract(&obj))); try expectEqual(1.5, obj.a); try expectEqualStrings("hello", obj.b); } @@ -689,15 +679,14 @@ test "cbor.extractAlloc struct" { b: []const u8, }; var buf: [128]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); + var writer: Io.Writer = .fixed(&buf); var arena = std.heap.ArenaAllocator.init(std.testing.allocator); const allocator = arena.allocator(); defer arena.deinit(); - const writer = stream.writer(); const v = TestStruct{ .a = 1.5, .b = "hello" }; - try writeValue(writer, v); + try writeValue(&writer, v); var obj: TestStruct = undefined; - try expect(try match(stream.getWritten(), extractAlloc(&obj, allocator))); + try expect(try match(writer.buffered(), extractAlloc(&obj, allocator))); try expectEqual(1.5, obj.a); try expectEqualStrings("hello", obj.b); } @@ -705,13 +694,12 @@ test "cbor.extractAlloc struct" { fn test_value_write_and_extract(T: type, value: T) !void { const test_value: T = value; var buf: [1024]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - const writer = stream.writer(); - try writeValue(writer, test_value); + var writer: Io.Writer = .fixed(&buf); + try writeValue(&writer, test_value); var result_value: T = undefined; - var iter: []const u8 = stream.getWritten(); + var iter: []const u8 = writer.buffered(); try expect(try matchValue(&iter, extract(&result_value))); switch (comptime @typeInfo(T)) {