atproto utils for zig zat.dev
atproto sdk zig
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124//! Record Key (rkey)//!//! record keys identify individual records within a collection.//!//! validation rules://! - 1-512 characters//! - allowed chars: A-Z, a-z, 0-9, period, hyphen, underscore, colon, tilde//! - cannot be "." or ".."//!//! note: TIDs are a common rkey format but not the only valid one.//! see tid.zig for TID-specific parsing.//!//! see: https://atproto.com/specs/record-key
const std = @import("std");
pub const Rkey = struct { /// the rkey string (borrowed, not owned) raw: []const u8,
pub const min_length = 1; pub const max_length = 512;
/// parse a record key string. returns null if invalid. pub fn parse(s: []const u8) ?Rkey { if (!isValid(s)) return null; return .{ .raw = s }; }
/// validate a record key string pub fn isValid(s: []const u8) bool { // length check if (s.len < min_length or s.len > max_length) return false;
// cannot be "." or ".." if (std.mem.eql(u8, s, ".") or std.mem.eql(u8, s, "..")) return false;
// check all characters are valid for (s) |c| { const valid = switch (c) { 'A'...'Z', 'a'...'z', '0'...'9' => true, '.', '-', '_', ':', '~' => true, else => false, }; if (!valid) return false; }
return true; }
/// get the rkey string pub fn str(self: Rkey) []const u8 { return self.raw; }};
// === tests from atproto.com/specs/record-key ===
test "valid: simple rkey" { try std.testing.expect(Rkey.parse("abc123") != null); try std.testing.expect(Rkey.parse("self") != null);}
test "valid: tid format" { try std.testing.expect(Rkey.parse("3jxtb5w2hkt2m") != null);}
test "valid: with allowed special chars" { try std.testing.expect(Rkey.parse("abc.def") != null); try std.testing.expect(Rkey.parse("abc-def") != null); try std.testing.expect(Rkey.parse("abc_def") != null); try std.testing.expect(Rkey.parse("abc:def") != null); try std.testing.expect(Rkey.parse("abc~def") != null);}
test "valid: mixed case" { try std.testing.expect(Rkey.parse("AbC123") != null); try std.testing.expect(Rkey.parse("ABC") != null);}
test "valid: single character" { try std.testing.expect(Rkey.parse("a") != null); try std.testing.expect(Rkey.parse("1") != null);}
test "valid: max length" { var buf: [512]u8 = undefined; @memset(&buf, 'a'); try std.testing.expect(Rkey.parse(&buf) != null);}
test "invalid: empty" { try std.testing.expect(Rkey.parse("") == null);}
test "invalid: dot" { try std.testing.expect(Rkey.parse(".") == null);}
test "invalid: double dot" { try std.testing.expect(Rkey.parse("..") == null);}
test "invalid: too long" { var buf: [513]u8 = undefined; @memset(&buf, 'a'); try std.testing.expect(Rkey.parse(&buf) == null);}
test "invalid: forbidden characters" { try std.testing.expect(Rkey.parse("abc/def") == null); try std.testing.expect(Rkey.parse("abc?def") == null); try std.testing.expect(Rkey.parse("abc#def") == null); try std.testing.expect(Rkey.parse("abc@def") == null); try std.testing.expect(Rkey.parse("abc def") == null); try std.testing.expect(Rkey.parse("abc\ndef") == null);}
test "invalid: non-ascii" { var buf = "abcdef".*; buf[2] = 200; // non-ascii byte try std.testing.expect(Rkey.parse(&buf) == null);}