+62
-8
src/atpasser/model/types/string.py
+62
-8
src/atpasser/model/types/string.py
···
124
124
@classmethod
125
125
def _validate_did(cls, v: str) -> None:
126
126
"""Validate DID format"""
127
-
if not v.startswith("did:"):
128
-
raise ValueError("DID must start with 'did:'")
129
127
if len(v) > 2048:
130
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")
131
131
132
132
@classmethod
133
133
def _validate_handle(cls, v: str) -> None:
134
134
"""Validate handle format"""
135
-
if not re.match(r"^[a-zA-Z0-9._-]+$", v):
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
136
raise ValueError("Handle contains invalid characters")
137
137
if len(v) > 253:
138
138
raise ValueError("Handle too long, max 253 chars")
···
150
150
151
151
@classmethod
152
152
def _validate_at_uri(cls, v: str) -> None:
153
-
"""Validate AT-URI format"""
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
+
"""
154
167
if not v.startswith("at://"):
155
168
raise ValueError("AT-URI must start with 'at://'")
156
-
if len(v) > 8000:
157
-
raise ValueError("AT-URI too long, max 8000 chars")
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")
158
210
159
211
@classmethod
160
212
def _validate_cid(cls, v: str) -> None:
···
169
221
"""Validate NSID format"""
170
222
if len(v) > 317:
171
223
raise ValueError("NSID too long, max 317 chars")
172
-
if not re.match(r"^[a-zA-Z0-9.-]+$", v):
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):
173
225
raise ValueError("NSID contains invalid characters")
174
226
175
227
@classmethod
···
177
229
"""Validate TID format"""
178
230
if len(v) > 13:
179
231
raise ValueError("TID too long, max 13 chars")
180
-
if not re.match(r"^[234567abcdefghijklmnopqrstuvwxyz]+$", v):
232
+
if not re.match(r"^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$", v):
181
233
raise ValueError("TID contains invalid characters")
182
234
183
235
@classmethod
···
185
237
"""Validate record-key format"""
186
238
if len(v) > 512:
187
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")
188
242
if not re.match(r"^[a-zA-Z0-9._:%-~]+$", v):
189
243
raise ValueError("Record key contains invalid characters")
190
244