classifyTATResponseCode wraps a deterministic (non-transient) failure from the unified Token Endpoint into the canonical typed errs.* error. The v3 endpoint reports failures using the OAuth 2.0 model — an `error` string plus an optional numeric `code` — instead of the legacy `{code, msg}` shape. in
(code int, oauthErr, errDesc, brand, appID string)
| 32 | // swallowing it. Transient/server-side failures (5xx / server_error) are |
| 33 | // filtered out by FetchTAT before this is called, so they stay untyped. |
| 34 | func classifyTATResponseCode(code int, oauthErr, errDesc, brand, appID string) error { |
| 35 | msg := errDesc |
| 36 | if msg == "" { |
| 37 | msg = oauthErr |
| 38 | } |
| 39 | switch oauthErr { |
| 40 | case "invalid_client", "unauthorized_client": |
| 41 | return errs.NewConfigError(errs.SubtypeInvalidClient, "%s", msg). |
| 42 | WithCode(code). |
| 43 | WithHint("%s", errclass.ConfigHint(errs.SubtypeInvalidClient)) |
| 44 | } |
| 45 | if err := errclass.BuildAPIError(map[string]any{ |
| 46 | "code": code, |
| 47 | "msg": msg, |
| 48 | }, errclass.ClassifyContext{ |
| 49 | Brand: brand, |
| 50 | AppID: appID, |
| 51 | }); err != nil { |
| 52 | return err |
| 53 | } |
| 54 | // BuildAPIError returns nil for code 0 (Feishu's success convention), but this |
| 55 | // function is only reached once FetchTAT has ruled out success — a non-credential |
| 56 | // OAuth error (e.g. invalid_scope) can arrive with code 0 and is still a |
| 57 | // deterministic rejection. Back it with a typed APIError so callers never receive |
| 58 | // the ("", nil) "empty token, no error" pair. |
| 59 | return errs.NewAPIError(errs.SubtypeUnknown, "%s", msg).WithCode(code) |
| 60 | } |
| 61 | |
| 62 | // DefaultAccountProvider resolves account from config.json via keychain. |
| 63 | type DefaultAccountProvider struct { |