Files
larksuite__cli/shortcuts/drive/drive_secure_label.go
evandance 98173ae5a9 feat(drive): emit typed error envelopes across the drive domain (#1205)
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.
2026-06-03 10:27:15 +08:00

125 lines
4.3 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package drive
import (
"context"
"fmt"
"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/internal/validate"
"github.com/larksuite/cli/shortcuts/common"
)
const (
secureLabelReadScope = "drive:file.meta.sec_label.read_only"
secureLabelUpdateScope = "docs:secure_label:write_only"
)
var secureLabelTypes = permApplyTypes
// DriveSecureLabelList lists secure labels available to the current user.
var DriveSecureLabelList = common.Shortcut{
Service: "drive",
Command: "+secure-label-list",
Description: "List secure labels available to the current user",
Risk: "read",
Scopes: []string{secureLabelReadScope},
AuthTypes: []string{"user"},
HasFormat: true,
Flags: []common.Flag{
{Name: "page-size", Type: "int", Default: "10", Desc: "page size, 1-10"},
{Name: "page-token", Desc: "pagination token from previous response"},
{Name: "lang", Desc: "label language", Enum: []string{"zh", "en", "ja"}},
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
pageSize := runtime.Int("page-size")
if pageSize < 1 || pageSize > 10 {
return errs.NewValidationError(errs.SubtypeInvalidArgument, "--page-size must be between 1 and 10").WithParam("--page-size")
}
return nil
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
return common.NewDryRunAPI().
Desc("List secure labels available to the current user").
GET("/open-apis/drive/v2/my_secure_labels").
Params(buildSecureLabelListParams(runtime))
},
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
data, err := runtime.CallAPITyped("GET",
"/open-apis/drive/v2/my_secure_labels",
buildSecureLabelListParams(runtime),
nil,
)
if err != nil {
return err
}
runtime.OutFormat(data, nil, nil)
return nil
},
}
// DriveSecureLabelUpdate updates the secure label on a Drive file/document.
var DriveSecureLabelUpdate = common.Shortcut{
Service: "drive",
Command: "+secure-label-update",
Description: "Update the secure label on a Drive file or document",
Risk: "write",
Scopes: []string{secureLabelUpdateScope},
AuthTypes: []string{"user"},
Flags: []common.Flag{
{Name: "token", Desc: "target file token or document URL (docx/sheets/base/file/wiki/doc/mindnote/slides)", Required: true},
{Name: "type", Desc: "target type; auto-inferred from URL when omitted", Enum: secureLabelTypes},
{Name: "label-id", Desc: "secure label ID to set", Required: true},
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
_, _, err := resolveSecureLabelTarget(runtime.Str("token"), runtime.Str("type"))
return err
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
token, docType, err := resolveSecureLabelTarget(runtime.Str("token"), runtime.Str("type"))
if err != nil {
return common.NewDryRunAPI().Set("error", err.Error())
}
return common.NewDryRunAPI().
Desc("Update Drive secure label").
PATCH("/open-apis/drive/v2/files/:file_token/secure_label").
Params(map[string]interface{}{"type": docType}).
Body(map[string]interface{}{"id": runtime.Str("label-id")}).
Set("file_token", token)
},
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
token, docType, err := resolveSecureLabelTarget(runtime.Str("token"), runtime.Str("type"))
if err != nil {
return err
}
body := map[string]interface{}{"id": runtime.Str("label-id")}
data, err := runtime.CallAPITyped("PATCH",
fmt.Sprintf("/open-apis/drive/v2/files/%s/secure_label", validate.EncodePathSegment(token)),
map[string]interface{}{"type": docType},
body,
)
if err != nil {
return err
}
runtime.Out(data, nil)
return nil
},
}
func buildSecureLabelListParams(runtime *common.RuntimeContext) map[string]interface{} {
params := map[string]interface{}{"page_size": runtime.Int("page-size")}
if pageToken := runtime.Str("page-token"); pageToken != "" {
params["page_token"] = pageToken
}
if lang := runtime.Str("lang"); lang != "" {
params["lang"] = lang
}
return params
}
func resolveSecureLabelTarget(raw, explicitType string) (token, docType string, err error) {
return resolvePermApplyTarget(raw, explicitType)
}