Initial Release
This commit is contained in:
commit
025f2983f8
5 changed files with 1517 additions and 0 deletions
21
LICENSE
Normal file
21
LICENSE
Normal 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
125
build.zig
Normal 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
22
build.zig.zon
Normal 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
914
src/cbor.zig
Normal 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
435
test/tests_cbor.zig
Normal 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));
|
||||
}
|
Loading…
Add table
Reference in a new issue