const std = @import("std"); const zat = @import("zat"); pub const Error = error{ Empty, TooLong, InvalidChar, InvalidDid, InvalidNsid, InvalidRecordKey, }; pub fn validateDid(did: []const u8) Error!void { if (did.len == 0) return Error.Empty; if (did.len > 2048) return Error.TooLong; if (zat.Did.parse(did) == null) return Error.InvalidDid; } pub fn validateNsid(nsid: []const u8) Error!void { if (nsid.len == 0) return Error.Empty; if (nsid.len > zat.Nsid.max_length) return Error.TooLong; if (zat.Nsid.parse(nsid) == null) return Error.InvalidNsid; } pub fn validateRecordKey(rkey: []const u8) Error!void { if (rkey.len == 0) return Error.Empty; if (rkey.len > zat.Rkey.max_length) return Error.TooLong; if (zat.Rkey.parse(rkey) == null) return Error.InvalidRecordKey; } pub fn validateRepoPath(path: []const u8) Error!void { const slash = std.mem.indexOfScalar(u8, path, '/') orelse return Error.InvalidRecordKey; if (std.mem.indexOfScalarPos(u8, path, slash + 1, '/') != null) return Error.InvalidRecordKey; try validateNsid(path[0..slash]); try validateRecordKey(path[slash + 1 ..]); } test "validates common atproto syntax" { try validateDid("did:plc:abc123"); try validateDid("did:web:example.com"); try validateNsid("com.atproto.repo"); try validateNsid("app.bsky.feed.post"); try validateRecordKey("3jzfcijpj2z23"); try validateRepoPath("app.bsky.feed.post/3jzfcijpj2z23"); } test "rejects invalid syntax" { try std.testing.expectError(Error.InvalidDid, validateDid("plc:abc")); try std.testing.expectError(Error.InvalidNsid, validateNsid("com")); try std.testing.expectError(Error.InvalidNsid, validateNsid("com..repo")); try std.testing.expectError(Error.InvalidRecordKey, validateRecordKey("bad/key")); }