Skip to content

Feats negative number for override.go - #1749

Merged
seefs001 merged 1 commit into
QuantumNous:alphafrom
nekohy:feats-negative-number
Sep 4, 2025
Merged

Feats negative number for override.go#1749
seefs001 merged 1 commit into
QuantumNous:alphafrom
nekohy:feats-negative-number

Conversation

@nekohy

@nekohy nekohy commented Sep 4, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Support negative indices in JSON paths (e.g., -1 for the last element) across all override operations and condition checks.
  • Bug Fixes
    • More robust path handling for array operations reduces failures when targeting elements from the end.
  • Improvements
    • Clearer error messages when an override operation fails, indicating the operation type and cause.

@coderabbitai

coderabbitai Bot commented Sep 4, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Implements negative index support in JSON path handling within relay/common/override.go by preprocessing paths (including condition checks and operations) via a new unexported helper. Updates delete/set/move/prepend/append to use processed paths and adds unified error propagation for failed operations. Imports regexp and strconv for parsing.

Changes

Cohort / File(s) Summary of changes
Negative index handling and path preprocessing
relay/common/override.go
Added processNegativeIndex to translate .-N to positive indices using array lengths; applied to condition checks and all operations (delete, set, move, prepend, append) via opPath/opFrom/opTo; added error wrapping "operation failed: "; extended imports (regexp, strconv); minor comments added.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller
  participant Override as OverrideLogic
  participant NegIdx as processNegativeIndex
  participant JSON as JSONStore

  Caller->>Override: apply(operation, path, from, to, json)
  rect rgba(231, 245, 254, 0.6)
    note right of Override: Preprocess paths (supports negative indices)
    Override->>NegIdx: process(path)
    NegIdx-->>Override: opPath
    Override->>NegIdx: process(from)
    NegIdx-->>Override: opFrom
    Override->>NegIdx: process(to)
    NegIdx-->>Override: opTo
  end

  alt delete/set/prepend/append
    Override->>JSON: mutate using opPath (or opTo)
    JSON-->>Override: result or error
  else move
    Override->>JSON: read from opFrom
    JSON-->>Override: value or error
    Override->>JSON: write to opTo
    JSON-->>Override: result or error
  end

  alt error
    Override-->>Caller: error "operation <mode> failed: <err>"
  else success
    Override-->>Caller: success
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I hop through arrays, left to right,
Then boop! from end with negative sight.
Paths flip signs, indices align—
Regex carrots on a vine.
Move, set, append—I do it neat,
Error burrows? Swiftly beat.
JSON meadow, changes complete! 🥕

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@seefs001
seefs001 merged commit 3d0bf36 into QuantumNous:alpha Sep 4, 2025
2 of 3 checks passed

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🧹 Nitpick comments (2)
relay/common/override.go (2)

8-9: Precompile the regex to avoid per-call allocations.

Move the regex to a package-level var and reuse it in processNegativeIndex.

Add near the top-level:

var negativeIndexRe = regexp.MustCompile(`\.-(\d+)`)

156-158: Good call: preprocess paths for conditions. Add the processed path to error context.

When compareGjsonValues fails, include both processed and original paths for quicker debugging.

Change the error return in Line 175 to:

return false, fmt.Errorf("comparison failed for path %s (orig: %s): %v", path, condition.Path, err)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 91a627d and e61c1dc.

📒 Files selected for processing (1)
  • relay/common/override.go (4 hunks)
🔇 Additional comments (3)
relay/common/override.go (3)

319-319: Delete with processed path — LGTM.


321-325: Set with KeepOrigin using processed path — LGTM.


328-330: Prepend/append with processed path — LGTM.

