···6969 }
7070}
71717272-func getNestedValue(obj map[string]interface{}, path string) interface{} {
7373- parts := strings.Split(path, ".")
7474- var current interface{} = obj
7575-7676- for _, part := range parts {
7777- if m, ok := current.(map[string]interface{}); ok {
7878- current = m[part]
7979- } else {
8080- return nil
8181- }
8282- }
8383-8484- return current
8585-}
8686-8772func (n *FilterNode) ValidateConfig(config map[string]interface{}) error {
8873 return nil
8974}
+37
nodes/transforms/helpers.go
···11+package transforms
22+33+import "strings"
44+55+// getNestedValue retrieves a value from a nested map using dot notation
66+func getNestedValue(obj map[string]interface{}, path string) interface{} {
77+ parts := strings.Split(path, ".")
88+ var current interface{} = obj
99+1010+ for _, part := range parts {
1111+ if m, ok := current.(map[string]interface{}); ok {
1212+ current = m[part]
1313+ } else {
1414+ return nil
1515+ }
1616+ }
1717+1818+ return current
1919+}
2020+2121+// toFloat attempts to convert various numeric types to float64
2222+func toFloat(v interface{}) (float64, bool) {
2323+ switch val := v.(type) {
2424+ case float64:
2525+ return val, true
2626+ case float32:
2727+ return float64(val), true
2828+ case int:
2929+ return float64(val), true
3030+ case int64:
3131+ return float64(val), true
3232+ case int32:
3333+ return float64(val), true
3434+ default:
3535+ return 0, false
3636+ }
3737+}
+104
nodes/transforms/map.go
···11+package transforms
22+33+import (
44+ "context"
55+ "fmt"
66+ "strings"
77+88+ "github.com/kierank/pipes/nodes"
99+)
1010+1111+type MapNode struct{}
1212+1313+func (n *MapNode) Type() string { return "map" }
1414+func (n *MapNode) Label() string { return "Map Fields" }
1515+func (n *MapNode) Description() string { return "Rename, extract, or create new fields" }
1616+func (n *MapNode) Category() string { return "transform" }
1717+func (n *MapNode) Inputs() int { return 1 }
1818+func (n *MapNode) Outputs() int { return 1 }
1919+2020+func (n *MapNode) Execute(ctx context.Context, config map[string]interface{}, inputs [][]interface{}, execCtx *nodes.Context) ([]interface{}, error) {
2121+ if len(inputs) == 0 || len(inputs[0]) == 0 {
2222+ return []interface{}{}, nil
2323+ }
2424+2525+ items := inputs[0]
2626+ mappings, _ := config["mappings"].(string)
2727+ keepOriginal, _ := config["keep_original"].(bool)
2828+2929+ if mappings == "" {
3030+ return items, nil
3131+ }
3232+3333+ // Parse mappings: "newField:sourceField, title:name"
3434+ fieldMap := parseMappings(mappings)
3535+3636+ var result []interface{}
3737+ for _, item := range items {
3838+ itemMap, ok := item.(map[string]interface{})
3939+ if !ok {
4040+ result = append(result, item)
4141+ continue
4242+ }
4343+4444+ var newItem map[string]interface{}
4545+ if keepOriginal {
4646+ newItem = make(map[string]interface{})
4747+ for k, v := range itemMap {
4848+ newItem[k] = v
4949+ }
5050+ } else {
5151+ newItem = make(map[string]interface{})
5252+ }
5353+5454+ for newField, sourceField := range fieldMap {
5555+ if val := getNestedValue(itemMap, sourceField); val != nil {
5656+ newItem[newField] = val
5757+ }
5858+ }
5959+6060+ result = append(result, newItem)
6161+ }
6262+6363+ execCtx.Log("map", "info", fmt.Sprintf("Mapped %d items", len(result)))
6464+ return result, nil
6565+}
6666+6767+func parseMappings(s string) map[string]string {
6868+ result := make(map[string]string)
6969+ parts := strings.Split(s, ",")
7070+ for _, part := range parts {
7171+ part = strings.TrimSpace(part)
7272+ if kv := strings.SplitN(part, ":", 2); len(kv) == 2 {
7373+ result[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
7474+ }
7575+ }
7676+ return result
7777+}
7878+7979+func (n *MapNode) ValidateConfig(config map[string]interface{}) error {
8080+ return nil
8181+}
8282+8383+func (n *MapNode) GetConfigSchema() *nodes.ConfigSchema {
8484+ return &nodes.ConfigSchema{
8585+ Fields: []nodes.ConfigField{
8686+ {
8787+ Name: "mappings",
8888+ Label: "Field Mappings",
8989+ Type: "textarea",
9090+ Required: true,
9191+ Placeholder: "title:name, url:link, summary:description",
9292+ HelpText: "Map fields as newField:sourceField, separated by commas. Use dot notation for nested fields.",
9393+ },
9494+ {
9595+ Name: "keep_original",
9696+ Label: "Keep Original Fields",
9797+ Type: "checkbox",
9898+ Required: false,
9999+ DefaultValue: true,
100100+ HelpText: "Keep all original fields in addition to mapped ones",
101101+ },
102102+ },
103103+ }
104104+}