You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Came out of #1360 / #1381. Indexing no longer needs this — IndexWorker correctly treats Optional.empty() from fetchGames as failure and empty months as HTTP 200 {"games":[]}. Filing so the API-shape findings do not evaporate.
What we learned about chess.com 404s
ChessClient.exchange still maps every 404 to Optional.empty(). That collapses two different outcomes:
Case
Status
x-chesscom-matched
Body
Missing player (monthly archive)
404
pubapi_player_games_archive_UserNotFound
User "…" not found.
Missing player (archives index)
404
pubapi_player_games_archives_UserNotFound
same
Missing player (profile / stats)
404
…_UserNotFound
same
Empty month
200
pubapi_player_games_archive
{"games":[]}
Listed archive upstream failure (intermittent)
404
(not reproduced in #1360 follow-up; body was internal-error text)
An internal error has occurred…
The useful discriminator on the 404s we can hit reliably is x-chesscom-matched ending in _UserNotFound. Secondary tells: Link → DisplayableException.jsonld vs Games.jsonld, and cache-control: max-age=30 vs 5.
Our HttpResponse already exposes headers via getHeaders().
ChessComGamesTool — today maps every empty Optional to "player not found", which mislabels a listed-archive internal-error 404.
Future callers that want “missing player” vs “upstream blew up” without re-deriving header rules.
Proposal
Enrich the client result rather than keeping bare Optional:
A small result type (name TBD) that can carry success payload or a structured miss/failure reason.
Reason derived from x-chesscom-matched (at least USER_NOT_FOUND vs NOT_FOUND / UPSTREAM_ERROR for non-UserNotFound 404s), optionally retaining status + matched route string for logs.
Apply to fetchGames first; fetchPlayer / fetchStats already mean “missing” on empty and can migrate later if useful.
Not in scope until a caller needs the split. Do not change IndexWorker’s “empty Optional = fail” contract without a reason.
Context
Came out of #1360 / #1381. Indexing no longer needs this —
IndexWorkercorrectly treatsOptional.empty()fromfetchGamesas failure and empty months as HTTP 200{"games":[]}. Filing so the API-shape findings do not evaporate.What we learned about chess.com 404s
ChessClient.exchangestill maps every 404 toOptional.empty(). That collapses two different outcomes:x-chesscom-matchedpubapi_player_games_archive_UserNotFoundUser "…" not found.pubapi_player_games_archives_UserNotFound…_UserNotFoundpubapi_player_games_archive{"games":[]}An internal error has occurred…The useful discriminator on the 404s we can hit reliably is
x-chesscom-matchedending in_UserNotFound. Secondary tells:Link→DisplayableException.jsonldvsGames.jsonld, andcache-control: max-age=30vs5.Our
HttpResponsealready exposes headers viagetHeaders().Why callers care
ChessComGamesTool— today maps every empty Optional to"player not found", which mislabels a listed-archive internal-error 404.Proposal
Enrich the client result rather than keeping bare
Optional:x-chesscom-matched(at leastUSER_NOT_FOUNDvsNOT_FOUND/UPSTREAM_ERRORfor non-UserNotFound 404s), optionally retaining status + matched route string for logs.fetchGamesfirst;fetchPlayer/fetchStatsalready mean “missing” on empty and can migrate later if useful.Not in scope until a caller needs the split. Do not change IndexWorker’s “empty Optional = fail” contract without a reason.
Non-goals