Initial Release

This commit is contained in:
CJ van den Berg 2024-02-08 22:23:40 +01:00
commit 025f2983f8
5 changed files with 1517 additions and 0 deletions

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 CJ van den Berg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

125
build.zig Normal file
View file

@ -0,0 +1,125 @@
const std = @import("std");
const CrossTarget = std.zig.CrossTarget;
const cppflags = [_][]const u8{
"-fcolor-diagnostics",
"-std=c++20",
"-Wall",
"-Wextra",
"-Werror",
"-Wno-unqualified-std-cast-call",
"-Wno-bitwise-instead-of-logical", //for notcurses
"-fno-sanitize=undefined",
"-gen-cdb-fragment-path",
".cache/cdb",
};
pub fn build(b: *std.Build) void {
const enable_tracy_option = b.option(bool, "enable_tracy", "Enable tracy client library (default: no)");
const tracy_enabled = if (enable_tracy_option) |enabled| enabled else false;
const options = b.addOptions();
options.addOption(bool, "enable_tracy", tracy_enabled);
const options_mod = options.createModule();
const target = b.standardTargetOptions(.{
.default_target = if (tracy_enabled)
CrossTarget.parse(.{ .arch_os_abi = "native-native-gnu" }) catch unreachable
else
CrossTarget.parse(.{ .arch_os_abi = "native-native-musl" }) catch unreachable,
});
const optimize = b.standardOptimizeOption(.{});
const mode = .{ .target = target, .optimize = optimize };
const asio_dep = b.dependency("asio", mode);
const tracy_dep = if (tracy_enabled) b.dependency("tracy", mode) else undefined;
const lib = b.addStaticLibrary(.{
.name = "thespian",
.target = target,
.optimize = optimize,
});
if (tracy_enabled) {
lib.defineCMacro("TRACY_ENABLE", null);
lib.defineCMacro("TRACY_CALLSTACK", null);
}
lib.addIncludePath(.{ .path = "src" });
lib.addIncludePath(.{ .path = "include" });
lib.addCSourceFiles(.{ .files = &[_][]const u8{
"src/backtrace.cpp",
"src/c/context.cpp",
"src/c/env.cpp",
"src/c/file_descriptor.cpp",
"src/c/handle.cpp",
"src/c/instance.cpp",
"src/c/metronome.cpp",
"src/c/signal.cpp",
"src/c/timeout.cpp",
"src/c/trace.cpp",
"src/cbor.cpp",
"src/executor_asio.cpp",
"src/hub.cpp",
"src/instance.cpp",
"src/trace.cpp",
}, .flags = &cppflags });
if (tracy_enabled) {
lib.linkLibrary(tracy_dep.artifact("tracy"));
}
lib.linkLibrary(asio_dep.artifact("asio"));
lib.linkLibCpp();
b.installArtifact(lib);
const cbor_mod = b.addModule("cbor", .{
.root_source_file = .{ .path = "src/cbor.zig" },
});
const thespian_mod = b.addModule("thespian", .{
.root_source_file = .{ .path = "src/thespian.zig" },
.imports = &.{
.{ .name = "cbor", .module = cbor_mod },
},
});
thespian_mod.addIncludePath(.{ .path = "include" });
thespian_mod.linkLibrary(lib);
const tests = b.addTest(.{
.root_source_file = .{ .path = "test/tests.zig" },
.target = target,
.optimize = optimize,
});
tests.root_module.addImport("build_options", options_mod);
tests.root_module.addImport("cbor", cbor_mod);
tests.root_module.addImport("thespian", thespian_mod);
tests.addIncludePath(.{ .path = "test" });
tests.addIncludePath(.{ .path = "src" });
tests.addIncludePath(.{ .path = "include" });
tests.addCSourceFiles(.{ .files = &[_][]const u8{
"test/cbor_match.cpp",
"test/debug.cpp",
"test/endpoint_unx.cpp",
"test/endpoint_tcp.cpp",
"test/hub_filter.cpp",
"test/ip_tcp_client_server.cpp",
"test/ip_udp_echo.cpp",
"test/metronome_test.cpp",
"test/perf_cbor.cpp",
"test/perf_hub.cpp",
"test/perf_ring.cpp",
"test/perf_spawn.cpp",
"test/spawn_exit.cpp",
"test/tests.cpp",
"test/timeout_test.cpp",
}, .flags = &cppflags });
tests.linkLibrary(lib);
tests.linkLibrary(asio_dep.artifact("asio"));
tests.linkLibCpp();
b.installArtifact(tests);
const test_run_cmd = b.addRunArtifact(tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&test_run_cmd.step);
}

