mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
Drive-domain errors now leave the CLI as typed, machine-branchable envelopes — a stable `type` plus `subtype` and named fields (param, params, retryable, log_id, hint) — so scripts and AI agents can branch on structure and act on a recovery hint instead of parsing prose. Changes: - Every error produced in the drive domain — validation, file I/O, and the failures returned from its Lark API calls — is emitted as a typed errs.* error; the exit code is derived from the error category. Drive's API calls now go through a shared typed classifier, so failures carry subtype, troubleshooter, a recovery hint, and the request's log_id whether the server returns it in the response body or the x-tt-logid header; an already-typed network/auth error is never downgraded into a generic API error. - Known API conditions (resource conflict, cross-tenant, cross-brand, ...) carry a recovery hint keyed by their error class; a command can refine that hint with command-specific guidance. - Batch partial failures (+push / +pull / +sync, where some items succeed and some fail) now report an honest ok:false multi-status result on stdout — the summary and every per-item outcome stay machine-readable — and exit non-zero, instead of a misleading ok:true success envelope. - Duplicate rel_path conflicts report each colliding path as a structured params entry (RFC 7807 invalid-params style). - Static guards lock the drive path so legacy error construction — direct envelopes or the auto-classifying API helpers — cannot be reintroduced, making drive the template for the remaining domains. Output changes worth noting for consumers: - Error envelopes now carry typed type/subtype and named fields; exit codes follow the error category (malformed or incomplete API responses are reported as internal errors rather than generic API errors). - Batch partial failures (+push / +pull / +sync) emit an ok:false result envelope on stdout (summary + per-item items[]) and exit non-zero; the per-item results stay on stdout rather than in a stderr error envelope. Errors surfaced through shared cross-domain helpers (scope precheck, media import upload, metadata lookup, save-path resolution) are not yet typed; they migrate with the shared layer in a follow-up change.
74 lines
2.5 KiB
Go
74 lines
2.5 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||
// SPDX-License-Identifier: MIT
|
||
|
||
package output
|
||
|
||
import (
|
||
"errors"
|
||
|
||
"github.com/larksuite/cli/errs"
|
||
)
|
||
|
||
// Fine-grained error types (permission, not_found, rate_limit, etc.)
|
||
// are communicated via the JSON error envelope's "type" field,
|
||
// not via exit codes.
|
||
const (
|
||
ExitOK = 0 // 成功
|
||
ExitAPI = 1 // API / 通用错误(含 permission、not_found、conflict、rate_limit)
|
||
ExitValidation = 2 // 参数校验失败
|
||
ExitAuth = 3 // 认证失败(token 无效 / 过期),或登录成功但请求 scopes 未全部授予
|
||
ExitNetwork = 4 // 网络错误(连接超时、DNS 解析失败等)
|
||
ExitInternal = 5 // 内部错误(不应发生)
|
||
ExitContentSafety = 6 // content safety violation (block mode)
|
||
ExitConfirmationRequired = 10 // 高风险操作需要 --yes 确认(agent 协议信号)
|
||
)
|
||
|
||
// ExitCodeForCategory maps an errs.Category to the shell exit code.
|
||
// Multiple categories may share an exit code (Authentication / Authorization /
|
||
// Config all map to 3), so the relationship is many-to-one.
|
||
func ExitCodeForCategory(cat errs.Category) int {
|
||
switch cat {
|
||
case errs.CategoryValidation:
|
||
return ExitValidation
|
||
case errs.CategoryAuthentication, errs.CategoryAuthorization, errs.CategoryConfig:
|
||
return ExitAuth
|
||
case errs.CategoryNetwork:
|
||
return ExitNetwork
|
||
case errs.CategoryAPI:
|
||
return ExitAPI
|
||
case errs.CategoryPolicy:
|
||
return ExitContentSafety
|
||
case errs.CategoryInternal:
|
||
return ExitInternal
|
||
case errs.CategoryConfirmation:
|
||
return ExitConfirmationRequired
|
||
}
|
||
return ExitInternal
|
||
}
|
||
|
||
// ExitCodeOf returns the shell exit code for any error.
|
||
// - typed errors (*errs.PermissionError, *errs.APIError, ...) → routed by Category
|
||
// - legacy *output.ExitError → uses its own Code field
|
||
// - *core.ConfigError → reaches the dispatcher as a legacy
|
||
// *output.ExitError via cmd/root asExitError (stage 1); the typed
|
||
// promotion path through internal/errcompat.PromoteConfigError is
|
||
// reserved for stage 2+.
|
||
// - untyped → ExitInternal
|
||
func ExitCodeOf(err error) int {
|
||
if err == nil {
|
||
return ExitOK
|
||
}
|
||
if _, ok := errs.ProblemOf(err); ok {
|
||
return ExitCodeForCategory(errs.CategoryOf(err))
|
||
}
|
||
var pfErr *PartialFailureError
|
||
if errors.As(err, &pfErr) {
|
||
return pfErr.Code
|
||
}
|
||
var exitErr *ExitError
|
||
if errors.As(err, &exitErr) {
|
||
return exitErr.Code
|
||
}
|
||
return ExitInternal
|
||
}
|