Files
larksuite__cli/internal/errclass/codemeta_spark.go
chenxingyang1019 e0e90a4e1b fix(apps): classify the online DDL ban and the file storage quota failure (#2460)
* fix(apps): classify the online DDL/DCL ban on +db-execute

Running DDL against the online branch of a multi-env app came back as
api/server_error with exit 1, hinted "fix the SQL and re-run", and carried a
statement position that did not exist. All three point the caller the wrong way:

  - server_error means "upstream 5xx, retryable"; this is a product rule and no
    number of retries changes it;
  - the SQL is fine — the target environment is what has to change;
  - "(at statement 1 of 1)" is fabricated. The server pre-validates the whole
    batch and returns a single ERROR sentinel, so a 5-statement request with the
    DDL in position 4 still rendered as "1 of 1". The CLI does not split the SQL,
    so it cannot know the real count and cannot detect the mismatch generally —
    only codes known to be batch-level rejections can drop the suffix.

Give code 4000001 its own arm: validation/failed_precondition (exit 2, "change
the environment, do not retry"), a hint pointing at dev plus +db-env-migrate, and
no statement position. Every other code keeps its current classification, wording
and position suffix; a test pins that.

4000001 is a dedicated server-side code (ErrOnlineEnvForbidDDLDCL, client-error
band), raised only by the pre-validation pass when env==online on a multi-env
workspace. Syntax errors and PG errors use different codes, so keying on it is
safe. Matching on the numeric value also covers the "k_dl_4000001" wire form,
since codeString already strips that prefix — both forms are tested.

"No statements were applied" is stated rather than inferred here: the validator
walks every statement and rejects the batch on the first DDL, so nothing lands.
The default arm would have inferred the opposite for a DDL in a later position
("Earlier statements were committed"), which is wrong for this code.

* fix(apps): classify tenant file storage quota exceeded

+file-upload against a tenant whose file storage is full returned api/unknown with
no hint at all, so a caller could not tell "the quota is full, stop" from "the
upstream had a bad minute, retry" — and had no next step either.

Register 400000055 as api/quota_exceeded. That is a dedicated server-side code
(ErrTenantStorageQuotaExceeded, client-error band) raised only on the upload path,
and the subtype already carries "retrying will not help", so the framework's
existing quota wording is enough and no domain-specific hint is added.

Left in CategoryAPI (exit 1) rather than Validation (exit 2): a full quota is not
something a different argument fixes, and exit 2 would imply it is.

The test asserts the hint is non-empty on purpose. The wording comes from the
shared APIHint table, so if quota_exceeded is ever dropped from there this fails
and says the code now needs its own wording, instead of silently shipping an
empty hint.
2026-08-24 16:49:33 +08:00

50 lines
4.4 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package errclass
import "github.com/larksuite/cli/errs"
// sparkCodeMeta holds stable Spark business-code classifications.
// Command-specific recovery guidance belongs in the Apps shortcut layer; the
// numeric code remains the source-specific discriminator on the error envelope.
var sparkCodeMeta = map[int]CodeMeta{
3340001: {Category: errs.CategoryAPI, Subtype: errs.SubtypeInvalidParameters}, // request parameters are invalid
3344027: {Category: errs.CategoryAPI, Subtype: errs.SubtypeQuotaExceeded}, // role user count exceeds the service limit
3344028: {Category: errs.CategoryAPI, Subtype: errs.SubtypeQuotaExceeded}, // role department count exceeds the service limit
3344029: {Category: errs.CategoryAPI, Subtype: errs.SubtypeQuotaExceeded}, // role chat count exceeds the service limit
3344030: {Category: errs.CategoryAuthorization, Subtype: errs.SubtypePermissionDenied}, // app administrator required
3344031: {Category: errs.CategoryAuthorization, Subtype: errs.SubtypePermissionDenied}, // app administrator or developer required
3344034: {Category: errs.CategoryAPI, Subtype: errs.SubtypeInvalidParameters}, // invalid role ID
3344035: {Category: errs.CategoryAPI, Subtype: errs.SubtypeNotFound}, // role does not exist
3344036: {Category: errs.CategoryAPI, Subtype: errs.SubtypeAlreadyExists}, // role ID already exists
3344037: {Category: errs.CategoryAPI, Subtype: errs.SubtypeQuotaExceeded}, // app role count exceeds the service limit
3344038: {Category: errs.CategoryAPI, Subtype: errs.SubtypeInvalidParameters}, // invalid role name
3344039: {Category: errs.CategoryAPI, Subtype: errs.SubtypeInvalidParameters}, // invalid role description
3344040: {Category: errs.CategoryAPI, Subtype: errs.SubtypeInvalidParameters}, // unsupported member type
3344041: {Category: errs.CategoryAPI, Subtype: errs.SubtypeInvalidParameters}, // invalid member ID
221800: {Category: errs.CategoryValidation, Subtype: errs.SubtypeFailedPrecondition}, // Miaoda not activated for this tenant
400002484: {Category: errs.CategoryValidation, Subtype: errs.SubtypeInvalidArgument}, // app id does not identify an app (missing or malformed)
400002465: {Category: errs.CategoryValidation, Subtype: errs.SubtypeFailedPrecondition}, // app has no database yet
500002759: {Category: errs.CategoryValidation, Subtype: errs.SubtypeFailedPrecondition}, // app has no database yet, pre-4xx renumber
400002655: {Category: errs.CategoryValidation, Subtype: errs.SubtypeFailedPrecondition}, // app has no running container yet (online observability)
400002469: {Category: errs.CategoryAPI, Subtype: errs.SubtypeNotFound}, // table does not exist
400002477: {Category: errs.CategoryAPI, Subtype: errs.SubtypeInvalidParameters}, // db sync mapping is invalid
400002478: {Category: errs.CategoryAPI, Subtype: errs.SubtypeInvalidParameters}, // db sync target schema mismatch
400002479: {Category: errs.CategoryValidation, Subtype: errs.SubtypeFailedPrecondition}, // db sync operation is not allowed in the current task state
400002480: {Category: errs.CategoryAPI, Subtype: errs.SubtypeNotFound}, // db sync task does not exist
400002481: {Category: errs.CategoryAPI, Subtype: errs.SubtypeInvalidParameters}, // db sync task ID is invalid
400002482: {Category: errs.CategoryAPI, Subtype: errs.SubtypeNotFound}, // db sync source table does not exist
400002483: {Category: errs.CategoryAPI, Subtype: errs.SubtypeNotFound}, // db sync target table does not exist
400000034: {Category: errs.CategoryAPI, Subtype: errs.SubtypeNotFound}, // file not found or no access
500000034: {Category: errs.CategoryAPI, Subtype: errs.SubtypeNotFound}, // file not found or no access, pre-4xx renumber
400000055: {Category: errs.CategoryAPI, Subtype: errs.SubtypeQuotaExceeded}, // tenant file storage quota exceeded
400002467: {Category: errs.CategoryAuthorization, Subtype: errs.SubtypePermissionDenied}, // app administrator or developer required
500002761: {Category: errs.CategoryAuthorization, Subtype: errs.SubtypePermissionDenied}, // app administrator or developer required, pre-4xx renumber
}
func init() { mergeCodeMeta(sparkCodeMeta, "spark") }