atproto pds in zig
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116const std = @import("std");
pub fn DocumentCache(comptime capacity: usize) type { if (capacity == 0) @compileError("DocumentCache requires at least one entry");
return struct { const Self = @This();
const Entry = struct { url: []u8, body: []u8, expires_at: i64, };
allocator: std.mem.Allocator, mutex: std.atomic.Mutex = .unlocked, entries: [capacity]?Entry = .{null} ** capacity, next: usize = 0,
pub fn init(allocator: std.mem.Allocator) Self { return .{ .allocator = allocator }; }
pub fn deinit(self: *Self) void { self.lock(); defer self.mutex.unlock(); for (&self.entries) |*slot| self.freeEntry(slot); }
pub fn get(self: *Self, allocator: std.mem.Allocator, url: []const u8, now: i64) !?[]u8 { self.lock(); defer self.mutex.unlock();
for (&self.entries) |*slot| { const entry = slot.* orelse continue; if (!std.mem.eql(u8, entry.url, url)) continue; if (entry.expires_at <= now) { self.freeEntry(slot); return null; } return try allocator.dupe(u8, entry.body); } return null; }
pub fn put(self: *Self, url: []const u8, body: []const u8, expires_at: i64) !void { const owned_url = try self.allocator.dupe(u8, url); errdefer self.allocator.free(owned_url); const owned_body = try self.allocator.dupe(u8, body); errdefer self.allocator.free(owned_body);
self.lock(); defer self.mutex.unlock();
var index: ?usize = null; for (&self.entries, 0..) |*slot, i| { if (slot.* == null) { if (index == null) index = i; continue; } if (std.mem.eql(u8, slot.*.?.url, url)) { index = i; break; } }
const selected = index orelse self.next; self.freeEntry(&self.entries[selected]); self.entries[selected] = .{ .url = owned_url, .body = owned_body, .expires_at = expires_at, }; self.next = (selected + 1) % capacity; }
fn lock(self: *Self) void { while (!self.mutex.tryLock()) std.Thread.yield() catch {}; }
fn freeEntry(self: *Self, slot: *?Entry) void { if (slot.*) |entry| { self.allocator.free(entry.url); self.allocator.free(entry.body); slot.* = null; } } };}
test "document cache returns fresh values and expires stale values" { var cache = DocumentCache(2).init(std.testing.allocator); defer cache.deinit();
try cache.put("https://client.example/metadata", "{\"name\":\"client\"}", 20); const fresh = (try cache.get(std.testing.allocator, "https://client.example/metadata", 19)).?; defer std.testing.allocator.free(fresh); try std.testing.expectEqualStrings("{\"name\":\"client\"}", fresh); try std.testing.expect((try cache.get(std.testing.allocator, "https://client.example/metadata", 20)) == null);}
test "document cache replaces matching entries and evicts at capacity" { var cache = DocumentCache(2).init(std.testing.allocator); defer cache.deinit();
try cache.put("https://client.example/a", "old", 20); try cache.put("https://client.example/a", "new", 30); const replaced = (try cache.get(std.testing.allocator, "https://client.example/a", 10)).?; defer std.testing.allocator.free(replaced); try std.testing.expectEqualStrings("new", replaced);
try cache.put("https://client.example/b", "b", 30); try cache.put("https://client.example/c", "c", 30); try std.testing.expect((try cache.get(std.testing.allocator, "https://client.example/a", 10)) == null);}