Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion relay/claude_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,12 @@ func ClaudeHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ

// apply param override
if len(info.ParamOverride) > 0 {
jsonData, err = relaycommon.ApplyParamOverride(jsonData, info.ParamOverride)
var isBlock bool
jsonData, isBlock, err = relaycommon.ApplyParamOverride(jsonData, info)
if err != nil {
if isBlock {
return types.NewError(err, types.ErrorCodeContidionTriggerBlock, types.ErrOptionWithSkipRetry())
}
return types.NewError(err, types.ErrorCodeChannelParamOverrideInvalid, types.ErrOptionWithSkipRetry())
}
}
Expand Down
213 changes: 213 additions & 0 deletions relay/common/condition.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package common

import (
"encoding/json"
"fmt"
"github.com/tidwall/gjson"
"regexp"
"strconv"
"strings"
)

// ConditionChecker 条件检查器接口
type ConditionChecker interface {
CheckConditions(jsonStr string, conditions []ConditionOperation, logic string) (bool, error)
CheckSingleCondition(jsonStr string, condition ConditionOperation) (bool, error)
}

// DefaultConditionChecker 默认条件检查器实现
type DefaultConditionChecker struct{}

// NewConditionChecker 创建新的条件检查器
func NewConditionChecker() ConditionChecker {
return &DefaultConditionChecker{}
}

type ConditionOperation struct {
Path string `json:"path"` // JSON路径
Mode string `json:"mode"` // full, prefix, suffix, contains, gt, gte, lt, lte
Value interface{} `json:"value"` // 匹配的值
Invert bool `json:"invert"` // 反选功能,true表示取反结果
PassMissingKey bool `json:"pass_missing_key"` // 未获取到json key时的行为
}

// CheckConditions 检查多个条件
func CheckConditions(jsonStr string, conditions []ConditionOperation, logic string) (bool, error) {
checker := NewConditionChecker()
return checker.CheckConditions(jsonStr, conditions, logic)
}

// CheckSingleCondition 检查单个条件
func CheckSingleCondition(jsonStr string, condition ConditionOperation) (bool, error) {
checker := NewConditionChecker()
return checker.CheckSingleCondition(jsonStr, condition)
}

func (c *DefaultConditionChecker) CheckConditions(jsonStr string, conditions []ConditionOperation, logic string) (bool, error) {
return checkConditions(jsonStr, conditions, logic)
}

func (c *DefaultConditionChecker) CheckSingleCondition(jsonStr string, condition ConditionOperation) (bool, error) {
return checkSingleCondition(jsonStr, condition)
}

func checkConditions(jsonStr string, conditions []ConditionOperation, logic string) (bool, error) {
if len(conditions) == 0 {
return true, nil // 没有条件,直接通过
}
results := make([]bool, len(conditions))
for i, condition := range conditions {
result, err := checkSingleCondition(jsonStr, condition)
if err != nil {
return false, err
}
results[i] = result
}

if strings.ToUpper(logic) == "AND" {
for _, result := range results {
if !result {
return false, nil
}
}
return true, nil
} else {
for _, result := range results {
if result {
return true, nil
}
}
return false, nil
}
}

func checkSingleCondition(jsonStr string, condition ConditionOperation) (bool, error) {
// 处理负数索引
path := processNegativeIndex(jsonStr, condition.Path)
value := gjson.Get(jsonStr, path)
if !value.Exists() {
if condition.PassMissingKey {
return true, nil
}
return false, nil
}

// 利用gjson的类型解析
targetBytes, err := json.Marshal(condition.Value)
if err != nil {
return false, fmt.Errorf("failed to marshal condition value: %v", err)
}
targetValue := gjson.ParseBytes(targetBytes)

result, err := compareGjsonValues(value, targetValue, strings.ToLower(condition.Mode))
if err != nil {
return false, fmt.Errorf("comparison failed for path %s: %v", condition.Path, err)
}

if condition.Invert {
result = !result
}
return result, nil
}

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
}
Comment on lines +113 to +142
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incorrect array path resolution for multiple negative indices (nested arrays).

arrayPath is derived from the original path for every match, so the second/next negative index may compute length from the wrong array. This mis-evaluates paths like a.-1.b.-1.

Apply a left-to-right replacement that recalculates arrayPath against the progressively updated path:

 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