Comment thread relay/common/override.go
Comment on lines +184 to +213
func processNegativeIndex(jsonStr string, path string) string {
re := regexp.MustCompile(`\.(-\d+)`)
matches := re.FindAllStringSubmatch(path, -1)

if len(matches) == 0 {
return path
}

result := path
for _, match := range matches {
negIndex := match[1]
index, _ := strconv.Atoi(negIndex)

arrayPath := strings.Split(path, negIndex)[0]
if strings.HasSuffix(arrayPath, ".") {
arrayPath = arrayPath[:len(arrayPath)-1]
}

array := gjson.Get(jsonStr, arrayPath)
if array.IsArray() {
length := len(array.Array())
actualIndex := length + index
if actualIndex >= 0 && actualIndex < length {
result = strings.Replace(result, match[0], "."+strconv.Itoa(actualIndex), 1)
}
}
}

return result
}

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.

⚠️ Potential issue

Bug: nested/multiple negative indices can resolve incorrectly.

arrayPath is computed from the original path and may still contain unresolved negative indices, breaking lookups for subsequent matches (e.g., a.-1.b.-2). Iterate left-to-right on the progressively updated result and compute arrayPath via match indices.

Apply this diff:

-func processNegativeIndex(jsonStr string, path string) string {
-	re := regexp.MustCompile(`\.(-\d+)`)
-	matches := re.FindAllStringSubmatch(path, -1)
-
-	if len(matches) == 0 {
-		return path
-	}
-
-	result := path
-	for _, match := range matches {
-		negIndex := match[1]
-		index, _ := strconv.Atoi(negIndex)
-
-		arrayPath := strings.Split(path, negIndex)[0]
-		if strings.HasSuffix(arrayPath, ".") {
-			arrayPath = arrayPath[:len(arrayPath)-1]
-		}
-
-		array := gjson.Get(jsonStr, arrayPath)
-		if array.IsArray() {
-			length := len(array.Array())
-			actualIndex := length + index
-			if actualIndex >= 0 && actualIndex < length {
-				result = strings.Replace(result, match[0], "."+strconv.Itoa(actualIndex), 1)
-			}
-		}
-	}
-
-	return result
-}
+// processNegativeIndex walks the path and resolves ".-N" against the array length at that point.
+func processNegativeIndex(jsonStr string, path string) string {
+	if path == "" {
+		return ""
+	}
+	re := regexp.MustCompile(`\.-(\d+)`) // or use package-level negativeIndexRe
+	result := path
+	offset := 0
+	for {
+		loc := re.FindStringSubmatchIndex(result[offset:])
+		if loc == nil {
+			break
+		}
+		start := offset + loc[0]
+		end := offset + loc[1]
+		digitsStart := offset + loc[2]
+		digitsEnd := offset + loc[3]
+		idx, err := strconv.Atoi("-" + result[digitsStart:digitsEnd])
+		if err != nil {
+			offset = end
+			continue
+		}
+		arrayPath := strings.TrimSuffix(result[:start], ".")
+		arr := gjson.Get(jsonStr, arrayPath)
+		if arr.IsArray() {
+			length := len(arr.Array())
+			actual := length + idx
+			if actual >= 0 && actual < length {
+				repl := "." + strconv.Itoa(actual)
+				result = result[:start] + repl + result[end:]
+				offset = start + len(repl)
+				continue
+			}
+		}
+		offset = end
+	}
+	return result
+}

Comment thread relay/common/override.go
Comment on lines +312 to 316
// 处理路径中的负数索引
opPath := processNegativeIndex(result, op.Path)
opFrom := processNegativeIndex(result, op.From)
opTo := processNegativeIndex(result, op.To)

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.

🛠️ Refactor suggestion

Validate required fields after preprocessing.

Prevent operations with empty paths (e.g., missing from/to for move) to avoid surprises like querying the root with empty path.

 		opPath := processNegativeIndex(result, op.Path)
 		opFrom := processNegativeIndex(result, op.From)
 		opTo := processNegativeIndex(result, op.To)
