|
|
@@ -10,12 +10,20 @@ import (
|
|
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
|
"github.com/QuantumNous/new-api/types"
|
|
|
+ "github.com/samber/lo"
|
|
|
"github.com/tidwall/gjson"
|
|
|
"github.com/tidwall/sjson"
|
|
|
)
|
|
|
|
|
|
var negativeIndexRegexp = regexp.MustCompile(`\.(-\d+)`)
|
|
|
|
|
|
+const (
|
|
|
+ paramOverrideContextRequestHeaders = "request_headers"
|
|
|
+ paramOverrideContextRequestHeadersRaw = "request_headers_raw"
|
|
|
+ paramOverrideContextHeaderOverride = "header_override"
|
|
|
+ paramOverrideContextHeaderOverrideNormalized = "header_override_normalized"
|
|
|
+)
|
|
|
+
|
|
|
type ConditionOperation struct {
|
|
|
Path string `json:"path"` // JSON路径
|
|
|
Mode string `json:"mode"` // full, prefix, suffix, contains, gt, gte, lt, lte
|
|
|
@@ -26,7 +34,7 @@ type ConditionOperation struct {
|
|
|
|
|
|
type ParamOperation struct {
|
|
|
Path string `json:"path"`
|
|
|
- Mode string `json:"mode"` // delete, set, move, copy, prepend, append, trim_prefix, trim_suffix, ensure_prefix, ensure_suffix, trim_space, to_lower, to_upper, replace, regex_replace, return_error, prune_objects
|
|
|
+ Mode string `json:"mode"` // delete, set, move, copy, prepend, append, trim_prefix, trim_suffix, ensure_prefix, ensure_suffix, trim_space, to_lower, to_upper, replace, regex_replace, return_error, prune_objects, set_header, delete_header, copy_header, move_header
|
|
|
Value interface{} `json:"value"`
|
|
|
KeepOrigin bool `json:"keep_origin"`
|
|
|
From string `json:"from,omitempty"`
|
|
|
@@ -121,6 +129,35 @@ func ApplyParamOverride(jsonData []byte, paramOverride map[string]interface{}, c
|
|
|
return applyOperationsLegacy(jsonData, paramOverride)
|
|
|
}
|
|
|
|
|
|
+func ApplyParamOverrideWithRelayInfo(jsonData []byte, info *RelayInfo) ([]byte, error) {
|
|
|
+ paramOverride := getParamOverrideMap(info)
|
|
|
+ if len(paramOverride) == 0 {
|
|
|
+ return jsonData, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ overrideCtx := BuildParamOverrideContext(info)
|
|
|
+ result, err := ApplyParamOverride(jsonData, paramOverride, overrideCtx)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ syncRuntimeHeaderOverrideFromContext(info, overrideCtx)
|
|
|
+ return result, nil
|
|
|
+}
|
|
|
+
|
|
|
+func getParamOverrideMap(info *RelayInfo) map[string]interface{} {
|
|
|
+ if info == nil || info.ChannelMeta == nil {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ return info.ChannelMeta.ParamOverride
|
|
|
+}
|
|
|
+
|
|
|
+func getHeaderOverrideMap(info *RelayInfo) map[string]interface{} {
|
|
|
+ if info == nil || info.ChannelMeta == nil {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ return info.ChannelMeta.HeadersOverride
|
|
|
+}
|
|
|
+
|
|
|
func tryParseOperations(paramOverride map[string]interface{}) ([]ParamOperation, bool) {
|
|
|
// 检查是否包含 "operations" 字段
|
|
|
if opsValue, exists := paramOverride["operations"]; exists {
|
|
|
@@ -161,29 +198,11 @@ func tryParseOperations(paramOverride map[string]interface{}) ([]ParamOperation,
|
|
|
|
|
|
// 解析条件
|
|
|
if conditions, exists := opMap["conditions"]; exists {
|
|
|
- if condSlice, ok := conditions.([]interface{}); ok {
|
|
|
- for _, cond := range condSlice {
|
|
|
- if condMap, ok := cond.(map[string]interface{}); ok {
|
|
|
- condition := ConditionOperation{}
|
|
|
- if path, ok := condMap["path"].(string); ok {
|
|
|
- condition.Path = path
|
|
|
- }
|
|
|
- if mode, ok := condMap["mode"].(string); ok {
|
|
|
- condition.Mode = mode
|
|
|
- }
|
|
|
- if value, ok := condMap["value"]; ok {
|
|
|
- condition.Value = value
|
|
|
- }
|
|
|
- if invert, ok := condMap["invert"].(bool); ok {
|
|
|
- condition.Invert = invert
|
|
|
- }
|
|
|
- if passMissingKey, ok := condMap["pass_missing_key"].(bool); ok {
|
|
|
- condition.PassMissingKey = passMissingKey
|
|
|
- }
|
|
|
- operation.Conditions = append(operation.Conditions, condition)
|
|
|
- }
|
|
|
- }
|
|
|
+ parsedConditions, err := parseConditionOperations(conditions)
|
|
|
+ if err != nil {
|
|
|
+ return nil, false
|
|
|
}
|
|
|
+ operation.Conditions = append(operation.Conditions, parsedConditions...)
|
|
|
}
|
|
|
|
|
|
operations = append(operations, operation)
|
|
|
@@ -212,20 +231,9 @@ func checkConditions(jsonStr, contextJSON string, conditions []ConditionOperatio
|
|
|
}
|
|
|
|
|
|
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
|
|
|
+ return lo.EveryBy(results, func(item bool) bool { return item }), nil
|
|
|
}
|
|
|
+ return lo.SomeBy(results, func(item bool) bool { return item }), nil
|
|
|
}
|
|
|
|
|
|
func checkSingleCondition(jsonStr, contextJSON string, condition ConditionOperation) (bool, error) {
|
|
|
@@ -382,13 +390,10 @@ func applyOperationsLegacy(jsonData []byte, paramOverride map[string]interface{}
|
|
|
}
|
|
|
|
|
|
func applyOperations(jsonStr string, operations []ParamOperation, conditionContext map[string]interface{}) (string, error) {
|
|
|
- var contextJSON string
|
|
|
- if conditionContext != nil && len(conditionContext) > 0 {
|
|
|
- ctxBytes, err := common.Marshal(conditionContext)
|
|
|
- if err != nil {
|
|
|
- return "", fmt.Errorf("failed to marshal condition context: %v", err)
|
|
|
- }
|
|
|
- contextJSON = string(ctxBytes)
|
|
|
+ context := ensureContextMap(conditionContext)
|
|
|
+ contextJSON, err := marshalContextJSON(context)
|
|
|
+ if err != nil {
|
|
|
+ return "", fmt.Errorf("failed to marshal condition context: %v", err)
|
|
|
}
|
|
|
|
|
|
result := jsonStr
|
|
|
@@ -453,6 +458,42 @@ func applyOperations(jsonStr string, operations []ParamOperation, conditionConte
|
|
|
return "", returnErr
|
|
|
case "prune_objects":
|
|
|
result, err = pruneObjects(result, opPath, contextJSON, op.Value)
|
|
|
+ case "set_header":
|
|
|
+ err = setHeaderOverrideInContext(context, op.Path, op.Value, op.KeepOrigin)
|
|
|
+ if err == nil {
|
|
|
+ contextJSON, err = marshalContextJSON(context)
|
|
|
+ }
|
|
|
+ case "delete_header":
|
|
|
+ err = deleteHeaderOverrideInContext(context, op.Path)
|
|
|
+ if err == nil {
|
|
|
+ contextJSON, err = marshalContextJSON(context)
|
|
|
+ }
|
|
|
+ case "copy_header":
|
|
|
+ sourceHeader := strings.TrimSpace(op.From)
|
|
|
+ targetHeader := strings.TrimSpace(op.To)
|
|
|
+ if sourceHeader == "" {
|
|
|
+ sourceHeader = strings.TrimSpace(op.Path)
|
|
|
+ }
|
|
|
+ if targetHeader == "" {
|
|
|
+ targetHeader = strings.TrimSpace(op.Path)
|
|
|
+ }
|
|
|
+ err = copyHeaderInContext(context, sourceHeader, targetHeader, op.KeepOrigin)
|
|
|
+ if err == nil {
|
|
|
+ contextJSON, err = marshalContextJSON(context)
|
|
|
+ }
|
|
|
+ case "move_header":
|
|
|
+ sourceHeader := strings.TrimSpace(op.From)
|
|
|
+ targetHeader := strings.TrimSpace(op.To)
|
|
|
+ if sourceHeader == "" {
|
|
|
+ sourceHeader = strings.TrimSpace(op.Path)
|
|
|
+ }
|
|
|
+ if targetHeader == "" {
|
|
|
+ targetHeader = strings.TrimSpace(op.Path)
|
|
|
+ }
|
|
|
+ err = moveHeaderInContext(context, sourceHeader, targetHeader, op.KeepOrigin)
|
|
|
+ if err == nil {
|
|
|
+ contextJSON, err = marshalContextJSON(context)
|
|
|
+ }
|
|
|
default:
|
|
|
return "", fmt.Errorf("unknown operation: %s", op.Mode)
|
|
|
}
|
|
|
@@ -543,6 +584,276 @@ func parseOverrideInt(v interface{}) (int, bool) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func ensureContextMap(conditionContext map[string]interface{}) map[string]interface{} {
|
|
|
+ if conditionContext != nil {
|
|
|
+ return conditionContext
|
|
|
+ }
|
|
|
+ return make(map[string]interface{})
|
|
|
+}
|
|
|
+
|
|
|
+func marshalContextJSON(context map[string]interface{}) (string, error) {
|
|
|
+ if context == nil || len(context) == 0 {
|
|
|
+ return "", nil
|
|
|
+ }
|
|
|
+ ctxBytes, err := common.Marshal(context)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+ return string(ctxBytes), nil
|
|
|
+}
|
|
|
+
|
|
|
+func setHeaderOverrideInContext(context map[string]interface{}, headerName string, value interface{}, keepOrigin bool) error {
|
|
|
+ headerName = strings.TrimSpace(headerName)
|
|
|
+ if headerName == "" {
|
|
|
+ return fmt.Errorf("header name is required")
|
|
|
+ }
|
|
|
+ if keepOrigin {
|
|
|
+ if _, exists := getHeaderValueFromContext(context, headerName); exists {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if value == nil {
|
|
|
+ return fmt.Errorf("header value is required")
|
|
|
+ }
|
|
|
+ headerValue := strings.TrimSpace(fmt.Sprintf("%v", value))
|
|
|
+ if headerValue == "" {
|
|
|
+ return fmt.Errorf("header value is required")
|
|
|
+ }
|
|
|
+
|
|
|
+ rawHeaders := ensureMapKeyInContext(context, paramOverrideContextHeaderOverride)
|
|
|
+ rawHeaders[headerName] = headerValue
|
|
|
+
|
|
|
+ normalizedHeaders := ensureMapKeyInContext(context, paramOverrideContextHeaderOverrideNormalized)
|
|
|
+ normalizedHeaders[normalizeHeaderContextKey(headerName)] = headerValue
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func copyHeaderInContext(context map[string]interface{}, fromHeader, toHeader string, keepOrigin bool) error {
|
|
|
+ fromHeader = strings.TrimSpace(fromHeader)
|
|
|
+ toHeader = strings.TrimSpace(toHeader)
|
|
|
+ if fromHeader == "" || toHeader == "" {
|
|
|
+ return fmt.Errorf("copy_header from/to is required")
|
|
|
+ }
|
|
|
+ value, exists := getHeaderValueFromContext(context, fromHeader)
|
|
|
+ if !exists {
|
|
|
+ return fmt.Errorf("source header does not exist: %s", fromHeader)
|
|
|
+ }
|
|
|
+ return setHeaderOverrideInContext(context, toHeader, value, keepOrigin)
|
|
|
+}
|
|
|
+
|
|
|
+func moveHeaderInContext(context map[string]interface{}, fromHeader, toHeader string, keepOrigin bool) error {
|
|
|
+ fromHeader = strings.TrimSpace(fromHeader)
|
|
|
+ toHeader = strings.TrimSpace(toHeader)
|
|
|
+ if fromHeader == "" || toHeader == "" {
|
|
|
+ return fmt.Errorf("move_header from/to is required")
|
|
|
+ }
|
|
|
+ if err := copyHeaderInContext(context, fromHeader, toHeader, keepOrigin); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if strings.EqualFold(fromHeader, toHeader) {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ return deleteHeaderOverrideInContext(context, fromHeader)
|
|
|
+}
|
|
|
+
|
|
|
+func deleteHeaderOverrideInContext(context map[string]interface{}, headerName string) error {
|
|
|
+ headerName = strings.TrimSpace(headerName)
|
|
|
+ if headerName == "" {
|
|
|
+ return fmt.Errorf("header name is required")
|
|
|
+ }
|
|
|
+ rawHeaders := ensureMapKeyInContext(context, paramOverrideContextHeaderOverride)
|
|
|
+ for key := range rawHeaders {
|
|
|
+ if strings.EqualFold(strings.TrimSpace(key), headerName) {
|
|
|
+ delete(rawHeaders, key)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ normalizedHeaders := ensureMapKeyInContext(context, paramOverrideContextHeaderOverrideNormalized)
|
|
|
+ delete(normalizedHeaders, normalizeHeaderContextKey(headerName))
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func ensureMapKeyInContext(context map[string]interface{}, key string) map[string]interface{} {
|
|
|
+ if context == nil {
|
|
|
+ return map[string]interface{}{}
|
|
|
+ }
|
|
|
+ if existing, ok := context[key]; ok {
|
|
|
+ if mapVal, ok := existing.(map[string]interface{}); ok {
|
|
|
+ return mapVal
|
|
|
+ }
|
|
|
+ }
|
|
|
+ result := make(map[string]interface{})
|
|
|
+ context[key] = result
|
|
|
+ return result
|
|
|
+}
|
|
|
+
|
|
|
+func getHeaderValueFromContext(context map[string]interface{}, headerName string) (string, bool) {
|
|
|
+ headerName = strings.TrimSpace(headerName)
|
|
|
+ if headerName == "" {
|
|
|
+ return "", false
|
|
|
+ }
|
|
|
+ if value, ok := findHeaderValueInMap(ensureMapKeyInContext(context, paramOverrideContextHeaderOverride), headerName); ok {
|
|
|
+ return value, true
|
|
|
+ }
|
|
|
+ if value, ok := findHeaderValueInMap(ensureMapKeyInContext(context, paramOverrideContextRequestHeadersRaw), headerName); ok {
|
|
|
+ return value, true
|
|
|
+ }
|
|
|
+
|
|
|
+ normalizedName := normalizeHeaderContextKey(headerName)
|
|
|
+ if normalizedName == "" {
|
|
|
+ return "", false
|
|
|
+ }
|
|
|
+ if value, ok := findHeaderValueInMap(ensureMapKeyInContext(context, paramOverrideContextHeaderOverrideNormalized), normalizedName); ok {
|
|
|
+ return value, true
|
|
|
+ }
|
|
|
+ if value, ok := findHeaderValueInMap(ensureMapKeyInContext(context, paramOverrideContextRequestHeaders), normalizedName); ok {
|
|
|
+ return value, true
|
|
|
+ }
|
|
|
+ return "", false
|
|
|
+}
|
|
|
+
|
|
|
+func findHeaderValueInMap(source map[string]interface{}, key string) (string, bool) {
|
|
|
+ if len(source) == 0 {
|
|
|
+ return "", false
|
|
|
+ }
|
|
|
+ entries := lo.Entries(source)
|
|
|
+ entry, ok := lo.Find(entries, func(item lo.Entry[string, interface{}]) bool {
|
|
|
+ return strings.EqualFold(strings.TrimSpace(item.Key), key)
|
|
|
+ })
|
|
|
+ if !ok {
|
|
|
+ return "", false
|
|
|
+ }
|
|
|
+ value := strings.TrimSpace(fmt.Sprintf("%v", entry.Value))
|
|
|
+ if value == "" {
|
|
|
+ return "", false
|
|
|
+ }
|
|
|
+ return value, true
|
|
|
+}
|
|
|
+
|
|
|
+func normalizeHeaderContextKey(key string) string {
|
|
|
+ key = strings.TrimSpace(strings.ToLower(key))
|
|
|
+ if key == "" {
|
|
|
+ return ""
|
|
|
+ }
|
|
|
+ var b strings.Builder
|
|
|
+ b.Grow(len(key))
|
|
|
+ previousUnderscore := false
|
|
|
+ for _, r := range key {
|
|
|
+ switch {
|
|
|
+ case r >= 'a' && r <= 'z':
|
|
|
+ b.WriteRune(r)
|
|
|
+ previousUnderscore = false
|
|
|
+ case r >= '0' && r <= '9':
|
|
|
+ b.WriteRune(r)
|
|
|
+ previousUnderscore = false
|
|
|
+ default:
|
|
|
+ if !previousUnderscore {
|
|
|
+ b.WriteByte('_')
|
|
|
+ previousUnderscore = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ result := strings.Trim(b.String(), "_")
|
|
|
+ return result
|
|
|
+}
|
|
|
+
|
|
|
+func buildNormalizedHeaders(headers map[string]string) map[string]interface{} {
|
|
|
+ if len(headers) == 0 {
|
|
|
+ return map[string]interface{}{}
|
|
|
+ }
|
|
|
+ entries := lo.Entries(headers)
|
|
|
+ normalizedEntries := lo.FilterMap(entries, func(item lo.Entry[string, string], _ int) (lo.Entry[string, string], bool) {
|
|
|
+ normalized := normalizeHeaderContextKey(item.Key)
|
|
|
+ value := strings.TrimSpace(item.Value)
|
|
|
+ if normalized == "" || value == "" {
|
|
|
+ return lo.Entry[string, string]{}, false
|
|
|
+ }
|
|
|
+ return lo.Entry[string, string]{Key: normalized, Value: value}, true
|
|
|
+ })
|
|
|
+ return lo.SliceToMap(normalizedEntries, func(item lo.Entry[string, string]) (string, interface{}) {
|
|
|
+ return item.Key, item.Value
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func buildRawHeaders(headers map[string]string) map[string]interface{} {
|
|
|
+ if len(headers) == 0 {
|
|
|
+ return map[string]interface{}{}
|
|
|
+ }
|
|
|
+ entries := lo.Entries(headers)
|
|
|
+ rawEntries := lo.FilterMap(entries, func(item lo.Entry[string, string], _ int) (lo.Entry[string, string], bool) {
|
|
|
+ key := strings.TrimSpace(item.Key)
|
|
|
+ value := strings.TrimSpace(item.Value)
|
|
|
+ if key == "" || value == "" {
|
|
|
+ return lo.Entry[string, string]{}, false
|
|
|
+ }
|
|
|
+ return lo.Entry[string, string]{Key: key, Value: value}, true
|
|
|
+ })
|
|
|
+ return lo.SliceToMap(rawEntries, func(item lo.Entry[string, string]) (string, interface{}) {
|
|
|
+ return item.Key, item.Value
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func buildHeaderOverrideContext(headers map[string]interface{}) (map[string]interface{}, map[string]interface{}) {
|
|
|
+ if len(headers) == 0 {
|
|
|
+ return map[string]interface{}{}, map[string]interface{}{}
|
|
|
+ }
|
|
|
+ entries := lo.Entries(headers)
|
|
|
+ rawEntries := lo.FilterMap(entries, func(item lo.Entry[string, interface{}], _ int) (lo.Entry[string, string], bool) {
|
|
|
+ key := strings.TrimSpace(item.Key)
|
|
|
+ value := strings.TrimSpace(fmt.Sprintf("%v", item.Value))
|
|
|
+ if key == "" || value == "" {
|
|
|
+ return lo.Entry[string, string]{}, false
|
|
|
+ }
|
|
|
+ return lo.Entry[string, string]{Key: key, Value: value}, true
|
|
|
+ })
|
|
|
+
|
|
|
+ raw := lo.SliceToMap(rawEntries, func(item lo.Entry[string, string]) (string, interface{}) {
|
|
|
+ return item.Key, item.Value
|
|
|
+ })
|
|
|
+ normalizedEntries := lo.FilterMap(rawEntries, func(item lo.Entry[string, string], _ int) (lo.Entry[string, string], bool) {
|
|
|
+ normalized := normalizeHeaderContextKey(item.Key)
|
|
|
+ if normalized == "" {
|
|
|
+ return lo.Entry[string, string]{}, false
|
|
|
+ }
|
|
|
+ return lo.Entry[string, string]{Key: normalized, Value: item.Value}, true
|
|
|
+ })
|
|
|
+ normalized := lo.SliceToMap(normalizedEntries, func(item lo.Entry[string, string]) (string, interface{}) {
|
|
|
+ return item.Key, item.Value
|
|
|
+ })
|
|
|
+ return raw, normalized
|
|
|
+}
|
|
|
+
|
|
|
+func syncRuntimeHeaderOverrideFromContext(info *RelayInfo, context map[string]interface{}) {
|
|
|
+ if info == nil || context == nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ raw, exists := context[paramOverrideContextHeaderOverride]
|
|
|
+ if !exists {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ rawMap, ok := raw.(map[string]interface{})
|
|
|
+ if !ok {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ entries := lo.Entries(rawMap)
|
|
|
+ sanitized := lo.FilterMap(entries, func(item lo.Entry[string, interface{}], _ int) (lo.Entry[string, interface{}], bool) {
|
|
|
+ key := strings.TrimSpace(item.Key)
|
|
|
+ if key == "" {
|
|
|
+ return lo.Entry[string, interface{}]{}, false
|
|
|
+ }
|
|
|
+ value := strings.TrimSpace(fmt.Sprintf("%v", item.Value))
|
|
|
+ if value == "" {
|
|
|
+ return lo.Entry[string, interface{}]{}, false
|
|
|
+ }
|
|
|
+ return lo.Entry[string, interface{}]{Key: key, Value: value}, true
|
|
|
+ })
|
|
|
+ info.RuntimeHeadersOverride = lo.SliceToMap(sanitized, func(item lo.Entry[string, interface{}]) (string, interface{}) {
|
|
|
+ return item.Key, item.Value
|
|
|
+ })
|
|
|
+ info.UseRuntimeHeadersOverride = true
|
|
|
+}
|
|
|
+
|
|
|
func moveValue(jsonStr, fromPath, toPath string) (string, error) {
|
|
|
sourceValue := gjson.Get(jsonStr, fromPath)
|
|
|
if !sourceValue.Exists() {
|
|
|
@@ -824,38 +1135,56 @@ func parsePruneObjectsOptions(value interface{}) (pruneObjectsOptions, error) {
|
|
|
}
|
|
|
|
|
|
func parseConditionOperations(raw interface{}) ([]ConditionOperation, error) {
|
|
|
- items, ok := raw.([]interface{})
|
|
|
- if !ok {
|
|
|
- return nil, fmt.Errorf("conditions must be an array")
|
|
|
- }
|
|
|
-
|
|
|
- result := make([]ConditionOperation, 0, len(items))
|
|
|
- for _, item := range items {
|
|
|
- itemMap, ok := item.(map[string]interface{})
|
|
|
- if !ok {
|
|
|
- return nil, fmt.Errorf("condition must be object")
|
|
|
- }
|
|
|
- path, _ := itemMap["path"].(string)
|
|
|
- mode, _ := itemMap["mode"].(string)
|
|
|
- if strings.TrimSpace(path) == "" || strings.TrimSpace(mode) == "" {
|
|
|
- return nil, fmt.Errorf("condition path/mode is required")
|
|
|
- }
|
|
|
- condition := ConditionOperation{
|
|
|
- Path: path,
|
|
|
- Mode: mode,
|
|
|
- }
|
|
|
- if value, exists := itemMap["value"]; exists {
|
|
|
- condition.Value = value
|
|
|
- }
|
|
|
- if invert, ok := itemMap["invert"].(bool); ok {
|
|
|
- condition.Invert = invert
|
|
|
+ switch typed := raw.(type) {
|
|
|
+ case map[string]interface{}:
|
|
|
+ entries := lo.Entries(typed)
|
|
|
+ conditions := lo.FilterMap(entries, func(item lo.Entry[string, interface{}], _ int) (ConditionOperation, bool) {
|
|
|
+ path := strings.TrimSpace(item.Key)
|
|
|
+ if path == "" {
|
|
|
+ return ConditionOperation{}, false
|
|
|
+ }
|
|
|
+ return ConditionOperation{
|
|
|
+ Path: path,
|
|
|
+ Mode: "full",
|
|
|
+ Value: item.Value,
|
|
|
+ }, true
|
|
|
+ })
|
|
|
+ if len(conditions) == 0 {
|
|
|
+ return nil, fmt.Errorf("conditions object must contain at least one key")
|
|
|
}
|
|
|
- if passMissingKey, ok := itemMap["pass_missing_key"].(bool); ok {
|
|
|
- condition.PassMissingKey = passMissingKey
|
|
|
+ return conditions, nil
|
|
|
+ case []interface{}:
|
|
|
+ items := typed
|
|
|
+ result := make([]ConditionOperation, 0, len(items))
|
|
|
+ for _, item := range items {
|
|
|
+ itemMap, ok := item.(map[string]interface{})
|
|
|
+ if !ok {
|
|
|
+ return nil, fmt.Errorf("condition must be object")
|
|
|
+ }
|
|
|
+ path, _ := itemMap["path"].(string)
|
|
|
+ mode, _ := itemMap["mode"].(string)
|
|
|
+ if strings.TrimSpace(path) == "" || strings.TrimSpace(mode) == "" {
|
|
|
+ return nil, fmt.Errorf("condition path/mode is required")
|
|
|
+ }
|
|
|
+ condition := ConditionOperation{
|
|
|
+ Path: path,
|
|
|
+ Mode: mode,
|
|
|
+ }
|
|
|
+ if value, exists := itemMap["value"]; exists {
|
|
|
+ condition.Value = value
|
|
|
+ }
|
|
|
+ if invert, ok := itemMap["invert"].(bool); ok {
|
|
|
+ condition.Invert = invert
|
|
|
+ }
|
|
|
+ if passMissingKey, ok := itemMap["pass_missing_key"].(bool); ok {
|
|
|
+ condition.PassMissingKey = passMissingKey
|
|
|
+ }
|
|
|
+ result = append(result, condition)
|
|
|
}
|
|
|
- result = append(result, condition)
|
|
|
+ return result, nil
|
|
|
+ default:
|
|
|
+ return nil, fmt.Errorf("conditions must be an array or object")
|
|
|
}
|
|
|
- return result, nil
|
|
|
}
|
|
|
|
|
|
func pruneObjectsNode(node interface{}, options pruneObjectsOptions, contextJSON string, isRoot bool) (interface{}, bool, error) {
|
|
|
@@ -970,6 +1299,17 @@ func BuildParamOverrideContext(info *RelayInfo) map[string]interface{} {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ ctx[paramOverrideContextRequestHeaders] = buildNormalizedHeaders(info.RequestHeaders)
|
|
|
+ ctx[paramOverrideContextRequestHeadersRaw] = buildRawHeaders(info.RequestHeaders)
|
|
|
+
|
|
|
+ headerOverrideSource := getHeaderOverrideMap(info)
|
|
|
+ if info.UseRuntimeHeadersOverride {
|
|
|
+ headerOverrideSource = info.RuntimeHeadersOverride
|
|
|
+ }
|
|
|
+ rawHeaderOverride, normalizedHeaderOverride := buildHeaderOverrideContext(headerOverrideSource)
|
|
|
+ ctx[paramOverrideContextHeaderOverride] = rawHeaderOverride
|
|
|
+ ctx[paramOverrideContextHeaderOverrideNormalized] = normalizedHeaderOverride
|
|
|
+
|
|
|
ctx["retry_index"] = info.RetryIndex
|
|
|
ctx["is_retry"] = info.RetryIndex > 0
|
|
|
ctx["retry"] = map[string]interface{}{
|