Skip to content

FLPATH-4634 | fix(catalog): parse array indices in nested field paths - #34

Open
LinskId wants to merge 1 commit into
dcm-project:mainfrom
LinskId:FLPATH-4634_fix_Catalog_field_paths_with_array_indices
Open

FLPATH-4634 | fix(catalog): parse array indices in nested field paths#34
LinskId wants to merge 1 commit into
dcm-project:mainfrom
LinskId:FLPATH-4634_fix_Catalog_field_paths_with_array_indices

Conversation

@LinskId

@LinskId LinskId commented Jul 26, 2026

Copy link
Copy Markdown
Contributor
  • setNestedValue/getNestedValue parse name[n] segments into real slices (create/grow as needed) instead of map keys.
  • Reject malformed bracket segments
  • Keep plain dotted paths, keep split by '.'
  • Add unit test

Test plan:

Code generation succeeds
go build ./... passes
go test ./internal/catalog/service/ -ginkgo.focus='array index paths|FLPATH-4634' -v passed (10 Passed 0 failed)
make fmt vet lint clean (0 issues)

Assisted by: Cursor
Signed-off-by: igavra <igavra@redhat.com>
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix catalog nested field paths to support array indices

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Interpret name[n] path segments as real slice indices, not map keys.
• Create/grow intermediate slices/maps and fail fast on malformed bracket syntax.
• Add unit coverage for multi-index, sparse arrays, and error scenarios.
Diagram

graph TD
  A["Catalog service callers"] --> B["setNestedValue / getNestedValue"] --> C["parsePathSegment"] --> D["ensureSliceElementMap"] --> E[("Nested map/slices")]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Adopt a standard path syntax (JSON Pointer / JSONPath) library
  • ➕ Reduces custom parsing/validation code
  • ➕ More predictable semantics for arrays and escaping
  • ➖ Introduces new dependency and potential semantic mismatch with existing dotted paths
  • ➖ Migration/compat concerns for current callers and stored defaults
2. Implement a small tokenizer/scanner for the full path (dots + brackets)
  • ➕ More robust than per-segment parsing; easier to extend (escaping, multiple indices) later
  • ➕ Centralizes error reporting and avoids repeated strings.Join work
  • ➖ More code/complexity than needed for current requirements (name[n] only)
  • ➖ Higher review and maintenance overhead

Recommendation: Keep the PR’s approach: parse each dot-separated segment for an optional single [index] suffix, validate strictly, and materialize slices as needed. It matches the existing dotted-path contract, fixes the disks[0]-as-map-key bug without expanding scope, and the added tests cover the important edge cases.

Files changed (2) +261 / -25

Bug fix (1) +166 / -25
nested_map.goSupport 'name[index]' segments in nested map read/write helpers +166/-25

Support 'name[index]' segments in nested map read/write helpers

• Adds parsing/validation for bracketed array indices in dot-separated paths. Updates set/get logic to create and grow '[]any' slices (and map elements) as required, with clear errors on malformed segments, type clashes, and out-of-range reads.

internal/catalog/service/nested_map.go

Tests (1) +95 / -0
nested_map_test.goAdd unit tests for indexed field paths and malformed bracket rejection +95/-0

Add unit tests for indexed field paths and malformed bracket rejection

• Introduces a focused test suite ensuring indexed segments build real arrays, support sparse indices, allow setting an indexed final segment, and validate error cases (malformed brackets, non-numeric indices, and non-array clashes).

internal/catalog/service/nested_map_test.go

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Unbounded index allocation 🐞 Bug ☼ Reliability
Description
setNestedValue/ensureSliceElementMap allocate/grow slices to length index+1 from the parsed path
without any maximum bound or overflow guard, so a large (but valid) index can cause OOM or a runtime
panic (e.g., index==MaxInt makes index+1 overflow) during spec building. These functions are called
with catalog field paths in spec_builder, so a misconfigured/malicious catalog field path can crash
the service while merging defaults/user values.
Code

internal/catalog/service/nested_map.go[R183-197]

