{"record":{"id":"40a5a0524ef40b6a","repo":"koala73/worldmonitor","slug":"gdacs-malformed-response-eventtype","errorCode":null,"errorMessage":"GDACS malformed response (${eventtype})","messagePattern":"GDACS malformed response \\((.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/seed-natural-events.mjs","lineNumber":379,"sourceCode":"  return fields;\n}\n\nasync function fetchGdacsType(eventtype, fetchFn, now) {\n  // MAP requires one type, but its VO route returns 404. SEARCH supplies\n  // recent volcano events; their iscurrent field determines closure below.\n  const url = new URL(eventtype === 'VO' ? GDACS_API.replace(/MAP$/, 'SEARCH') : GDACS_API);\n  if (eventtype === 'VO') {\n    url.search = new URLSearchParams({\n      eventlist: 'VO',\n      fromDate: new Date(now - DAYS * 86_400_000).toISOString().slice(0, 10),\n      toDate: new Date(now).toISOString().slice(0, 10),\n      pageSize: '100', pageNumber: '1',\n    }).toString();\n  } else url.searchParams.set('eventtype', eventtype);\n  const data = await fetchEventSourceJson(`gdacs:${eventtype}`, url.toString(), fetchFn);\n  if (!Array.isArray(data?.features) || data.features.some(feature =>\n    !['Point', 'Polygon', 'MultiPolygon', 'LineString', 'MultiLineString'].includes(feature?.geometry?.type))) {\n    throw new Error(`GDACS malformed response (${eventtype})`);\n  }\n  if (eventtype === 'VO' && data.features.length >= 100) throw new Error('GDACS SEARCH page incomplete (VO)');\n  // MAP also carries impact polygons and forecast tracks. They are not event\n  // records; retaining them would inflate snapshots without adding coverage.\n  const points = data.features.filter(feature => feature.geometry.type === 'Point');\n  if (!validGdacsFeatures(points, eventtype)) throw new Error(`GDACS malformed response (${eventtype})`);\n  return points;\n}\n\nconst GDACS_ALERT_RANK = { Red: 0, Orange: 1 };\n\nexport async function fetchGdacs(fetchFn = globalThis.fetch, { previousSources = null, now = Date.now() } = {}) {\n  const types = Object.keys(GDACS_TO_CATEGORY);\n  const settled = await Promise.allSettled(types.map((eventtype) => fetchGdacsType(eventtype, fetchFn, now)));\n  const failedTypes = [];\n  const features = [];\n  const snapshots = {};\n  settled.forEach((result, i) => {","sourceCodeStart":361,"sourceCodeEnd":397,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/scripts/seed-natural-events.mjs#L361-L397","documentation":"Thrown by fetchGdacsType when the GDACS (Global Disaster Alerts and Coordination System) response for an event type fails shape validation: data.features is not an array, or any feature's geometry.type is outside the accepted set (Point, Polygon, MultiPolygon, LineString, MultiLineString). The same message is reused when validGdacsFeatures rejects the Point subset.","triggerScenarios":"GDACS GeoJSON feed for an eventtype (e.g. TC, EQ, VO, FL) returns a non-array features field, a feature with null/unknown geometry.type, or the Point records fail validGdacsFeatures checks.","commonSituations":"GDACS changes its GeoJSON schema; the feed returns an XML/HTML error page parsed loosely; WAF/blocked requests return malformed bodies; new geometry kinds appear for an event type.","solutions":["Fetch the failing eventtype URL directly and inspect the GeoJSON for the offending feature/geometry.","Retry — GDACS feeds intermittently serve maintenance or error pages.","Loosen/extend the accepted geometry.type list if GDACS added a legitimate new geometry kind.","Verify the request is not being blocked (check for HTML error bodies; keep the browser-like User-Agent)."],"exampleFix":"// before\nif (!Array.isArray(data?.features) || data.features.some(feature =>\n  !['Point', 'Polygon', 'MultiPolygon', 'LineString', 'MultiLineString'].includes(feature?.geometry?.type))) {\n  throw new Error(`GDACS malformed response (${eventtype})`);\n}\n// after\nconst bad = data?.features?.filter(f => !['Point', 'Polygon', 'MultiPolygon', 'LineString', 'MultiLineString'].includes(f?.geometry?.type)) ?? [];\nif (bad.length) console.warn(`GDACS ${eventtype}: skipping ${bad.length} features with unknown geometry`);","handlingStrategy":"validation","validationCode":"const res = await fetch(url); const data = await res.json(); const bad = (data?.features || []).filter(f => !['Point','Polygon','MultiPolygon','LineString','MultiLineString'].includes(f?.geometry?.type)); if (bad.length) console.warn(`GDACS features with unknown geometry: ${bad.length}`);","typeGuard":"function hasKnownGeometry(feature) { return ['Point', 'Polygon', 'MultiPolygon', 'LineString', 'MultiLineString'].includes(feature?.geometry?.type); }","tryCatchPattern":"try { const points = await fetchGdacsType(type, fetchFn); } catch (e) { if (/GDACS malformed response/.test(e.message)) { console.error(`GDACS feed for ${type} failed shape validation; inspect raw GeoJSON`); } throw e; }","preventionTips":["Validate GDACS GeoJSON against a fixture in CI to detect schema drift","Drop-and-warn unknown geometry kinds instead of discarding the whole type","Detect HTML error pages (non-JSON or missing features) distinctly from schema changes","Track GDACS API announcements for geometry/field changes"],"tags":["api","geojson","schema-validation","gdacs"],"backgroundTag":"unexpected-response-shape","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-09-22T01:50:49.965Z","contentChangedAt":"2026-09-22T01:50:49.965Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}