Files
larksuite-cli/internal/errclass/codemeta.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
}
}