mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
175 lines
5.2 KiB
Go
175 lines
5.2 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package base
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/larksuite/cli/extension/fileio"
|
|
"github.com/larksuite/cli/shortcuts/common"
|
|
)
|
|
|
|
// parseCtx carries file I/O dependency for JSON/file parsing helpers.
|
|
type parseCtx struct {
|
|
fio fileio.FileIO
|
|
}
|
|
|
|
func newParseCtx(runtime *common.RuntimeContext) *parseCtx {
|
|
return &parseCtx{fio: runtime.FileIO()}
|
|
}
|
|
|
|
func baseTableID(runtime *common.RuntimeContext) string {
|
|
return strings.TrimSpace(runtime.Str("table-id"))
|
|
}
|
|
|
|
func pageSizeLimitAliasFlag() common.Flag {
|
|
return common.Flag{Name: "page-size", Type: "int", Default: "0", Desc: "hidden alias for --limit", Hidden: true}
|
|
}
|
|
|
|
func getPaginationLimit(runtime *common.RuntimeContext) int {
|
|
if !runtime.Changed("limit") && runtime.Changed("page-size") {
|
|
return runtime.Int("page-size")
|
|
}
|
|
return runtime.Int("limit")
|
|
}
|
|
|
|
func validateLimitPageSizeAlias(runtime *common.RuntimeContext) error {
|
|
if runtime.Changed("limit") && runtime.Changed("page-size") {
|
|
return common.ValidationErrorf("--limit and --page-size are mutually exclusive; use --limit").
|
|
WithParam("--page-size")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func loadJSONInput(pc *parseCtx, raw string, flagName string) (string, error) {
|
|
raw = strings.TrimSpace(raw)
|
|
if raw == "" {
|
|
return "", baseFlagErrorf("--%s cannot be empty", flagName)
|
|
}
|
|
if !strings.HasPrefix(raw, "@") {
|
|
return raw, nil
|
|
}
|
|
path := strings.TrimSpace(strings.TrimPrefix(raw, "@"))
|
|
if path == "" {
|
|
return "", baseFlagErrorf("--%s file path cannot be empty after @", flagName)
|
|
}
|
|
if pc.fio == nil {
|
|
return "", baseMissingFileIOError("--%s @file inputs require a FileIO provider", flagName)
|
|
}
|
|
f, err := pc.fio.Open(path)
|
|
if err != nil {
|
|
var pathErr *fileio.PathValidationError
|
|
if errors.As(err, &pathErr) {
|
|
return "", baseFlagErrorf("--%s invalid JSON file path %q: %v", flagName, path, pathErr.Err)
|
|
}
|
|
return "", baseFlagErrorf("--%s cannot open JSON file %q: %v", flagName, path, err)
|
|
}
|
|
defer f.Close()
|
|
data, err := io.ReadAll(f)
|
|
if err != nil {
|
|
return "", baseFlagErrorf("--%s cannot read JSON file %q: %v", flagName, path, err)
|
|
}
|
|
content := strings.TrimSpace(string(data))
|
|
if content == "" {
|
|
return "", baseFlagErrorf("--%s JSON file %q is empty", flagName, path)
|
|
}
|
|
return content, nil
|
|
}
|
|
|
|
func jsonInputTip(flagName string) string {
|
|
return fmt.Sprintf("tip: pass a valid JSON directly, or use --%s @file.json; for complex JSON/DSL, read the lark-base reference and match the documented shape", flagName)
|
|
}
|
|
|
|
func formatJSONError(flagName string, target string, err error) error {
|
|
if syntaxErr, ok := err.(*json.SyntaxError); ok {
|
|
return baseFlagErrorf("--%s invalid JSON %s near byte %d (%v); %s", flagName, target, syntaxErr.Offset, err, jsonInputTip(flagName))
|
|
}
|
|
if typeErr, ok := err.(*json.UnmarshalTypeError); ok {
|
|
if typeErr.Field != "" {
|
|
return baseFlagErrorf("--%s invalid JSON %s at field %q (%v); %s", flagName, target, typeErr.Field, err, jsonInputTip(flagName))
|
|
}
|
|
return baseFlagErrorf("--%s invalid JSON %s (%v); %s", flagName, target, err, jsonInputTip(flagName))
|
|
}
|
|
return baseFlagErrorf("--%s invalid JSON %s (%v); %s", flagName, target, err, jsonInputTip(flagName))
|
|
}
|
|
|
|
func baseAction(runtime *common.RuntimeContext, boolFlags []string, stringFlags []string) (string, error) {
|
|
active := []string{}
|
|
for _, name := range boolFlags {
|
|
if runtime.Bool(name) {
|
|
active = append(active, name)
|
|
}
|
|
}
|
|
for _, name := range stringFlags {
|
|
if strings.TrimSpace(runtime.Str(name)) != "" {
|
|
active = append(active, name)
|
|
}
|
|
}
|
|
if len(active) == 0 {
|
|
return "", baseFlagErrorf("specify one action")
|
|
}
|
|
if len(active) > 1 {
|
|
flags := make([]string, 0, len(active))
|
|
for _, item := range active {
|
|
flags = append(flags, "--"+item)
|
|
}
|
|
return "", baseFlagErrorf("actions are mutually exclusive: %s", strings.Join(flags, ", "))
|
|
}
|
|
return active[0], nil
|
|
}
|
|
|
|
func parseObjectList(pc *parseCtx, raw string, flagName string) ([]map[string]interface{}, error) {
|
|
raw = strings.TrimSpace(raw)
|
|
if raw == "" {
|
|
return nil, nil
|
|
}
|
|
var err error
|
|
raw, err = loadJSONInput(pc, raw, flagName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if strings.HasPrefix(raw, "[") {
|
|
arr, err := parseJSONArray(pc, raw, flagName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items := make([]map[string]interface{}, 0, len(arr))
|
|
for idx, item := range arr {
|
|
obj, ok := item.(map[string]interface{})
|
|
if !ok {
|
|
return nil, baseFlagErrorf("--%s item %d must be an object", flagName, idx+1)
|
|
}
|
|
items = append(items, obj)
|
|
}
|
|
return items, nil
|
|
}
|
|
obj, err := parseJSONObject(pc, raw, flagName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []map[string]interface{}{obj}, nil
|
|
}
|
|
|
|
func parseJSONValue(pc *parseCtx, raw string, flagName string) (interface{}, error) {
|
|
var err error
|
|
raw, err = loadJSONInput(pc, raw, flagName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var value interface{}
|
|
if err := common.ParseJSON([]byte(raw), &value); err != nil {
|
|
return nil, formatJSONError(flagName, "value", err)
|
|
}
|
|
switch value.(type) {
|
|
case map[string]interface{}, []interface{}:
|
|
return value, nil
|
|
default:
|
|
return nil, baseFlagErrorf("--%s must be a JSON object or array", flagName)
|
|
}
|
|
}
|