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 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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)