fix(traffic): say which upstream declined a road load

Street Traffic draws on OpenStreetMap for geometry and TomTom for flow,
and the row said "Road data temporarily unavailable" whichever one
failed. The reporter of #661 went and verified a TomTom key while every
public Overpass mirror was refusing their network with HTTP 406 — a
status fetchRoads already had and dropped on the floor.

Classify the refusal where the layer talks to Overpass: 429 is
"Overpass rate-limited", 504 is "Overpass timed out", anything else is
"Overpass refused the road query (HTTP <status>)". The first two are the
words alpr's Overpass source already uses; the status is what says which
upstream declined, which a single-source layer never has to.

The error carries its status as a RoadRequestError, following
LiveSourceError in src/sources/live/contract.js, so a caller can branch
on the code without parsing English. The catch promotes a classified
refusal and keeps the general line for everything else — the branch is
on the error type, not on its text, because a dropped socket throws
whatever the platform felt like saying and "Failed to fetch" on a panel
row helps nobody.

Retries, backoff, caching and the mirror rotation are untouched.
This commit is contained in:
daikaginza committed 2026-09-21 10:49:37 +07:00
1 parent 0dbde1e36c
commit c24399e99d
6 files changed
+149 -4

No files matched your search

+12 -3
View File
@@ -4,6 +4,7 @@ import {
TILE_CACHE_MAX_ENTRIES,
FAST_FETCH_ALTITUDE,
} from './policy.js';
import { RoadRequestError, roadRequestError } from './source.js';
export function createIngestion({
state: layerState,
@@ -67,7 +68,7 @@ export function createIngestion({
);
if (!response.ok) {
throw new Error(`Overpass API returned ${response.status}`);
throw roadRequestError(response.status);
}
if (!state) {
@@ -307,8 +308,16 @@ export function createIngestion({
renderedSomething = true;
} catch (e) {
if (e?.name === 'AbortError') return;
if (generation === layerState._loadGeneration && !renderedSomething)
layerState._roadError = 'Road data temporarily unavailable';
if (generation === layerState._loadGeneration && !renderedSomething) {
// A refusal we classified carries its own line; anything else — a
// dropped connection, a malformed snapshot — keeps the general one,
// because `e.message` from the platform is not a sentence anyone
// should have to read off a panel.
layerState._roadError =
e instanceof RoadRequestError
? e.message
: 'Road data temporarily unavailable';
}
console.warn('[Data:Traffic] Fetch error:', e);
} finally {
if (generation === layerState._loadGeneration) {