+	raw, exists := parent[name]
+	var slice []any
+	if !exists {
+		slice = make([]any, index+1)
+	} else {
+		var ok bool
+		slice, ok = raw.([]any)
+		if !ok {
+			return nil, fmt.Errorf("path segment %q is not an array", pathSoFar)
+		}
+		if index >= len(slice) {
+			grown := make([]any, index+1)
+			copy(grown, slice)
+			slice = grown
+		}
Evidence
The index parsed by parsePathSegment is only validated as a non-negative int, and later used to size
slices as index+1 in both setNestedValue (last segment) and ensureSliceElementMap (intermediate
segments). spec_builder applies setNestedValue using field paths from catalog configuration, so
large indices can be reached during normal spec building and crash the process.

internal/catalog/service/nested_map.go[21-44]
internal/catalog/service/nested_map.go[93-110]
internal/catalog/service/nested_map.go[181-199]
internal/catalog/service/spec_builder.go[114-131]
internal/catalog/service/spec_builder.go[133-162]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Indexed path segments (e.g., `disks[123]`) directly drive slice allocation via `make([]any, index+1)` and growth to `index+1` in multiple places. With no cap and no overflow guard, large indices can exhaust memory or panic due to `index+1` overflow.

### Issue Context
These helpers are used when building resource specs from catalog field defaults and user values.

### Fix Focus Areas
- Add an explicit maximum allowed array index (and/or maximum slice length) and return a descriptive error if exceeded.
- Add an overflow guard before computing `index+1` (e.g., reject indices where `index+1` would overflow or exceed the max).
- Apply the same validation consistently for both intermediate segments (`ensureSliceElementMap`) and last-segment indexed sets (`setNestedValue`).
- Add unit tests that assert large indices are rejected without attempting allocation.

- internal/catalog/service/nested_map.go[21-44]
- internal/catalog/service/nested_map.go[93-110]
- internal/catalog/service/nested_map.go[181-199]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Duplicated index in error 🐞 Bug ⚙ Maintainability
Description
ensureSliceElementMap can produce misleading error text by appending "[index]" to pathSoFar even
though pathSoFar already includes the indexed segment, resulting in messages like
"storage.disks[0][0]". This makes troubleshooting malformed shapes harder and can confuse error
matching.
Code

internal/catalog/service/nested_map.go[R206-209]

+	elem, ok := slice[index].(map[string]any)
+	if !ok {
+		return nil, fmt.Errorf("path segment %q[%d] is not a map", pathSoFar, index)
+	}
Evidence
setNestedValue constructs pathSoFar by joining the original dotted parts (which contain brackets),
then ensureSliceElementMap formats an error by appending the index again, duplicating it in the
message.

internal/catalog/service/nested_map.go[55-61]
internal/catalog/service/nested_map.go[206-210]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
An error path is formatted as `pathSoFar[index]`, but `pathSoFar` already includes the bracketed index from the original segment (e.g., `storage.disks[0]`). This duplicates the index in error messages.

### Issue Context
`pathSoFar` is built from the original `parts` strings (which still contain `[n]`).

### Fix Focus Areas
- Change the error format to avoid re-appending `[index]` (e.g., `fmt.Errorf("path segment %q is not a map", pathSoFar)`), or pass an unindexed path string into `ensureSliceElementMap`.

- internal/catalog/service/nested_map.go[55-61]
- internal/catalog/service/nested_map.go[206-210]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +183 to +197
raw, exists := parent[name]
var slice []any
if !exists {
slice = make([]any, index+1)
} else {
var ok bool
slice, ok = raw.([]any)
if !ok {
return nil, fmt.Errorf("path segment %q is not an array", pathSoFar)
}
if index >= len(slice) {
grown := make([]any, index+1)
copy(grown, slice)
slice = grown
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Unbounded index allocation 🐞 Bug ☼ Reliability

setNestedValue/ensureSliceElementMap allocate/grow slices to length index+1 from the parsed path
without any maximum bound or overflow guard, so a large (but valid) index can cause OOM or a runtime
panic (e.g., index==MaxInt makes index+1 overflow) during spec building. These functions are called
with catalog field paths in spec_builder, so a misconfigured/malicious catalog field path can crash
the service while merging defaults/user values.
Agent Prompt
### Issue description
Indexed path segments (e.g., `disks[123]`) directly drive slice allocation via `make([]any, index+1)` and growth to `index+1` in multiple places. With no cap and no overflow guard, large indices can exhaust memory or panic due to `index+1` overflow.

### Issue Context
These helpers are used when building resource specs from catalog field defaults and user values.

### Fix Focus Areas
- Add an explicit maximum allowed array index (and/or maximum slice length) and return a descriptive error if exceeded.
- Add an overflow guard before computing `index+1` (e.g., reject indices where `index+1` would overflow or exceed the max).
- Apply the same validation consistently for both intermediate segments (`ensureSliceElementMap`) and last-segment indexed sets (`setNestedValue`).
- Add unit tests that assert large indices are rejected without attempting allocation.

- internal/catalog/service/nested_map.go[21-44]
- internal/catalog/service/nested_map.go[93-110]
- internal/catalog/service/nested_map.go[181-199]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +206 to +209
elem, ok := slice[index].(map[string]any)
if !ok {
return nil, fmt.Errorf("path segment %q[%d] is not a map", pathSoFar, index)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational

2. Duplicated index in error 🐞 Bug ⚙ Maintainability

ensureSliceElementMap can produce misleading error text by appending "[index]" to pathSoFar even
though pathSoFar already includes the indexed segment, resulting in messages like
"storage.disks[0][0]". This makes troubleshooting malformed shapes harder and can confuse error
matching.
Agent Prompt
### Issue description
An error path is formatted as `pathSoFar[index]`, but `pathSoFar` already includes the bracketed index from the original segment (e.g., `storage.disks[0]`). This duplicates the index in error messages.

### Issue Context
`pathSoFar` is built from the original `parts` strings (which still contain `[n]`).

### Fix Focus Areas
- Change the error format to avoid re-appending `[index]` (e.g., `fmt.Errorf("path segment %q is not a map", pathSoFar)`), or pass an unindexed path string into `ensureSliceElementMap`.

- internal/catalog/service/nested_map.go[55-61]
- internal/catalog/service/nested_map.go[206-210]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

}

// parsePathSegment parses "key" or "key[n]"; malformed brackets return an error.
func parsePathSegment(seg string) (pathSegment, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we use a regexp for that?

var segRe = regexp.MustCompile(`^([^\[\]]+?)(?:\[(\d+)\])?$`)

  func parsePathSegment(seg string) (pathSegment, error) {
        if seg == "" {
                return pathSegment{}, fmt.Errorf("path segment cannot be empty")
        }
        m := segRe.FindStringSubmatch(seg)
        if m == nil {
                return pathSegment{}, fmt.Errorf("invalid path segment %q: expected name or name[index]", seg)
        }
        if m[2] == "" {
                return pathSegment{name: m[1]}, nil
        }
        idx, _ := strconv.Atoi(m[2])
        return pathSegment{name: m[1], index: idx, hasIndex: true}, nil
  }

@gabriel-farache gabriel-farache left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no hard blocker

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants