@@ -23,9 +23,15 @@ class GtsIdValidationResult:
2323 id : str
2424 valid : bool
2525 error : str = ""
26+ is_wildcard : bool = False
2627
2728 def to_dict (self ) -> Dict [str , Any ]:
28- return {"id" : self .id , "valid" : self .valid , "error" : self .error }
29+ return {
30+ "id" : self .id ,
31+ "valid" : self .valid ,
32+ "error" : self .error ,
33+ "is_wildcard" : self .is_wildcard ,
34+ }
2935
3036
3137@dataclass
@@ -60,13 +66,17 @@ class GtsIdParseResult:
6066 ok : bool
6167 segments : List [GtsIdSegment ] = field (default_factory = list )
6268 error : str = ""
69+ is_wildcard : bool = False
70+ is_schema : bool = False
6371
6472 def to_dict (self ) -> Dict [str , Any ]:
6573 return {
6674 "id" : self .id ,
6775 "ok" : self .ok ,
6876 "segments" : [s .to_dict () for s in self .segments ],
6977 "error" : self .error ,
78+ "is_wildcard" : self .is_wildcard ,
79+ "is_schema" : self .is_schema ,
7080 }
7181
7282
@@ -331,6 +341,18 @@ def add_entity(
331341 ok = False , error = "Unable to detect GTS ID in schema"
332342 )
333343
344+ # Validate $id prefix for schemas: must use gts:// URI, not plain gts.
345+ if entity .is_schema and validate :
346+ raw_id = content .get ("$id" , "" )
347+ if isinstance (raw_id , str ):
348+ # Reject plain gts. prefix (without gts://)
349+ if raw_id .startswith ("gts." ) and not raw_id .startswith ("gts://" ):
350+ return GtsAddEntityResult (
351+ ok = False ,
352+ error = "Schema $id must use gts:// URI format, not plain gts. prefix" ,
353+ is_schema = True ,
354+ )
355+
334356 # Register the entity (use raw_id for non-GTS instances)
335357 self .store .register (entity )
336358
@@ -376,15 +398,29 @@ def add_schema(self, type_id: str, schema: Dict[str, Any]) -> GtsAddSchemaResult
376398 return GtsAddSchemaResult (ok = False , error = str (e ))
377399
378400 def validate_id (self , gts_id : str ) -> GtsIdValidationResult :
401+ # Check if it's a wildcard pattern (contains *)
402+ is_wildcard = "*" in gts_id
379403 try :
380- _ = GtsID (gts_id )
381- return GtsIdValidationResult (id = gts_id , valid = True )
404+ if is_wildcard :
405+ # For wildcards, try parsing as GtsWildcard
406+ _ = GtsWildcard (gts_id )
407+ else :
408+ _ = GtsID (gts_id )
409+ return GtsIdValidationResult (id = gts_id , valid = True , is_wildcard = is_wildcard )
382410 except Exception as e :
383- return GtsIdValidationResult (id = gts_id , valid = False , error = str (e ))
411+ return GtsIdValidationResult (
412+ id = gts_id , valid = False , error = str (e ), is_wildcard = is_wildcard
413+ )
384414
385415 def parse_id (self , gts_id : str ) -> GtsIdParseResult :
416+ # Check if it's a wildcard pattern (contains *)
417+ is_wildcard = "*" in gts_id
386418 try :
387- segs = GtsID (gts_id ).gts_id_segments
419+ if is_wildcard :
420+ parsed = GtsWildcard (gts_id )
421+ else :
422+ parsed = GtsID (gts_id )
423+ segs = parsed .gts_id_segments
388424 segments = [
389425 GtsIdSegment (
390426 vendor = s .vendor ,
@@ -397,12 +433,27 @@ def parse_id(self, gts_id: str) -> GtsIdParseResult:
397433 )
398434 for s in segs
399435 ]
400- return GtsIdParseResult (id = gts_id , ok = True , segments = segments )
436+ # is_schema: true if ends with ~ and not a wildcard ending with ~*
437+ is_schema = gts_id .endswith ("~" ) and not is_wildcard
438+ return GtsIdParseResult (
439+ id = gts_id ,
440+ ok = True ,
441+ segments = segments ,
442+ is_wildcard = is_wildcard ,
443+ is_schema = is_schema ,
444+ )
401445 except Exception as e :
402- return GtsIdParseResult (id = gts_id , ok = False , error = str (e ))
446+ return GtsIdParseResult (
447+ id = gts_id , ok = False , error = str (e ), is_wildcard = is_wildcard
448+ )
403449
404450 def match_id_pattern (self , candidate : str , pattern : str ) -> GtsIdMatchResult :
405451 try :
452+ # If candidate contains '*', validate it as a wildcard pattern
453+ # This catches malformed wildcards like 'a*' (wildcard not on token boundary)
454+ if "*" in candidate :
455+ # Validate candidate as a wildcard pattern first
456+ _ = GtsWildcard (candidate )
406457 c = GtsID (candidate )
407458 p = GtsWildcard (pattern )
408459 match = c .wildcard_match (p )
0 commit comments