Parse and validate AT Protocol Lexicons with DTO generation for Laravel
1<?php
2
3namespace SocialDept\AtpSchema\Validation\Rules;
4
5use Closure;
6use Illuminate\Contracts\Validation\ValidationRule;
7
8class AtUri implements ValidationRule
9{
10 /**
11 * Run the validation rule.
12 */
13 public function validate(string $attribute, mixed $value, Closure $fail): void
14 {
15 if (! is_string($value)) {
16 $fail("The {$attribute} must be a string.");
17
18 return;
19 }
20
21 if (! $this->isValidAtUri($value)) {
22 $fail("The {$attribute} is not a valid AT URI.");
23 }
24 }
25
26 /**
27 * Validate AT URI format.
28 *
29 * Format: at://did:plc:xyz/collection/rkey
30 * or: at://handle.domain/collection/rkey
31 */
32 protected function isValidAtUri(string $value): bool
33 {
34 // Must start with at://
35 if (! str_starts_with($value, 'at://')) {
36 return false;
37 }
38
39 // Remove at:// prefix
40 $remainder = substr($value, 5);
41
42 // Must have at least authority part
43 if (empty($remainder)) {
44 return false;
45 }
46
47 // Split into authority and path
48 $parts = explode('/', $remainder, 2);
49 $authority = $parts[0];
50
51 // Validate authority (DID or handle)
52 $didRule = new Did();
53 $handleRule = new Handle();
54
55 $isValidDid = true;
56 $isValidHandle = true;
57
58 $didRule->validate('authority', $authority, function () use (&$isValidDid) {
59 $isValidDid = false;
60 });
61
62 $handleRule->validate('authority', $authority, function () use (&$isValidHandle) {
63 $isValidHandle = false;
64 });
65
66 if (! $isValidDid && ! $isValidHandle) {
67 return false;
68 }
69
70 // If there's a path, validate it
71 if (isset($parts[1]) && ! empty($parts[1])) {
72 // Path should be collection/rkey format
73 $pathParts = explode('/', $parts[1]);
74
75 if (count($pathParts) < 1) {
76 return false;
77 }
78
79 // Each path segment should be valid
80 foreach ($pathParts as $segment) {
81 if (empty($segment) || ! $this->isValidPathSegment($segment)) {
82 return false;
83 }
84 }
85 }
86
87 return true;
88 }
89
90 /**
91 * Check if path segment is valid.
92 */
93 protected function isValidPathSegment(string $segment): bool
94 {
95 // Path segments should be alphanumeric with dots, hyphens, underscores
96 return (bool) preg_match('/^[a-zA-Z0-9._-]+$/', $segment);
97 }
98}