Files
1Panel/core/utils/mcp/projection.go
蘭 d7355f3ccc feat(mcp): add 1Panel MCP and enhance MCP credential management (#14189)
* feat(mcp): add 1Panel MCP and enhance MCP credential management

* feat(i18n): implement MCP document localization and remove legacy code
2026-10-26 13:59:49 +08:00

106 lines
2.4 KiB
Go

package mcp
import (
"encoding/json"
"errors"
"strings"
)
type projectionNode struct {
leaf bool
children map[string]*projectionNode
}
func projectionTree(fields []string) (*projectionNode, error) {
root := &projectionNode{children: map[string]*projectionNode{}}
if len(fields) > 256 {
return nil, errors.New("too many response fields")
}
for _, field := range fields {
parts := strings.Split(field, ".")
if len(parts) > 16 {
return nil, errors.New("response path too deep")
}
node := root
for _, part := range parts {
if !fieldPart.MatchString(part) || node.leaf {
return nil, errors.New("invalid response path")
}
if node.children[part] == nil {
node.children[part] = &projectionNode{children: map[string]*projectionNode{}}
}
node = node.children[part]
}
if node.leaf || len(node.children) > 0 {
return nil, errors.New("conflicting response fields")
}
node.leaf = true
}
return root, nil
}
// ProjectResult never copies a complete business object. A manifest lists
// scalar leaves explicitly, and paths transparently traverse arrays.
func ProjectResult(value any, fields []string) (any, error) {
root, err := projectionTree(fields)
if err != nil {
return nil, err
}
raw, err := json.Marshal(value)
if err != nil {
return nil, err
}
var data any
if err = json.Unmarshal(raw, &data); err != nil {
return nil, err
}
return projectValue(data, root, 0)
}
func projectValue(value any, node *projectionNode, depth int) (any, error) {
if depth > 32 {
return nil, errors.New("response too deep")
}
if value == nil {
return nil, nil
}
if list, ok := value.([]any); ok {
out := make([]any, 0, len(list))
for _, item := range list {
v, err := projectValue(item, node, depth+1)
if err != nil {
return nil, err
}
out = append(out, v)
}
return out, nil
}
if node.leaf {
switch value.(type) {
case string, float64, bool:
return value, nil
default:
return nil, errors.New("response field must be a scalar leaf")
}
}
if len(node.children) == 0 {
return map[string]any{}, nil
}
obj, ok := value.(map[string]any)
if !ok {
return nil, errors.New("response shape changed")
}
out := map[string]any{}
for key, child := range node.children {
v, exists := obj[key]
if !exists {
continue
}
selected, err := projectValue(v, child, depth+1)
if err != nil {
return nil, err
}
out[key] = selected
}
return out, nil
}