···124 @classmethod
125 def _validate_did(cls, v: str) -> None:
126 """Validate DID format"""
127- if not v.startswith("did:"):
128- raise ValueError("DID must start with 'did:'")
129 if len(v) > 2048:
130 raise ValueError("DID too long, max 2048 chars")
00131132 @classmethod
133 def _validate_handle(cls, v: str) -> None:
134 """Validate handle format"""
135- if not re.match(r"^[a-zA-Z0-9._-]+$", v):
136 raise ValueError("Handle contains invalid characters")
137 if len(v) > 253:
138 raise ValueError("Handle too long, max 253 chars")
···150151 @classmethod
152 def _validate_at_uri(cls, v: str) -> None:
153- """Validate AT-URI format"""
0000000000000154 if not v.startswith("at://"):
155 raise ValueError("AT-URI must start with 'at://'")
156- if len(v) > 8000:
157- raise ValueError("AT-URI too long, max 8000 chars")
000000000000000000000000000000000000000158159 @classmethod
160 def _validate_cid(cls, v: str) -> None:
···169 """Validate NSID format"""
170 if len(v) > 317:
171 raise ValueError("NSID too long, max 317 chars")
172- if not re.match(r"^[a-zA-Z0-9.-]+$", v):
173 raise ValueError("NSID contains invalid characters")
174175 @classmethod
···177 """Validate TID format"""
178 if len(v) > 13:
179 raise ValueError("TID too long, max 13 chars")
180- if not re.match(r"^[234567abcdefghijklmnopqrstuvwxyz]+$", v):
181 raise ValueError("TID contains invalid characters")
182183 @classmethod
···185 """Validate record-key format"""
186 if len(v) > 512:
187 raise ValueError("Record key too long, max 512 chars")
00188 if not re.match(r"^[a-zA-Z0-9._:%-~]+$", v):
189 raise ValueError("Record key contains invalid characters")
190
···124 @classmethod
125 def _validate_did(cls, v: str) -> None:
126 """Validate DID format"""
00127 if len(v) > 2048:
128 raise ValueError("DID too long, max 2048 chars")
129+ if not re.match(r"^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$", v):
130+ raise ValueError("Invalid URI format")
131132 @classmethod
133 def _validate_handle(cls, v: str) -> None:
134 """Validate handle format"""
135+ if not re.match(r"^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$", v):
136 raise ValueError("Handle contains invalid characters")
137 if len(v) > 253:
138 raise ValueError("Handle too long, max 253 chars")
···150151 @classmethod
152 def _validate_at_uri(cls, v: str) -> None:
153+ """
154+ Validate AT-URI format according to AT Protocol specification.
155+156+ Args:
157+ v: AT-URI string to validate
158+159+ Raises:
160+ ValueError: If URI violates any of these rules:
161+ - Must start with 'at://'
162+ - Max length 8KB
163+ - No trailing slash
164+ - Authority must be valid DID or handle
165+ - Path segments must follow NSID/RKEY rules if present
166+ """
167 if not v.startswith("at://"):
168 raise ValueError("AT-URI must start with 'at://'")
169+ if len(v) > 8192: # 8KB
170+ raise ValueError("AT-URI too long, max 8KB")
171+ if v.endswith('/'):
172+ raise ValueError("AT-URI cannot have trailing slash")
173+174+ # Split into parts
175+ parts = v[5:].split('/') # Skip 'at://'
176+ authority = parts[0]
177+178+ # Validate authority (DID or handle)
179+ if not authority:
180+ raise ValueError("AT-URI must have authority")
181+182+ if authority.startswith('did:'):
183+ # Basic DID format check - actual DID validation is done elsewhere
184+ if len(authority) > 2048:
185+ raise ValueError("DID too long")
186+ if ':' not in authority[4:]:
187+ raise ValueError("Invalid DID format")
188+ else:
189+ # Handle validation
190+ if not re.match(r'^[a-z0-9.-]+$', authority):
191+ raise ValueError("Invalid handle characters")
192+ if len(authority) > 253:
193+ raise ValueError("Handle too long")
194+195+ # Validate path segments if present
196+ if len(parts) > 1:
197+ if len(parts) > 3:
198+ raise ValueError("AT-URI path too deep")
199+200+ collection = parts[1]
201+ if not re.match(r'^[a-zA-Z0-9.-]+$', collection):
202+ raise ValueError("Invalid collection NSID")
203+204+ if len(parts) > 2:
205+ rkey = parts[2]
206+ if not rkey:
207+ raise ValueError("Record key cannot be empty")
208+ if not re.match(r'^[a-zA-Z0-9._:%-~]+$', rkey):
209+ raise ValueError("Invalid record key characters")
210211 @classmethod
212 def _validate_cid(cls, v: str) -> None:
···221 """Validate NSID format"""
222 if len(v) > 317:
223 raise ValueError("NSID too long, max 317 chars")
224+ if not re.match(r"^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z]([a-zA-Z0-9]{0,62})?)$", v):
225 raise ValueError("NSID contains invalid characters")
226227 @classmethod
···229 """Validate TID format"""
230 if len(v) > 13:
231 raise ValueError("TID too long, max 13 chars")
232+ if not re.match(r"^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$", v):
233 raise ValueError("TID contains invalid characters")
234235 @classmethod
···237 """Validate record-key format"""
238 if len(v) > 512:
239 raise ValueError("Record key too long, max 512 chars")
240+ if v == "." or v == "..":
241+ raise ValueError(f"Record key is {v}, which is not allowed")
242 if not re.match(r"^[a-zA-Z0-9._:%-~]+$", v):
243 raise ValueError("Record key contains invalid characters")
244