@@ -65,28 +65,76 @@ class RegistryEntry:
6565 versions : dict [str , RegistryVersion ]
6666
6767
68- def parse_directory (document : dict [str , Any ]) -> list [RegistryEntry ]:
69- """Every usable entry in a directory document.
68+ # The source every skip is attributed to, in the position `sync.signals.intake` puts a manifest's
69+ # filename. One `unreadable` list holds faults from several inputs, so each string has to say
70+ # which one it came from before it says what was wrong with it.
71+ _SOURCE = "registry directory"
72+
73+
74+ def _timestamp (detail : dict [str , Any ]) -> str | None :
75+ """When this version last moved, preferring `updated` and falling back on `added`.
76+
77+ The fallback triggers on a value this tier cannot use rather than on a falsy one. A numeric
78+ `updated` is truthy, so `detail.get("updated") or detail.get("added")` let it win and then
79+ failed the string check, skipping a version whose `added` was perfectly readable. Usable means
80+ a non-empty string: `versions_after` compares timestamps as strings and `""` compares less
81+ than every real one, so an empty value admitted here would be reported as a version that
82+ never moves.
83+ """
84+ for key in ("updated" , "added" ):
85+ value = detail .get (key )
86+ if isinstance (value , str ) and value :
87+ return value
88+ return None
89+
90+
91+ def parse_directory (document : dict [str , Any ]) -> tuple [list [RegistryEntry ], tuple [str , ...]]:
92+ """Every usable entry in a directory document, and every entry or version it declined.
7093
7194 A public directory is untrusted input and it is large. A malformed entry is skipped rather
7295 than raised on: one bad row must not cost the other thousands, and an entry with no versions
7396 has nothing to say about what changed, so skipping loses nothing that could have been used.
97+
98+ What it does lose is the vendor. A skipped entry is one Sync will never offer to watch, and
99+ reported as an absence it is indistinguishable from a directory that never listed it -- so the
100+ second half of the return says what was declined and why, in the key and shape
101+ `IntakeReport.unreadable` established and `ReachabilityRanking` already carries. Empty rather
102+ than absent on a clean document, because a caller cannot otherwise tell a clean read from a
103+ parser that never recorded a fault.
104+
105+ An entry that kept none of its versions is recorded too, and is deliberately not a fifth
106+ cause: it follows from the version-scoped skips already recorded above it. It is there because
107+ it is the only record that says the vendor is gone rather than one of its versions.
74108 """
75109 entries : list [RegistryEntry ] = []
110+ unreadable : list [str ] = []
76111 for api_id , body in document .items ():
77112 if not isinstance (body , dict ):
113+ unreadable .append (f"{ _SOURCE } : '{ api_id } ' is not an object" )
78114 continue
79115 raw_versions = body .get ("versions" )
80116 if not isinstance (raw_versions , dict ) or not raw_versions :
117+ unreadable .append (f"{ _SOURCE } : '{ api_id } ' declares no versions object" )
81118 continue
82119
83120 versions : dict [str , RegistryVersion ] = {}
84121 for version , detail in raw_versions .items ():
85122 if not isinstance (detail , dict ):
123+ unreadable .append (f"{ _SOURCE } : '{ api_id } ' version '{ version } ' is not an object" )
86124 continue
87125 spec_url = detail .get ("swaggerUrl" )
88- updated = detail .get ("updated" ) or detail .get ("added" )
89- if not isinstance (spec_url , str ) or not isinstance (updated , str ):
126+ if not isinstance (spec_url , str ):
127+ unreadable .append (
128+ f"{ _SOURCE } : '{ api_id } ' version '{ version } ' declares no swaggerUrl string, "
129+ f"so there is nothing to download"
130+ )
131+ continue
132+ updated = _timestamp (detail )
133+ if updated is None :
134+ unreadable .append (
135+ f"{ _SOURCE } : '{ api_id } ' version '{ version } ' declares no usable updated or "
136+ f"added timestamp, so nothing can compare it against a watermark"
137+ )
90138 continue
91139 versions [version ] = RegistryVersion (
92140 version = version ,
@@ -96,6 +144,10 @@ def parse_directory(document: dict[str, Any]) -> list[RegistryEntry]:
96144 )
97145
98146 if not versions :
147+ unreadable .append (
148+ f"{ _SOURCE } : '{ api_id } ' is not discoverable -- none of the "
149+ f"{ len (raw_versions )} version(s) it declares could be read, each recorded above"
150+ )
99151 continue
100152 preferred = body .get ("preferred" )
101153 entries .append (
@@ -105,7 +157,7 @@ def parse_directory(document: dict[str, Any]) -> list[RegistryEntry]:
105157 versions = versions ,
106158 )
107159 )
108- return entries
160+ return entries , tuple ( unreadable )
109161
110162
111163def versions_after (entries : list [RegistryEntry ], watermark : str ) -> list [tuple [RegistryEntry , str ]]:
0 commit comments