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 maxInt = std.math.maxInt; const minInt = std.math.minInt; const json = std.json; const fba = std.heap.FixedBufferAllocator; pub const Error = error{ IntegerTooLarge, IntegerTooSmall, InvalidType, TooShort, WriteFailed, OutOfMemory, InvalidFloatType, InvalidArrayType, InvalidMapType, InvalidPIntType, InvalidUnion, JsonIncompatibleType, NotAnObject, BadArrayAllocExtract, }; pub const JsonEncodeError = (Error || error{ UnsupportedType, }); pub const JsonDecodeError = (Error || error{ BufferUnderrun, SyntaxError, UnexpectedEndOfInput, }); const cbor_magic_null: u8 = 0xf6; const cbor_magic_true: u8 = 0xf5; const cbor_magic_false: u8 = 0xf4; const cbor_magic_float16: u8 = 0xf9; const cbor_magic_float32: u8 = 0xfa; const cbor_magic_float64: u8 = 0xfb; const cbor_magic_type_array: u8 = 4; const cbor_magic_type_map: u8 = 5; const value_type = enum(u8) { number, bytes, string, array, map, tag, boolean, null, float, any, more, unknown, }; pub const number = value_type.number; pub const bytes = value_type.bytes; pub const string = value_type.string; pub const array = value_type.array; pub const map = value_type.map; pub const tag = value_type.tag; pub const boolean = value_type.boolean; pub const null_ = value_type.null; pub const any = value_type.any; pub const more = value_type.more; const null_value_buf = [_]u8{0xF6}; pub const null_value: []const u8 = &null_value_buf; pub fn isNull(val: []const u8) bool { return eql(u8, val, null_value); } fn isAny(value: anytype) bool { return if (comptime @TypeOf(value) == value_type) value == value_type.any else false; } 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 { _ = try writer.write(&[_]u8{value}); } fn writeTypedVal(writer: *Io.Writer, type_: u8, value: u64) Io.Writer.Error!void { const t: u8 = type_ << 5; var buf: [9]u8 = undefined; const slice = if (value < 24) blk: { buf[0] = t | @as(u8, @truncate(value)); break :blk buf[0..1]; } else if (value < 256) blk: { buf[0] = t | 24; buf[1] = @truncate(value); break :blk buf[0..2]; } else if (value < 65536) blk: { buf[0] = t | 25; std.mem.writeInt(u16, buf[1..3], @truncate(value), .big); break :blk buf[0..3]; } else if (value < 4294967296) blk: { buf[0] = t | 26; std.mem.writeInt(u32, buf[1..5], @truncate(value), .big); break :blk buf[0..5]; } else blk: { buf[0] = t | 27; std.mem.writeInt(u64, buf[1..9], value, .big); break :blk buf[0..9]; }; _ = try writer.write(slice); } pub fn writeArrayHeader(writer: *Io.Writer, sz: usize) Io.Writer.Error!void { return writeTypedVal(writer, cbor_magic_type_array, sz); } pub fn writeMapHeader(writer: *Io.Writer, sz: usize) Io.Writer.Error!void { return writeTypedVal(writer, cbor_magic_type_map, sz); } 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; 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 { 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 { return writeTypedVal(writer, 0, value); } fn writeF16(writer: *Io.Writer, value: f16) Io.Writer.Error!void { var buf: [3]u8 = undefined; buf[0] = cbor_magic_float16; std.mem.writeInt(u16, buf[1..3], @bitCast(value), .big); _ = try writer.write(&buf); } fn writeF32(writer: *Io.Writer, value: f32) Io.Writer.Error!void { var buf: [5]u8 = undefined; buf[0] = cbor_magic_float32; std.mem.writeInt(u32, buf[1..5], @bitCast(value), .big); _ = try writer.write(&buf); } fn writeF64(writer: *Io.Writer, value: f64) Io.Writer.Error!void { var buf: [9]u8 = undefined; buf[0] = cbor_magic_float64; std.mem.writeInt(u64, buf[1..9], @bitCast(value), .big); _ = try writer.write(&buf); } 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: *Io.Writer, value: bool) Io.Writer.Error!void { return write(writer, if (value) cbor_magic_true else cbor_magic_false); } fn writeNull(writer: *Io.Writer) Io.Writer.Error!void { return write(writer, cbor_magic_null); } fn writeErrorset(writer: *Io.Writer, err: anyerror) Io.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()); } 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 { const T = @TypeOf(value); switch (@typeInfo(T)) { .int => |info| return if (info.signedness == .unsigned) writeU64(writer, @intCast(value)) else writeI64(writer, @intCast(value)), .comptime_int => return if (value >= 0) writeU64(writer, @intCast(value)) else writeI64(writer, @intCast(value)), .bool => return writeBool(writer, value), .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); } if (info.is_tuple) { if (info.fields.len == 0) return writeNull(writer); try writeArrayHeader(writer, info.fields.len); inline for (info.fields) |f| try writeValue(writer, @field(value, f.name)); } else { if (info.fields.len == 0) return writeNull(writer); try writeMapHeader(writer, info.fields.len); inline for (info.fields) |f| { try writeString(writer, f.name); try writeValue(writer, @field(value, f.name)); } } }, .pointer => |ptr_info| switch (ptr_info.size) { .one => return writeValue(writer, value.*), .many, .c => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"), .slice => { if (ptr_info.child == u8) return writeString(writer, value); if (value.len == 0) return writeNull(writer); try writeArrayHeader(writer, value.len); for (value) |elem| try writeValue(writer, elem); }, }, .array => |info| { if (info.child == u8) return writeString(writer, &value); if (value.len == 0) return writeNull(writer); try writeArrayHeader(writer, value.len); for (value) |elem| try writeValue(writer, elem); }, .vector => |info| { try writeArrayHeader(writer, info.len); var i: usize = 0; while (i < info.len) : (i += 1) { try writeValue(writer, value[i]); } }, .null => try writeNull(writer), .float => |info| switch (info.bits) { 16 => try writeF16(writer, value), 32 => try writeF32(writer, value), 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(); } const CborType = struct { type: u8, minor: u5, major: u3 }; pub fn decodeType(iter: *[]const u8) error{TooShort}!CborType { if (iter.len < 1) return error.TooShort; const type_: u8 = iter.*[0]; const bits: packed struct { minor: u5, major: u3 } = @bitCast(type_); iter.* = iter.*[1..]; return .{ .type = type_, .minor = bits.minor, .major = bits.major }; } fn decodeUIntLength(iter: *[]const u8, length: usize) error{TooShort}!u64 { if (iter.len < length) return error.TooShort; var acc: u64 = 0; for (iter.*[0..length]) |byte| acc = (acc << 8) | byte; iter.* = iter.*[length..]; return acc; } 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.InvalidPIntType, }; } fn decodeNInt(iter: *[]const u8, minor: u5) Error!i64 { const n = try decodePInt(iter, minor); if (n > maxInt(i64)) return error.IntegerTooSmall; return -1 - @as(i64, @intCast(n)); } pub fn decodeMapHeader(iter: *[]const u8) error{ TooShort, InvalidMapType, InvalidPIntType }!usize { const t = try decodeType(iter); if (t.type == cbor_magic_null) return 0; if (t.major != 5) return error.InvalidMapType; return @intCast(try decodePInt(iter, t.minor)); } 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.InvalidArrayType; return @intCast(try decodePInt(iter, t.minor)); } fn decodeString(iter_: *[]const u8, minor: u5) Error![]const u8 { var iter = iter_.*; const len: usize = @intCast(try decodePInt(&iter, minor)); if (iter.len < len) return error.TooShort; const s = iter[0..len]; iter = iter[len..]; iter_.* = iter; return s; } fn decodeBytes(iter: *[]const u8, minor: u5) Error![]const u8 { return decodeString(iter, minor); } fn decodeJsonArray(iter_: *[]const u8, minor: u5, arr: *json.Array) Error!bool { var iter = iter_.*; var n = try decodePInt(&iter, minor); while (n > 0) { const value = try arr.addOne(); if (!try matchJsonValue(&iter, value, arr.allocator)) return false; n -= 1; } iter_.* = iter; return true; } fn decodeJsonObject(iter_: *[]const u8, minor: u5, obj: *json.ObjectMap) Error!bool { var iter = iter_.*; var n = try decodePInt(&iter, minor); while (n > 0) { var key: []const u8 = undefined; var value: json.Value = .null; if (!try matchString(&iter, &key)) return false; if (!try matchJsonValue(&iter, &value, obj.allocator)) return false; _ = try obj.getOrPutValue(key, value); n -= 1; } iter_.* = iter; return true; } fn decodeFloat(comptime T: type, iter_: *[]const u8, t: CborType) Error!T { var v: T = undefined; var iter = iter_.*; switch (t.type) { cbor_magic_float16 => { if (iter.len < 2) return error.TooShort; var f: f16 = undefined; var f_bytes = std.mem.asBytes(&f); switch (native_endian) { .big => @memcpy(f_bytes, iter[0..2]), .little => { f_bytes[0] = iter[1]; f_bytes[1] = iter[0]; }, } v = @floatCast(f); iter = iter[2..]; }, cbor_magic_float32 => { if (iter.len < 4) return error.TooShort; var f: f32 = undefined; var f_bytes = std.mem.asBytes(&f); switch (native_endian) { .big => @memcpy(f_bytes, iter[0..4]), .little => { f_bytes[0] = iter[3]; f_bytes[1] = iter[2]; f_bytes[2] = iter[1]; f_bytes[3] = iter[0]; }, } v = @floatCast(f); iter = iter[4..]; }, cbor_magic_float64 => { if (iter.len < 8) return error.TooShort; var f: f64 = undefined; var f_bytes = std.mem.asBytes(&f); switch (native_endian) { .big => @memcpy(f_bytes, iter[0..8]), .little => { f_bytes[0] = iter[7]; f_bytes[1] = iter[6]; f_bytes[2] = iter[5]; f_bytes[3] = iter[4]; f_bytes[4] = iter[3]; f_bytes[5] = iter[2]; f_bytes[6] = iter[1]; f_bytes[7] = iter[0]; }, } v = @floatCast(f); iter = iter[8..]; }, else => return error.InvalidFloatType, } iter_.* = iter; return v; } pub fn matchInt(comptime T: type, iter_: *[]const u8, val: *T) Error!bool { var iter = iter_.*; const t = try decodeType(&iter); val.* = switch (t.major) { 0 => blk: { // positive integer const v = try decodePInt(&iter, t.minor); if (v > maxInt(T)) return error.IntegerTooLarge; break :blk @intCast(v); }, 1 => blk: { // negative integer const v = try decodeNInt(&iter, t.minor); if (v < minInt(T)) return error.IntegerTooSmall; break :blk @intCast(v); }, else => return false, }; iter_.* = iter; return true; } pub fn matchIntValue(comptime T: type, iter: *[]const u8, val: T) Error!bool { var v: T = 0; 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); if (t.major == 7) { // special if (t.type == cbor_magic_false) { v.* = false; iter_.* = iter; return true; } if (t.type == cbor_magic_true) { v.* = true; iter_.* = iter; return true; } } return false; } fn matchBoolValue(iter: *[]const u8, val: bool) Error!bool { var v: bool = false; return if (try matchBool(iter, &v)) v == val else false; } fn matchFloat(comptime T: type, iter_: *[]const u8, v: *T) Error!bool { var iter = iter_.*; const t = try decodeType(&iter); v.* = decodeFloat(T, &iter, t) catch |e| switch (e) { error.InvalidType => return false, else => return e, }; iter_.* = iter; return true; } fn matchFloatValue(comptime T: type, iter: *[]const u8, val: T) Error!bool { var v: T = 0.0; return if (try matchFloat(T, iter, &v)) v == val else false; } pub fn matchEnum(comptime T: type, iter_: *[]const u8, val: *T) Error!bool { var iter = iter_.*; var str: []const u8 = undefined; if (try matchString(&iter, &str)) if (std.meta.stringToEnum(T, str)) |val_| { val.* = val_; iter_.* = iter; return true; }; return false; } 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 err, 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 err, 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) return error.TooShort; iter.* = iter.*[len..]; } fn skipBytes(iter: *[]const u8, minor: u5) Error!void { return skipString(iter, minor); } fn skipArray(iter: *[]const u8, minor: u5) Error!void { var len = try decodePInt(iter, minor); while (len > 0) { try skipValue(iter); len -= 1; } } fn skipMap(iter: *[]const u8, minor: u5) Error!void { var len = try decodePInt(iter, minor); len *= 2; while (len > 0) { try skipValue(iter); len -= 1; } } pub fn skipValue(iter: *[]const u8) Error!void { try skipValueType(iter, try decodeType(iter)); } fn skipValueType(iter: *[]const u8, t: CborType) Error!void { switch (t.major) { 0 => { // positive integer _ = try decodePInt(iter, t.minor); }, 1 => { // negative integer _ = try decodeNInt(iter, t.minor); }, 2 => { // bytes try skipBytes(iter, t.minor); }, 3 => { // string try skipString(iter, t.minor); }, 4 => { // array try skipArray(iter, t.minor); }, 5 => { // map try skipMap(iter, t.minor); }, 6 => { // tag return error.InvalidType; }, 7 => switch (t.type) { // special cbor_magic_null, cbor_magic_false, cbor_magic_true => return, cbor_magic_float16 => { if (iter.len < 2) return error.TooShort; iter.* = iter.*[2..]; }, cbor_magic_float32 => { if (iter.len < 4) return error.TooShort; iter.* = iter.*[4..]; }, cbor_magic_float64 => { if (iter.len < 8) return error.TooShort; iter.* = iter.*[8..]; }, else => return error.InvalidType, }, } } fn matchType(iter_: *[]const u8, v: *value_type) Error!bool { var iter = iter_.*; const t = try decodeType(&iter); try skipValueType(&iter, t); switch (t.major) { 0, 1 => v.* = value_type.number, // positive integer or negative integer 2 => v.* = value_type.bytes, // bytes 3 => v.* = value_type.string, // string 4 => v.* = value_type.array, // array 5 => v.* = value_type.map, // map 7 => switch (t.type) { // special cbor_magic_null => v.* = value_type.null, cbor_magic_false, cbor_magic_true => v.* = value_type.boolean, cbor_magic_float16, cbor_magic_float32, cbor_magic_float64 => v.* = value_type.float, else => return false, }, else => return false, } iter_.* = iter; return true; } fn matchValueType(iter: *[]const u8, t: value_type) Error!bool { var v: value_type = value_type.unknown; return if (try matchType(iter, &v)) (t == value_type.any or t == v) else false; } pub fn matchString(iter_: *[]const u8, val: *[]const u8) Error!bool { var iter = iter_.*; const t = try decodeType(&iter); val.* = switch (t.major) { 2 => try decodeBytes(&iter, t.minor), // bytes 3 => try decodeString(&iter, t.minor), // string else => return false, }; iter_.* = iter; return true; } fn matchStringValue(iter: *[]const u8, lit: []const u8) Error!bool { var val: []const u8 = undefined; return if (try matchString(iter, &val)) eql(u8, val, lit) else false; } fn matchError(comptime T: type) noreturn { @compileError("cannot match type '" ++ @typeName(T) ++ "' to cbor stream"); } pub fn matchValue(iter: *[]const u8, value: anytype) Error!bool { if (@TypeOf(value) == value_type) return matchValueType(iter, value); const T = comptime @TypeOf(value); if (comptime isExtractor(T)) return value.extract(iter); return switch (comptime @typeInfo(T)) { .int => return matchIntValue(T, iter, value), .comptime_int => return matchIntValue(i64, iter, value), .bool => matchBoolValue(iter, value), .pointer => |info| switch (info.size) { .one => matchValue(iter, value.*), .many, .c => matchError(T), .slice => if (info.child == u8) matchStringValue(iter, value) else matchSlice(iter, value), }, .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 matchSlice(iter, &value), .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"), }; } fn matchJsonValue(iter_: *[]const u8, v: *json.Value, a: std.mem.Allocator) Error!bool { var iter = iter_.*; const t = try decodeType(&iter); const ret = switch (t.major) { 0 => ret: { // positive integer v.* = json.Value{ .integer = @intCast(try decodePInt(&iter, t.minor)) }; break :ret true; }, 1 => ret: { // negative integer v.* = json.Value{ .integer = try decodeNInt(&iter, t.minor) }; break :ret true; }, 2 => ret: { // bytes break :ret false; }, 3 => ret: { // string v.* = json.Value{ .string = try decodeString(&iter, t.minor) }; break :ret true; }, 4 => ret: { // array v.* = json.Value{ .array = json.Array.init(a) }; break :ret try decodeJsonArray(&iter, t.minor, &v.array); }, 5 => ret: { // map v.* = json.Value{ .object = json.ObjectMap.init(a) }; break :ret try decodeJsonObject(&iter, t.minor, &v.object); }, 6 => ret: { // tag break :ret false; }, 7 => ret: { // special switch (t.type) { cbor_magic_false => { v.* = json.Value{ .bool = false }; break :ret true; }, cbor_magic_true => { v.* = json.Value{ .bool = true }; break :ret true; }, cbor_magic_null => { v.* = json.Value{ .null = {} }; break :ret true; }, cbor_magic_float16, cbor_magic_float32, cbor_magic_float64 => { v.* = json.Value{ .float = try decodeFloat(f64, &iter, t) }; break :ret true; }, else => break :ret false, } }, }; if (ret) iter_.* = iter; return ret; } fn matchArrayMore(iter_: *[]const u8, n_: u64) Error!bool { var iter = iter_.*; var n = n_; while (n > 0) { if (!try matchValue(&iter, value_type.any)) return false; n -= 1; } iter_.* = iter; return true; } 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, }; inline for (info.fields) |f| { const value = @field(arr, f.name); if (isMore(value)) break; } else if (info.fields.len != n) return false; inline for (info.fields) |f| { const value = @field(arr, f.name); if (isMore(value)) if (try matchArrayMore(&iter, n)) { iter_.* = iter; return true; } else { return false; }; if (n == 0) return false; const matched = try matchValue(&iter, @field(arr, f.name)); if (!matched) return false; n -= 1; } if (n == 0) iter_.* = iter; return n == 0; } fn matchSlice(iter_: *[]const u8, arr: anytype) 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 != arr.len) return false; for (arr) |elem| { if (!try matchValue(&iter, elem)) return false; } iter_.* = iter; return true; } 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; const ret = try decodeJsonObject(&iter, t.minor, obj); if (ret) iter_.* = iter; return ret; } pub fn match(buf: []const u8, pattern: anytype) Error!bool { var iter: []const u8 = buf; return matchValue(&iter, pattern); } 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"); } fn hasExtractorTag(info: anytype) bool { if (info.is_tuple) return false; inline for (info.decls) |decl| { if (comptime eql(u8, decl.name, "EXTRACTOR_TAG")) return true; } return false; } fn isExtractor(comptime T: type) bool { return comptime switch (@typeInfo(T)) { .@"struct" => |info| hasExtractorTag(info), else => false, }; } 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 T == json.Value) { return matchJsonValue(iter, self.dest, self.allocator); } else 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| { if (try matchNull(iter)) { self.dest.* = null; return true; } 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(); pub const EXTRACTOR_TAG = struct {}; const T = json.Value; pub fn init(dest: *T) Self { return .{ .dest = dest }; } pub fn extract(self: Self, iter: *[]const u8) Error!bool { var null_heap_: [0]u8 = undefined; var heap = fba.init(&null_heap_); return matchJsonValue(iter, self.dest, heap.allocator()); } }; const JsonObjectExtractor = struct { dest: *T, const Self = @This(); pub const EXTRACTOR_TAG = struct {}; const T = json.ObjectMap; pub fn init(dest: *T) Self { return .{ .dest = dest }; } pub fn extract(self: Self, iter: *[]const u8) Error!bool { return matchJsonObject(iter, self.dest); } }; fn Extractor(comptime T: type) type { if (T == json.Value) return JsonValueExtractor; if (T == json.ObjectMap) return JsonObjectExtractor; return struct { dest: *T, const Self = @This(); pub const EXTRACTOR_TAG = struct {}; pub fn init(dest: *T) Self { return .{ .dest = dest }; } pub fn extract(self: Self, iter: *[]const u8) Error!bool { 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 extractError(T); }, else => extractError(T), }, .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; return true; } return false; }, .float => return matchFloat(T, iter, self.dest), .@"enum" => if (@hasDecl(T, "cborExtract")) { return self.dest.cborExtract(iter); } else 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)), }, } } }; } 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); } pub fn extract(dest: anytype) ExtractorType(@TypeOf(dest)) { comptime { if (!isExtractor(ExtractorType(@TypeOf(dest)))) @compileError("isExtractor self check failed for " ++ @typeName(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(); pub const EXTRACTOR_TAG = struct {}; pub fn init(dest: *[]const u8) Self { return .{ .dest = dest }; } pub fn extract(self: Self, iter: *[]const u8) Error!bool { const b = iter.*; try skipValue(iter); self.dest.* = b[0..(b.len - iter.len)]; return true; } }; 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(); } 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); 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.Stringify.Options) !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); defer buf.deinit(); var s: json.Stringify = .{ .writer = &buf.writer, .options = .{ .whitespace = .indent_1 }, }; var iter: []const u8 = cbor_buf; try JsonWriter.jsonWriteValue(&s, &iter); return buf.toOwnedSlice(); } pub fn toJsonOptsAlloc(a: std.mem.Allocator, cbor_buf: []const u8, opts: std.json.Stringify.Options) JsonEncodeError![]const u8 { var buf = Io.Writer.Allocating.init(a); defer buf.deinit(); var s: json.Stringify = .{ .writer = &buf.writer, .options = opts }; var iter: []const u8 = cbor_buf; try JsonWriter.jsonWriteValue(&s, &iter); return buf.toOwnedSlice(); } pub fn writeJsonValue(writer: *Io.Writer, value: json.Value) !void { switch (value) { .null => try writeNull(writer), .array => |arr| { try writeArrayHeader(writer, arr.items.len); for (arr.items) |item| try writeJsonValue(writer, item); }, .object => |obj| { try writeMapHeader(writer, obj.count()); var it = obj.iterator(); while (it.next()) |entry| { try writeString(writer, entry.key_ptr.*); try writeJsonValue(writer, entry.value_ptr.*); } }, inline else => |v| try writeValue(writer, v), } } fn jsonScanUntil(writer: *Io.Writer, scanner: *json.Scanner, end_token: anytype, allocator: std.mem.Allocator) (JsonDecodeError || Io.Writer.Error)!usize { var partial = std.array_list.Managed(u8).init(allocator); defer partial.deinit(); var count: usize = 0; var token = try scanner.next(); while (token != end_token) : (token = try scanner.next()) { count += 1; switch (token) { .object_begin => try writeJsonObject(writer, scanner, allocator), .array_begin => try writeJsonArray(writer, scanner, allocator), .true => try writeBool(writer, true), .false => try writeBool(writer, false), .null => try writeNull(writer), .number => |v| { try partial.appendSlice(v); try writeJsonValue(writer, json.Value.parseFromNumberSlice(partial.items)); try partial.resize(0); }, .partial_number => |v| { try partial.appendSlice(v); count -= 1; }, .string => |v| { try partial.appendSlice(v); try writeString(writer, partial.items); try partial.resize(0); }, .partial_string => |v| { try partial.appendSlice(v); count -= 1; }, .partial_string_escaped_1 => |v| { try partial.appendSlice(&v); count -= 1; }, .partial_string_escaped_2 => |v| { try partial.appendSlice(&v); count -= 1; }, .partial_string_escaped_3 => |v| { try partial.appendSlice(&v); count -= 1; }, .partial_string_escaped_4 => |v| { try partial.appendSlice(&v); count -= 1; }, else => return error.SyntaxError, } } return count; } fn writeJsonArray(writer_: *Io.Writer, scanner: *json.Scanner, allocator: std.mem.Allocator) (JsonDecodeError || Io.Writer.Error)!void { var buf = Io.Writer.Allocating.init(allocator); defer buf.deinit(); const count = try jsonScanUntil(&buf.writer, scanner, .array_end, allocator); try writeArrayHeader(writer_, count); try writer_.writeAll(buf.written()); } fn writeJsonObject(writer_: *Io.Writer, scanner: *json.Scanner, allocator: std.mem.Allocator) (JsonDecodeError || Io.Writer.Error)!void { var buf = Io.Writer.Allocating.init(allocator); defer buf.deinit(); const count = try jsonScanUntil(&buf.writer, scanner, .object_end, allocator); try writeMapHeader(writer_, count / 2); try writer_.writeAll(buf.written()); } 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()); const allocator = sfa.get(); var writer: Io.Writer = .fixed(cbor_buf); var scanner = json.Scanner.initCompleteInput(allocator, json_buf); defer scanner.deinit(); _ = try jsonScanUntil(&writer, &scanner, .end_of_document, allocator); return writer.buffered(); } pub fn fromJsonAlloc(a: std.mem.Allocator, json_buf: []const u8) JsonDecodeError![]const u8 { var stream: std.Io.Writer.Allocating = .init(a); defer stream.deinit(); var scanner = json.Scanner.initCompleteInput(a, json_buf); defer scanner.deinit(); _ = try jsonScanUntil(&stream.writer, &scanner, .end_of_document, a); return stream.toOwnedSlice(); }