{"record":{"id":"53df01c96782665a","repo":"koala73/worldmonitor","slug":"gdacs-malformed-response","errorCode":null,"errorMessage":"GDACS malformed response","messagePattern":"GDACS malformed response","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/seed-natural-events.mjs","lineNumber":219,"sourceCode":"\n  const pressureMatch = desc.match(/(\\d{3,4})\\s*(?:mb|hPa|mbar)/i);\n  if (pressureMatch) {\n    const p = parseInt(pressureMatch[1], 10);\n    if (p >= 850 && p <= 1050) fields.pressureMb = p;\n  }\n\n  return fields;\n}\n\nasync function fetchGdacs(fetchFn = globalThis.fetch) {\n  const res = await fetchFn(GDACS_API, {\n    headers: { Accept: 'application/json', 'User-Agent': CHROME_UA },\n    signal: AbortSignal.timeout(15_000),\n  });\n  if (!res.ok) throw new Error(`GDACS ${res.status}`);\n\n  const data = await res.json();\n  if (!Array.isArray(data?.features)) throw new Error('GDACS malformed response');\n  const features = data.features;\n  const seen = new Set();\n  const events = [];\n\n  for (const f of features) {\n    if (!f.geometry || f.geometry.type !== 'Point') continue;\n    const props = f.properties;\n    const key = `${props.eventtype}-${props.eventid}`;\n    if (seen.has(key)) continue;\n    seen.add(key);\n\n    if (props.alertlevel === 'Green') continue;\n\n    const category = GDACS_TO_CATEGORY[props.eventtype] || 'manmade';\n    const alertPrefix = props.alertlevel === 'Red' ? '\\u{1F534} ' : props.alertlevel === 'Orange' ? '\\u{1F7E0} ' : '';\n    const description = props.description || EVENT_TYPE_NAMES[props.eventtype] || props.eventtype;\n    const severity = props.severitydata?.severitytext || '';\n","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/seed-natural-events.mjs#L201-L237","documentation":"After a successful GDACS fetch, fetchGdacs parses the body as JSON and requires it to be a GeoJSON FeatureCollection, i.e. data?.features must be an array. This error is thrown when the response is valid HTTP/JSON but not in the expected GeoJSON shape (missing 'features', wrong root type, or an HTML/error page served as JSON-ish text).","triggerScenarios":"Calling fetchGdacs() when GDACS returns 200 with an unexpected body: an HTML maintenance or consent page, a JSON error object like {error: ...} with no features array, a truncated response, or a null/empty body.","commonSituations":"GDACS behind a captive-portal or WAF returning an HTML block page with status 200; GDACS changing its API response schema; proxy/CDN error pages served with 200; body intercepted and rewritten by a corporate proxy.","solutions":["Inspect the raw body (res.text()) to see what GDACS actually returned — an HTML page means you are being blocked or proxied","Confirm GDACS is serving GeoJSON: curl the GDACS_API URL with the same Accept/User-Agent headers and check the root object has type:'FeatureCollection'","If a WAF/consent page is returned with 200, adjust headers (realistic User-Agent, Accept: application/json) or use an allowlisted egress IP","Wrap res.json() in its own try/catch to distinguish invalid JSON from valid-but-wrong-shape JSON","Add a schema check that logs JSON.stringify(data).slice(0, 200) on failure to speed diagnosis of upstream format changes"],"exampleFix":"// before\nconst data = await res.json();\nif (!Array.isArray(data?.features)) throw new Error('GDACS malformed response');\n// after\nlet data;\ntry {\n  data = await res.json();\n} catch (err) {\n  throw new Error(`GDACS returned non-JSON body: ${err.message}`);\n}\nif (!Array.isArray(data?.features)) {\n  throw new Error(`GDACS malformed response: ${JSON.stringify(data).slice(0, 200)}`);\n}","handlingStrategy":"validation","validationCode":"function looksLikeGdacsFeatureCollection(data) {\n  return data !== null && typeof data === 'object'\n    && data.type === 'FeatureCollection'\n    && Array.isArray(data.features);\n}\nconst data = await res.json();\nif (!looksLikeGdacsFeatureCollection(data)) throw new Error('GDACS malformed response');","typeGuard":"function isGdacsFeatureCollection(v) {\n  return typeof v === 'object' && v !== null\n    && v.type === 'FeatureCollection'\n    && Array.isArray(v.features);\n}","tryCatchPattern":"try {\n  const data = await res.json();\n  if (!isGdacsFeatureCollection(data)) throw new Error('GDACS malformed response');\n} catch (err) {\n  console.error('GDACS payload rejected:', err.message);\n  return fallbackEvents(); // use cached/seeded events\n}","preventionTips":["Check res headers content-type includes application/json before parsing","Validate the GeoJSON root type immediately after parse","Keep a fallback cached event set so one bad response does not break the dashboard","Log a body preview on failure to detect WAF/consent HTML pages"],"tags":["geojson","schema","upstream-api","seeding"],"backgroundTag":"unexpected-response-shape","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}