mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 22:24:31 +08:00
95 lines
5.5 KiB
Go
95 lines
5.5 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package errclass
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/larksuite/cli/errs"
|
|
)
|
|
|
|
// CodeMeta is the classification metadata attached to a Lark numeric code.
|
|
// It does NOT carry Message or Hint — those are derived at the dispatcher
|
|
// (see BuildAPIError).
|
|
//
|
|
// Risk + Action are populated only for codes that route to CategoryConfirmation;
|
|
// the dispatcher falls back to RiskUnknown + ctx.LarkCmd when either is empty
|
|
// so the envelope is never wire-invalid.
|
|
type CodeMeta struct {
|
|
Category errs.Category
|
|
Subtype errs.Subtype
|
|
Retryable bool
|
|
Risk string // CategoryConfirmation arm only; empty otherwise
|
|
Action string // CategoryConfirmation arm only; empty otherwise
|
|
}
|
|
|
|
// codeMeta is the central registry. Top-level entries (auth/authorization/api/
|
|
// policy/config codes shared across services) live here; service-specific
|
|
// sub-tables (e.g. task) live in dedicated files like codemeta_task.go and
|
|
// merge into this map via init().
|
|
//
|
|
// Go language guarantees package-level vars initialize before init() functions,
|
|
// so sub-tables registering via init() can always assume codeMeta is non-nil.
|
|
var codeMeta = map[int]CodeMeta{
|
|
// CategoryAuthentication
|
|
99991661: {Category: errs.CategoryAuthentication, Subtype: errs.SubtypeTokenMissing}, // Authorization header missing
|
|
99991671: {Category: errs.CategoryAuthentication, Subtype: errs.SubtypeTokenInvalid}, // token format error (must start with t- / u-)
|
|
99991668: {Category: errs.CategoryAuthentication, Subtype: errs.SubtypeTokenInvalid}, // UAT invalid/expired (server does not distinguish)
|
|
99991663: {Category: errs.CategoryAuthentication, Subtype: errs.SubtypeTokenInvalid}, // access_token invalid
|
|
99991677: {Category: errs.CategoryAuthentication, Subtype: errs.SubtypeTokenExpired}, // UAT expired
|
|
20026: {Category: errs.CategoryAuthentication, Subtype: errs.SubtypeRefreshTokenInvalid}, // refresh_token v1 legacy format
|
|
20037: {Category: errs.CategoryAuthentication, Subtype: errs.SubtypeRefreshTokenExpired}, // refresh_token expired
|
|
20064: {Category: errs.CategoryAuthentication, Subtype: errs.SubtypeRefreshTokenRevoked}, // refresh_token revoked
|
|
20073: {Category: errs.CategoryAuthentication, Subtype: errs.SubtypeRefreshTokenReused}, // refresh_token already used
|
|
20050: {Category: errs.CategoryAuthentication, Subtype: errs.SubtypeRefreshServerError, Retryable: true}, // refresh endpoint transient error
|
|
|
|
// CategoryAuthorization
|
|
99991672: {Category: errs.CategoryAuthorization, Subtype: errs.SubtypeAppScopeNotApplied},
|
|
99991676: {Category: errs.CategoryAuthorization, Subtype: errs.SubtypeTokenScopeInsufficient},
|
|
99991679: {Category: errs.CategoryAuthorization, Subtype: errs.SubtypeMissingScope}, // user authorized app but did not grant this scope
|
|
230027: {Category: errs.CategoryAuthorization, Subtype: errs.SubtypeUserUnauthorized}, // user never authorized the app
|
|
99991673: {Category: errs.CategoryAuthorization, Subtype: errs.SubtypeAppUnavailable}, // app status unavailable
|
|
99991662: {Category: errs.CategoryAuthorization, Subtype: errs.SubtypeAppDisabled}, // app currently disabled in tenant
|
|
|
|
// CategoryAPI
|
|
99991400: {Category: errs.CategoryAPI, Subtype: errs.SubtypeRateLimit, Retryable: true},
|
|
1061045: {Category: errs.CategoryAPI, Subtype: errs.SubtypeConflict, Retryable: true},
|
|
131009: {Category: errs.CategoryAPI, Subtype: errs.SubtypeConflict, Retryable: true}, // wiki write-path lock contention; retryable with backoff
|
|
1064510: {Category: errs.CategoryAPI, Subtype: errs.SubtypeCrossTenant},
|
|
1064511: {Category: errs.CategoryAPI, Subtype: errs.SubtypeCrossBrand},
|
|
1310246: {Category: errs.CategoryAPI, Subtype: errs.SubtypeInvalidParameters},
|
|
1063006: {Category: errs.CategoryAPI, Subtype: errs.SubtypeRateLimit}, // drive perm-apply quota; 5/day, not short-term retryable
|
|
1063007: {Category: errs.CategoryAPI, Subtype: errs.SubtypeInvalidParameters},
|
|
231205: {Category: errs.CategoryAPI, Subtype: errs.SubtypeOwnershipMismatch},
|
|
|
|
// CategoryConfig
|
|
99991543: {Category: errs.CategoryConfig, Subtype: errs.SubtypeInvalidClient}, // RFC 6749 §5.2 — app_id / app_secret incorrect (Open API)
|
|
10014: {Category: errs.CategoryConfig, Subtype: errs.SubtypeInvalidClient}, // legacy TAT endpoint — "app secret invalid" (pre-v3 variant of 99991543; CLI now reports invalid_client)
|
|
|
|
// CategoryPolicy
|
|
21000: {Category: errs.CategoryPolicy, Subtype: errs.SubtypeChallengeRequired},
|
|
21001: {Category: errs.CategoryPolicy, Subtype: errs.SubtypeAccessDenied},
|
|
}
|
|
|
|
// LookupCodeMeta is the single lookup entry. Returns ok=false for unknown codes —
|
|
// the caller (BuildAPIError) is responsible for falling back to
|
|
// CategoryAPI/SubtypeUnknown.
|
|
func LookupCodeMeta(code int) (CodeMeta, bool) {
|
|
m, ok := codeMeta[code]
|
|
return m, ok
|
|
}
|
|
|
|
// mergeCodeMeta is invoked by sub-table init() functions to merge service-specific
|
|
// codes into the central registry. Panics on duplicate code so a misregistration
|
|
// fails fast at startup rather than producing silently-inconsistent classification.
|
|
func mergeCodeMeta(src map[int]CodeMeta, owner string) {
|
|
for code, meta := range src {
|
|
if existing, dup := codeMeta[code]; dup {
|
|
panic(fmt.Sprintf("codeMeta dup: code %d already mapped %+v; %s wants %+v",
|
|
code, existing, owner, meta))
|
|
}
|
|
codeMeta[code] = meta
|
|
}
|
|
}
|