+		// basic validation
+		switch op.Mode {
+		case "move":
+			if opFrom == "" || opTo == "" {
+				return "", fmt.Errorf("move requires both 'from' and 'to'")
+			}
+		default:
+			if opPath == "" {
+				return "", fmt.Errorf("%s requires non-empty 'path'", op.Mode)
+			}
+		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 处理路径中的负数索引
opPath := processNegativeIndex(result, op.Path)
opFrom := processNegativeIndex(result, op.From)
opTo := processNegativeIndex(result, op.To)
// 处理路径中的负数索引
opPath := processNegativeIndex(result, op.Path)
opFrom := processNegativeIndex(result, op.From)
opTo := processNegativeIndex(result, op.To)
// basic validation
switch op.Mode {
case "move":
if opFrom == "" || opTo == "" {
return "", fmt.Errorf("move requires both 'from' and 'to'")
}
default:
if opPath == "" {
return "", fmt.Errorf("%s requires non-empty 'path'", op.Mode)
}
}
🤖 Prompt for AI Agents
In relay/common/override.go around lines 312 to 316, after calling
processNegativeIndex for op.Path, op.From, and op.To, validate that required
path fields are non-empty (e.g., op.Path for most ops, and both op.From and
op.To for move operations); if any required path is empty, return an explicit
error (or skip the operation) instead of continuing and potentially querying the
root — ensure validation happens after preprocessing so negative-index
resolution is considered.

Comment thread relay/common/override.go
result, err = sjson.Set(result, opPath, op.Value)
case "move":
result, err = moveValue(result, op.From, op.To)
result, err = moveValue(result, opFrom, opTo)

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.

🛠️ Refactor suggestion

Moving into a descendant of source will delete the newly set value.

Current order (set then delete) removes the destination when toPath is under fromPath. Handle descendant case by deleting first, then setting with the captured value.

Proposed change in moveValue (outside this hunk):

 func moveValue(jsonStr, fromPath, toPath string) (string, error) {
   sourceValue := gjson.Get(jsonStr, fromPath)
   if !sourceValue.Exists() {
     return jsonStr, fmt.Errorf("source path does not exist: %s", fromPath)
   }
-  result, err := sjson.Set(jsonStr, toPath, sourceValue.Value())
-  if err != nil {
-    return "", err
-  }
-  return sjson.Delete(result, fromPath)
+  // Delete-first if destination is a descendant of source.
+  if toPath == fromPath || strings.HasPrefix(toPath, fromPath+".") {
+    tmp, err := sjson.Delete(jsonStr, fromPath)
+    if err != nil {
+      return "", err
+    }
+    return sjson.Set(tmp, toPath, sourceValue.Value())
+  }
+  result, err := sjson.Set(jsonStr, toPath, sourceValue.Value())
+  if err != nil {
+    return "", err
+  }
+  return sjson.Delete(result, fromPath)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
result, err = moveValue(result, opFrom, opTo)
func moveValue(jsonStr, fromPath, toPath string) (string, error) {
sourceValue := gjson.Get(jsonStr, fromPath)
if !sourceValue.Exists() {
return jsonStr, fmt.Errorf("source path does not exist: %s", fromPath)
}
// Delete-first if destination is a descendant of source.
if toPath == fromPath || strings.HasPrefix(toPath, fromPath+".") {
tmp, err := sjson.Delete(jsonStr, fromPath)
if err != nil {
return "", err
}
return sjson.Set(tmp, toPath, sourceValue.Value())
}
// Default: set then delete
result, err := sjson.Set(jsonStr, toPath, sourceValue.Value())
if err != nil {
return "", err
}
return sjson.Delete(result, fromPath)
}
🤖 Prompt for AI Agents
In relay/common/override.go around line 326, the call currently does set then
delete which causes the destination to be removed when toPath is a descendant of
fromPath; modify moveValue so it first detects if toPath is a descendant of
fromPath, capture the source value into a temporary variable, perform the delete
on fromPath first, then set the captured value at toPath (otherwise keep the
existing set-then-delete flow). Ensure the descendant check is robust (path
prefix or tree-structure comparison) and preserve existing error handling and
return values.

@coderabbitai coderabbitai Bot mentioned this pull request Dec 2, 2025
x22x22 pushed a commit to x22x22/new-api that referenced this pull request Apr 24, 2026
jiutubaba pushed a commit to jiutubaba/fx-api that referenced this pull request May 17, 2026
…ng-effort

fix: support xhigh reasoning effort in usage records
@nekohy
nekohy deleted the feats-negative-number branch May 22, 2026 06:07
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.

2 participants