Skip to content

Commit a44eef3

Browse files
committed
allow sub item indexing
1 parent 69dbb15 commit a44eef3

1 file changed

Lines changed: 42 additions & 20 deletions

File tree

cli/internal/runner/context.go

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ import (
44
"encoding/json"
55
"fmt"
66
"os"
7+
"regexp"
78
"strconv"
89
"strings"
910

1011
"yapi.run/cli/internal/vars"
1112
)
1213

14+
// arrayIndexPattern matches path segments with array indices like "tracks[0]" or "items[123]"
15+
var arrayIndexPattern = regexp.MustCompile(`^(.+?)\[(\d+)\]$`)
16+
1317
// StepResult holds the output of a single chain step.
1418
type StepResult struct {
1519
BodyRaw string
@@ -142,18 +146,9 @@ func (c *ChainContext) resolveChainVar(key string) (string, error) {
142146
}
143147

144148
func jsonPathLookup(data any, path []string) (string, error) {
145-
current := data
146-
for i, key := range path {
147-
switch v := current.(type) {
148-
case map[string]any:
149-
val, ok := v[key]
150-
if !ok {
151-
return "", fmt.Errorf("key '%s' not found at path '%s'", key, strings.Join(path[:i+1], "."))
152-
}
153-
current = val
154-
default:
155-
return "", fmt.Errorf("path segment '%s' is not an object", strings.Join(path[:i], "."))
156-
}
149+
current, err := jsonPathLookupRaw(data, path)
150+
if err != nil {
151+
return "", err
157152
}
158153
// Convert final value to string
159154
switch v := current.(type) {
@@ -235,19 +230,46 @@ func (c *ChainContext) ResolveVariableRaw(input string) (any, bool) {
235230
return val, true
236231
}
237232

238-
// jsonPathLookupRaw returns the raw typed value at the given path
233+
// jsonPathLookupRaw returns the raw typed value at the given path.
234+
// Supports array indexing like "tracks[0]" or "items[2]".
239235
func jsonPathLookupRaw(data any, path []string) (any, error) {
240236
current := data
241-
for i, key := range path {
242-
switch v := current.(type) {
243-
case map[string]any:
244-
val, ok := v[key]
237+
for i, segment := range path {
238+
// Check if segment contains array index like "tracks[0]"
239+
if match := arrayIndexPattern.FindStringSubmatch(segment); match != nil {
240+
key := match[1]
241+
idx, _ := strconv.Atoi(match[2]) // regex guarantees valid int
242+
243+
// First, access the map key
244+
m, ok := current.(map[string]any)
245+
if !ok {
246+
return nil, fmt.Errorf("path segment '%s' is not an object", strings.Join(path[:i], "."))
247+
}
248+
val, ok := m[key]
249+
if !ok {
250+
return nil, fmt.Errorf("key '%s' not found at path '%s'", key, strings.Join(path[:i], ".")+"."+key)
251+
}
252+
253+
// Then, access the array index
254+
arr, ok := val.([]any)
255+
if !ok {
256+
return nil, fmt.Errorf("'%s' is not an array", key)
257+
}
258+
if idx < 0 || idx >= len(arr) {
259+
return nil, fmt.Errorf("index %d out of bounds for array '%s' (length %d)", idx, key, len(arr))
260+
}
261+
current = arr[idx]
262+
} else {
263+
// Regular map key access
264+
m, ok := current.(map[string]any)
265+
if !ok {
266+
return nil, fmt.Errorf("path segment '%s' is not an object", strings.Join(path[:i], "."))
267+
}
268+
val, ok := m[segment]
245269
if !ok {
246-
return nil, fmt.Errorf("key '%s' not found at path '%s'", key, strings.Join(path[:i+1], "."))
270+
return nil, fmt.Errorf("key '%s' not found at path '%s'", segment, strings.Join(path[:i+1], "."))
247271
}
248272
current = val
249-
default:
250-
return nil, fmt.Errorf("path segment '%s' is not an object", strings.Join(path[:i], "."))
251273
}
252274
}
253275
return current, nil

0 commit comments

Comments
 (0)