this repo has no description
at main 163 lines 5.3 kB view raw
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}