HandleResponse routes a raw *larkcore.ApiResp to the appropriate output: 1. If Content-Type is JSON, check for business errors first (even with --output). 2. If --output is set and response is not a JSON error, save to file. 3. If Content-Type is non-JSON and no --output, auto-save binary to file.
(resp *larkcore.ApiResp, opts ResponseOptions)
| 66 | // 2. If --output is set and response is not a JSON error, save to file. |
| 67 | // 3. If Content-Type is non-JSON and no --output, auto-save binary to file. |
| 68 | func HandleResponse(resp *larkcore.ApiResp, opts ResponseOptions) error { |
| 69 | ct := resp.Header.Get("Content-Type") |
| 70 | identity := opts.Identity |
| 71 | if identity == "" { |
| 72 | identity = core.AsUser |
| 73 | } |
| 74 | check := opts.CheckError |
| 75 | if check == nil { |
| 76 | // Default check routes through BuildAPIError, producing typed |
| 77 | // *errs.PermissionError / AuthenticationError / etc. A zero-value |
| 78 | // *APIClient is safe here because BuildAPIError gracefully degrades |
| 79 | // identity-aware fields (ConsoleURL etc.) when AppID is empty. |
| 80 | check = func(r interface{}, id core.Identity) error { |
| 81 | return (&APIClient{}).CheckResponse(r, id) |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | // Non-JSON error responses (e.g. 404 text/plain from gateway): return error |
| 86 | // directly instead of falling through to the binary-save path. |
| 87 | if resp.StatusCode >= 400 && !IsJSONContentType(ct) && ct != "" { |
| 88 | return httpStatusError(resp.StatusCode, resp.RawBody) |
| 89 | } |
| 90 | |
| 91 | // JSON responses: always check for business errors before saving. |
| 92 | if IsJSONContentType(ct) || ct == "" { |
| 93 | result, err := ParseJSONResponse(resp) |
| 94 | if err != nil { |
| 95 | // An unparseable / empty body on an HTTP error (common with a |
| 96 | // missing Content-Type) must be classified by status, not reported |
| 97 | // as an internal decode failure, matching the non-JSON branch above. |
| 98 | if resp.StatusCode >= 400 { |
| 99 | return httpStatusError(resp.StatusCode, resp.RawBody) |
| 100 | } |
| 101 | return WrapJSONResponseParseError(err, resp.RawBody) |
| 102 | } |
| 103 | if apiErr := check(result, identity); apiErr != nil { |
| 104 | return apiErr |
| 105 | } |
| 106 | // CheckResponse treats business code 0 as success, so a 4xx/5xx whose |
| 107 | // JSON body omits a non-zero code would otherwise be served as a |
| 108 | // successful result. Classify by HTTP status so it is never swallowed. |
| 109 | if resp.StatusCode >= 400 { |
| 110 | return httpStatusError(resp.StatusCode, resp.RawBody) |
| 111 | } |
| 112 | if opts.OutputPath != "" { |
| 113 | // File downloads keep the existing raw-response scan path because the |
| 114 | // saved payload is the API response body, not the success envelope. |
| 115 | scanResult := output.ScanForSafety(opts.CommandPath, result, opts.ErrOut) |
| 116 | if scanResult.Blocked { |
| 117 | return scanResult.BlockErr |
| 118 | } |
| 119 | if scanResult.Alert != nil { |
| 120 | output.WriteAlertWarning(opts.ErrOut, scanResult.Alert) |
| 121 | } |
| 122 | return saveAndPrint(opts.FileIO, resp, opts.OutputPath, opts.Out) |
| 123 | } |
| 124 | |
| 125 | if opts.JqExpr != "" || opts.Format == output.FormatJSON { |