diff --git a/processor/jsonpath/jsonpath.go b/processor/jsonpath/jsonpath.go
deleted file mode 100644
index 79498db..0000000
--- a/processor/jsonpath/jsonpath.go
+++ /dev/null
@@ -1,490 +0,0 @@
-package jsonpath
-
-import (
- "fmt"
- "strconv"
-)
-
-// JSONStep represents a single step in a JSONPath query
-type JSONStep struct {
- Type StepType
- Key string // For Child/RecursiveDescent
- Index int // For Index (use -1 for wildcard "*")
-}
-
-// JSONNode represents a value in the JSON data with its path
-type JSONNode struct {
- Value interface{} // The value found at the path
- Path string // The exact JSONPath where the value was found
-}
-
-// StepType defines the types of steps in a JSONPath
-type StepType int
-
-const (
- RootStep StepType = iota // $ - The root element
- ChildStep // .key - Direct child access
- RecursiveDescentStep // ..key - Recursive search for key
- WildcardStep // .* - All children of an object
- IndexStep // [n] - Array index access (or [*] for all elements)
-)
-
-// TraversalMode determines how the traversal behaves
-type TraversalMode int
-
-const (
- CollectMode TraversalMode = iota // Just collect matched nodes
- ModifyFirstMode // Modify first matching node
- ModifyAllMode // Modify all matching nodes
-)
-
-// 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 $; received: %q", path)
- }
-
- steps := []JSONStep{}
- i := 0
-
- for i < len(path) {
- switch path[i] {
- case '$':
- steps = append(steps, JSONStep{Type: RootStep})
- i++
- case '.':
- i++
- if i < len(path) && path[i] == '.' {
- // Recursive descent
- i++
- key, nextPos := readKey(path, i)
- steps = append(steps, JSONStep{Type: RecursiveDescentStep, Key: key})
- i = nextPos
- } else {
- // Child step or wildcard
- key, nextPos := readKey(path, i)
- if key == "*" {
- steps = append(steps, JSONStep{Type: WildcardStep})
- } else {
- steps = append(steps, JSONStep{Type: ChildStep, Key: key})
- }
- i = nextPos
- }
- case '[':
- // Index step
- i++
- indexStr, nextPos := readIndex(path, i)
- if indexStr == "*" {
- steps = append(steps, JSONStep{Type: IndexStep, Index: -1})
- } else {
- index, err := strconv.Atoi(indexStr)
- if err != nil {
- 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 at position %d; path: %q", path[i], i, path)
- }
- }
-
- return steps, nil
-}
-
-// readKey extracts a key name from the path
-func readKey(path string, start int) (string, int) {
- i := start
- for ; i < len(path); i++ {
- if path[i] == '.' || path[i] == '[' {
- break
- }
- }
- return path[start:i], i
-}
-
-// readIndex extracts an array index or wildcard from the path
-func readIndex(path string, start int) (string, int) {
- i := start
- for ; i < len(path); i++ {
- if path[i] == ']' {
- break
- }
- }
- return path[start:i], i
-}
-
-// 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, error) {
- steps, err := ParseJSONPath(path)
- if err != nil {
- return nil, fmt.Errorf("failed to parse JSONPath %q: %w", path, err)
- }
-
- results := []JSONNode{}
- 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{}) error {
- steps, err := ParseJSONPath(path)
- if err != nil {
- return fmt.Errorf("failed to parse JSONPath %q: %w", path, err)
- }
-
- success := false
- 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{}) error {
- steps, err := ParseJSONPath(path)
- if err != nil {
- return fmt.Errorf("failed to parse JSONPath %q: %w", path, err)
- }
-
- success := false
- 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) error {
- if node == nil || *success && mode == ModifyFirstMode {
- return nil
- }
-
- // Skip root step
- actualSteps := steps
- if len(steps) > 0 && steps[0].Type == RootStep {
- actualSteps = steps[1:]
- }
-
- // If we have no steps left, we're setting the root value
- if len(actualSteps) == 0 {
- // For the root node, we need to handle it differently depending on what's passed in
- // since we can't directly replace the interface{} variable
-
- // We'll signal success and let the JSONProcessor handle updating the root
- *success = true
- return nil
- }
-
- // Process the first step
- step := actualSteps[0]
- remainingSteps := actualSteps[1:]
- isLastStep := len(remainingSteps) == 0
-
- switch step.Type {
- case ChildStep:
- m, ok := node.(map[string]interface{})
- if !ok {
- return fmt.Errorf("node at path %q is not a map; actual type: %T", currentPath, node)
- }
-
- childPath := currentPath + "." + step.Key
-
- if isLastStep {
- // We've reached the target, set the value
- m[step.Key] = value
- *success = true
- return nil
- }
-
- // Create intermediate nodes if necessary
- child, exists := m[step.Key]
- if !exists {
- // Create missing intermediate node
- if len(remainingSteps) > 0 && remainingSteps[0].Type == IndexStep {
- child = []interface{}{}
- } else {
- child = map[string]interface{}{}
- }
- m[step.Key] = child
- }
-
- 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 fmt.Errorf("node at path %q is not an array; actual type: %T", currentPath, node)
- }
-
- // Handle wildcard index
- if step.Index == -1 {
- for i, item := range arr {
- itemPath := fmt.Sprintf("%s[%d]", currentPath, i)
- if isLastStep {
- arr[i] = value
- *success = true
- if mode == ModifyFirstMode {
- return nil
- }
- } else {
- 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 nil
- }
- }
- }
- return nil
- }
-
- // Handle specific index
- if step.Index >= 0 && step.Index < len(arr) {
- item := arr[step.Index]
- itemPath := fmt.Sprintf("%s[%d]", currentPath, step.Index)
- if isLastStep {
- arr[step.Index] = value
- *success = true
- } else {
- err := setWithPath(item, remainingSteps, success, value, itemPath, mode)
- if err != nil {
- return fmt.Errorf("failed to set value at JSONPath %q: %w", itemPath, err)
- }
- }
- }
-
- case RecursiveDescentStep:
- // For recursive descent, first check direct match at this level
- if m, ok := node.(map[string]interface{}); ok && step.Key != "*" {
- if val, exists := m[step.Key]; exists {
- directPath := currentPath + "." + step.Key
- if isLastStep {
- m[step.Key] = value
- *success = true
- if mode == ModifyFirstMode {
- return nil
- }
- } else {
- 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 nil
- }
- }
- }
- }
-
- // Then continue recursion to all children
- switch n := node.(type) {
- case map[string]interface{}:
- for k, v := range n {
- childPath := currentPath + "." + k
- // Skip keys we've already processed directly
- if step.Key != "*" && k == step.Key {
- continue
- }
- 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 nil
- }
- }
- case []interface{}:
- for i, v := range n {
- childPath := fmt.Sprintf("%s[%d]", currentPath, i)
- 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 nil
- }
- }
- }
-
- case WildcardStep:
- m, ok := node.(map[string]interface{})
- if !ok {
- return fmt.Errorf("node at path %q is not a map; actual type: %T", currentPath, node)
- }
-
- for k, v := range m {
- childPath := currentPath + "." + k
- if isLastStep {
- m[k] = value
- *success = true
- if mode == ModifyFirstMode {
- return nil
- }
- } else {
- 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 nil
- }
- }
- }
- }
- return nil
-}
-
-// traverseWithPaths tracks both nodes and their paths during traversal
-func traverseWithPaths(node interface{}, steps []JSONStep, results *[]JSONNode, currentPath string) error {
- if len(steps) == 0 || node == nil {
- return fmt.Errorf("cannot traverse with empty steps or nil node; steps length: %d, node: %v", len(steps), node)
- }
-
- // Skip root step
- actualSteps := steps
- if steps[0].Type == RootStep {
- if len(steps) == 1 {
- *results = append(*results, JSONNode{Value: node, Path: currentPath})
- return nil
- }
- actualSteps = steps[1:]
- }
-
- // Process the first step
- step := actualSteps[0]
- remainingSteps := actualSteps[1:]
- isLastStep := len(remainingSteps) == 0
-
- switch step.Type {
- case ChildStep:
- m, ok := node.(map[string]interface{})
- if !ok {
- return fmt.Errorf("node is not a map; actual type: %T", node)
- }
-
- child, exists := m[step.Key]
- if !exists {
- 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 {
- 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 fmt.Errorf("node is not an array; actual type: %T", node)
- }
-
- // Handle wildcard index
- if step.Index == -1 {
- for i, item := range arr {
- itemPath := fmt.Sprintf("%s[%d]", currentPath, i)
- if isLastStep {
- *results = append(*results, JSONNode{Value: item, Path: itemPath})
- } else {
- err := traverseWithPaths(item, remainingSteps, results, itemPath)
- if err != nil {
- return fmt.Errorf("failed to traverse JSONPath %q: %w", itemPath, err)
- }
- }
- }
- return nil
- }
-
- // Handle specific index
- if step.Index >= 0 && step.Index < len(arr) {
- item := arr[step.Index]
- itemPath := fmt.Sprintf("%s[%d]", currentPath, step.Index)
- if isLastStep {
- *results = append(*results, JSONNode{Value: item, Path: itemPath})
- } else {
- 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:
- // For recursive descent, first check direct match at this level
- if m, ok := node.(map[string]interface{}); ok && step.Key != "*" {
- if val, exists := m[step.Key]; exists {
- directPath := currentPath + "." + step.Key
- if isLastStep {
- *results = append(*results, JSONNode{Value: val, Path: directPath})
- } else {
- err := traverseWithPaths(val, remainingSteps, results, directPath)
- if err != nil {
- return fmt.Errorf("failed to traverse JSONPath %q: %w", directPath, err)
- }
- }
- }
- }
-
- // For wildcard, collect this node
- if step.Key == "*" && isLastStep {
- *results = append(*results, JSONNode{Value: node, Path: currentPath})
- }
-
- // Then continue recursion to all children
- switch n := node.(type) {
- case map[string]interface{}:
- for k, v := range n {
- childPath := currentPath + "." + k
- 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)
- 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 fmt.Errorf("node is not a map; actual type: %T", node)
- }
-
- for k, v := range m {
- childPath := currentPath + "." + k
- if isLastStep {
- *results = append(*results, JSONNode{Value: v, Path: childPath})
- } else {
- 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
deleted file mode 100644
index 94fa707..0000000
--- a/processor/jsonpath/jsonpath_get_set_test.go
+++ /dev/null
@@ -1,577 +0,0 @@
-package jsonpath
-
-import (
- "reflect"
- "testing"
-)
-
-func TestGetWithPathsBasic(t *testing.T) {
- tests := []struct {
- name string
- data map[string]interface{}
- path string
- expected []JSONNode
- error bool
- }{
- {
- name: "simple property",
- data: map[string]interface{}{
- "name": "John",
- "age": 30,
- },
- path: "$.name",
- expected: []JSONNode{
- {Value: "John", Path: "$.name"},
- },
- },
- {
- name: "nested property",
- data: map[string]interface{}{
- "user": map[string]interface{}{
- "name": "John",
- "age": 30,
- },
- },
- path: "$.user.name",
- expected: []JSONNode{
- {Value: "John", Path: "$.user.name"},
- },
- },
- {
- name: "array access",
- data: map[string]interface{}{
- "users": []interface{}{
- map[string]interface{}{"name": "John", "age": 30},
- map[string]interface{}{"name": "Jane", "age": 25},
- },
- },
- path: "$.users[1].name",
- expected: []JSONNode{
- {Value: "Jane", Path: "$.users[1].name"},
- },
- },
- {
- name: "wildcard",
- data: map[string]interface{}{
- "users": []interface{}{
- map[string]interface{}{"name": "John", "age": 30},
- map[string]interface{}{"name": "Jane", "age": 25},
- },
- },
- path: "$.users[*].name",
- expected: []JSONNode{
- {Value: "John", Path: "$.users[0].name"},
- {Value: "Jane", Path: "$.users[1].name"},
- },
- },
- {
- name: "recursive descent",
- data: map[string]interface{}{
- "user": map[string]interface{}{
- "name": "John",
- "profile": map[string]interface{}{
- "email": "john@example.com",
- },
- },
- "admin": map[string]interface{}{
- "email": "admin@example.com",
- },
- },
- path: "$..email",
- expected: []JSONNode{
- {Value: "john@example.com", Path: "$.user.profile.email"},
- {Value: "admin@example.com", Path: "$.admin.email"},
- },
- },
- {
- name: "nonexistent path",
- data: map[string]interface{}{
- "user": map[string]interface{}{
- "name": "John",
- },
- },
- path: "$.user.email",
- expected: []JSONNode{},
- error: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- 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" {
- if len(result) > 0 {
- t.Errorf("GetWithPaths() returned %v, expected empty result", result)
- }
- return
- }
-
- // Check if lengths match
- if len(result) != len(tt.expected) {
- t.Errorf("GetWithPaths() returned %d items, expected %d", len(result), len(tt.expected))
- return
- }
-
- // For wildcard results, we need to check containment rather than exact order
- if tt.name == "wildcard" || tt.name == "recursive descent" {
- // For each expected item, check if it exists in the results by both value and path
- for _, expected := range tt.expected {
- found := false
- for _, r := range result {
- if reflect.DeepEqual(r.Value, expected.Value) && r.Path == expected.Path {
- found = true
- break
- }
- }
- if !found {
- t.Errorf("GetWithPaths() missing expected value: %v with path: %s", expected.Value, expected.Path)
- }
- }
- } else {
- // Otherwise check exact equality of both values and paths
- for i, expected := range tt.expected {
- if !reflect.DeepEqual(result[i].Value, expected.Value) {
- t.Errorf("GetWithPaths() value at [%d] = %v, expected %v", i, result[i].Value, expected.Value)
- }
- if result[i].Path != expected.Path {
- t.Errorf("GetWithPaths() path at [%d] = %s, expected %s", i, result[i].Path, expected.Path)
- }
- }
- }
- })
- }
-}
-
-func TestSet(t *testing.T) {
- t.Run("simple property", func(t *testing.T) {
- data := map[string]interface{}{
- "name": "John",
- "age": 30,
- }
- 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"])
- }
- })
-
- t.Run("nested property", func(t *testing.T) {
- data := map[string]interface{}{
- "user": map[string]interface{}{
- "name": "John",
- "age": 30,
- },
- }
- 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")
- }
- if user["name"] != "Jane" {
- t.Errorf("Set() failed: expected user.name to be 'Jane', got %v", user["name"])
- }
- })
-
- t.Run("array element", func(t *testing.T) {
- data := map[string]interface{}{
- "users": []interface{}{
- map[string]interface{}{"name": "John", "age": 30},
- map[string]interface{}{"name": "Jane", "age": 25},
- },
- }
- 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 {
- t.Fatalf("Users is not a slice")
- }
- user0, ok := users[0].(map[string]interface{})
- if !ok {
- t.Fatalf("User is not a map")
- }
- if user0["name"] != "Bob" {
- t.Errorf("Set() failed: expected users[0].name to be 'Bob', got %v", user0["name"])
- }
- })
-
- t.Run("complex value", func(t *testing.T) {
- data := map[string]interface{}{
- "user": map[string]interface{}{
- "name": "John",
- "profile": map[string]interface{}{
- "email": "john@example.com",
- },
- },
- }
-
- newProfile := map[string]interface{}{
- "email": "john.doe@example.com",
- "phone": "123-456-7890",
- }
-
- 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")
- }
-
- profile, ok := userMap["profile"].(map[string]interface{})
- if !ok {
- t.Fatalf("Profile is not a map")
- }
-
- if profile["email"] != "john.doe@example.com" || profile["phone"] != "123-456-7890" {
- t.Errorf("Set() failed: expected profile to be updated with new values")
- }
- })
-
- t.Run("create new property", func(t *testing.T) {
- data := map[string]interface{}{
- "user": map[string]interface{}{
- "name": "John",
- },
- }
-
- 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 {
- t.Fatalf("User is not a map")
- }
-
- if email, exists := userMap["email"]; !exists || email != "john@example.com" {
- t.Errorf("Set() failed: expected user.email to be 'john@example.com', got %v", userMap["email"])
- }
- })
-
- t.Run("create nested properties", func(t *testing.T) {
- data := map[string]interface{}{
- "user": map[string]interface{}{
- "name": "John",
- },
- }
-
- 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 {
- t.Fatalf("User is not a map")
- }
-
- contact, ok := userMap["contact"].(map[string]interface{})
- if !ok {
- t.Fatalf("Contact is not a map")
- }
-
- if email, exists := contact["email"]; !exists || email != "john@example.com" {
- t.Errorf("Set() failed: expected user.contact.email to be 'john@example.com', got %v", contact["email"])
- }
- })
-
- t.Run("create array and element", func(t *testing.T) {
- data := map[string]interface{}{
- "user": map[string]interface{}{
- "name": "John",
- },
- }
-
- // This should create an empty addresses array, but won't be able to set index 0
- // since the array is empty
- err := Set(data, "$.user.addresses[0].street", "123 Main St")
- if err != nil {
- t.Errorf("Set() returned error: %v", err)
- return
- }
- })
-
- t.Run("multiple targets (should only update first)", func(t *testing.T) {
- data := map[string]interface{}{
- "users": []interface{}{
- map[string]interface{}{"active": true},
- map[string]interface{}{"active": true},
- },
- }
-
- err := Set(data, "$.users[*].active", false)
- if err != nil {
- t.Errorf("Set() returned error: %v", err)
- return
- }
-
- users, ok := data["users"].([]interface{})
- if !ok {
- t.Fatalf("Users is not a slice")
- }
-
- user0, ok := users[0].(map[string]interface{})
- if !ok {
- t.Fatalf("User0 is not a map")
- }
-
- user1, ok := users[1].(map[string]interface{})
- if !ok {
- t.Fatalf("User1 is not a map")
- }
-
- // Only the first one should be changed
- if active, exists := user0["active"]; !exists || active != false {
- t.Errorf("Set() failed: expected users[0].active to be false, got %v", user0["active"])
- }
-
- // The second one should remain unchanged
- if active, exists := user1["active"]; !exists || active != true {
- t.Errorf("Set() incorrectly modified users[1].active: expected true, got %v", user1["active"])
- }
- })
-
- t.Run("setting on root should not fail (anymore)", func(t *testing.T) {
- data := map[string]interface{}{
- "name": "John",
- }
-
- err := Set(data, "$", "Jane")
- if err != nil {
- t.Errorf("Set() returned error: %v", err)
- return
- }
-
- // Data should be unchanged
- if data["name"] != "John" {
- t.Errorf("Data was modified when setting on root")
- }
- })
-}
-
-func TestSetAll(t *testing.T) {
- t.Run("simple property", func(t *testing.T) {
- data := map[string]interface{}{
- "name": "John",
- "age": 30,
- }
- 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"])
- }
- })
-
- t.Run("all array elements", func(t *testing.T) {
- data := map[string]interface{}{
- "users": []interface{}{
- map[string]interface{}{"active": true},
- map[string]interface{}{"active": true},
- },
- }
-
- err := SetAll(data, "$.users[*].active", false)
- if err != nil {
- t.Errorf("SetAll() returned error: %v", err)
- return
- }
-
- users, ok := data["users"].([]interface{})
- if !ok {
- t.Fatalf("Users is not a slice")
- }
-
- // Both elements should be updated
- for i, user := range users {
- userMap, ok := user.(map[string]interface{})
- if !ok {
- t.Fatalf("User%d is not a map", i)
- }
-
- if active, exists := userMap["active"]; !exists || active != false {
- t.Errorf("SetAll() failed: expected users[%d].active to be false, got %v", i, userMap["active"])
- }
- }
- })
-
- t.Run("recursive descent", func(t *testing.T) {
- data := map[string]interface{}{
- "user": map[string]interface{}{
- "profile": map[string]interface{}{
- "active": true,
- },
- },
- "admin": map[string]interface{}{
- "profile": map[string]interface{}{
- "active": true,
- },
- },
- }
-
- err := SetAll(data, "$..active", false)
- if err != nil {
- t.Errorf("SetAll() returned error: %v", err)
- return
- }
-
- // Check user profile
- userProfile, ok := data["user"].(map[string]interface{})["profile"].(map[string]interface{})
- if !ok {
- t.Fatalf("Failed to access user.profile")
- }
- if active, exists := userProfile["active"]; !exists || active != false {
- t.Errorf("SetAll() didn't update user.profile.active, got: %v", active)
- }
-
- // Check admin profile
- adminProfile, ok := data["admin"].(map[string]interface{})["profile"].(map[string]interface{})
- if !ok {
- t.Fatalf("Failed to access admin.profile")
- }
- if active, exists := adminProfile["active"]; !exists || active != false {
- t.Errorf("SetAll() didn't update admin.profile.active, got: %v", active)
- }
- })
-}
-
-func TestGetWithPathsExtended(t *testing.T) {
- tests := []struct {
- name string
- data map[string]interface{}
- path string
- expected []JSONNode
- }{
- {
- name: "simple property",
- data: map[string]interface{}{
- "name": "John",
- "age": 30,
- },
- path: "$.name",
- expected: []JSONNode{
- {Value: "John", Path: "$.name"},
- },
- },
- {
- name: "nested property",
- data: map[string]interface{}{
- "user": map[string]interface{}{
- "name": "John",
- "age": 30,
- },
- },
- path: "$.user.name",
- expected: []JSONNode{
- {Value: "John", Path: "$.user.name"},
- },
- },
- {
- name: "array access",
- data: map[string]interface{}{
- "users": []interface{}{
- map[string]interface{}{"name": "John", "age": 30},
- map[string]interface{}{"name": "Jane", "age": 25},
- },
- },
- path: "$.users[1].name",
- expected: []JSONNode{
- {Value: "Jane", Path: "$.users[1].name"},
- },
- },
- {
- name: "wildcard",
- data: map[string]interface{}{
- "users": []interface{}{
- map[string]interface{}{"name": "John", "age": 30},
- map[string]interface{}{"name": "Jane", "age": 25},
- },
- },
- path: "$.users[*].name",
- expected: []JSONNode{
- {Value: "John", Path: "$.users[0].name"},
- {Value: "Jane", Path: "$.users[1].name"},
- },
- },
- {
- name: "recursive descent",
- data: map[string]interface{}{
- "user": map[string]interface{}{
- "name": "John",
- "profile": map[string]interface{}{
- "email": "john@example.com",
- },
- },
- "admin": map[string]interface{}{
- "email": "admin@example.com",
- },
- },
- path: "$..email",
- expected: []JSONNode{
- {Value: "john@example.com", Path: "$.user.profile.email"},
- {Value: "admin@example.com", Path: "$.admin.email"},
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- 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) {
- t.Errorf("GetWithPaths() returned %d items, expected %d", len(result), len(tt.expected))
- return
- }
-
- // For each expected item, find its match in the results and verify both value and path
- for _, expected := range tt.expected {
- found := false
- for _, r := range result {
- // Check if value matches
- if reflect.DeepEqual(r.Value, expected.Value) {
- found = true
- // Check if path matches
- if r.Path != expected.Path {
- t.Errorf("Path mismatch for value %v: got %s, expected %s", r.Value, r.Path, expected.Path)
- }
- break
- }
- }
- if !found {
- t.Errorf("Expected node with value %v and path %s not found in results", expected.Value, expected.Path)
- }
- }
- })
- }
-}
diff --git a/processor/jsonpath/jsonpath_test.go b/processor/jsonpath/jsonpath_test.go
deleted file mode 100644
index 69a7175..0000000
--- a/processor/jsonpath/jsonpath_test.go
+++ /dev/null
@@ -1,318 +0,0 @@
-package jsonpath
-
-import (
- "reflect"
- "testing"
-)
-
-var testData = map[string]interface{}{
- "store": map[string]interface{}{
- "book": []interface{}{
- map[string]interface{}{
- "title": "The Fellowship of the Ring",
- "price": 22.99,
- },
- map[string]interface{}{
- "title": "The Two Towers",
- "price": 23.45,
- },
- },
- "bicycle": map[string]interface{}{
- "color": "red",
- "price": 199.95,
- },
- },
-}
-
-func TestParser(t *testing.T) {
- tests := []struct {
- path string
- steps []JSONStep
- wantErr bool
- }{
- {
- path: "$.store.bicycle.color",
- steps: []JSONStep{
- {Type: RootStep},
- {Type: ChildStep, Key: "store"},
- {Type: ChildStep, Key: "bicycle"},
- {Type: ChildStep, Key: "color"},
- },
- },
- {
- path: "$..price",
- steps: []JSONStep{
- {Type: RootStep},
- {Type: RecursiveDescentStep, Key: "price"},
- },
- },
- {
- path: "$.store.book[*].title",
- steps: []JSONStep{
- {Type: RootStep},
- {Type: ChildStep, Key: "store"},
- {Type: ChildStep, Key: "book"},
- {Type: IndexStep, Index: -1}, // Wildcard
- {Type: ChildStep, Key: "title"},
- },
- },
- {
- path: "$.store.book[0]",
- steps: []JSONStep{
- {Type: RootStep},
- {Type: ChildStep, Key: "store"},
- {Type: ChildStep, Key: "book"},
- {Type: IndexStep, Index: 0},
- },
- },
- {
- path: "invalid.path",
- wantErr: true,
- },
- {
- path: "$.store.book[abc]",
- wantErr: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.path, func(t *testing.T) {
- steps, err := ParseJSONPath(tt.path)
- if (err != nil) != tt.wantErr {
- t.Fatalf("ParseJSONPath() error = %v, wantErr %v", err, tt.wantErr)
- }
- if !tt.wantErr && !reflect.DeepEqual(steps, tt.steps) {
- t.Errorf("ParseJSONPath() steps = %+v, want %+v", steps, tt.steps)
- }
- })
- }
-}
-
-func TestEvaluator(t *testing.T) {
- tests := []struct {
- name string
- path string
- expected []JSONNode
- error bool
- }{
- {
- name: "simple_property_access",
- path: "$.store.bicycle.color",
- expected: []JSONNode{
- {Value: "red", Path: "$.store.bicycle.color"},
- },
- },
- {
- name: "array_index_access",
- path: "$.store.book[0].title",
- expected: []JSONNode{
- {Value: "The Fellowship of the Ring", Path: "$.store.book[0].title"},
- },
- },
- {
- name: "wildcard_array_access",
- path: "$.store.book[*].title",
- expected: []JSONNode{
- {Value: "The Fellowship of the Ring", Path: "$.store.book[0].title"},
- {Value: "The Two Towers", Path: "$.store.book[1].title"},
- },
- },
- {
- name: "recursive_price_search",
- path: "$..price",
- expected: []JSONNode{
- {Value: 22.99, Path: "$.store.book[0].price"},
- {Value: 23.45, Path: "$.store.book[1].price"},
- {Value: 199.95, Path: "$.store.bicycle.price"},
- },
- },
- {
- name: "wildcard_recursive",
- path: "$..*",
- expected: []JSONNode{
- // These will be compared by value only, paths will be validated separately
- {Value: testData["store"].(map[string]interface{})["book"]},
- {Value: testData["store"].(map[string]interface{})["bicycle"]},
- {Value: testData["store"].(map[string]interface{})["book"].([]interface{})[0]},
- {Value: testData["store"].(map[string]interface{})["book"].([]interface{})[1]},
- {Value: "The Fellowship of the Ring"},
- {Value: 22.99},
- {Value: "The Two Towers"},
- {Value: 23.45},
- {Value: "red"},
- {Value: 199.95},
- },
- },
- {
- 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, 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" {
- // Skip length check for wildcard recursive since it might vary
- // Just verify that each expected item is in the results
-
- // Validate values match and paths are filled in
- for _, e := range tt.expected {
- found := false
- for _, r := range result {
- if reflect.DeepEqual(r.Value, e.Value) {
- found = true
- break
- }
- }
- if !found {
- t.Errorf("Expected value %v not found in results", e.Value)
- }
- }
- return
- }
-
- if len(result) != len(tt.expected) {
- t.Errorf("Expected %d items, got %d", len(tt.expected), len(result))
- }
-
- // Validate both values and paths
- for i, e := range tt.expected {
- if i < len(result) {
- if !reflect.DeepEqual(result[i].Value, e.Value) {
- t.Errorf("Value at [%d]: got %v, expected %v", i, result[i].Value, e.Value)
- }
- if result[i].Path != e.Path {
- t.Errorf("Path at [%d]: got %s, expected %s", i, result[i].Path, e.Path)
- }
- }
- }
- })
- }
-}
-
-func TestEdgeCases(t *testing.T) {
- t.Run("empty_data", func(t *testing.T) {
- 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)
- }
- })
-
- t.Run("empty_path", func(t *testing.T) {
- _, err := ParseJSONPath("")
- if err == nil {
- t.Error("Expected error for empty path")
- }
- })
-
- t.Run("numeric_keys", func(t *testing.T) {
- data := map[string]interface{}{
- "42": "answer",
- }
- 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)
- }
- })
-}
-
-func TestGetWithPaths(t *testing.T) {
- tests := []struct {
- name string
- path string
- expected []JSONNode
- }{
- {
- name: "simple_property_access",
- path: "$.store.bicycle.color",
- expected: []JSONNode{
- {Value: "red", Path: "$.store.bicycle.color"},
- },
- },
- {
- name: "array_index_access",
- path: "$.store.book[0].title",
- expected: []JSONNode{
- {Value: "The Fellowship of the Ring", Path: "$.store.book[0].title"},
- },
- },
- {
- name: "wildcard_array_access",
- path: "$.store.book[*].title",
- expected: []JSONNode{
- {Value: "The Fellowship of the Ring", Path: "$.store.book[0].title"},
- {Value: "The Two Towers", Path: "$.store.book[1].title"},
- },
- },
- {
- name: "recursive_price_search",
- path: "$..price",
- expected: []JSONNode{
- {Value: 22.99, Path: "$.store.book[0].price"},
- {Value: 23.45, Path: "$.store.book[1].price"},
- {Value: 199.95, Path: "$.store.bicycle.price"},
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- 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) {
- t.Errorf("GetWithPaths() returned %d items, expected %d", len(result), len(tt.expected))
- return
- }
-
- // For each expected item, find its match in the results and verify both value and path
- for _, expected := range tt.expected {
- found := false
- for _, r := range result {
- // First verify the value matches
- if reflect.DeepEqual(r.Value, expected.Value) {
- found = true
- // Then verify the path matches
- if r.Path != expected.Path {
- t.Errorf("Path mismatch for value %v: got %s, expected %s", r.Value, r.Path, expected.Path)
- }
- break
- }
- }
- if !found {
- t.Errorf("Expected node with value %v and path %s not found in results", expected.Value, expected.Path)
- }
- }
- })
- }
-}
diff --git a/processor/xpath/parser_manual_test.go b/processor/xpath/parser_manual_test.go
deleted file mode 100644
index d852c95..0000000
--- a/processor/xpath/parser_manual_test.go
+++ /dev/null
@@ -1,4 +0,0 @@
-// The package is now using github.com/antchfx/xmlquery for XPath parsing.
-// The parsing functionality tests have been removed since we're now
-// delegating XPath parsing to the xmlquery library.
-package xpath
diff --git a/processor/xpath/parser_test.go b/processor/xpath/parser_test.go
deleted file mode 100644
index d852c95..0000000
--- a/processor/xpath/parser_test.go
+++ /dev/null
@@ -1,4 +0,0 @@
-// The package is now using github.com/antchfx/xmlquery for XPath parsing.
-// The parsing functionality tests have been removed since we're now
-// delegating XPath parsing to the xmlquery library.
-package xpath
diff --git a/processor/xpath/xpath.go b/processor/xpath/xpath.go
deleted file mode 100644
index d05bb1d..0000000
--- a/processor/xpath/xpath.go
+++ /dev/null
@@ -1,133 +0,0 @@
-package xpath
-
-import (
- "errors"
- "fmt"
-
- "github.com/antchfx/xmlquery"
-)
-
-// Get retrieves nodes from XML data using an XPath expression
-func Get(node *xmlquery.Node, path string) ([]*xmlquery.Node, error) {
- if node == nil {
- return nil, errors.New("nil node provided")
- }
-
- // Execute xpath query directly
- nodes, err := xmlquery.QueryAll(node, path)
- if err != nil {
- return nil, fmt.Errorf("failed to execute XPath query: %v", err)
- }
-
- return nodes, nil
-}
-
-// Set updates a single node in the XML data using an XPath expression
-func Set(node *xmlquery.Node, path string, value interface{}) error {
- if node == nil {
- return errors.New("nil node provided")
- }
-
- // Find the node to update
- nodes, err := xmlquery.QueryAll(node, path)
- if err != nil {
- return fmt.Errorf("failed to execute XPath query: %v", err)
- }
-
- if len(nodes) == 0 {
- return fmt.Errorf("no nodes found for path: %s", path)
- }
-
- // Update the first matching node
- updateNodeValue(nodes[0], value)
-
- return nil
-}
-
-// SetAll updates all nodes that match the XPath expression
-func SetAll(node *xmlquery.Node, path string, value interface{}) error {
- if node == nil {
- return errors.New("nil node provided")
- }
-
- // Find all nodes to update
- nodes, err := xmlquery.QueryAll(node, path)
- if err != nil {
- return fmt.Errorf("failed to execute XPath query: %v", err)
- }
-
- if len(nodes) == 0 {
- return fmt.Errorf("no nodes found for path: %s", path)
- }
-
- // Update all matching nodes
- for _, matchNode := range nodes {
- updateNodeValue(matchNode, value)
- }
-
- return nil
-}
-
-// Helper function to update a node's value
-func updateNodeValue(node *xmlquery.Node, value interface{}) {
- strValue := fmt.Sprintf("%v", value)
-
- // Handle different node types
- switch node.Type {
- case xmlquery.AttributeNode:
- // For attribute nodes, update the attribute value
- parent := node.Parent
- if parent != nil {
- for i, attr := range parent.Attr {
- if attr.Name.Local == node.Data {
- parent.Attr[i].Value = strValue
- break
- }
- }
- }
- case xmlquery.TextNode:
- // For text nodes, update the text content
- node.Data = strValue
- case xmlquery.ElementNode:
- // For element nodes, clear existing text children and add a new text node
- // First, remove all existing text children
- var nonTextChildren []*xmlquery.Node
- for child := node.FirstChild; child != nil; child = child.NextSibling {
- if child.Type != xmlquery.TextNode {
- nonTextChildren = append(nonTextChildren, child)
- }
- }
-
- // Clear all children
- node.FirstChild = nil
- node.LastChild = nil
-
- // Add a new text node
- textNode := &xmlquery.Node{
- Type: xmlquery.TextNode,
- Data: strValue,
- Parent: node,
- }
-
- // Set the text node as the first child
- node.FirstChild = textNode
- node.LastChild = textNode
-
- // Add back non-text children
- for _, child := range nonTextChildren {
- child.Parent = node
-
- // If this is the first child being added back
- if node.FirstChild == textNode && node.LastChild == textNode {
- node.FirstChild.NextSibling = child
- child.PrevSibling = node.FirstChild
- node.LastChild = child
- } else {
- // Add to the end of the chain
- node.LastChild.NextSibling = child
- child.PrevSibling = node.LastChild
- node.LastChild = child
- }
- }
- }
-}
diff --git a/processor/xpath/xpath_test.go b/processor/xpath/xpath_test.go
deleted file mode 100644
index 95ab90e..0000000
--- a/processor/xpath/xpath_test.go
+++ /dev/null
@@ -1,474 +0,0 @@
-package xpath
-
-import (
- "strings"
- "testing"
-
- "github.com/antchfx/xmlquery"
-)
-
-// Parse test XML data once at the beginning for use in multiple tests
-func parseTestXML(t *testing.T, xmlData string) *xmlquery.Node {
- doc, err := xmlquery.Parse(strings.NewReader(xmlData))
- if err != nil {
- t.Fatalf("Failed to parse test XML: %v", err)
- }
- return doc
-}
-
-// XML test data as a string for our tests
-var testXML = `
-
-
- The Fellowship of the Ring
- J.R.R. Tolkien
- 1954
- 22.99
-
-
- The Two Towers
- J.R.R. Tolkien
- 1954
- 23.45
-
-
- Learning XML
- Erik T. Ray
- 2003
- 39.95
-
-
- red
- 199.95
-
-
-`
-
-func TestEvaluator(t *testing.T) {
- // Parse the test XML data once for all test cases
- doc := parseTestXML(t, testXML)
-
- tests := []struct {
- name string
- path string
- error bool
- }{
- {
- name: "simple_element_access",
- path: "/store/bicycle/color",
- },
- {
- name: "recursive_element_access",
- path: "//price",
- },
- {
- name: "wildcard_element_access",
- path: "/store/book/*",
- },
- {
- name: "attribute_exists_predicate",
- path: "//title[@lang]",
- },
- {
- name: "attribute_equals_predicate",
- path: "//title[@lang='en']",
- },
- {
- name: "value_comparison_predicate",
- path: "/store/book[price>35.00]/title",
- error: true,
- },
- {
- name: "last_predicate",
- path: "/store/book[last()]/title",
- error: true,
- },
- {
- name: "last_minus_predicate",
- path: "/store/book[last()-1]/title",
- error: true,
- },
- {
- name: "position_predicate",
- path: "/store/book[position()<3]/title",
- error: true,
- },
- {
- name: "invalid_index",
- path: "/store/book[10]/title",
- error: true,
- },
- {
- name: "nonexistent_element",
- path: "/store/nonexistent",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result, err := Get(doc, tt.path)
-
- // Handle expected errors
- if tt.error {
- if err == nil && len(result) == 0 {
- // If we expected an error but got empty results instead, that's okay
- return
- }
- if err != nil {
- // If we got an error as expected, that's okay
- return
- }
- } else if err != nil {
- // If we didn't expect an error but got one, that's a test failure
- t.Errorf("Get(%q) returned unexpected error: %v", tt.path, err)
- return
- }
-
- // Special cases where we don't care about exact matches
- switch tt.name {
- case "wildcard_element_access":
- // Just check that we got some elements
- if len(result) == 0 {
- t.Errorf("Expected multiple elements for wildcard, got none")
- }
- return
- case "attribute_exists_predicate", "attribute_equals_predicate":
- // Just check that we got some titles
- if len(result) == 0 {
- t.Errorf("Expected titles with lang attribute, got none")
- }
- // Ensure all are title elements
- for _, node := range result {
- if node.Data != "title" {
- t.Errorf("Expected title elements, got: %s", node.Data)
- }
- }
- return
- case "nonexistent_element":
- // Just check that we got empty results
- if len(result) != 0 {
- t.Errorf("Expected empty results for nonexistent element, got %d items", len(result))
- }
- return
- }
-
- // For other cases, just verify we got results
- if len(result) == 0 {
- t.Errorf("Expected results for path %s, got none", tt.path)
- }
- })
- }
-}
-
-func TestEdgeCases(t *testing.T) {
- t.Run("nil_node", func(t *testing.T) {
- result, err := Get(nil, "/store/book")
- if err == nil {
- t.Errorf("Expected error for nil node")
- return
- }
- if len(result) > 0 {
- t.Errorf("Expected empty result, got %v", result)
- }
- })
-
- t.Run("invalid_xml", func(t *testing.T) {
- invalidXML, err := xmlquery.Parse(strings.NewReader("xml"))
- if err != nil {
- // If parsing fails, that's expected
- return
- }
-
- _, err = Get(invalidXML, "/store")
- if err == nil {
- t.Error("Expected error for invalid XML structure")
- }
- })
-
- // For these tests with the simple XML, we expect just one result
- simpleXML := `Test`
- doc := parseTestXML(t, simpleXML)
-
- t.Run("current_node", func(t *testing.T) {
- result, err := Get(doc, "/root/book/.")
- if err != nil {
- t.Errorf("Get() returned error: %v", err)
- return
- }
- if len(result) > 1 {
- t.Errorf("Expected at most 1 result, got %d", len(result))
- }
- if len(result) > 0 {
- // Verify it's the book node
- if result[0].Data != "book" {
- t.Errorf("Expected book node, got %v", result[0].Data)
- }
- }
- })
-
- t.Run("attributes", func(t *testing.T) {
- result, err := Get(doc, "/root/book/title/@lang")
- if err != nil {
- t.Errorf("Get() returned error: %v", err)
- return
- }
- if len(result) != 1 || result[0].InnerText() != "en" {
- t.Errorf("Expected 'en', got %v", result[0].InnerText())
- }
- })
-}
-
-func TestGetWithPaths(t *testing.T) {
- // Use a simplified, well-formed XML document
- simpleXML := `
-
- The Book Title
- Author Name
- 19.99
-
-
- red
- 199.95
-
-`
-
- // Parse the XML for testing
- doc := parseTestXML(t, simpleXML)
-
- // Debug: Print the test XML
- t.Logf("Test XML:\n%s", simpleXML)
-
- tests := []struct {
- name string
- path string
- expectedValue string
- }{
- {
- name: "simple_element_access",
- path: "/store/bicycle/color",
- expectedValue: "red",
- },
- {
- name: "attribute_access",
- path: "/store/book/title/@lang",
- expectedValue: "en",
- },
- {
- name: "recursive_with_attribute",
- path: "//title[@lang='en']",
- expectedValue: "The Book Title",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- // Debug: Print the path we're looking for
- t.Logf("Looking for path: %s", tt.path)
-
- result, err := Get(doc, tt.path)
- if err != nil {
- t.Errorf("Get(%q) returned error: %v", tt.path, err)
- return
- }
-
- // Debug: Print the results
- t.Logf("Got %d results", len(result))
- for i, r := range result {
- t.Logf("Result %d: Node=%s, Value=%v", i, r.Data, r.InnerText())
- }
-
- // Check that we got results
- if len(result) == 0 {
- t.Errorf("Get(%q) returned no results", tt.path)
- return
- }
-
- // For attribute access test, do more specific checks
- if tt.name == "attribute_access" {
- // Check the first result's value matches expected
- if result[0].InnerText() != tt.expectedValue {
- t.Errorf("Attribute value: got %v, expected %s", result[0].InnerText(), tt.expectedValue)
- }
- }
-
- // For simple element access, check the text content
- if tt.name == "simple_element_access" {
- if text := result[0].InnerText(); text != tt.expectedValue {
- t.Errorf("Element text: got %s, expected %s", text, tt.expectedValue)
- }
- }
-
- // For recursive with attribute test, check title elements with lang="en"
- if tt.name == "recursive_with_attribute" {
- for _, node := range result {
- // Check the node is a title
- if node.Data != "title" {
- t.Errorf("Expected title element, got %s", node.Data)
- }
-
- // Check text content
- if text := node.InnerText(); text != tt.expectedValue {
- t.Errorf("Text content: got %s, expected %s", text, tt.expectedValue)
- }
-
- // Check attributes - find the lang attribute
- hasLang := false
- for _, attr := range node.Attr {
- if attr.Name.Local == "lang" && attr.Value == "en" {
- hasLang = true
- break
- }
- }
- if !hasLang {
- t.Errorf("Expected lang=\"en\" attribute, but it was not found")
- }
- }
- }
- })
- }
-}
-
-func TestSet(t *testing.T) {
- t.Run("simple element", func(t *testing.T) {
- xmlData := `John`
- doc := parseTestXML(t, xmlData)
-
- err := Set(doc, "/root/name", "Jane")
- if err != nil {
- t.Errorf("Set() returned error: %v", err)
- return
- }
-
- // Verify the change
- result, err := Get(doc, "/root/name")
- if err != nil {
- t.Errorf("Get() returned error: %v", err)
- return
- }
- if len(result) != 1 {
- t.Errorf("Expected 1 result, got %d", len(result))
- return
- }
-
- // Check text content
- if text := result[0].InnerText(); text != "Jane" {
- t.Errorf("Expected text 'Jane', got '%s'", text)
- }
- })
-
- t.Run("attribute", func(t *testing.T) {
- xmlData := ``
- doc := parseTestXML(t, xmlData)
-
- err := Set(doc, "/root/element/@id", "456")
- if err != nil {
- t.Errorf("Set() returned error: %v", err)
- return
- }
-
- // Verify the change
- result, err := Get(doc, "/root/element/@id")
- if err != nil {
- t.Errorf("Get() returned error: %v", err)
- return
- }
- if len(result) != 1 {
- t.Errorf("Expected 1 result, got %d", len(result))
- return
- }
-
- // For attributes, check the inner text
- if text := result[0].InnerText(); text != "456" {
- t.Errorf("Expected attribute value '456', got '%s'", text)
- }
- })
-
- t.Run("indexed element", func(t *testing.T) {
- xmlData := `- first
- second
`
- doc := parseTestXML(t, xmlData)
-
- err := Set(doc, "/root/items/item[1]", "changed")
- if err != nil {
- t.Errorf("Set() returned error: %v", err)
- return
- }
-
- // Verify the change using XPath that specifically targets the first item
- result, err := Get(doc, "/root/items/item[1]")
- if err != nil {
- t.Errorf("Get() returned error: %v", err)
- return
- }
-
- // Check if we have results
- if len(result) == 0 {
- t.Errorf("Expected at least one result for /root/items/item[1]")
- return
- }
-
- // Check text content
- if text := result[0].InnerText(); text != "changed" {
- t.Errorf("Expected text 'changed', got '%s'", text)
- }
- })
-}
-
-func TestSetAll(t *testing.T) {
- t.Run("multiple elements", func(t *testing.T) {
- xmlData := `- first
- second
`
- doc := parseTestXML(t, xmlData)
-
- err := SetAll(doc, "//item", "changed")
- if err != nil {
- t.Errorf("SetAll() returned error: %v", err)
- return
- }
-
- // Verify all items are changed
- result, err := Get(doc, "//item")
- if err != nil {
- t.Errorf("Get() returned error: %v", err)
- return
- }
- if len(result) != 2 {
- t.Errorf("Expected 2 results, got %d", len(result))
- return
- }
-
- // Check each node
- for i, node := range result {
- if text := node.InnerText(); text != "changed" {
- t.Errorf("Item %d: expected text 'changed', got '%s'", i, text)
- }
- }
- })
-
- t.Run("attributes", func(t *testing.T) {
- xmlData := ` `
- doc := parseTestXML(t, xmlData)
-
- err := SetAll(doc, "//item/@id", "new")
- if err != nil {
- t.Errorf("SetAll() returned error: %v", err)
- return
- }
-
- // Verify all attributes are changed
- result, err := Get(doc, "//item/@id")
- if err != nil {
- t.Errorf("Get() returned error: %v", err)
- return
- }
- if len(result) != 2 {
- t.Errorf("Expected 2 results, got %d", len(result))
- return
- }
-
- // For attributes, check inner text
- for i, node := range result {
- if text := node.InnerText(); text != "new" {
- t.Errorf("Attribute %d: expected value 'new', got '%s'", i, text)
- }
- }
- })
-}