From 0f7754802dbe93533fe4efb4cf83a0ab5527409c Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 8 Jun 2022 22:54:41 -0700 Subject: [PATCH] Split util into own package --- build.zig | 12 +- src/http/Router.zig | 230 ----------------------------------- src/{ => main}/db.zig | 0 src/{ => main}/http.zig | 0 src/{ => main}/main.zig | 2 +- src/{ => main}/routing.zig | 0 src/util.zig | 237 ------------------------------------- src/util/Uuid.zig | 115 ++++++++++++++++++ src/util/ciutf8.zig | 106 +++++++++++++++++ src/util/lib.zig | 7 ++ 10 files changed, 240 insertions(+), 469 deletions(-) delete mode 100644 src/http/Router.zig rename src/{ => main}/db.zig (100%) rename src/{ => main}/http.zig (100%) rename src/{ => main}/main.zig (98%) rename src/{ => main}/routing.zig (100%) delete mode 100644 src/util.zig create mode 100644 src/util/Uuid.zig create mode 100644 src/util/ciutf8.zig create mode 100644 src/util/lib.zig diff --git a/build.zig b/build.zig index 0d7ae3b..acf2393 100644 --- a/build.zig +++ b/build.zig @@ -1,5 +1,9 @@ const std = @import("std"); +const static_libs = [_][]const u8{ + "util", +}; + pub fn build(b: *std.build.Builder) void { // Standard target options allows the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which @@ -11,9 +15,15 @@ pub fn build(b: *std.build.Builder) void { // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. const mode = b.standardReleaseOptions(); - const exe = b.addExecutable("apub", "src/main.zig"); + const exe = b.addExecutable("apub", "src/main/main.zig"); exe.setTarget(target); exe.setBuildMode(mode); + + inline for (static_libs) |name| { + const lib = b.addStaticLibrary(name, "src/" ++ name ++ "/lib.zig"); + exe.linkLibrary(lib); + } + exe.install(); const run_cmd = exe.run(); diff --git a/src/http/Router.zig b/src/http/Router.zig deleted file mode 100644 index 41cb68d..0000000 --- a/src/http/Router.zig +++ /dev/null @@ -1,230 +0,0 @@ -const std = @import("std"); - -const util = @import("../util.zig"); -const http = @import("../http.zig"); - -const Method = http.Method; -const ciutf8 = util.ciutf8; - -pub fn Router(comptime Context: type) type { - return struct { - const Self = @This(); - routes: []const Route, - - pub const Route = struct { - const Segment = union(enum) { - param: []const u8, - literal: []const u8, - }; - - pub const Handler = fn (*Context, *const Route) callconv(.Async) anyerror!void; - fn normalize(comptime path: []const u8) []const u8 { - var arr: [path.len]u8 = undefined; - - comptime var i = 0; - for (path) |ch| { - if (i == 0 and ch == '/') continue; - if (i > 0 and ch == '/' and arr[i - 1] == '/') continue; - - arr[i] = ch; - i += 1; - } - - if (i > 0 and arr[i - 1] == '/') { - i -= 1; - } - - return arr[0..i]; - } - - fn parseSegments(comptime path: []const u8) []const Segment { - var count = 1; - for (path) |ch| { - if (ch == '/') count += 1; - } - - var segment_array: [count]Segment = undefined; - - var segment_start = 0; - for (segment_array) |*seg| { - var index = segment_start; - while (index < path.len) : (index += 1) { - if (path[index] == '/') { - break; - } - } - - const slice = path[segment_start..index]; - if (slice.len > 0 and slice[0] == ':') { - // doing this kinda jankily to get around segfaults in compiler - const param = path[segment_start + 1 .. index]; - seg.* = .{ .param = param }; - } else { - seg.* = .{ .literal = slice }; - } - - segment_start = index + 1; - } - - return &segment_array; - } - - pub fn from(method: Method, comptime path: []const u8, handler: Handler) Route { - const segments = parseSegments(normalize(path)); - return Route{ .method = method, .path = segments, .handler = handler }; - } - - fn nextSegment(path: []const u8) ?[]const u8 { - var start: usize = 0; - var end: usize = start; - while (end < path.len) : (end += 1) { - // skip leading slash - if (end == start and path[start] == '/') { - start += 1; - continue; - } else if (path[end] == '/') { - break; - } - } - - if (start == end) return null; - - return path[start..end]; - } - - pub fn matches(self: Route, path: []const u8) bool { - var segment_start: usize = 0; - for (self.path) |seg| { - var index = segment_start; - while (index < path.len) : (index += 1) { - // skip leading slash - if (index == segment_start and path[index] == '/') { - segment_start += 1; - continue; - } else if (path[index] == '/') { - break; - } - } - - const slice = path[segment_start..index]; - const match = switch (seg) { - .literal => |str| ciutf8.eql(slice, str), - .param => true, - }; - - if (!match) return false; - - segment_start = index + 1; - } - - // check for trailing path - while (segment_start < path.len) : (segment_start += 1) { - if (path[segment_start] != '/') return false; - } - - return true; - } - - pub fn arg(self: Route, name: []const u8, path: []const u8) []const u8 { - var index: usize = 0; - for (self.path) |seg| { - const slice = nextSegment(path[index..]); - if (slice == null) return ""; - - index = @ptrToInt(slice.?.ptr) - @ptrToInt(path.ptr) + slice.?.len + 1; - - switch (seg) { - .param => |param| { - if (std.mem.eql(u8, param, name)) { - return slice.?; - } - }, - .literal => continue, - } - } - - std.log.err("unknown parameter {s}", .{name}); - return ""; - } - - method: Method, - path: []const Segment, - handler: Handler, - }; - - fn handleNotFound(ctx: *Context) !void { - try ctx.response.writer.writeAll("HTTP/1.1 404 Not Found\r\n\r\n"); - } - - pub fn routeRequest(self: Self, ctx: *Context) !void { - for (self.routes) |*route| { - if (route.method == ctx.request.method and route.matches(ctx.request.path)) { - std.log.info("{s} {s}", .{ @tagName(ctx.request.method), ctx.request.path }); - //ctx.request.route = route; - - var buf = try ctx.allocator.allocWithOptions(u8, @frameSize(route.handler), 8, null); - defer ctx.allocator.free(buf); - return await @asyncCall(buf, {}, route.handler, .{ ctx, route }); - } - } - - std.log.info("404 {s} {s}", .{ @tagName(ctx.request.method), ctx.request.path }); - try handleNotFound(ctx); - } - }; -} - -const TestContext = struct { - request: struct { - method: Method, - path: []const u8, - }, - allocator: std.mem.Allocator = std.testing.allocator, -}; -const TestRouter = Router(TestContext); -const TestRoute = TestRouter.Route; - -fn CallTracker(comptime _uniq: anytype, comptime next: TestRoute.Handler) type { - _ = _uniq; - return struct { - var calls: u32 = 0; - fn func(ctx: *TestContext, route: *const TestRoute) !void { - calls += 1; - return next(ctx, route); - } - - fn expectCalled(times: u32) !void { - return std.testing.expectEqual(times, calls); - } - - fn reset() void { - calls = 0; - } - }; -} - -fn expectNotCalled(_: *TestContext, _: *const TestRoute) !void { - return error.TestWrongRouteChosen; -} - -fn dummyHandler(_: *TestContext, _: *const TestRoute) !void {} - -test "routeRequest" { - const call_tracker = CallTracker(.{}, dummyHandler); - const test_routes = [_]TestRoute{ - TestRoute.from(.GET, "/ab", expectNotCalled), - TestRoute.from(.GET, "/abc", call_tracker.func), - TestRoute.from(.GET, "/abcdefg", expectNotCalled), - TestRoute.from(.GET, "/", expectNotCalled), - }; - var context = TestContext{ - .request = .{ - .method = .GET, - .path = "/abc", - }, - }; - - try (TestRouter{ .routes = &test_routes }).routeRequest(&context); - - try call_tracker.expectCalled(1); -} diff --git a/src/db.zig b/src/main/db.zig similarity index 100% rename from src/db.zig rename to src/main/db.zig diff --git a/src/http.zig b/src/main/http.zig similarity index 100% rename from src/http.zig rename to src/main/http.zig diff --git a/src/main.zig b/src/main/main.zig similarity index 98% rename from src/main.zig rename to src/main/main.zig index 6cce479..4e99bf3 100644 --- a/src/main.zig +++ b/src/main/main.zig @@ -1,7 +1,7 @@ const std = @import("std"); +const util = @import("util"); pub const db = @import("./db.zig"); -pub const util = @import("./util.zig"); pub const http = @import("./http.zig"); pub const routing = @import("./routing.zig"); diff --git a/src/routing.zig b/src/main/routing.zig similarity index 100% rename from src/routing.zig rename to src/main/routing.zig diff --git a/src/util.zig b/src/util.zig deleted file mode 100644 index b73b2af..0000000 --- a/src/util.zig +++ /dev/null @@ -1,237 +0,0 @@ -const std = @import("std"); - -threadlocal var tls_prng: ?std.rand.DefaultPrng = null; - -fn initPrng() void { - const higher_seed = (@bitCast(u64, std.Thread.getCurrentId()) & 0xffffffff) << 32; - const lower_seed = @bitCast(u64, std.time.milliTimestamp()) & 0xffffffff; - - tls_prng = std.rand.DefaultPrng.init(higher_seed | lower_seed); -} - -pub fn getRandom() std.rand.Random { - if (tls_prng) |*prng| { - return prng.random(); - } - - initPrng(); - return tls_prng.?.random(); -} - -pub const Uuid = struct { - data: [16]u8, - - pub const Nil = Uuid{ .data = @bitCast([16]u8, @as(u128, 0)) }; - pub const StringLen = 36; - - pub fn eql(lhs: Uuid, rhs: Uuid) bool { - return std.mem.eql(u8, &lhs.data, &rhs.data); - } - - pub fn format(value: Uuid, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { - try std.fmt.format(writer, "{x:0>2}{x:0>2}{x:0>2}{x:0>2}-{x:0>2}{x:0>2}-{x:0>2}{x:0>2}-{x:0>2}{x:0>2}-{x:0>2}{x:0>2}{x:0>2}{x:0>2}{x:0>2}{x:0>2}", .{ - value.data[0], - value.data[1], - value.data[2], - value.data[3], - value.data[4], - value.data[5], - value.data[6], - value.data[7], - value.data[8], - value.data[9], - value.data[10], - value.data[11], - value.data[12], - value.data[13], - value.data[14], - value.data[15], - }); - } - - pub const ParseError = error{ - InvalidCharacter, - InvalidLength, - }; - pub fn parse(str: []const u8) ParseError!Uuid { - if (str.len != StringLen) return error.InvalidLength; - - var uuid: Uuid = undefined; - var str_i: usize = 0; - var i: usize = 0; - while (i < 16 and str_i < str.len) : ({ - i += 1; - str_i += 2; - }) { - uuid.data[i] = std.fmt.parseInt(u8, str[str_i .. str_i + 2], 16) catch |err| switch (err) { - error.InvalidCharacter => return error.InvalidCharacter, - else => unreachable, - }; - - if (i == 3 or i == 5 or i == 7 or i == 9) { - if (str[str_i + 2] != '-') return error.InvalidCharacter; - str_i += 1; - } - } - - return uuid; - } - - pub fn randV4(rand: std.rand.Random) Uuid { - var ret: Uuid = undefined; - rand.bytes(&ret.data); - - // signify that this is a random v4 uuid - ret.data[7] = (0b0100_0000) | (ret.data[7] & 0b1111); - ret.data[9] = (0b1000_0000) | (ret.data[9] & 0b11_1111); - - return ret; - } -}; - -test "parse uuid" { - try std.testing.expectEqual( - Uuid.Nil, - try Uuid.parse("00000000-0000-0000-0000-000000000000"), - ); - - try std.testing.expectEqual( - Uuid{ - .data = @bitCast([16]u8, @as(u128, 0x4ba7b74522ad_1da8_c242_d312_60515ff7)), - }, - try Uuid.parse("f75f5160-12d3-42c2-a81d-ad2245b7a74b"), - ); -} - -test "format uuid" { - try std.testing.expectFmt("00000000-0000-0000-0000-000000000000", "{}", .{Uuid.Nil}); - - const uuid = Uuid{ - .data = @bitCast([16]u8, @as(u128, 0x4ba7b74522ad_1da8_c242_d312_60515ff7)), - }; - try std.testing.expectFmt("f75f5160-12d3-42c2-a81d-ad2245b7a74b", "{}", .{uuid}); - - try std.testing.expectError(error.InvalidLength, Uuid.parse("fsdfs")); - try std.testing.expectError(error.InvalidCharacter, Uuid.parse("00000000-0000-0000-xxxx-000000000000")); - try std.testing.expectError(error.InvalidLength, Uuid.parse("00000000-0000-0000-0000-000000000000fsdfs")); - try std.testing.expectError(error.InvalidCharacter, Uuid.parse("00000000-0000x0000-0000-000000000000")); -} - -test "roundtrip random uuid" { - const uuid = Uuid.randV4(getRandom()); - - var buf: [36]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); - try Uuid.format(uuid, "", .{}, fbs.writer()); - - const parsed = try Uuid.parse(&buf); - - try std.testing.expectEqual(uuid, parsed); -} - -pub const ciutf8 = struct { - const Hash = std.hash.Wyhash; - const View = std.unicode.Utf8View; - const toLower = std.ascii.toLower; - const isAscii = std.ascii.isASCII; - const seed = 1; - - pub fn hash(str: []const u8) u64 { - // fallback to regular hash on invalid utf8 - const view = View.init(str) catch return Hash.hash(seed, str); - var iter = view.iterator(); - - var h = Hash.init(seed); - - var it = iter.nextCodepointSlice(); - while (it != null) : (it = iter.nextCodepointSlice()) { - if (it.?.len == 1 and isAscii(it.?[0])) { - const ch = [1]u8{toLower(it.?[0])}; - h.update(&ch); - } else { - h.update(it.?); - } - } - - return h.final(); - } - - pub fn eql(a: []const u8, b: []const u8) bool { - if (a.len != b.len) return false; - - const va = View.init(a) catch return std.mem.eql(u8, a, b); - const vb = View.init(b) catch return false; - - var iter_a = va.iterator(); - var iter_b = vb.iterator(); - - var it_a = iter_a.nextCodepointSlice(); - var it_b = iter_b.nextCodepointSlice(); - - while (it_a != null and it_b != null) : ({ - it_a = iter_a.nextCodepointSlice(); - it_b = iter_b.nextCodepointSlice(); - }) { - if (it_a.?.len != it_b.?.len) return false; - - if (it_a.?.len == 1) { - if (isAscii(it_a.?[0]) and isAscii(it_b.?[0])) { - const ch_a = toLower(it_a.?[0]); - const ch_b = toLower(it_b.?[0]); - - if (ch_a != ch_b) return false; - } else if (it_a.?[0] != it_b.?[0]) return false; - } else if (!std.mem.eql(u8, it_a.?, it_b.?)) return false; - } - - return it_a == null and it_b == null; - } -}; - -test "case insensitive eql with utf-8 chars" { - const t = std.testing; - try t.expectEqual(true, ciutf8.eql("abc 💯 def", "aBc 💯 DEF")); - try t.expectEqual(false, ciutf8.eql("xyz 💯 ijk", "aBc 💯 DEF")); - try t.expectEqual(false, ciutf8.eql("abc 💯 def", "aBc x DEF")); - try t.expectEqual(true, ciutf8.eql("💯", "💯")); - try t.expectEqual(false, ciutf8.eql("💯", "a")); - try t.expectEqual(false, ciutf8.eql("💯", "💯 continues")); - try t.expectEqual(false, ciutf8.eql("💯 fsdfs", "💯")); - try t.expectEqual(false, ciutf8.eql("💯", "")); - try t.expectEqual(false, ciutf8.eql("", "💯")); - - try t.expectEqual(true, ciutf8.eql("abc x def", "aBc x DEF")); - try t.expectEqual(false, ciutf8.eql("xyz x ijk", "aBc x DEF")); - try t.expectEqual(true, ciutf8.eql("x", "x")); - try t.expectEqual(false, ciutf8.eql("x", "a")); - try t.expectEqual(false, ciutf8.eql("x", "x continues")); - try t.expectEqual(false, ciutf8.eql("x fsdfs", "x")); - try t.expectEqual(false, ciutf8.eql("x", "")); - try t.expectEqual(false, ciutf8.eql("", "x")); - - try t.expectEqual(true, ciutf8.eql("", "")); -} - -test "case insensitive hash with utf-8 chars" { - const t = std.testing; - try t.expect(ciutf8.hash("abc 💯 def") == ciutf8.hash("aBc 💯 DEF")); - try t.expect(ciutf8.hash("xyz 💯 ijk") != ciutf8.hash("aBc 💯 DEF")); - try t.expect(ciutf8.hash("abc 💯 def") != ciutf8.hash("aBc x DEF")); - try t.expect(ciutf8.hash("💯") == ciutf8.hash("💯")); - try t.expect(ciutf8.hash("💯") != ciutf8.hash("a")); - try t.expect(ciutf8.hash("💯") != ciutf8.hash("💯 continues")); - try t.expect(ciutf8.hash("💯 fsdfs") != ciutf8.hash("💯")); - try t.expect(ciutf8.hash("💯") != ciutf8.hash("")); - try t.expect(ciutf8.hash("") != ciutf8.hash("💯")); - - try t.expect(ciutf8.hash("abc x def") == ciutf8.hash("aBc x DEF")); - try t.expect(ciutf8.hash("xyz x ijk") != ciutf8.hash("aBc x DEF")); - try t.expect(ciutf8.hash("x") == ciutf8.hash("x")); - try t.expect(ciutf8.hash("x") != ciutf8.hash("a")); - try t.expect(ciutf8.hash("x") != ciutf8.hash("x continues")); - try t.expect(ciutf8.hash("x fsdfs") != ciutf8.hash("x")); - try t.expect(ciutf8.hash("x") != ciutf8.hash("")); - try t.expect(ciutf8.hash("") != ciutf8.hash("x")); - - try t.expect(ciutf8.hash("") == ciutf8.hash("")); -} diff --git a/src/util/Uuid.zig b/src/util/Uuid.zig new file mode 100644 index 0000000..d027038 --- /dev/null +++ b/src/util/Uuid.zig @@ -0,0 +1,115 @@ +const Uuid = @This(); + +const std = @import("std"); + +data: [16]u8, + +pub const Nil = Uuid{ .data = @bitCast([16]u8, @as(u128, 0)) }; +pub const StringLen = 36; + +pub fn eql(lhs: Uuid, rhs: Uuid) bool { + return std.mem.eql(u8, &lhs.data, &rhs.data); +} + +pub fn format(value: Uuid, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try std.fmt.format(writer, "{x:0>2}{x:0>2}{x:0>2}{x:0>2}-{x:0>2}{x:0>2}-{x:0>2}{x:0>2}-{x:0>2}{x:0>2}-{x:0>2}{x:0>2}{x:0>2}{x:0>2}{x:0>2}{x:0>2}", .{ + value.data[0], + value.data[1], + value.data[2], + value.data[3], + value.data[4], + value.data[5], + value.data[6], + value.data[7], + value.data[8], + value.data[9], + value.data[10], + value.data[11], + value.data[12], + value.data[13], + value.data[14], + value.data[15], + }); +} + +pub const ParseError = error{ + InvalidCharacter, + InvalidLength, +}; + +pub fn parse(str: []const u8) ParseError!Uuid { + if (str.len != StringLen) return error.InvalidLength; + + var uuid: Uuid = undefined; + var str_i: usize = 0; + var i: usize = 0; + while (i < 16 and str_i < str.len) : ({ + i += 1; + str_i += 2; + }) { + uuid.data[i] = std.fmt.parseInt(u8, str[str_i .. str_i + 2], 16) catch |err| switch (err) { + error.InvalidCharacter => return error.InvalidCharacter, + else => unreachable, + }; + + if (i == 3 or i == 5 or i == 7 or i == 9) { + if (str[str_i + 2] != '-') return error.InvalidCharacter; + str_i += 1; + } + } + + return uuid; +} + +pub fn randV4(rand: std.rand.Random) Uuid { + var ret: Uuid = undefined; + rand.bytes(&ret.data); + + // signify that this is a random v4 uuid + ret.data[7] = (0b0100_0000) | (ret.data[7] & 0b1111); + ret.data[9] = (0b1000_0000) | (ret.data[9] & 0b11_1111); + + return ret; +} + +test "parse uuid" { + try std.testing.expectEqual( + Uuid.Nil, + try Uuid.parse("00000000-0000-0000-0000-000000000000"), + ); + + try std.testing.expectEqual( + Uuid{ + .data = @bitCast([16]u8, @as(u128, 0x4ba7b74522ad_1da8_c242_d312_60515ff7)), + }, + try Uuid.parse("f75f5160-12d3-42c2-a81d-ad2245b7a74b"), + ); +} + +test "format uuid" { + try std.testing.expectFmt("00000000-0000-0000-0000-000000000000", "{}", .{Uuid.Nil}); + + const uuid = Uuid{ + .data = @bitCast([16]u8, @as(u128, 0x4ba7b74522ad_1da8_c242_d312_60515ff7)), + }; + try std.testing.expectFmt("f75f5160-12d3-42c2-a81d-ad2245b7a74b", "{}", .{uuid}); + + try std.testing.expectError(error.InvalidLength, Uuid.parse("fsdfs")); + try std.testing.expectError(error.InvalidCharacter, Uuid.parse("00000000-0000-0000-xxxx-000000000000")); + try std.testing.expectError(error.InvalidLength, Uuid.parse("00000000-0000-0000-0000-000000000000fsdfs")); + try std.testing.expectError(error.InvalidCharacter, Uuid.parse("00000000-0000x0000-0000-000000000000")); +} + +test "roundtrip random uuid" { + const test_seed = 12345; + var test_prng = std.rand.DefaultPrng.init(test_seed); + const uuid = Uuid.randV4(test_prng.random()); + + var buf: [36]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buf); + try Uuid.format(uuid, "", .{}, fbs.writer()); + + const parsed = try Uuid.parse(&buf); + + try std.testing.expectEqual(uuid, parsed); +} diff --git a/src/util/ciutf8.zig b/src/util/ciutf8.zig new file mode 100644 index 0000000..39d1a20 --- /dev/null +++ b/src/util/ciutf8.zig @@ -0,0 +1,106 @@ +const std = @import("std"); + +const Hash = std.hash.Wyhash; +const View = std.unicode.Utf8View; +const toLower = std.ascii.toLower; +const isAscii = std.ascii.isASCII; +const hash_seed = 1; + +pub fn hash(str: []const u8) u64 { + // fallback to regular hash on invalid utf8 + const view = View.init(str) catch return Hash.hash(hash_seed, str); + var iter = view.iterator(); + + var h = Hash.init(hash_seed); + + var it = iter.nextCodepointSlice(); + while (it != null) : (it = iter.nextCodepointSlice()) { + if (it.?.len == 1 and isAscii(it.?[0])) { + const ch = [1]u8{toLower(it.?[0])}; + h.update(&ch); + } else { + h.update(it.?); + } + } + + return h.final(); +} + +pub fn eql(a: []const u8, b: []const u8) bool { + if (a.len != b.len) return false; + + const va = View.init(a) catch return std.mem.eql(u8, a, b); + const vb = View.init(b) catch return false; + + var iter_a = va.iterator(); + var iter_b = vb.iterator(); + + var it_a = iter_a.nextCodepointSlice(); + var it_b = iter_b.nextCodepointSlice(); + + while (it_a != null and it_b != null) : ({ + it_a = iter_a.nextCodepointSlice(); + it_b = iter_b.nextCodepointSlice(); + }) { + if (it_a.?.len != it_b.?.len) return false; + + if (it_a.?.len == 1) { + if (isAscii(it_a.?[0]) and isAscii(it_b.?[0])) { + const ch_a = toLower(it_a.?[0]); + const ch_b = toLower(it_b.?[0]); + + if (ch_a != ch_b) return false; + } else if (it_a.?[0] != it_b.?[0]) return false; + } else if (!std.mem.eql(u8, it_a.?, it_b.?)) return false; + } + + return it_a == null and it_b == null; +} + +test "case insensitive eql with utf-8 chars" { + const t = std.testing; + try t.expectEqual(true, eql("abc 💯 def", "aBc 💯 DEF")); + try t.expectEqual(false, eql("xyz 💯 ijk", "aBc 💯 DEF")); + try t.expectEqual(false, eql("abc 💯 def", "aBc x DEF")); + try t.expectEqual(true, eql("💯", "💯")); + try t.expectEqual(false, eql("💯", "a")); + try t.expectEqual(false, eql("💯", "💯 continues")); + try t.expectEqual(false, eql("💯 fsdfs", "💯")); + try t.expectEqual(false, eql("💯", "")); + try t.expectEqual(false, eql("", "💯")); + + try t.expectEqual(true, eql("abc x def", "aBc x DEF")); + try t.expectEqual(false, eql("xyz x ijk", "aBc x DEF")); + try t.expectEqual(true, eql("x", "x")); + try t.expectEqual(false, eql("x", "a")); + try t.expectEqual(false, eql("x", "x continues")); + try t.expectEqual(false, eql("x fsdfs", "x")); + try t.expectEqual(false, eql("x", "")); + try t.expectEqual(false, eql("", "x")); + + try t.expectEqual(true, eql("", "")); +} + +test "case insensitive hash with utf-8 chars" { + const t = std.testing; + try t.expect(hash("abc 💯 def") == hash("aBc 💯 DEF")); + try t.expect(hash("xyz 💯 ijk") != hash("aBc 💯 DEF")); + try t.expect(hash("abc 💯 def") != hash("aBc x DEF")); + try t.expect(hash("💯") == hash("💯")); + try t.expect(hash("💯") != hash("a")); + try t.expect(hash("💯") != hash("💯 continues")); + try t.expect(hash("💯 fsdfs") != hash("💯")); + try t.expect(hash("💯") != hash("")); + try t.expect(hash("") != hash("💯")); + + try t.expect(hash("abc x def") == hash("aBc x DEF")); + try t.expect(hash("xyz x ijk") != hash("aBc x DEF")); + try t.expect(hash("x") == hash("x")); + try t.expect(hash("x") != hash("a")); + try t.expect(hash("x") != hash("x continues")); + try t.expect(hash("x fsdfs") != hash("x")); + try t.expect(hash("x") != hash("")); + try t.expect(hash("") != hash("x")); + + try t.expect(hash("") == hash("")); +} diff --git a/src/util/lib.zig b/src/util/lib.zig new file mode 100644 index 0000000..4a7e3df --- /dev/null +++ b/src/util/lib.zig @@ -0,0 +1,7 @@ +pub const ciutf8 = @import("./ciutf8.zig"); +pub const Uuid = @import("./Uuid.zig"); + +test { + _ = ciutf8; + _ = Uuid; +}