Improve error handling across the board
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user