Files
dingtalk-workspace-cli/internal/compat/output_format.go
T
xianfeng wang bbca507792 feat(discovery): MCP discovery service abstraction with positional params, examples, defaults, and schema v3 extensions (#156)
* discovery

* discovery

* wk discovery

* wk discovery

* wk discovery

* wk discovery

* wk discovery

* wk discovery

* discovery example and default

* position param

* Fix test

* dev auto login

* fix test

* fix conflict
2026-04-24 14:08:17 +08:00

108 lines
3.0 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package compat
import (
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/market"
)
// buildOutputTransform compiles a CLIOutputFormat into a payload-shaping
// function applied to executor.Result.Response before the formatter runs.
// See discovery-schema-v3 §2.5.
//
// Apply order (deterministic): drop → rename → columns.
// Columns filter only takes effect under --format=table (the formatter
// consults it via the "_columns" marker key this function injects).
// Returns nil when the spec is empty so callers can skip wiring.
func buildOutputTransform(spec market.CLIOutputFormat) func(map[string]any) map[string]any {
if len(spec.Drop) == 0 && len(spec.Rename) == 0 && len(spec.Columns) == 0 {
return nil
}
dropped := append([]string(nil), spec.Drop...)
renamed := make(map[string]string, len(spec.Rename))
for k, v := range spec.Rename {
renamed[k] = v
}
columns := append([]string(nil), spec.Columns...)
return func(resp map[string]any) map[string]any {
if resp == nil {
return resp
}
applyDrop(resp, dropped)
applyRename(resp, renamed)
if len(columns) > 0 {
resp["_columns"] = append([]string(nil), columns...)
}
return resp
}
}
// applyDrop removes the named keys at the top level and one level of nested
// object. Missing keys are silently ignored. Keys with "." are treated as a
// two-part path (parent.child).
func applyDrop(m map[string]any, keys []string) {
for _, key := range keys {
if key == "" {
continue
}
delete(m, key)
}
for _, v := range m {
if inner, ok := v.(map[string]any); ok {
for _, key := range keys {
if key == "" {
continue
}
delete(inner, key)
}
}
}
}
// applyRename moves fields from src key to dst key at top level and one level
// of nested object. Collisions overwrite silently. Missing src keys are
// no-ops.
func applyRename(m map[string]any, mapping map[string]string) {
if len(mapping) == 0 {
return
}
// First pass: top level.
for src, dst := range mapping {
if src == "" || dst == "" || src == dst {
continue
}
if v, ok := m[src]; ok {
m[dst] = v
delete(m, src)
}
}
// Second pass: one level nested.
for _, v := range m {
inner, ok := v.(map[string]any)
if !ok {
continue
}
for src, dst := range mapping {
if src == "" || dst == "" || src == dst {
continue
}
if val, ok := inner[src]; ok {
inner[dst] = val
delete(inner, src)
}
}
}
}