Files
larksuite-cli/shortcuts/base/base_shortcut_helpers.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)
}
}