diff --git a/README.md b/README.md index e719d8f..21a64d0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,2 @@ # 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) diff --git a/build.zig b/build.zig index 12cf970..aab561e 100644 --- a/build.zig +++ b/build.zig @@ -9,11 +9,9 @@ pub fn build(b: *std.Build) void { }); const tests = b.addTest(.{ - .root_module = b.createModule(.{ - .root_source_file = b.path("test/tests.zig"), - .target = target, - .optimize = optimize, - }), + .root_source_file = b.path("test/tests.zig"), + .target = target, + .optimize = optimize, }); tests.root_module.addImport("cbor", cbor_mod); diff --git a/src/cbor.zig b/src/cbor.zig index 0760cec..2d791c8 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,16 +15,7 @@ pub const Error = error{ IntegerTooSmall, InvalidType, TooShort, - WriteFailed, OutOfMemory, - InvalidFloatType, - InvalidArrayType, - InvalidMapType, - InvalidPIntType, - InvalidUnion, - JsonIncompatibleType, - NotAnObject, - BadArrayAllocExtract, }; pub const JsonEncodeError = (Error || error{ @@ -87,11 +78,11 @@ fn isMore(value: anytype) bool { return if (comptime @TypeOf(value) == value_type) value == value_type.more else false; } -fn write(writer: *Io.Writer, value: u8) Io.Writer.Error!void { +fn write(writer: anytype, value: u8) @TypeOf(writer).Error!void { _ = try writer.write(&[_]u8{value}); } -fn writeTypedVal(writer: *Io.Writer, type_: u8, value: u64) Io.Writer.Error!void { +fn writeTypedVal(writer: anytype, type_: u8, value: u64) @TypeOf(writer).Error!void { const t: u8 = type_ << 5; if (value < 24) { try write(writer, t | @as(u8, @truncate(value))); @@ -121,35 +112,35 @@ fn writeTypedVal(writer: *Io.Writer, type_: u8, value: u64) Io.Writer.Error!void } } -pub fn writeArrayHeader(writer: *Io.Writer, sz: usize) Io.Writer.Error!void { +pub fn writeArrayHeader(writer: anytype, sz: usize) @TypeOf(writer).Error!void { return writeTypedVal(writer, cbor_magic_type_array, sz); } -pub fn writeMapHeader(writer: *Io.Writer, sz: usize) Io.Writer.Error!void { +pub fn writeMapHeader(writer: anytype, sz: usize) @TypeOf(writer).Error!void { return writeTypedVal(writer, cbor_magic_type_map, sz); } -pub fn writeArray(writer: *Io.Writer, args: anytype) Io.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)); } -fn writeI64(writer: *Io.Writer, value: i64) Io.Writer.Error!void { +fn writeI64(writer: anytype, value: i64) @TypeOf(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: *Io.Writer, value: u64) Io.Writer.Error!void { +fn writeU64(writer: anytype, value: u64) @TypeOf(writer).Error!void { return writeTypedVal(writer, 0, value); } -fn writeF16(writer: *Io.Writer, value: f16) Io.Writer.Error!void { +fn writeF16(writer: anytype, value: f16) @TypeOf(writer).Error!void { try write(writer, cbor_magic_float16); const value_bytes = std.mem.asBytes(&value); switch (native_endian) { @@ -161,7 +152,7 @@ fn writeF16(writer: *Io.Writer, value: f16) Io.Writer.Error!void { } } -fn writeF32(writer: *Io.Writer, value: f32) Io.Writer.Error!void { +fn writeF32(writer: anytype, value: f32) @TypeOf(writer).Error!void { try write(writer, cbor_magic_float32); const value_bytes = std.mem.asBytes(&value); switch (native_endian) { @@ -175,7 +166,7 @@ fn writeF32(writer: *Io.Writer, value: f32) Io.Writer.Error!void { } } -fn writeF64(writer: *Io.Writer, value: f64) Io.Writer.Error!void { +fn writeF64(writer: anytype, value: f64) @TypeOf(writer).Error!void { try write(writer, cbor_magic_float64); const value_bytes = std.mem.asBytes(&value); switch (native_endian) { @@ -193,66 +184,29 @@ fn writeF64(writer: *Io.Writer, value: f64) Io.Writer.Error!void { } } -fn writeString(writer: *Io.Writer, s: []const u8) Io.Writer.Error!void { +fn writeString(writer: anytype, s: []const u8) @TypeOf(writer).Error!void { try writeTypedVal(writer, 3, s.len); _ = try writer.write(s); } -fn writeBool(writer: *Io.Writer, value: bool) Io.Writer.Error!void { +fn writeBool(writer: anytype, value: bool) @TypeOf(writer).Error!void { return write(writer, if (value) cbor_magic_true else cbor_magic_false); } -fn writeNull(writer: *Io.Writer) Io.Writer.Error!void { +fn writeNull(writer: anytype) @TypeOf(writer).Error!void { return write(writer, cbor_magic_null); } -fn writeErrorset(writer: *Io.Writer, err: anyerror) Io.Writer.Error!void { +fn writeErrorset(writer: anytype, err: anyerror) @TypeOf(writer).Error!void { var buf: [256]u8 = undefined; - 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()); + 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()); } -fn writeEnum(writer: *Io.Writer, value: anytype) Io.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: *Io.Writer, value: anytype, info: std.builtin.Type.Union) Io.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: *Io.Writer, value: anytype) Io.Writer.Error!void { +pub fn writeValue(writer: anytype, value: anytype) @TypeOf(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)), @@ -260,11 +214,23 @@ pub fn writeValue(writer: *Io.Writer, value: anytype) Io.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| return writeUnion(writer, value, info), - .@"struct" => |info| { - if (std.meta.hasFn(T, "cborEncode")) { - return value.cborEncode(writer); + .@"union" => |info| { + 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)}); } + }, + .@"struct" => |info| { if (info.is_tuple) { if (info.fields.len == 0) return writeNull(writer); try writeArrayHeader(writer, info.fields.len); @@ -311,20 +277,14 @@ pub fn writeValue(writer: *Io.Writer, value: anytype) Io.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"), } } pub fn fmt(buf: []u8, value: anytype) []const u8 { - var writer: Io.Writer = .fixed(buf); - writeValue(&writer, value) catch unreachable; - return writer.buffered(); + var stream = fixedBufferStream(buf); + writeValue(stream.writer(), value) catch unreachable; + return stream.getWritten(); } const CborType = struct { type: u8, minor: u5, major: u3 }; @@ -338,7 +298,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) error{TooShort}!u64 { +fn decodeUIntLengthRecurse(iter: *[]const u8, length: usize, acc: u64) !u64 { if (iter.len < 1) return error.TooShort; const v: u8 = iter.*[0]; @@ -355,14 +315,14 @@ fn decodeUIntLength(iter: *[]const u8, length: usize) !u64 { return decodeUIntLengthRecurse(iter, length, 0); } -fn decodePInt(iter: *[]const u8, minor: u5) error{ TooShort, InvalidPIntType }!u64 { +fn decodePInt(iter: *[]const u8, minor: u5) !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.InvalidPIntType, + else => error.InvalidType, }; } @@ -370,21 +330,21 @@ 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{ TooShort, InvalidMapType, InvalidPIntType }!usize { +pub fn decodeMapHeader(iter: *[]const u8) Error!usize { const t = try decodeType(iter); if (t.type == cbor_magic_null) return 0; if (t.major != 5) - return error.InvalidMapType; + return error.InvalidType; return @intCast(try decodePInt(iter, t.minor)); } -pub fn decodeArrayHeader(iter: *[]const u8) error{ TooShort, InvalidArrayType, InvalidPIntType }!usize { +pub fn decodeArrayHeader(iter: *[]const u8) Error!usize { const t = try decodeType(iter); if (t.type == cbor_magic_null) return 0; if (t.major != 4) - return error.InvalidArrayType; + return error.InvalidType; return @intCast(try decodePInt(iter, t.minor)); } @@ -489,7 +449,7 @@ fn decodeFloat(comptime T: type, iter_: *[]const u8, t: CborType) Error!T { v = @floatCast(f); iter = iter[8..]; }, - else => return error.InvalidFloatType, + else => return error.InvalidType, } iter_.* = iter; return v; @@ -523,17 +483,6 @@ 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); @@ -588,191 +537,6 @@ 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) @@ -901,20 +665,14 @@ 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"), }; } @@ -985,11 +743,7 @@ 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 = decodeArrayHeader(&iter) catch |e| switch (e) { - error.InvalidArrayType => return false, - error.InvalidPIntType => return e, - error.TooShort => return e, - }; + var n = try decodeArrayHeader(&iter); inline for (info.fields) |f| { const value = @field(arr, f.name); if (isMore(value)) @@ -1014,40 +768,13 @@ 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 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); if (t.type == cbor_magic_null) return true; if (t.major != 5) - return error.NotAnObject; + return error.InvalidType; const ret = try decodeJsonObject(&iter, t.minor, obj); if (ret) iter_.* = iter; return ret; @@ -1059,11 +786,7 @@ pub fn match(buf: []const u8, pattern: anytype) Error!bool { } fn extractError(comptime T: type) noreturn { - @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"); + @compileError("cannot extract type '" ++ @typeName(T) ++ "' from cbor stream"); } fn hasExtractorTag(info: anytype) bool { @@ -1082,110 +805,6 @@ 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 { - 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 => 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"), - }, - } - } - } - }; -} - const JsonValueExtractor = struct { dest: *T, const Self = @This(); @@ -1244,10 +863,6 @@ 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; @@ -1257,14 +872,7 @@ 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 => 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)), - }, + else => extractError(T), } } }; @@ -1273,10 +881,7 @@ 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 if (isExtractableAlloc(T_type_info.pointer.child)) - extractErrorAlloc(T_type_info.pointer.child) - else - Extractor(T_type_info.pointer.child); + return Extractor(T_type_info.pointer.child); } pub fn extract(dest: anytype) ExtractorType(@TypeOf(dest)) { @@ -1287,21 +892,6 @@ 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(); @@ -1323,107 +913,109 @@ pub fn extract_cbor(dest: *[]const u8) CborExtractor { return CborExtractor.init(dest); } -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(); - } +pub fn JsonStream(comptime T: type) type { + return JsonStreamWriter(T.Writer); +} - 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) { +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(); + } + + 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 { const t = try decodeType(iter); - if (t.major != 3) return error.InvalidType; - try w.objectField(try decodeString(iter, t.minor)); - try jsonWriteValue(w, 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.InvalidType, + }; } - 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 }, }; - var iter: []const u8 = cbor_buf; - try JsonWriter.jsonWriteValue(&s, &iter); - return writer.buffered(); } -pub fn toJsonPrettyAlloc(a: std.mem.Allocator, cbor_buf: []const u8) (JsonEncodeError || Io.Writer.Error)![]const u8 { - var buf = Io.Writer.Allocating.init(a); +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(); +} + +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); defer buf.deinit(); - var s: json.Stringify = .{ - .writer = &buf.writer, - .options = .{ .whitespace = .indent_1 }, - }; + var s = json.writeStream(buf.writer(), .{}); var iter: []const u8 = cbor_buf; - try JsonWriter.jsonWriteValue(&s, &iter); + 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); return buf.toOwnedSlice(); } pub fn toJsonOptsAlloc(a: std.mem.Allocator, cbor_buf: []const u8, opts: std.json.StringifyOptions) JsonEncodeError![]const u8 { - var buf = Io.Writer.Allocating.init(a); + var buf = std.ArrayList(u8).init(a); defer buf.deinit(); - var s: json.Stringify = .{ .writer = &buf.writer, .options = opts }; + var s = json.writeStream(buf.writer(), opts); var iter: []const u8 = cbor_buf; - try JsonWriter.jsonWriteValue(&s, &iter); + try JsonStream(@TypeOf(buf)).jsonWriteValue(&s, &iter); return buf.toOwnedSlice(); } -pub fn writeJsonValue(writer: *Io.Writer, value: json.Value) !void { +pub fn writeJsonValue(writer: anytype, value: json.Value) !void { try switch (value) { .array => |_| unreachable, .object => |_| unreachable, @@ -1432,11 +1024,11 @@ pub fn writeJsonValue(writer: *Io.Writer, value: json.Value) !void { }; } -fn jsonScanUntil(writer: *Io.Writer, scanner: *json.Scanner, end_token: anytype) (JsonDecodeError || Io.Writer.Error)!usize { +fn jsonScanUntil(writer: anytype, scanner: *json.Scanner, end_token: anytype) (JsonDecodeError || @TypeOf(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.array_list.Managed(u8).init(sfa.get()); + var partial = std.ArrayList(u8).init(sfa.get()); var count: usize = 0; var token = try scanner.next(); @@ -1492,43 +1084,44 @@ fn jsonScanUntil(writer: *Io.Writer, scanner: *json.Scanner, end_token: anytype) return count; } -fn writeJsonArray(writer_: *Io.Writer, scanner: *json.Scanner) (JsonDecodeError || Io.Writer.Error)!void { +fn writeJsonArray(writer_: anytype, scanner: *json.Scanner) (JsonDecodeError || @TypeOf(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 = Io.Writer.Allocating.init(sfa.get()); - const writer = &buf.writer; + var buf = std.ArrayList(u8).init(sfa.get()); + const writer = buf.writer(); const count = try jsonScanUntil(writer, scanner, .array_end); try writeArrayHeader(writer_, count); - try writer_.writeAll(buf.written()); + try writer_.writeAll(buf.items); } -fn writeJsonObject(writer_: *Io.Writer, scanner: *json.Scanner) (JsonDecodeError || Io.Writer.Error)!void { +fn writeJsonObject(writer_: anytype, scanner: *json.Scanner) (JsonDecodeError || @TypeOf(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 = Io.Writer.Allocating.init(sfa.get()); - const writer = &buf.writer; + var buf = std.ArrayList(u8).init(sfa.get()); + const writer = buf.writer(); const count = try jsonScanUntil(writer, scanner, .object_end); try writeMapHeader(writer_, count / 2); - try writer_.writeAll(buf.written()); + try writer_.writeAll(buf.items); } -pub fn fromJson(json_buf: []const u8, cbor_buf: []u8) (JsonDecodeError || Io.Writer.Error)![]const u8 { +pub fn fromJson(json_buf: []const u8, cbor_buf: []u8) (JsonDecodeError || error{NoSpaceLeft})![]const u8 { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); var sfa = std.heap.stackFallback(1024, arena.allocator()); - var writer: Io.Writer = .fixed(cbor_buf); + var stream = fixedBufferStream(cbor_buf); + const writer = stream.writer(); var scanner = json.Scanner.initCompleteInput(sfa.get(), json_buf); defer scanner.deinit(); - _ = try jsonScanUntil(&writer, &scanner, .end_of_document); - return writer.buffered(); + _ = try jsonScanUntil(writer, &scanner, .end_of_document); + return stream.getWritten(); } pub fn fromJsonAlloc(a: std.mem.Allocator, json_buf: []const u8) JsonDecodeError![]const u8 { - var stream = std.array_list.Managed(u8).init(a); + var stream = std.ArrayList(u8).init(a); defer stream.deinit(); const writer = stream.writer(); diff --git a/test/tests.zig b/test/tests.zig index 653d9cb..1d27410 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1,11 +1,9 @@ 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; -const expectEqualStrings = std.testing.expectEqualStrings; const expectError = std.testing.expectError; const fmt = cbor_mod.fmt; @@ -25,7 +23,6 @@ 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; @@ -207,29 +204,31 @@ test "cbor.nulls" { test "cbor.stream_writer" { var buf: [128]u8 = undefined; - 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 } })); + 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 } })); } test "cbor.stream_object_writer" { var buf: [128]u8 = undefined; - 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 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 json_buf: [128]u8 = undefined; const json = try toJson(obj, &json_buf); try expectEqualDeep(json, @@ -520,253 +519,5 @@ test "cbor.extract_cbor f64" { const json2 = try toJsonAlloc(std.testing.allocator, sub); defer std.testing.allocator.free(json2); - try expectEqualStrings("[0.9689138531684875]", json2); -} - -test "cbor.writeValue enum" { - const TestEnum = enum { a, b }; - var buf: [128]u8 = undefined; - 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(writer.buffered(), extract(&m))); - try expectEqualStrings(expected, m.string); -} - -test "cbor.extract enum" { - const TestEnum = enum { a, b }; - var buf: [128]u8 = undefined; - var writer: Io.Writer = .fixed(&buf); - try writeValue(&writer, TestEnum.a); - var m: TestEnum = undefined; - try expect(try match(writer.buffered(), 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 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 = writer.buffered(); - 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 writer: Io.Writer = .fixed(&buf); - try writeValue(&writer, TestUnion.b); - - try expect(try match(writer.buffered(), @tagName(TestUnion.b))); - var tagName = std.json.Value{ .null = {} }; - try expect(try match(writer.buffered(), 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 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 } }); - var json_buf: [128]u8 = undefined; - const json = try toJson(writer.buffered(), &json_buf); - try expectEqualStrings(json, - \\["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 writer: Io.Writer = .fixed(&buf); - try writeValue(&writer, TestUnion{ .b = "should work" }); - var m: TestUnion = undefined; - try expect(try match(writer.buffered(), 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 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 } }); - var m: TestUnion = undefined; - 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 writer: Io.Writer = .fixed(&buf); - try writeValue(&writer, TestStruct{}); - var m: TestStruct = undefined; - try expect(try match(writer.buffered(), 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.5,"b":"hello"} - ); -} - -test "cbor.extract struct" { - const TestStruct = struct { - a: f32, - b: []const u8, - }; - var buf: [128]u8 = undefined; - var writer: Io.Writer = .fixed(&buf); - 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 = writer.buffered(); - const t = try decodeType(&iter); - try expectEqual(5, t.major); - const json = try toJson(writer.buffered(), &json_buf); - try expectEqualStrings(json, - \\{"a":1.5,"b":"hello"} - ); - try expect(try match(writer.buffered(), 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 writer: Io.Writer = .fixed(&buf); - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - const allocator = arena.allocator(); - defer arena.deinit(); - const v = TestStruct{ .a = 1.5, .b = "hello" }; - try writeValue(&writer, v); - var obj: TestStruct = undefined; - try expect(try match(writer.buffered(), extractAlloc(&obj, allocator))); - 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 writer: Io.Writer = .fixed(&buf); - try writeValue(&writer, test_value); - - var result_value: T = undefined; - - var iter: []const u8 = writer.buffered(); - 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 }); + try expectEqualDeep("[9.689138531684875e-1]", json2); }