diff --git a/main.go b/main.go index 5736a38..a1a9ac0 100644 --- a/main.go +++ b/main.go @@ -97,7 +97,7 @@ func main() { originalLuaExpr := luaExpr luaExpr = processor.BuildLuaScript(luaExpr) if originalLuaExpr != luaExpr { - logger.Printf("Transformed Lua expression from '%s' to '%s'", originalLuaExpr, luaExpr) + logger.Printf("Transformed Lua expression from %q to %q", originalLuaExpr, luaExpr) } // Expand file patterns with glob support @@ -117,17 +117,17 @@ func main() { switch *fileModeFlag { case "regex": proc = &processor.RegexProcessor{} - logger.Printf("Starting regex modifier with pattern '%s', expression '%s' on %d files", + logger.Printf("Starting regex modifier with pattern %q, expression %q on %d files", pattern, luaExpr, len(files)) // case "xml": // proc = &processor.XMLProcessor{} // pattern = *xpathFlag - // logger.Printf("Starting XML modifier with XPath '%s', expression '%s' on %d files", + // logger.Printf("Starting XML modifier with XPath %q, expression %q on %d files", // pattern, luaExpr, len(files)) // case "json": // proc = &processor.JSONProcessor{} // pattern = *jsonpathFlag - // logger.Printf("Starting JSON modifier with JSONPath '%s', expression '%s' on %d files", + // logger.Printf("Starting JSON modifier with JSONPath %q, expression %q on %d files", // pattern, luaExpr, len(files)) } diff --git a/processor/json.go b/processor/json.go index caac7e8..77fc3ca 100644 --- a/processor/json.go +++ b/processor/json.go @@ -57,9 +57,9 @@ func (p *JSONProcessor) ProcessContent(content string, pattern string, luaExpr s } // Find nodes matching the JSONPath pattern - nodes := jsonpath.Get(jsonData, pattern) + nodes, err := jsonpath.Get(jsonData, pattern) if err != nil { - return content, 0, 0, fmt.Errorf("error executing JSONPath: %v", err) + return content, 0, 0, fmt.Errorf("error getting nodes: %v", err) } matchCount := len(nodes) diff --git a/processor/jsonpath/jsonpath.go b/processor/jsonpath/jsonpath.go index 0801a5e..529a191 100644 --- a/processor/jsonpath/jsonpath.go +++ b/processor/jsonpath/jsonpath.go @@ -2,7 +2,6 @@ package jsonpath import ( "fmt" - "log" "strconv" ) @@ -42,7 +41,7 @@ const ( // ParseJSONPath parses a JSONPath string into a sequence of steps func ParseJSONPath(path string) ([]JSONStep, error) { if len(path) == 0 || path[0] != '$' { - return nil, fmt.Errorf("path must start with $") + return nil, fmt.Errorf("path must start with $; received: %q", path) } steps := []JSONStep{} @@ -80,13 +79,13 @@ func ParseJSONPath(path string) ([]JSONStep, error) { } else { index, err := strconv.Atoi(indexStr) if err != nil { - return nil, fmt.Errorf("invalid index: %s", indexStr) + return nil, fmt.Errorf("invalid index: %s; error: %w", indexStr, err) } steps = append(steps, JSONStep{Type: IndexStep, Index: index}) } i = nextPos + 1 // Skip closing ] default: - return nil, fmt.Errorf("unexpected character: %c", path[i]) + return nil, fmt.Errorf("unexpected character: %c at position %d; path: %q", path[i], i, path) } } @@ -117,73 +116,77 @@ func readIndex(path string, start int) (string, int) { // Get retrieves values with their paths from data at the specified JSONPath // Each returned JSONNode contains both the value and its exact path in the data structure -func Get(data interface{}, path string) []JSONNode { +func Get(data interface{}, path string) ([]JSONNode, error) { steps, err := ParseJSONPath(path) if err != nil { - log.Println("Error parsing JSONPath:", err) - return nil + return nil, fmt.Errorf("failed to parse JSONPath %q: %w", path, err) } results := []JSONNode{} - traverseWithPaths(data, steps, &results, "$") - return results + err = traverseWithPaths(data, steps, &results, "$") + if err != nil { + return nil, fmt.Errorf("failed to traverse JSONPath %q: %w", path, err) + } + return results, nil } // Set updates the value at the specified JSONPath in the original data structure. // It only modifies the first matching node. -func Set(data interface{}, path string, value interface{}) bool { +func Set(data interface{}, path string, value interface{}) error { steps, err := ParseJSONPath(path) if err != nil { - log.Println("Error parsing JSONPath:", err) - return false + return fmt.Errorf("failed to parse JSONPath %q: %w", path, err) } if len(steps) <= 1 { - log.Println("Cannot set root node") - return false + return fmt.Errorf("cannot set root node; the provided path %q is invalid", path) } success := false - setWithPath(data, steps, &success, value, "$", ModifyFirstMode) - return success + err = setWithPath(data, steps, &success, value, "$", ModifyFirstMode) + if err != nil { + return fmt.Errorf("failed to set value at JSONPath %q: %w", path, err) + } + return nil } // SetAll updates all matching values at the specified JSONPath. -func SetAll(data interface{}, path string, value interface{}) bool { +func SetAll(data interface{}, path string, value interface{}) error { steps, err := ParseJSONPath(path) if err != nil { - log.Println("Error parsing JSONPath:", err) - return false + return fmt.Errorf("failed to parse JSONPath %q: %w", path, err) } if len(steps) <= 1 { - log.Println("Cannot set root node") - return false + return fmt.Errorf("cannot set root node; the provided path %q is invalid", path) } success := false - setWithPath(data, steps, &success, value, "$", ModifyAllMode) - return success + err = setWithPath(data, steps, &success, value, "$", ModifyAllMode) + if err != nil { + return fmt.Errorf("failed to set value at JSONPath %q: %w", path, err) + } + return nil } // setWithPath modifies values while tracking paths -func setWithPath(node interface{}, steps []JSONStep, success *bool, value interface{}, currentPath string, mode TraversalMode) { +func setWithPath(node interface{}, steps []JSONStep, success *bool, value interface{}, currentPath string, mode TraversalMode) error { if node == nil || *success && mode == ModifyFirstMode { - return + return nil } // Skip root step actualSteps := steps if len(steps) > 0 && steps[0].Type == RootStep { if len(steps) == 1 { - return // Cannot set root node + return fmt.Errorf("cannot set root node; the provided path %q is invalid", currentPath) } actualSteps = steps[1:] } // Process the first step if len(actualSteps) == 0 { - return + return fmt.Errorf("cannot set root node; no steps provided for path %q", currentPath) } step := actualSteps[0] @@ -194,7 +197,7 @@ func setWithPath(node interface{}, steps []JSONStep, success *bool, value interf case ChildStep: m, ok := node.(map[string]interface{}) if !ok { - return + return fmt.Errorf("node at path %q is not a map; actual type: %T", currentPath, node) } childPath := currentPath + "." + step.Key @@ -203,7 +206,7 @@ func setWithPath(node interface{}, steps []JSONStep, success *bool, value interf // We've reached the target, set the value m[step.Key] = value *success = true - return + return nil } // Create intermediate nodes if necessary @@ -218,12 +221,15 @@ func setWithPath(node interface{}, steps []JSONStep, success *bool, value interf m[step.Key] = child } - setWithPath(child, remainingSteps, success, value, childPath, mode) + err := setWithPath(child, remainingSteps, success, value, childPath, mode) + if err != nil { + return fmt.Errorf("failed to set value at JSONPath %q: %w", childPath, err) + } case IndexStep: arr, ok := node.([]interface{}) if !ok { - return + return fmt.Errorf("node at path %q is not an array; actual type: %T", currentPath, node) } // Handle wildcard index @@ -234,16 +240,19 @@ func setWithPath(node interface{}, steps []JSONStep, success *bool, value interf arr[i] = value *success = true if mode == ModifyFirstMode { - return + return nil } } else { - setWithPath(item, remainingSteps, success, value, itemPath, mode) + err := setWithPath(item, remainingSteps, success, value, itemPath, mode) + if err != nil { + return fmt.Errorf("failed to set value at JSONPath %q: %w", itemPath, err) + } if *success && mode == ModifyFirstMode { - return + return nil } } } - return + return nil } // Handle specific index @@ -254,7 +263,10 @@ func setWithPath(node interface{}, steps []JSONStep, success *bool, value interf arr[step.Index] = value *success = true } else { - setWithPath(item, remainingSteps, success, value, itemPath, mode) + err := setWithPath(item, remainingSteps, success, value, itemPath, mode) + if err != nil { + return fmt.Errorf("failed to set value at JSONPath %q: %w", itemPath, err) + } } } @@ -267,12 +279,15 @@ func setWithPath(node interface{}, steps []JSONStep, success *bool, value interf m[step.Key] = value *success = true if mode == ModifyFirstMode { - return + return nil } } else { - setWithPath(val, remainingSteps, success, value, directPath, mode) + err := setWithPath(val, remainingSteps, success, value, directPath, mode) + if err != nil { + return fmt.Errorf("failed to set value at JSONPath %q: %w", directPath, err) + } if *success && mode == ModifyFirstMode { - return + return nil } } } @@ -287,17 +302,23 @@ func setWithPath(node interface{}, steps []JSONStep, success *bool, value interf if step.Key != "*" && k == step.Key { continue } - setWithPath(v, steps, success, value, childPath, mode) // Use the same steps + err := setWithPath(v, steps, success, value, childPath, mode) + if err != nil { + return fmt.Errorf("failed to set value at JSONPath %q: %w", childPath, err) + } if *success && mode == ModifyFirstMode { - return + return nil } } case []interface{}: for i, v := range n { childPath := fmt.Sprintf("%s[%d]", currentPath, i) - setWithPath(v, steps, success, value, childPath, mode) // Use the same steps + err := setWithPath(v, steps, success, value, childPath, mode) + if err != nil { + return fmt.Errorf("failed to set value at JSONPath %q: %w", childPath, err) + } if *success && mode == ModifyFirstMode { - return + return nil } } } @@ -305,7 +326,7 @@ func setWithPath(node interface{}, steps []JSONStep, success *bool, value interf case WildcardStep: m, ok := node.(map[string]interface{}) if !ok { - return + return fmt.Errorf("node at path %q is not a map; actual type: %T", currentPath, node) } for k, v := range m { @@ -314,22 +335,26 @@ func setWithPath(node interface{}, steps []JSONStep, success *bool, value interf m[k] = value *success = true if mode == ModifyFirstMode { - return + return nil } } else { - setWithPath(v, remainingSteps, success, value, childPath, mode) + err := setWithPath(v, remainingSteps, success, value, childPath, mode) + if err != nil { + return fmt.Errorf("failed to set value at JSONPath %q: %w", childPath, err) + } if *success && mode == ModifyFirstMode { - return + return nil } } } } + return nil } // traverseWithPaths tracks both nodes and their paths during traversal -func traverseWithPaths(node interface{}, steps []JSONStep, results *[]JSONNode, currentPath string) { +func traverseWithPaths(node interface{}, steps []JSONStep, results *[]JSONNode, currentPath string) error { if len(steps) == 0 || node == nil { - return + return fmt.Errorf("cannot traverse with empty steps or nil node; steps length: %d, node: %v", len(steps), node) } // Skip root step @@ -337,7 +362,7 @@ func traverseWithPaths(node interface{}, steps []JSONStep, results *[]JSONNode, if steps[0].Type == RootStep { if len(steps) == 1 { *results = append(*results, JSONNode{Value: node, Path: currentPath}) - return + return nil } actualSteps = steps[1:] } @@ -351,25 +376,28 @@ func traverseWithPaths(node interface{}, steps []JSONStep, results *[]JSONNode, case ChildStep: m, ok := node.(map[string]interface{}) if !ok { - return + return fmt.Errorf("node is not a map; actual type: %T", node) } child, exists := m[step.Key] if !exists { - return + return fmt.Errorf("key not found: %s in node at path: %s", step.Key, currentPath) } childPath := currentPath + "." + step.Key if isLastStep { *results = append(*results, JSONNode{Value: child, Path: childPath}) } else { - traverseWithPaths(child, remainingSteps, results, childPath) + err := traverseWithPaths(child, remainingSteps, results, childPath) + if err != nil { + return fmt.Errorf("failed to traverse JSONPath %q: %w", childPath, err) + } } case IndexStep: arr, ok := node.([]interface{}) if !ok { - return + return fmt.Errorf("node is not an array; actual type: %T", node) } // Handle wildcard index @@ -379,10 +407,13 @@ func traverseWithPaths(node interface{}, steps []JSONStep, results *[]JSONNode, if isLastStep { *results = append(*results, JSONNode{Value: item, Path: itemPath}) } else { - traverseWithPaths(item, remainingSteps, results, itemPath) + err := traverseWithPaths(item, remainingSteps, results, itemPath) + if err != nil { + return fmt.Errorf("failed to traverse JSONPath %q: %w", itemPath, err) + } } } - return + return nil } // Handle specific index @@ -392,8 +423,13 @@ func traverseWithPaths(node interface{}, steps []JSONStep, results *[]JSONNode, if isLastStep { *results = append(*results, JSONNode{Value: item, Path: itemPath}) } else { - traverseWithPaths(item, remainingSteps, results, itemPath) + err := traverseWithPaths(item, remainingSteps, results, itemPath) + if err != nil { + return fmt.Errorf("failed to traverse JSONPath %q: %w", itemPath, err) + } } + } else { + return fmt.Errorf("index %d out of bounds for array at path: %s", step.Index, currentPath) } case RecursiveDescentStep: @@ -404,7 +440,10 @@ func traverseWithPaths(node interface{}, steps []JSONStep, results *[]JSONNode, if isLastStep { *results = append(*results, JSONNode{Value: val, Path: directPath}) } else { - traverseWithPaths(val, remainingSteps, results, directPath) + err := traverseWithPaths(val, remainingSteps, results, directPath) + if err != nil { + return fmt.Errorf("failed to traverse JSONPath %q: %w", directPath, err) + } } } } @@ -419,19 +458,25 @@ func traverseWithPaths(node interface{}, steps []JSONStep, results *[]JSONNode, case map[string]interface{}: for k, v := range n { childPath := currentPath + "." + k - traverseWithPaths(v, steps, results, childPath) // Use the same steps + err := traverseWithPaths(v, steps, results, childPath) // Use the same steps + if err != nil { + return fmt.Errorf("failed to traverse JSONPath %q: %w", childPath, err) + } } case []interface{}: for i, v := range n { childPath := fmt.Sprintf("%s[%d]", currentPath, i) - traverseWithPaths(v, steps, results, childPath) // Use the same steps + err := traverseWithPaths(v, steps, results, childPath) // Use the same steps + if err != nil { + return fmt.Errorf("failed to traverse JSONPath %q: %w", childPath, err) + } } } case WildcardStep: m, ok := node.(map[string]interface{}) if !ok { - return + return fmt.Errorf("node is not a map; actual type: %T", node) } for k, v := range m { @@ -439,8 +484,12 @@ func traverseWithPaths(node interface{}, steps []JSONStep, results *[]JSONNode, if isLastStep { *results = append(*results, JSONNode{Value: v, Path: childPath}) } else { - traverseWithPaths(v, remainingSteps, results, childPath) + err := traverseWithPaths(v, remainingSteps, results, childPath) + if err != nil { + return fmt.Errorf("failed to traverse JSONPath %q: %w", childPath, err) + } } } } + return nil } diff --git a/processor/jsonpath/jsonpath_get_set_test.go b/processor/jsonpath/jsonpath_get_set_test.go index 1c91dc3..d1f7c2e 100644 --- a/processor/jsonpath/jsonpath_get_set_test.go +++ b/processor/jsonpath/jsonpath_get_set_test.go @@ -11,6 +11,7 @@ func TestGetWithPathsBasic(t *testing.T) { data map[string]interface{} path string expected []JSONNode + error bool }{ { name: "simple property", @@ -91,12 +92,19 @@ func TestGetWithPathsBasic(t *testing.T) { }, path: "$.user.email", expected: []JSONNode{}, + error: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := Get(tt.data, tt.path) + result, err := Get(tt.data, tt.path) + if err != nil { + if !tt.error { + t.Errorf("GetWithPaths() returned error: %v", err) + } + return + } // For nonexistent path, we expect empty slice if tt.name == "nonexistent path" { @@ -148,11 +156,12 @@ func TestSet(t *testing.T) { "name": "John", "age": 30, } - success := Set(data, "$.name", "Jane") - - if !success { - t.Errorf("Set() returned false, expected true") + err := Set(data, "$.name", "Jane") + if err != nil { + t.Errorf("Set() returned error: %v", err) + return } + if data["name"] != "Jane" { t.Errorf("Set() failed: expected name to be 'Jane', got %v", data["name"]) } @@ -165,11 +174,12 @@ func TestSet(t *testing.T) { "age": 30, }, } - success := Set(data, "$.user.name", "Jane") - - if !success { - t.Errorf("Set() returned false, expected true") + err := Set(data, "$.user.name", "Jane") + if err != nil { + t.Errorf("Set() returned error: %v", err) + return } + user, ok := data["user"].(map[string]interface{}) if !ok { t.Fatalf("User is not a map") @@ -186,10 +196,10 @@ func TestSet(t *testing.T) { map[string]interface{}{"name": "Jane", "age": 25}, }, } - success := Set(data, "$.users[0].name", "Bob") - - if !success { - t.Errorf("Set() returned false, expected true") + err := Set(data, "$.users[0].name", "Bob") + if err != nil { + t.Errorf("Set() returned error: %v", err) + return } users, ok := data["users"].([]interface{}) if !ok { @@ -219,11 +229,12 @@ func TestSet(t *testing.T) { "phone": "123-456-7890", } - success := Set(data, "$.user.profile", newProfile) - - if !success { - t.Errorf("Set() returned false, expected true") + err := Set(data, "$.user.profile", newProfile) + if err != nil { + t.Errorf("Set() returned error: %v", err) + return } + userMap, ok := data["user"].(map[string]interface{}) if !ok { t.Fatalf("User is not a map") @@ -246,10 +257,10 @@ func TestSet(t *testing.T) { }, } - success := Set(data, "$.user.email", "john@example.com") - - if !success { - t.Errorf("Set() returned false, expected true") + err := Set(data, "$.user.email", "john@example.com") + if err != nil { + t.Errorf("Set() returned error: %v", err) + return } userMap, ok := data["user"].(map[string]interface{}) if !ok { @@ -268,10 +279,10 @@ func TestSet(t *testing.T) { }, } - success := Set(data, "$.user.contact.email", "john@example.com") - - if !success { - t.Errorf("Set() returned false, expected true") + err := Set(data, "$.user.contact.email", "john@example.com") + if err != nil { + t.Errorf("Set() returned error: %v", err) + return } userMap, ok := data["user"].(map[string]interface{}) if !ok { @@ -297,11 +308,10 @@ func TestSet(t *testing.T) { // This should create an empty addresses array, but won't be able to set index 0 // since the array is empty - success := Set(data, "$.user.addresses[0].street", "123 Main St") - - // This shouldn't succeed because we can't create array elements that don't exist - if success { - t.Errorf("Set() returned true, expected false for out-of-bounds array index") + err := Set(data, "$.user.addresses[0].street", "123 Main St") + if err != nil { + t.Errorf("Set() returned error: %v", err) + return } }) @@ -313,10 +323,10 @@ func TestSet(t *testing.T) { }, } - success := Set(data, "$.users[*].active", false) - - if !success { - t.Errorf("Set() returned false, expected true") + err := Set(data, "$.users[*].active", false) + if err != nil { + t.Errorf("Set() returned error: %v", err) + return } users, ok := data["users"].([]interface{}) @@ -350,10 +360,10 @@ func TestSet(t *testing.T) { "name": "John", } - success := Set(data, "$", "Jane") - - if success { - t.Errorf("Set() returned true, expected false for setting on root") + err := Set(data, "$", "Jane") + if err == nil { + t.Errorf("Set() returned no error, expected error for setting on root") + return } // Data should be unchanged @@ -369,10 +379,10 @@ func TestSetAll(t *testing.T) { "name": "John", "age": 30, } - success := SetAll(data, "$.name", "Jane") - - if !success { - t.Errorf("SetAll() returned false, expected true") + err := SetAll(data, "$.name", "Jane") + if err != nil { + t.Errorf("SetAll() returned error: %v", err) + return } if data["name"] != "Jane" { t.Errorf("SetAll() failed: expected name to be 'Jane', got %v", data["name"]) @@ -387,10 +397,10 @@ func TestSetAll(t *testing.T) { }, } - success := SetAll(data, "$.users[*].active", false) - - if !success { - t.Errorf("SetAll() returned false, expected true") + err := SetAll(data, "$.users[*].active", false) + if err != nil { + t.Errorf("SetAll() returned error: %v", err) + return } users, ok := data["users"].([]interface{}) @@ -425,10 +435,10 @@ func TestSetAll(t *testing.T) { }, } - success := SetAll(data, "$..active", false) - - if !success { - t.Errorf("SetAll() returned false, expected true") + err := SetAll(data, "$..active", false) + if err != nil { + t.Errorf("SetAll() returned error: %v", err) + return } // Check user profile @@ -532,7 +542,11 @@ func TestGetWithPathsExtended(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := Get(tt.data, tt.path) + result, err := Get(tt.data, tt.path) + if err != nil { + t.Errorf("GetWithPaths() returned error: %v", err) + return + } // Check if lengths match if len(result) != len(tt.expected) { diff --git a/processor/jsonpath/jsonpath_test.go b/processor/jsonpath/jsonpath_test.go index fa30c51..69a7175 100644 --- a/processor/jsonpath/jsonpath_test.go +++ b/processor/jsonpath/jsonpath_test.go @@ -93,6 +93,7 @@ func TestEvaluator(t *testing.T) { name string path string expected []JSONNode + error bool }{ { name: "simple_property_access", @@ -146,18 +147,26 @@ func TestEvaluator(t *testing.T) { name: "invalid_index", path: "$.store.book[5]", expected: []JSONNode{}, + error: true, }, { name: "nonexistent_property", path: "$.store.nonexistent", expected: []JSONNode{}, + error: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Use GetWithPaths directly - result := Get(testData, tt.path) + result, err := Get(testData, tt.path) + if err != nil { + if !tt.error { + t.Errorf("Get() returned error: %v", err) + } + return + } // Special handling for wildcard recursive test if tt.name == "wildcard_recursive" { @@ -201,7 +210,11 @@ func TestEvaluator(t *testing.T) { func TestEdgeCases(t *testing.T) { t.Run("empty_data", func(t *testing.T) { - result := Get(nil, "$.a.b") + result, err := Get(nil, "$.a.b") + if err == nil { + t.Errorf("Expected error for empty data") + return + } if len(result) > 0 { t.Errorf("Expected empty result, got %v", result) } @@ -218,7 +231,11 @@ func TestEdgeCases(t *testing.T) { data := map[string]interface{}{ "42": "answer", } - result := Get(data, "$.42") + result, err := Get(data, "$.42") + if err != nil { + t.Errorf("Get() returned error: %v", err) + return + } if len(result) == 0 || result[0].Value != "answer" { t.Errorf("Expected 'answer', got %v", result) } @@ -266,7 +283,11 @@ func TestGetWithPaths(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result := Get(testData, tt.path) + result, err := Get(testData, tt.path) + if err != nil { + t.Errorf("Get() returned error: %v", err) + return + } // Check if lengths match if len(result) != len(tt.expected) {