forked from
rockorager.dev/zig-atproto
this repo has no description
1const std = @import("std");
2
3const Allocator = std.mem.Allocator;
4
5pub const DidError = error{
6 InvalidDid,
7 UnsupportedMethod,
8};
9
10pub const Method = enum {
11 plc,
12 web,
13};
14
15pub const Did = union(Method) {
16 plc: [24]u8,
17 web: Web,
18
19 pub fn init(bytes: []const u8) DidError!Did {
20 var iter = std.mem.splitScalar(u8, bytes, ':');
21 if (!std.mem.eql(u8, iter.first(), "did")) {
22 return error.InvalidDid;
23 }
24
25 const kind_str = iter.next() orelse return error.InvalidDid;
26 const kind = std.meta.stringToEnum(Method, kind_str) orelse
27 return error.UnsupportedMethod;
28
29 switch (kind) {
30 .plc => {
31 const id = iter.next() orelse return error.InvalidDid;
32 if (id.len != 24) return error.InvalidDid;
33 var did: [24]u8 = undefined;
34 for (id, 0..) |c, i| {
35 switch (c) {
36 'A'...'Z' => did[i] = c + 0x20,
37 'a'...'z', '2'...'7' => did[i] = c,
38
39 else => return error.InvalidDid,
40 }
41 }
42 return .{ .plc = did };
43 },
44
45 .web => {
46 const id = iter.next() orelse return error.InvalidDid;
47
48 const host: []const u8, const port: u16 = blk: {
49 const end = std.mem.indexOfScalar(u8, id, '%') orelse break :blk .{ id, 443 };
50 const port = std.fmt.parseInt(u16, id[end + 3 ..], 10) catch return error.InvalidDid;
51 break :blk .{ id[0..end], port };
52 };
53
54 return .{ .web = .{
55 .host = host,
56 .port = port,
57 .path = iter.rest(),
58 } };
59 },
60 }
61 }
62
63 pub fn format(
64 self: Did,
65 comptime _: []const u8,
66 _: std.fmt.FormatOptions,
67 writer: anytype,
68 ) !void {
69 switch (self) {
70 .plc => |plc| try writer.print("did:plc:{s}", .{&plc}),
71 .web => |web| try writer.print("did:web:{s}", .{web}),
72 }
73 }
74};
75
76pub const Web = struct {
77 host: []const u8,
78 port: u16 = 443,
79 path: []const u8 = "",
80
81 pub fn format(
82 self: Web,
83 comptime _: []const u8,
84 _: std.fmt.FormatOptions,
85 writer: anytype,
86 ) !void {
87 try writer.print("{s}", .{self.host});
88
89 switch (self.port) {
90 443 => {},
91 else => try writer.print("%3A{d}", .{self.port}),
92 }
93
94 if (self.path.len > 0) {
95 try writer.print(":{s}", .{self.path});
96 }
97 }
98};
99
100test "did:plc: valid" {
101 const id = "aBcDejfkllalsjlkllsjk234";
102 const did = try Did.init("did:plc:" ++ id);
103 var out: [24]u8 = undefined;
104 try std.testing.expectEqualStrings(std.ascii.lowerString(&out, id), &did.plc);
105}
106
107test "did:plc: invalid character" {
108 const did = "did:plc:0BcDejfkllalsjlkllsjk234";
109 try std.testing.expectError(DidError.InvalidDid, Did.init(did));
110}
111
112test "did:plc: invalid length" {
113 const did = "did:plc:kllsjk234";
114 try std.testing.expectError(DidError.InvalidDid, Did.init(did));
115}
116
117test "did:web: host only" {
118 const did = "did:web:rockorager.dev";
119 const actual = try Did.init(did);
120 try std.testing.expectEqualStrings("rockorager.dev", actual.web.host);
121 try std.testing.expectEqual(443, actual.web.port);
122 try std.testing.expectEqualStrings("", actual.web.path);
123
124 const fmt = try std.fmt.allocPrint(std.testing.allocator, "{s}", .{actual});
125 defer std.testing.allocator.free(fmt);
126 try std.testing.expectEqualStrings("did:web:rockorager.dev", fmt);
127}
128
129test "did:web: host + port" {
130 const did = "did:web:rockorager.dev%3a80";
131 const actual = try Did.init(did);
132 try std.testing.expectEqualStrings("rockorager.dev", actual.web.host);
133 try std.testing.expectEqual(80, actual.web.port);
134 try std.testing.expectEqualStrings("", actual.web.path);
135
136 const fmt = try std.fmt.allocPrint(std.testing.allocator, "{s}", .{actual});
137 defer std.testing.allocator.free(fmt);
138 try std.testing.expectEqualStrings("did:web:rockorager.dev%3A80", fmt);
139}
140
141test "did:web: host + path" {
142 const did = "did:web:rockorager.dev:user:alice";
143 const actual = try Did.init(did);
144 try std.testing.expectEqualStrings("rockorager.dev", actual.web.host);
145 try std.testing.expectEqual(443, actual.web.port);
146 try std.testing.expectEqualStrings("user:alice", actual.web.path);
147
148 const fmt = try std.fmt.allocPrint(std.testing.allocator, "{s}", .{actual});
149 defer std.testing.allocator.free(fmt);
150 try std.testing.expectEqualStrings("did:web:rockorager.dev:user:alice", fmt);
151}
152
153test "did:web: host + port + path" {
154 const did = "did:web:rockorager.dev%3A80:user:alice";
155 const actual = try Did.init(did);
156 try std.testing.expectEqualStrings("rockorager.dev", actual.web.host);
157 try std.testing.expectEqual(80, actual.web.port);
158 try std.testing.expectEqualStrings("user:alice", actual.web.path);
159
160 const fmt = try std.fmt.allocPrint(std.testing.allocator, "{s}", .{actual});
161 defer std.testing.allocator.free(fmt);
162 try std.testing.expectEqualStrings("did:web:rockorager.dev%3A80:user:alice", fmt);
163}