{"record":{"id":"0ce91ebf58caeaa1","repo":"jackwener/OpenCLI","slug":"trip-com-package-search-returned-malformed-payload","errorCode":null,"errorMessage":"Trip.com package search returned malformed payload: missing grouplist array","messagePattern":"Trip\\.com package search returned malformed payload: missing grouplist array","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/trip/utils.js","lineNumber":967,"sourceCode":"        response = await fetch(PACKAGE_SEARCH_ENDPOINT, {\n            method: 'POST',\n            headers: { 'content-type': 'application/json', currency: 'USD' },\n            body: JSON.stringify(body),\n        });\n    } catch (err) {\n        throw new CommandExecutionError(`Trip.com package search fetch failed: ${err instanceof Error ? err.message : String(err)}`);\n    }\n    if (!response.ok) {\n        throw new CommandExecutionError(`Trip.com package search failed with status ${response.status}`);\n    }\n    let payload;\n    try {\n        payload = await response.json();\n    } catch (err) {\n        throw new CommandExecutionError(`Trip.com package search returned invalid JSON: ${err instanceof Error ? err.message : String(err)}`);\n    }\n    if (!Array.isArray(payload?.grouplist)) {\n        throw new CommandExecutionError('Trip.com package search returned malformed payload: missing grouplist array');\n    }\n    return payload.grouplist;\n}\n\n/**\n * Project a package flight group into the stable adapter column shape. A group's\n * `flightlist` is the itinerary legs (one for a nonstop), so the route summary\n * reads the departure off the first leg and the arrival off the last, with the\n * leg count minus one as the stop count. `price` is the per-person package\n * starting fare (`policylist[0].price.price`); missing values stay `null`.\n */\nexport function mapPackageRow(group, index) {\n    const legs = Array.isArray(group?.flightlist) ? group.flightlist : [];\n    const first = legs[0] || {};\n    const last = legs[legs.length - 1] || {};\n    const binfo = first.binfo || {};\n    const price = group?.policylist?.[0]?.price?.price;\n    const str = (v) => (v == null || v === '') ? null : String(v).replace(/\\s+/g, ' ').trim();","sourceCodeStart":949,"sourceCodeEnd":985,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/trip/utils.js#L949-L985","documentation":"The Trip.com package-search adapter parsed the HTTP response successfully as JSON, but the resulting object did not contain a `grouplist` array, which is the field the adapter relies on for package flight groups. This is thrown as a CommandExecutionError because the underlying command (an upstream Trip.com API/scrape call) executed but produced an unexpected shape. It signals an upstream contract change, an anti-bot/error page rendered as JSON, or a non-200 body that still parses as JSON.","triggerScenarios":"Calling fetchPackageSearch (via the `groups` command) when Trip.com returns JSON without `grouplist` — e.g. the API changed its response schema, returned a CAPTCHA/error JSON envelope, or an empty/error payload for the searched route/date.","commonSituations":"Trip.com silently changing their package-search response schema; rate-limiting or bot detection returning a JSON error object; querying obscure routes/dates where the upstream returns no group data but a valid JSON object; stale adapter code after a Trip.com site update.","solutions":["Re-run the search with different dates/route to rule out an empty upstream result before assuming a schema change.","Log the raw payload (before the grouplist check) to see what Trip.com actually returned (error envelope, CAPTCHA, or schema change).","Check for an updated version of the opencli trip adapter that matches the current Trip.com response schema.","If Trip.com changed the schema, update fetchPackageSearch to read the new field name and re-validate with Array.isArray.","Retry later or from a different network if anti-bot responses are suspected."],"exampleFix":"// before\nif (!Array.isArray(payload?.grouplist)) {\n    throw new CommandExecutionError('Trip.com package search returned malformed payload: missing grouplist array');\n}\nreturn payload.grouplist;\n// after\nconst groups = payload?.grouplist ?? payload?.data?.grouplist;\nif (!Array.isArray(groups)) {\n    throw new CommandExecutionError(`Trip.com package search returned malformed payload: missing grouplist array (keys: ${Object.keys(payload ?? {}).join(',')})`);\n}\nreturn groups;","handlingStrategy":"validation","validationCode":"const payload = await response.json().catch(() => null);\nif (!payload || typeof payload !== 'object' || !Array.isArray(payload.grouplist)) {\n    throw new Error('Trip.com package search payload missing grouplist array');\n}","typeGuard":"function hasGrouplist(p) {\n    return typeof p === 'object' && p !== null && Array.isArray(p.grouplist);\n}","tryCatchPattern":"try {\n    const groups = await fetchPackageSearch(params);\n} catch (err) {\n    if (err instanceof CommandExecutionError && err.message.includes('malformed payload')) {\n        // log raw payload for diagnosis, surface a friendly 'Trip.com returned no package data' message\n    } else {\n        throw err;\n    }\n}","preventionTips":["Log the full upstream payload when validation fails to detect schema drift early","Pin and update the adapter when Trip.com changes their API shape","Treat CAPTCHA/error JSON envelopes as a distinct case from missing data","Alert on repeated malformed-payload occurrences (likely anti-bot or schema change)"],"tags":["api","schema-validation","upstream","trip-com"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}