22
build.zig.zon Normal file
View file

@ -0,0 +1,22 @@
.{
.name = "thespian",
.version = "0.0.1",
.dependencies = .{
.asio = .{
.url = "https://github.com/kassane/asio/archive/5fc70d29f458f5b596a20bdf3d638294eef91478.tar.gz",
.hash = "1220926d7e934fcb9e4e55f25e24d17613fbe9b145798581e0ae7ea3679b37f4e44d",
},
.tracy = .{
.url = "https://github.com/neurocyte/zig-tracy/archive/d2113e7d778ebe7a063e95b5182ff145343aac38.tar.gz",
.hash = "122005c37f1324dcdd00600f266b64f0a66a73428550015ece51d52ae40a552608d1",
},
},
.paths = .{
"include",
"src",
"test",
"build.zig",
"build.zig.zon",
},
}

914
src/cbor.zig Normal file
View file

@ -0,0 +1,914 @@
const std = @import("std");
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;
const fba = std.heap.FixedBufferAllocator;
pub const CborError = error{
CborIntegerTooLarge,
CborIntegerTooSmall,
CborInvalidType,
CborTooShort,
OutOfMemory,
};
pub const CborJsonError = error{
BufferUnderrun,
CborIntegerTooLarge,
CborIntegerTooSmall,
CborInvalidType,
CborTooShort,
CborUnsupportedType,
NoSpaceLeft,
OutOfMemory,
Overflow,
SyntaxError,
UnexpectedEndOfInput,
};
const cbor_magic_null: u8 = 0xf6;
const cbor_magic_true: u8 = 0xf5;
const cbor_magic_false: u8 = 0xf4;
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,
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: anytype, value: u8) @TypeOf(writer).Error!void {
_ = try writer.write(&[_]u8{value});
}
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)));
} else if (value < 256) {
try write(writer, t | 24);
try write(writer, @as(u8, @truncate(value)));
} else if (value < 65536) {
try write(writer, t | 25);
try write(writer, @as(u8, @truncate(value >> 8)));
try write(writer, @as(u8, @truncate(value)));
} else if (value < 4294967296) {
try write(writer, t | 26);
try write(writer, @as(u8, @truncate(value >> 24)));
try write(writer, @as(u8, @truncate(value >> 16)));
try write(writer, @as(u8, @truncate(value >> 8)));
try write(writer, @as(u8, @truncate(value)));
} else {
try write(writer, t | 27);
try write(writer, @as(u8, @truncate(value >> 56)));
try write(writer, @as(u8, @truncate(value >> 48)));
try write(writer, @as(u8, @truncate(value >> 40)));
try write(writer, @as(u8, @truncate(value >> 32)));
try write(writer, @as(u8, @truncate(value >> 24)));
try write(writer, @as(u8, @truncate(value >> 16)));
try write(writer, @as(u8, @truncate(value >> 8)));
try write(writer, @as(u8, @truncate(value)));
}
}
pub fn writeArrayHeader(writer: anytype, sz: usize) @TypeOf(writer).Error!void {
return writeTypedVal(writer, cbor_magic_type_array, sz);
}
pub fn writeMapHeader(writer: anytype, sz: usize) @TypeOf(writer).Error!void {
return writeTypedVal(writer, cbor_magic_type_map, sz);
}
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;
try writeArrayHeader(writer, fields_info.len);
inline for (fields_info) |field_info|
try writeValue(writer, @field(args, field_info.name));
}
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: anytype, value: u64) @TypeOf(writer).Error!void {
return writeTypedVal(writer, 0, value);
}
fn writeString(writer: anytype, s: []const u8) @TypeOf(writer).Error!void {
try writeTypedVal(writer, 3, s.len);
_ = try writer.write(s);
}
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: anytype) @TypeOf(writer).Error!void {
return write(writer, cbor_magic_null);
}
fn writeErrorset(writer: anytype, err: anyerror) @TypeOf(writer).Error!void {
var buf: [256]u8 = undefined;
const errmsg = try bufPrint(&buf, "error.{s}", .{@errorName(err)});
return writeString(writer, errmsg);
}
pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void {
const T = @TypeOf(value);
switch (@typeInfo(T)) {
.Int, .ComptimeInt => return if (T == u64) writeU64(writer, value) else writeI64(writer, @intCast(value)),
.Bool => return writeBool(writer, value),
.Optional => return if (value) |v| writeValue(writer, v) else writeNull(writer),
.ErrorUnion => return if (value) |v| writeValue(writer, v) else |err| writeValue(writer, err),
.ErrorSet => return writeErrorset(writer, value),
.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);
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),
else => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"),
}
}
pub fn fmt(buf: []u8, value: anytype) []const u8 {
var stream = fixedBufferStream(buf);
writeValue(stream.writer(), value) catch unreachable;
return stream.getWritten();
}
const CborType = struct { type: u8, minor: u5, major: u3 };
pub fn decodeType(iter: *[]const u8) error{CborTooShort}!CborType {
if (iter.len < 1)
return error.CborTooShort;
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 decodeUIntLengthRecurse(iter: *[]const u8, length: usize, acc: u64) !u64 {
if (iter.len < 1)
return error.CborTooShort;
const v: u8 = iter.*[0];
iter.* = iter.*[1..];
var i = acc | v;
if (length == 1)
return i;
i <<= 8;
// return @call(.always_tail, decodeUIntLengthRecurse, .{ iter, length - 1, i }); FIXME: @call(.always_tail) seems broken as of 0.11.0-dev.2964+e9cbdb2cf
return decodeUIntLengthRecurse(iter, length - 1, i);
}
fn decodeUIntLength(iter: *[]const u8, length: usize) !u64 {
return decodeUIntLengthRecurse(iter, length, 0);
}
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.CborInvalidType,
};
}
fn decodeNInt(iter: *[]const u8, minor: u5) CborError!i64 {
return -@as(i64, @intCast(try decodePInt(iter, minor) + 1));
}
pub fn decodeMapHeader(iter: *[]const u8) CborError!usize {
const t = try decodeType(iter);
if (t.type == cbor_magic_null)
return 0;
if (t.major != 5)
return error.CborInvalidType;
return decodePInt(iter, t.minor);
}
pub fn decodeArrayHeader(iter: *[]const u8) CborError!usize {
const t = try decodeType(iter);
if (t.type == cbor_magic_null)
return 0;
if (t.major != 4)
return error.CborInvalidType;
return decodePInt(iter, t.minor);
}
fn decodeString(iter_: *[]const u8, minor: u5) CborError![]const u8 {
var iter = iter_.*;
const len = try decodePInt(&iter, minor);
if (iter.len < len)
return error.CborTooShort;
const s = iter[0..len];
iter = iter[len..];
iter_.* = iter;
return s;
}
fn decodeBytes(iter: *[]const u8, minor: u5) CborError![]const u8 {
return decodeString(iter, minor);
}
fn decodeJsonArray(iter_: *[]const u8, minor: u5, arr: *json.Array) CborError!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) CborError!bool {
var iter = iter_.*;
var n = try decodePInt(&iter, minor);
while (n > 0) {
var key: []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;
}
pub fn matchInt(comptime T: type, iter_: *[]const u8, val: *T) CborError!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.CborIntegerTooLarge;
break :blk @intCast(v);
},
1 => blk: { // negative integer
const v = try decodeNInt(&iter, t.minor);
if (v < minInt(T))
return error.CborIntegerTooSmall;
break :blk @intCast(v);
},
else => return false,
};
iter_.* = iter;
return true;
}
pub fn matchIntValue(comptime T: type, iter: *[]const u8, val: T) CborError!bool {
var v: T = 0;
return if (try matchInt(T, iter, &v)) v == val else false;
}
pub fn matchBool(iter_: *[]const u8, v: *bool) CborError!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) CborError!bool {
var v: bool = false;
return if (try matchBool(iter, &v)) v == val else false;
}
fn skipString(iter: *[]const u8, minor: u5) CborError!void {
const len = try decodePInt(iter, minor);
if (iter.len < len)
return error.CborTooShort;
iter.* = iter.*[len..];
}
fn skipBytes(iter: *[]const u8, minor: u5) CborError!void {
return skipString(iter, minor);
}
fn skipArray(iter: *[]const u8, minor: u5) CborError!void {
var len = try decodePInt(iter, minor);
while (len > 0) {
try skipValue(iter);
len -= 1;
}
}
fn skipMap(iter: *[]const u8, minor: u5) CborError!void {
var len = try decodePInt(iter, minor);
len *= 2;
while (len > 0) {
try skipValue(iter);
len -= 1;
}
}
pub fn skipValue(iter: *[]const u8) CborError!void {
const t = try decodeType(iter);
try skipValueType(iter, t.major, t.minor);
}
fn skipValueType(iter: *[]const u8, major: u3, minor: u5) CborError!void {
switch (major) {
0 => { // positive integer
_ = try decodePInt(iter, minor);
},
1 => { // negative integer
_ = try decodeNInt(iter, minor);
},
2 => { // bytes
try skipBytes(iter, minor);
},
3 => { // string
try skipString(iter, minor);
},
4 => { // array
try skipArray(iter, minor);
},
5 => { // map
try skipMap(iter, minor);
},
6 => { // tag
return error.CborInvalidType;
},
7 => { // special
return;
},
}
}
fn matchType(iter_: *[]const u8, v: *value_type) CborError!bool {
var iter = iter_.*;
const t = try decodeType(&iter);
try skipValueType(&iter, t.major, t.minor);
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 => { // special
if (t.type == cbor_magic_null) {
v.* = value_type.null;
} else {
if (t.type == cbor_magic_false or t.type == cbor_magic_true) {
v.* = value_type.boolean;
} else {
return false;
}
}
},
else => return false,
}
iter_.* = iter;
return true;
}
fn matchValueType(iter: *[]const u8, t: value_type) CborError!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) CborError!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) CborError!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) CborError!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),
.ComptimeInt => 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 matchArray(iter, value, info),
},
.Struct => |info| if (info.is_tuple)
matchArray(iter, value, info)
else
matchError(T),
.Array => |info| if (info.child == u8) matchStringValue(iter, &value) else matchArray(iter, value, info),
else => @compileError("cannot match value type '" ++ @typeName(T) ++ "' to cbor stream"),
};
}
fn matchJsonValue(iter_: *[]const u8, v: *json.Value, a: std.mem.Allocator) CborError!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;
},
else => break :ret false,
}
},
};
if (ret) iter_.* = iter;
return ret;
}
fn matchArrayMore(iter_: *[]const u8, n_: u64) CborError!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) CborError!bool {
var iter = iter_.*;
var n = try decodeArrayHeader(&iter);
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))
return matchArrayMore(&iter, n);
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 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.CborInvalidType;
const ret = try decodeJsonObject(&iter, t.minor, obj);
if (ret) iter_.* = iter;
return ret;
}
pub fn match(buf: []const u8, pattern: anytype) CborError!bool {
var iter: []const u8 = buf;
return matchValue(&iter, pattern);
}
fn extractError(comptime T: type) noreturn {
@compileError("cannot extract type '" ++ @typeName(T) ++ "' from cbor stream");
}
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,
};
}
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) CborError!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) CborError!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) CborError!bool {
switch (comptime @typeInfo(T)) {
.Int, .ComptimeInt => 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),
},
else => extractError(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 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);
}
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) CborError!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 fn JsonStream(comptime T: type) type {
return struct {
const Writer = T.Writer;
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.CborInvalidType;
try w.objectField(try decodeString(iter, t.minor));
try jsonWriteValue(w, iter);
}
try w.endObject();
}
pub fn jsonWriteValue(w: *JsonWriter, iter: *[]const u8) (CborJsonError || Writer.Error)!void {
const t = try decodeType(iter);
if (t.type == cbor_magic_false)
return w.write(false);
if (t.type == cbor_magic_true)
return w.write(true);
if (t.type == cbor_magic_null)
return w.write(null);
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.CborUnsupportedType, // 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.CborInvalidType,
};
}
};
}
pub fn toJson(cbor_buf: []const u8, json_buf: []u8) CborJsonError![]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 toJsonPretty(cbor_buf: []const u8, json_buf: []u8) CborJsonError![]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();
}
fn writeJsonValue(writer: anytype, value: json.Value) !void {
try switch (value) {
.array => |_| unreachable,
.object => |_| unreachable,
.null => writeNull(writer),
.float => |_| error.CborUnsupportedType,
inline else => |v| writeValue(writer, v),
};
}
fn jsonScanUntil(writer: anytype, scanner: *json.Scanner, end_token: anytype) CborJsonError!usize {
var partial = try std.BoundedArray(u8, 4096).init(0);
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),
.array_begin => try writeJsonArray(writer, scanner),
.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.slice()));
try partial.resize(0);
},
.partial_number => |v| {
try partial.appendSlice(v);
count -= 1;
},
.string => |v| {
try partial.appendSlice(v);
try writeString(writer, partial.slice());
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;
}
pub const local_heap_size = 4096 * 16;
fn writeJsonArray(writer_: anytype, scanner: *json.Scanner) CborJsonError!void {
var buf: [local_heap_size]u8 = undefined;
var stream = fixedBufferStream(&buf);
const writer = stream.writer();
const count = try jsonScanUntil(writer, scanner, .array_end);
try writeArrayHeader(writer_, count);
try writer_.writeAll(stream.getWritten());
}
fn writeJsonObject(writer_: anytype, scanner: *json.Scanner) CborJsonError!void {
var buf: [local_heap_size]u8 = undefined;
var stream = fixedBufferStream(&buf);
const writer = stream.writer();
const count = try jsonScanUntil(writer, scanner, .object_end);
try writeMapHeader(writer_, count / 2);
try writer_.writeAll(stream.getWritten());
}
pub fn fromJson(json_buf: []const u8, cbor_buf: []u8) ![]const u8 {
var local_heap_: [local_heap_size]u8 = undefined;
var heap = fba.init(&local_heap_);
var stream = fixedBufferStream(cbor_buf);
const writer = stream.writer();
var scanner = json.Scanner.initCompleteInput(heap.allocator(), json_buf);
defer scanner.deinit();
_ = try jsonScanUntil(writer, &scanner, .end_of_document);
return stream.getWritten();
}