+  re := regexp.MustCompile(`\.(-\d+)`)
+  result := path
+  for {
+    loc := re.FindStringSubmatchIndex(result)
+    if loc == nil {
+      break
+    }
+    negIndex := result[loc[2]:loc[3]] // e.g. "-1"
+    idx, _ := strconv.Atoi(negIndex)  // negative
+
+    arrayPath := result[:loc[0]]
+    if strings.HasSuffix(arrayPath, ".") {
+      arrayPath = arrayPath[:len(arrayPath)-1]
+    }
+    arr := gjson.Get(jsonStr, arrayPath)
+    if !arr.IsArray() {
+      // Not an array; stop to avoid infinite loop and let lookup fail naturally.
+      break
+    }
+    length := len(arr.Array())
+    actualIndex := length + idx
+    if actualIndex < 0 || actualIndex >= length {
+      // Out of range; leave as-is so gjson returns missing.
+      break
+    }
+    replacement := "." + strconv.Itoa(actualIndex)
+    // Replace this occurrence and continue scanning the updated path.
+    result = result[:loc[0]] + replacement + result[loc[1]:]
+  }
+  return result
 }
📝 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
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
}
func processNegativeIndex(jsonStr string, path string) string {
re := regexp.MustCompile(`\.(-\d+)`)
result := path
for {
loc := re.FindStringSubmatchIndex(result)
if loc == nil {
break
}
negIndex := result[loc[2]:loc[3]] // e.g. "-1"
idx, _ := strconv.Atoi(negIndex) // negative
arrayPath := result[:loc[0]]
if strings.HasSuffix(arrayPath, ".") {
arrayPath = arrayPath[:len(arrayPath)-1]
}
arr := gjson.Get(jsonStr, arrayPath)
if !arr.IsArray() {
// Not an array; stop to avoid infinite loop and let lookup fail naturally.
break
}
length := len(arr.Array())
actualIndex := length + idx
if actualIndex < 0 || actualIndex >= length {
// Out of range; leave as-is so gjson returns missing.
break
}
replacement := "." + strconv.Itoa(actualIndex)
// Replace this occurrence and continue scanning the updated path.
result = result[:loc[0]] + replacement + result[loc[1]:]
}
return result
}
🤖 Prompt for AI Agents
In relay/common/condition.go around lines 113 to 142, the function
processNegativeIndex computes arrayPath from the original path for every
negative-index match which breaks nested/multiple negative indices (e.g.,
a.-1.b.-1); change the loop to perform left-to-right replacements by deriving
arrayPath from the progressively updated result (use strings.Split(result,
negIndex)[0] instead of strings.Split(path, negIndex)[0]), update result
immediately after a successful replacement, and continue using the updated
result for subsequent matches (keeping the existing dot-trim and bounds checks).


// compareGjsonValues 直接比较两个gjson.Result,支持所有比较模式
func compareGjsonValues(jsonValue, targetValue gjson.Result, mode string) (bool, error) {
switch mode {
case "full":
return compareEqual(jsonValue, targetValue)
case "prefix":
return strings.HasPrefix(jsonValue.String(), targetValue.String()), nil
case "suffix":
return strings.HasSuffix(jsonValue.String(), targetValue.String()), nil
case "contains":
return strings.Contains(jsonValue.String(), targetValue.String()), nil
case "gt":
return compareNumeric(jsonValue, targetValue, "gt")
case "gte":
return compareNumeric(jsonValue, targetValue, "gte")
case "lt":
return compareNumeric(jsonValue, targetValue, "lt")
case "lte":
return compareNumeric(jsonValue, targetValue, "lte")
default:
return false, fmt.Errorf("unsupported comparison mode: %s", mode)
}
}

func compareEqual(jsonValue, targetValue gjson.Result) (bool, error) {
// 对布尔值特殊处理
if (jsonValue.Type == gjson.True || jsonValue.Type == gjson.False) &&
(targetValue.Type == gjson.True || targetValue.Type == gjson.False) {
return jsonValue.Bool() == targetValue.Bool(), nil
}

// 如果类型不同,报错
if jsonValue.Type != targetValue.Type {
return false, fmt.Errorf("compare for different types, got %v and %v", jsonValue.Type, targetValue.Type)
}

switch jsonValue.Type {
case gjson.True, gjson.False:
return jsonValue.Bool() == targetValue.Bool(), nil
case gjson.Number:
return jsonValue.Num == targetValue.Num, nil
case gjson.String:
return jsonValue.String() == targetValue.String(), nil
default:
return jsonValue.String() == targetValue.String(), nil
}
}

func compareNumeric(jsonValue, targetValue gjson.Result, operator string) (bool, error) {
// 只有数字类型才支持数值比较
if jsonValue.Type != gjson.Number || targetValue.Type != gjson.Number {
return false, fmt.Errorf("numeric comparison requires both values to be numbers, got %v and %v", jsonValue.Type, targetValue.Type)
}

jsonNum := jsonValue.Num
targetNum := targetValue.Num

switch operator {
case "gt":
return jsonNum > targetNum, nil
case "gte":
return jsonNum >= targetNum, nil
case "lt":
return jsonNum < targetNum, nil
case "lte":
return jsonNum <= targetNum, nil
default:
return false, fmt.Errorf("unsupported numeric operator: %s", operator)
}
}
Loading