mirror of
https://github.com/larksuite/cli.git
synced 2026-07-06 16:18:05 +08:00
Failures from the minutes and video-conference commands now surface as structured, typed errors carrying a stable category and subtype — spanning input validation, missing permissions, network and file-I/O failures, and remote API errors — so callers can branch on the error kind instead of parsing free-form text. Batch commands report partial failures explicitly, emitting per-item results with a non-zero exit instead of masking them.
187 lines
5.5 KiB
Go
187 lines
5.5 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package common
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/larksuite/cli/errs"
|
|
"github.com/larksuite/cli/extension/fileio"
|
|
"github.com/larksuite/cli/internal/output"
|
|
)
|
|
|
|
// FlagErrorf returns a validation error with flag context (exit code 2).
|
|
//
|
|
// Deprecated: use ValidationErrorf for typed error envelopes.
|
|
func FlagErrorf(format string, args ...any) error {
|
|
return output.ErrValidation(format, args...)
|
|
}
|
|
|
|
// ValidationErrorf returns a typed validation error with invalid_argument subtype.
|
|
func ValidationErrorf(format string, args ...any) *errs.ValidationError {
|
|
return errs.NewValidationError(errs.SubtypeInvalidArgument, format, args...)
|
|
}
|
|
|
|
// MutuallyExclusive checks that at most one of the given flags is set.
|
|
//
|
|
// Deprecated: use MutuallyExclusiveTyped for typed error envelopes.
|
|
func MutuallyExclusive(rt *RuntimeContext, flags ...string) error {
|
|
var set []string
|
|
for _, f := range flags {
|
|
val := rt.Str(f)
|
|
if val != "" {
|
|
set = append(set, "--"+f)
|
|
}
|
|
}
|
|
if len(set) > 1 {
|
|
return FlagErrorf("%s are mutually exclusive", strings.Join(set, " and "))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MutuallyExclusiveTyped checks that at most one of the given flags is set.
|
|
func MutuallyExclusiveTyped(rt *RuntimeContext, flags ...string) error {
|
|
var set []string
|
|
for _, f := range flags {
|
|
val := rt.Str(f)
|
|
if val != "" {
|
|
set = append(set, "--"+f)
|
|
}
|
|
}
|
|
if len(set) > 1 {
|
|
return ValidationErrorf("%s are mutually exclusive", strings.Join(set, " and ")).
|
|
WithParams(invalidParams(set, "mutually exclusive")...)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AtLeastOne checks that at least one of the given flags is set.
|
|
//
|
|
// Deprecated: use AtLeastOneTyped for typed error envelopes.
|
|
func AtLeastOne(rt *RuntimeContext, flags ...string) error {
|
|
for _, f := range flags {
|
|
if rt.Str(f) != "" {
|
|
return nil
|
|
}
|
|
}
|
|
names := make([]string, len(flags))
|
|
for i, f := range flags {
|
|
names[i] = "--" + f
|
|
}
|
|
return FlagErrorf("specify at least one of %s", strings.Join(names, " or "))
|
|
}
|
|
|
|
// AtLeastOneTyped checks that at least one of the given flags is set.
|
|
func AtLeastOneTyped(rt *RuntimeContext, flags ...string) error {
|
|
for _, f := range flags {
|
|
if rt.Str(f) != "" {
|
|
return nil
|
|
}
|
|
}
|
|
names := make([]string, len(flags))
|
|
for i, f := range flags {
|
|
names[i] = "--" + f
|
|
}
|
|
return ValidationErrorf("specify at least one of %s", strings.Join(names, " or ")).
|
|
WithParams(invalidParams(names, "required; specify at least one")...)
|
|
}
|
|
|
|
// ExactlyOne checks that exactly one of the given flags is set.
|
|
//
|
|
// Deprecated: use ExactlyOneTyped for typed error envelopes.
|
|
func ExactlyOne(rt *RuntimeContext, flags ...string) error {
|
|
if err := AtLeastOne(rt, flags...); err != nil {
|
|
return err
|
|
}
|
|
return MutuallyExclusive(rt, flags...)
|
|
}
|
|
|
|
// ExactlyOneTyped checks that exactly one of the given flags is set.
|
|
func ExactlyOneTyped(rt *RuntimeContext, flags ...string) error {
|
|
if err := AtLeastOneTyped(rt, flags...); err != nil {
|
|
return err
|
|
}
|
|
return MutuallyExclusiveTyped(rt, flags...)
|
|
}
|
|
|
|
// ValidatePageSizeTyped validates that the named flag (if set) is an integer within [minVal, maxVal].
|
|
// It returns the parsed value (or defaultVal if the flag is empty) and any validation error.
|
|
func ValidatePageSizeTyped(rt *RuntimeContext, flagName string, defaultVal, minVal, maxVal int) (int, error) {
|
|
s := rt.Str(flagName)
|
|
param := "--" + flagName
|
|
if s == "" {
|
|
return defaultVal, nil
|
|
}
|
|
n, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return 0, ValidationErrorf("invalid --%s %q: must be an integer", flagName, s).WithParam(param)
|
|
}
|
|
if n < minVal || n > maxVal {
|
|
return 0, ValidationErrorf("invalid --%s %d: must be between %d and %d", flagName, n, minVal, maxVal).
|
|
WithParam(param)
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
// ParseIntBounded parses an int flag and clamps it to [min, max].
|
|
func ParseIntBounded(rt *RuntimeContext, name string, min, max int) int {
|
|
v := rt.Int(name)
|
|
if v < min {
|
|
return min
|
|
}
|
|
if v > max {
|
|
return max
|
|
}
|
|
return v
|
|
}
|
|
|
|
// ValidateSafePath ensures path is relative and resolves within the current
|
|
// working directory. It catches traversal, symlink escape, and control
|
|
// characters by delegating to FileIO.ResolvePath. Works for both file and
|
|
// directory paths.
|
|
//
|
|
// Deprecated: use ValidateSafePathTyped for typed error envelopes.
|
|
func ValidateSafePath(fio fileio.FileIO, path string) error {
|
|
_, err := fio.ResolvePath(path)
|
|
return err
|
|
}
|
|
|
|
// ValidateSafePathTyped ensures path resolves within the current working directory.
|
|
func ValidateSafePathTyped(fio fileio.FileIO, path string) error {
|
|
_, err := fio.ResolvePath(path)
|
|
if err != nil {
|
|
return ValidationErrorf("%s", err).WithCause(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RejectDangerousCharsTyped returns an error if value contains ASCII control
|
|
// characters or dangerous Unicode code points.
|
|
func RejectDangerousCharsTyped(paramName, value string) error {
|
|
for _, r := range value {
|
|
if r < 0x20 && r != '\t' && r != '\n' {
|
|
return ValidationErrorf("parameter %q contains control character U+%04X", paramName, r).
|
|
WithParam(paramName)
|
|
}
|
|
if r == 0x7F {
|
|
return ValidationErrorf("parameter %q contains DEL character", paramName).
|
|
WithParam(paramName)
|
|
}
|
|
if IsDangerousUnicode(r) {
|
|
return ValidationErrorf("parameter %q contains dangerous Unicode character U+%04X", paramName, r).
|
|
WithParam(paramName)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func invalidParams(names []string, reason string) []errs.InvalidParam {
|
|
params := make([]errs.InvalidParam, len(names))
|
|
for i, name := range names {
|
|
params[i] = errs.InvalidParam{Name: name, Reason: reason}
|
|
}
|
|
return params
|
|
}
|