435
test/tests_cbor.zig Normal file
View file

@ -0,0 +1,435 @@
const std = @import("std");
const cbor_mod = @import("cbor");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const expectEqualDeep = std.testing.expectEqualDeep;
const expectError = std.testing.expectError;
const fmt = cbor_mod.fmt;
const toJson = cbor_mod.toJson;
const toJsonPretty = cbor_mod.toJsonPretty;
const fromJson = cbor_mod.fromJson;
const decodeType = cbor_mod.decodeType;
const matchInt = cbor_mod.matchInt;
const matchIntValue = cbor_mod.matchIntValue;
const matchValue = cbor_mod.matchValue;
const match = cbor_mod.match;
const isNull = cbor_mod.isNull;
const writeArrayHeader = cbor_mod.writeArrayHeader;
const writeMapHeader = cbor_mod.writeMapHeader;
const writeValue = cbor_mod.writeValue;
const extract = cbor_mod.extract;
const extract_cbor = cbor_mod.extract_cbor;
const more = cbor_mod.more;
const any = cbor_mod.any;
const string = cbor_mod.string;
const number = cbor_mod.number;
const array = cbor_mod.array;
const map = cbor_mod.map;
test "cbor simple" {
var buf: [128]u8 = undefined;
try expectEqualDeep(
fmt(&buf, .{ "five", 5, "four", 4, .{ "three", 3 } }),
&[_]u8{ 0x85, 0x64, 0x66, 0x69, 0x76, 0x65, 0x05, 0x64, 0x66, 0x6f, 0x75, 0x72, 0x04, 0x82, 0x65, 0x74, 0x68, 0x72, 0x65, 0x65, 0x03 },
);
}
test "cbor exit message" {
var buf: [128]u8 = undefined;
try expectEqualDeep(
fmt(&buf, .{ "exit", "normal" }),
&[_]u8{ 0x82, 0x64, 0x65, 0x78, 0x69, 0x74, 0x66, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C },
);
}
test "cbor error.OutOfMemory" {
var buf: [128]u8 = undefined;
try expectEqualDeep(
fmt(&buf, .{ "exit", error.OutOfMemory }),
&[_]u8{ 0x82, 0x64, 0x65, 0x78, 0x69, 0x74, 0x71, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x2E, 0x4F, 0x75, 0x74, 0x4F, 0x66, 0x4D, 0x65, 0x6D, 0x6F, 0x72, 0x79 },
);
}
test "cbor error.OutOfMemory json" {
var buf: [128]u8 = undefined;
var json_buf: [128]u8 = undefined;
const cbor = fmt(&buf, .{ "exit", error.OutOfMemory });
try expectEqualDeep(toJson(cbor, &json_buf),
\\["exit","error.OutOfMemory"]
);
}
test "cbor.decodeType2" {
var buf = [_]u8{ 0x20, 0xDF };
var iter: []const u8 = &buf;
const t = try decodeType(&iter);
try expectEqual(t.major, 1);
try expectEqual(t.minor, 0);
try expectEqual(iter[0], 0xDF);
}
test "cbor.decodeType3" {
var buf = [_]u8{ 0x03, 0xDF };
var iter: []const u8 = &buf;
const t = try decodeType(&iter);
try expectEqual(t.major, 0);
try expectEqual(t.minor, 3);
try expectEqual(iter[0], 0xDF);
}
test "cbor.matchI64 small" {
var buf = [_]u8{ 1, 0xDF };
var iter: []const u8 = &buf;
var val: i64 = 0;
try expect(try matchInt(i64, &iter, &val));
try expectEqual(val, 1);
try expectEqual(iter[0], 0xDF);
}
test "cbor.matchI64 1byte" {
var buf = [_]u8{ 0x18, 0x1A, 0xDF };
var iter: []const u8 = &buf;
var val: i64 = 0;
try expect(try matchInt(i64, &iter, &val));
try expectEqual(val, 26);
try expectEqual(iter[0], 0xDF);
}
test "cbor.matchI64 2byte" {
var buf = [_]u8{ 0x19, 0x01, 0x07, 0xDF };
var iter: []const u8 = &buf;
var val: i64 = 0;
try expect(try matchInt(i64, &iter, &val));
try expectEqual(val, 263);
try expectEqual(iter[0], 0xDF);
}
test "cbor.matchI64 8byte" {
var buf = [_]u8{ 0x1B, 0x00, 0x00, 0xEF, 0x6F, 0xC1, 0x4A, 0x0A, 0x1F, 0xDF };
var iter: []const u8 = &buf;
var val: i64 = 0;
try expect(try matchInt(i64, &iter, &val));
try expectEqual(val, 263263263263263);
try expectEqual(iter[0], 0xDF);
}
test "cbor.matchI64 error.CborIntegerTooLarge" {
var buf = [_]u8{ 0x1B, 0xA9, 0x0A, 0xDE, 0x0D, 0x4E, 0x2B, 0x8A, 0x1F, 0xDF };
var iter: []const u8 = &buf;
var val: i64 = 0;
const result = matchInt(i64, &iter, &val);
try expectError(error.CborIntegerTooLarge, result);
}
test "cbor.matchI64 error.CborTooShort" {
var buf = [_]u8{ 0x19, 0x01 };
var iter: []const u8 = &buf;
var val: i64 = 0;
const result = matchInt(i64, &iter, &val);
try expectError(error.CborTooShort, result);
}
test "cbor.matchI64Value" {
var buf = [_]u8{ 7, 0xDF };
var iter: []const u8 = &buf;
try expect(try matchIntValue(i64, &iter, 7));
}
test "cbor.matchValue(i64)" {
var buf: [128]u8 = undefined;
var iter = fmt(&buf, 7);
try expect(try matchValue(&iter, 7));
}
test "cbor.matchValue(i64) multi" {
var buf: [128]u8 = undefined;
const iter = fmt(&buf, 7);
const iter2 = fmt(buf[iter.len..], 8);
var iter3 = buf[0 .. iter.len + iter2.len];
try expect(try matchValue(&iter3, 7));
try expect(try matchValue(&iter3, 8));
}
test "cbor.match(.{i64...})" {
var buf: [128]u8 = undefined;
const v = .{ 5, 4, 3, 123456, 234567890 };
const m = fmt(&buf, v);
try expect(try match(m, v));
}
test "cbor.match(.{i64... more})" {
var buf: [128]u8 = undefined;
const v = .{ 5, 4, 3, 123456, 234567890, 6, 5, 4, 3, 2, 1 };
const m = fmt(&buf, v);
try expect(try match(m, .{ 5, 4, 3, more }));
}
test "cbor.match(.{any, i64... more})" {
var buf: [128]u8 = undefined;
const v = .{ "cbor", 4, 3, 123456, 234567890, 6, 5, 4, 3, 2, 1 };
const m = fmt(&buf, v);
try expect(try match(m, .{ any, 4, 3, more }));
}
test "cbor.match(.{types...})" {
var buf: [128]u8 = undefined;
const v = .{ "five", 5, "four", 4, .{ "three", 3 } };
const m = fmt(&buf, v);
try expect(try match(m, .{ string, number, string, number, array }));
try expect(!try match(m, .{ string, number, string, number }));
try expect(!try match(m, .{ number, string, number, array, any }));
try expect(try match(m, .{ any, number, string, number, any }));
try expect(try match(m, .{ "five", number, string, 4, any }));
try expect(try match(m, .{ "five", 5, "four", 4, array }));
try expect(try match(m, .{ "five", 5, more }));
try expect(try match(m, .{ "five", 5, "four", 4, array, more }));
try expect(!try match(m, .{ "five", 5, "four", 4, array, any }));
try expect(!try match(m, .{ "four", 5, "five", 4, array, any }));
try expect(try match(m, .{ "five", 5, "four", 4, .{ "three", 3 } }));
}
test "cbor.nulls" {
var buf: [128]u8 = undefined;
try expect(isNull(fmt(&buf, .{})));
try expect(!isNull(fmt(&buf, .{1})));
}
test "cbor.stream_writer" {
var buf: [128]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf);
const writer = stream.writer();
try writeArrayHeader(writer, 5);
try writeValue(writer, "five");
try writeValue(writer, 5);
try writeValue(writer, "four");
try writeValue(writer, 4);
try writeArrayHeader(writer, 2);
try writeValue(writer, "three");
try writeValue(writer, 3);
try expect(try match(stream.getWritten(), .{ "five", 5, "four", 4, .{ "three", 3 } }));
}
test "cbor.stream_object_writer" {
var buf: [128]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf);
const writer = stream.writer();
try writeMapHeader(writer, 3);
try writeValue(writer, "five");
try writeValue(writer, 5);
try writeValue(writer, "four");
try writeValue(writer, 4);
try writeValue(writer, "three");
try writeValue(writer, 3);
const obj = stream.getWritten();
var json_buf: [128]u8 = undefined;
const json = try toJson(obj, &json_buf);
try expectEqualDeep(json,
\\{"five":5,"four":4,"three":3}
);
}
test "cbor.match_bool" {
var buf: [128]u8 = undefined;
const m = fmt(&buf, .{ false, true, 5, "five" });
try expect(try match(m, .{ false, true, 5, "five" }));
try expect(!try match(m, .{ true, false, 5, "five" }));
}
test "cbor.extract_match" {
var buf: [128]u8 = undefined;
var t: bool = false;
const extractor = extract(&t);
var iter = fmt(&buf, true);
try expect(try extractor.extract(&iter));
try expect(t);
iter = fmt(&buf, false);
try expect(try extractor.extract(&iter));
try expect(!t);
const m = fmt(&buf, .{ false, true, 5, "five" });
try expect(try match(m, .{ extract(&t), true, 5, "five" }));
try expect(!t);
try expect(try match(m, .{ false, extract(&t), 5, "five" }));
try expect(t);
var i: i64 = undefined;
try expect(try match(m, .{ false, true, extract(&i), "five" }));
try expect(i == 5);
var u: u64 = undefined;
try expect(try match(m, .{ false, true, extract(&u), "five" }));
try expect(u == 5);
var s: []const u8 = undefined;
try expect(try match(m, .{ false, true, 5, extract(&s) }));
try expect(std.mem.eql(u8, "five", s));
}
test "cbor.extract_cbor" {
var buf: [128]u8 = undefined;
const v = .{ "five", 5, "four", 4, .{ "three", 3 } };
const m = fmt(&buf, v);
try expect(try match(m, .{ "five", 5, "four", 4, .{ "three", 3 } }));
var sub: []const u8 = undefined;
try expect(try match(m, .{ "five", 5, "four", 4, extract_cbor(&sub) }));
try expect(try match(sub, .{ "three", 3 }));
}
test "cbor.extract_nested" {
var buf: [128]u8 = undefined;
const v = .{ "five", 5, "four", 4, .{ "three", 3 } };
const m = fmt(&buf, v);
var u: u64 = undefined;
try expect(try match(m, .{ "five", 5, "four", 4, .{ "three", extract(&u) } }));
try expect(u == 3);
}
test "cbor.match_map" {
var buf: [128]u8 = undefined;
const v = .{ "five", 5, "four", 4, .{ .three = 3 } };
const m = fmt(&buf, v);
try expect(try match(m, .{ "five", 5, "four", 4, map }));
}
test "cbor.extract_map_cbor" {
var buf: [128]u8 = undefined;
const v = .{ "five", 5, "four", 4, .{ .three = 3 } };
const m = fmt(&buf, v);
var map_cbor: []const u8 = undefined;
try expect(try match(m, .{ "five", 5, "four", 4, extract_cbor(&map_cbor) }));
var json_buf: [256]u8 = undefined;
const json = try toJson(map_cbor, &json_buf);
try expectEqualDeep(json,
\\{"three":3}
);
}
test "cbor.extract_map" {
var buf: [128]u8 = undefined;
const v = .{ "five", 5, "four", 4, .{ .three = 3 } };
const m = fmt(&buf, v);
var obj = std.json.ObjectMap.init(std.testing.allocator);
defer obj.deinit();
try expect(try match(m, .{ "five", 5, "four", 4, extract(&obj) }));
try expect(obj.contains("three"));
try expectEqual(obj.get("three").?, std.json.Value{ .integer = 3 });
}
test "cbor.extract_map_map" {
var buf: [128]u8 = undefined;
const v = .{ "five", 5, "four", 4, .{ .three = 3, .child = .{ .two = 2, .one = .{ 1, 2, 3, true, false, null } } } };
const m = fmt(&buf, v);
var a = std.heap.ArenaAllocator.init(std.testing.allocator);
defer a.deinit();
var obj = std.json.ObjectMap.init(a.allocator());
defer obj.deinit();
try expect(try match(m, .{ "five", 5, "four", 4, extract(&obj) }));
try expect(obj.contains("three"));
try expectEqual(obj.get("three").?, std.json.Value{ .integer = 3 });
var child = obj.get("child").?.object;
try expectEqual(child.get("two"), std.json.Value{ .integer = 2 });
var json_buf: [256]u8 = undefined;
const json = try toJson(m, &json_buf);
try expectEqualDeep(json,
\\["five",5,"four",4,{"three":3,"child":{"two":2,"one":[1,2,3,true,false,null]}}]
);
}
test "cbor.extract_value" {
var buf: [128]u8 = undefined;
const v = .{ "five", 5, "four", 4, .{ .three = 3 } };
const m = fmt(&buf, v);
var value = std.json.Value{ .null = {} };
var value_int = std.json.Value{ .null = {} };
try expect(try match(m, .{ "five", 5, extract(&value), extract(&value_int), any }));
try expectEqualDeep(value.string, "four");
try expectEqualDeep(value_int.integer, 4);
}
test "cbor.match_more_nested" {
var buf: [128]u8 = undefined;
const v = .{ .{124}, .{ "three", 3 } };
const m = fmt(&buf, v);
try expect(try match(m, .{ .{more}, .{more} }));
}
test "cbor.extract_number_limits" {
var buf: [128]u8 = undefined;
const bigint: u64 = 18446744073709551615;
var u: u64 = undefined;
var i: i64 = undefined;
const m = fmt(&buf, bigint);
try expect(try match(m, extract(&u)));
try expectError(error.CborIntegerTooLarge, match(m, extract(&i)));
}
test "cbor.toJson" {
var buf: [128]u8 = undefined;
var json_buf: [128]u8 = undefined;
const a = fmt(&buf, .{ "string\r\n", "\tstring\t", "\r\nstring" });
const json = try toJson(a, &json_buf);
try expect(try match(a, .{ "string\r\n", "\tstring\t", "\r\nstring" }));
try expectEqualDeep(json,
\\["string\r\n","\tstring\t","\r\nstring"]
);
}
test "cbor.toJson_object" {
var buf: [128]u8 = undefined;
var json_buf: [128]u8 = undefined;
const a = fmt(&buf, .{ .five = 5, .four = 4, .three = 3 });
const json = try toJson(a, &json_buf);
try expectEqualDeep(json,
\\{"five":5,"four":4,"three":3}
);
}
test "cbor.toJsonPretty_object" {
var buf: [128]u8 = undefined;
var json_buf: [128]u8 = undefined;
const a = fmt(&buf, .{ .five = 5, .four = 4, .three = 3 });
const json = try toJsonPretty(a, &json_buf);
try expectEqualDeep(json,
\\{
\\ "five": 5,
\\ "four": 4,
\\ "three": 3
\\}
);
}
test "cbor.fromJson_small" {
var cbor_buf: [128]u8 = undefined;
const json_buf: []const u8 =
\\[12345]
;
const cbor = try fromJson(json_buf, &cbor_buf);
try expect(try match(cbor, .{12345}));
}
test "cbor.fromJson" {
var cbor_buf: [128]u8 = undefined;
const json_buf: []const u8 =
\\["string\r\n","\tstring\t","\r\nstring",12345]
;
const cbor = try fromJson(json_buf, &cbor_buf);
try expect(try match(cbor, .{ "string\r\n", "\tstring\t", "\r\nstring", 12345 }));
}
test "cbor.fromJson_object" {
var cbor_buf: [128]u8 = undefined;
const json_buf: []const u8 =
\\{"five":5,"four":4,"three":3}
;
const cbor = try fromJson(json_buf, &cbor_buf);
try expect(try match(cbor, map));
}