Files
larksuite-cli/shortcuts/base/field_ops.go
kongenpei 052e2112bf fix: validate base shortcut JSON object inputs (#458)
* fix: validate base shortcut JSON object inputs

* fix: reject null in base JSON object parser

---------

Co-authored-by: kongenpei <kongenpei@users.noreply.github.com>
2026-04-14 19:30:23 +08:00

219 lines
7.2 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package base
import (
"context"
"strings"
"github.com/larksuite/cli/shortcuts/common"
)
func dryRunFieldList(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
offset := runtime.Int("offset")
if offset < 0 {
offset = 0
}
limit := common.ParseIntBounded(runtime, "limit", 1, 200)
return common.NewDryRunAPI().
GET("/open-apis/base/v3/bases/:base_token/tables/:table_id/fields").
Params(map[string]interface{}{"offset": offset, "limit": limit}).
Set("base_token", runtime.Str("base-token")).
Set("table_id", baseTableID(runtime))
}
func dryRunFieldGet(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
return common.NewDryRunAPI().
GET("/open-apis/base/v3/bases/:base_token/tables/:table_id/fields/:field_id").
Set("base_token", runtime.Str("base-token")).
Set("table_id", baseTableID(runtime)).
Set("field_id", runtime.Str("field-id"))
}
func dryRunFieldCreate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
pc := newParseCtx(runtime)
body, _ := parseJSONObject(pc, runtime.Str("json"), "json")
return common.NewDryRunAPI().
POST("/open-apis/base/v3/bases/:base_token/tables/:table_id/fields").
Body(body).
Set("base_token", runtime.Str("base-token")).
Set("table_id", baseTableID(runtime))
}
func dryRunFieldUpdate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
pc := newParseCtx(runtime)
body, _ := parseJSONObject(pc, runtime.Str("json"), "json")
return common.NewDryRunAPI().
PUT("/open-apis/base/v3/bases/:base_token/tables/:table_id/fields/:field_id").
Body(body).
Set("base_token", runtime.Str("base-token")).
Set("table_id", baseTableID(runtime)).
Set("field_id", runtime.Str("field-id"))
}
func dryRunFieldDelete(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
return common.NewDryRunAPI().
DELETE("/open-apis/base/v3/bases/:base_token/tables/:table_id/fields/:field_id").
Set("base_token", runtime.Str("base-token")).
Set("table_id", baseTableID(runtime)).
Set("field_id", runtime.Str("field-id"))
}
func dryRunFieldSearchOptions(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
params := map[string]interface{}{
"offset": runtime.Int("offset"),
"limit": runtime.Int("limit"),
}
if params["limit"].(int) <= 0 {
params["limit"] = 30
}
if keyword := strings.TrimSpace(runtime.Str("keyword")); keyword != "" {
params["query"] = keyword
}
return common.NewDryRunAPI().
GET("/open-apis/base/v3/bases/:base_token/tables/:table_id/fields/:field_id/options").
Params(params).
Set("base_token", runtime.Str("base-token")).
Set("table_id", baseTableID(runtime)).
Set("field_id", runtime.Str("field-id"))
}
func validateFieldJSON(runtime *common.RuntimeContext) (map[string]interface{}, error) {
pc := newParseCtx(runtime)
return parseJSONObject(pc, runtime.Str("json"), "json")
}
func validateFormulaLookupGuideAck(runtime *common.RuntimeContext, command string, body map[string]interface{}) error {
fieldType := strings.ToLower(strings.TrimSpace(common.GetString(body, "type")))
if (fieldType == "formula" || fieldType == "lookup") && !runtime.Bool("i-have-read-guide") {
guidePath := "skills/lark-base/references/formula-field-guide.md"
if fieldType == "lookup" {
guidePath = "skills/lark-base/references/lookup-field-guide.md"
}
return common.FlagErrorf("--i-have-read-guide is required for %s when --json.type is %q; read %s first, then retry with --i-have-read-guide", command, fieldType, guidePath)
}
return nil
}
func validateFieldCreate(runtime *common.RuntimeContext) error {
body, err := validateFieldJSON(runtime)
if err != nil {
return err
}
return validateFormulaLookupGuideAck(runtime, "+field-create", body)
}
func validateFieldUpdate(runtime *common.RuntimeContext) error {
body, err := validateFieldJSON(runtime)
if err != nil {
return err
}
return validateFormulaLookupGuideAck(runtime, "+field-update", body)
}
func executeFieldList(runtime *common.RuntimeContext) error {
offset := runtime.Int("offset")
if offset < 0 {
offset = 0
}
limit := common.ParseIntBounded(runtime, "limit", 1, 200)
fields, total, err := listAllFields(runtime, runtime.Str("base-token"), baseTableID(runtime), offset, limit)
if err != nil {
return err
}
if total == 0 {
total = len(fields)
}
runtime.Out(map[string]interface{}{"fields": fields, "total": total}, nil)
return nil
}
func executeFieldGet(runtime *common.RuntimeContext) error {
baseToken := runtime.Str("base-token")
tableIDValue := baseTableID(runtime)
fieldRef := runtime.Str("field-id")
data, err := baseV3Call(runtime, "GET", baseV3Path("bases", baseToken, "tables", tableIDValue, "fields", fieldRef), nil, nil)
if err != nil {
return err
}
runtime.Out(map[string]interface{}{"field": data}, nil)
return nil
}
func executeFieldCreate(runtime *common.RuntimeContext) error {
pc := newParseCtx(runtime)
body, err := parseJSONObject(pc, runtime.Str("json"), "json")
if err != nil {
return err
}
data, err := baseV3Call(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "tables", baseTableID(runtime), "fields"), nil, body)
if err != nil {
return err
}
runtime.Out(map[string]interface{}{"field": data, "created": true}, nil)
return nil
}
func executeFieldUpdate(runtime *common.RuntimeContext) error {
pc := newParseCtx(runtime)
baseToken := runtime.Str("base-token")
tableIDValue := baseTableID(runtime)
body, err := parseJSONObject(pc, runtime.Str("json"), "json")
if err != nil {
return err
}
fieldRef := runtime.Str("field-id")
data, err := baseV3Call(runtime, "PUT", baseV3Path("bases", baseToken, "tables", tableIDValue, "fields", fieldRef), nil, body)
if err != nil {
return err
}
runtime.Out(map[string]interface{}{"field": data, "updated": true}, nil)
return nil
}
func executeFieldDelete(runtime *common.RuntimeContext) error {
baseToken := runtime.Str("base-token")
tableIDValue := baseTableID(runtime)
fieldRef := runtime.Str("field-id")
_, err := baseV3Call(runtime, "DELETE", baseV3Path("bases", baseToken, "tables", tableIDValue, "fields", fieldRef), nil, nil)
if err != nil {
return err
}
runtime.Out(map[string]interface{}{"deleted": true, "field_id": fieldRef, "field_name": fieldRef}, nil)
return nil
}
func executeFieldSearchOptions(runtime *common.RuntimeContext) error {
baseToken := runtime.Str("base-token")
tableIDValue := baseTableID(runtime)
fieldRef := runtime.Str("field-id")
params := map[string]interface{}{
"offset": runtime.Int("offset"),
"limit": runtime.Int("limit"),
}
if params["limit"].(int) <= 0 {
params["limit"] = 30
}
if keyword := strings.TrimSpace(runtime.Str("keyword")); keyword != "" {
params["query"] = keyword
}
data, err := baseV3Call(runtime, "GET", baseV3Path("bases", baseToken, "tables", tableIDValue, "fields", fieldRef, "options"), params, nil)
if err != nil {
return err
}
options, _ := data["options"].([]interface{})
total := toInt(data["total"])
if total == 0 {
total = len(options)
}
runtime.Out(map[string]interface{}{
"field_id": fieldRef,
"field_name": fieldRef,
"keyword": strings.TrimSpace(runtime.Str("keyword")),
"options": options,
"total": total,
}, nil)
return nil
}