mirror of
https://github.com/larksuite/cli.git
synced 2026-09-14 18:42:53 +08:00
e1a2c15b8b
--page-all stopped paginating when a page after the first failed, but returned the pages it already had as a complete, successful result: exit code 0 and an ok:true envelope, with the merged result reporting has_more:false so the output asserted completeness over missing data. The loop recognised only two failure signals -- a transport error, or a JSON object whose code read as a non-zero float -- and treated everything else as a normal page. A gateway 5xx carrying no code was accumulated as data. A page whose code could not be read at all (absent, null, a string, 0.5, 1e-324) read as 0 through the float, so the loop accepted it as a continuation, or paginated from it and re-issued the request for its cursor; the generic api command is not restricted to GET, so that replayed a POST or DELETE. A body declaring a non-JSON Content-Type was parsed anyway, and a body with content after its first JSON value was accepted as that value, cursor included. Three decisions, each owned once: Whether a page failed is decided exactly as HandleResponse decides it for a plain api response, in the same order: CheckResponse first, so a readable business code wins over the HTTP status, then a status of 400 or more. A business or HTTP failure therefore classifies the same way whether or not --page-all is set. Page 1 keeps its output contract and is handed back whole with a nil error for the command layer to classify and dump; a later page fails the run. Whether a page is a step in the pagination is decided by pageDeclaresSuccess: its business code must be exactly 0, read as a JSON number whose every mantissa digit is 0. The rule is asymmetric by page position on purpose. A later page that did not declare success fails the run before it is emitted or accumulated: the page before it promised more data, and accepting this one, with or without a cursor or data of its own, would end the run as complete over that promise. A first page that did not declare success but carries a continuation token is refused before output with a non-zero exit, so the streaming formats, which have no envelope to carry has_more, get a machine-readable signal. A first page that did not declare success and carries no continuation token is output on the existing contract: there is nothing to paginate, and plain api would have shown the same response. Whether a body is JSON at all is decided by one rule, isJSONBody, shared with HandleResponse: a declared non-JSON Content-Type is never parsed. On a 4xx/5xx it is classified by status; on a 2xx it is not a page, because plain api treats it as a download and a paginated run cannot. ParseJSONResponse now accepts exactly one JSON value, so trailing garbage or a second value fails the parse on every path that uses it. callPage keeps the raw response so the loop can read the status. The identity used for classification is resolved inside the loop, StreamPages reports how much a failed run had already emitted, and the PaginationOptions field comments are corrected. The rule is tested as a decision table -- code shape x data variant (cursor, terminal list, no data) x HTTP status x first/later page x PaginateAll/StreamPages -- with every cell's expectation derived from CheckResponse's own verdict, the status, the page position and the variant, asserting request count, callback count, category and error.code. A parity test checks the same shapes, plus unparseable and trailing-content bodies, against HandleResponse. The command layer pins stdout for each format. Left to a follow-up: CheckResponse and errclass still read the code through float64, so 230027.5 classifies as 230027 and 9007199254740993.0 reports 9007199254740992. Plain api and --page-all now behave identically there; an exact decode changes every command's classification and needs its own compatibility review. Closes #2477