• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

davidhoo / jsonpath / 25475134996

07 May 2026 03:53AM UTC coverage: 79.756% (-0.7%) from 80.502%
25475134996

push

github

davidhoo
fix: remove unused functions (hasStartAnchor, hasEndAnchor, nodeListEqualWithLocation)

273 of 371 new or added lines in 4 files covered. (73.58%)

13 existing lines in 1 file now uncovered.

2939 of 3685 relevant lines covered (79.76%)

653.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.12
/types.go
1
package jsonpath
2

3
import (
4
        "fmt"
5
        "regexp"
6
        "strings"
7
)
8

9
// filterCondition represents a filter condition in a filter expression
10
type filterCondition struct {
11
        field    string
12
        operator string
13
        value    interface{}
14
        isRoot   bool // true if field references root ($) instead of current element (@)
15
}
16

17
// exprNode represents a node in the filter expression tree
18
type exprNode interface {
19
        evaluate(item interface{}, root interface{}) (bool, error)
20
}
21

22
// conditionNode wraps a single filter condition
23
type conditionNode struct {
24
        cond filterCondition
25
}
26

27
func (n *conditionNode) evaluate(item interface{}, root interface{}) (bool, error) {
6,544✔
28
        return evaluateSingleCondition(n.cond, item, root)
6,544✔
29
}
6,544✔
30

31
// andNode represents an AND operation
32
type andNode struct {
33
        children []exprNode
34
}
35

36
func (n *andNode) evaluate(item interface{}, root interface{}) (bool, error) {
582✔
37
        for _, child := range n.children {
1,634✔
38
                result, err := child.evaluate(item, root)
1,052✔
39
                if err != nil {
1,052✔
40
                        return false, err
×
41
                }
×
42
                if !result {
1,436✔
43
                        return false, nil
384✔
44
                }
384✔
45
        }
46
        return true, nil
198✔
47
}
48

49
// orNode represents an OR operation
50
type orNode struct {
51
        children []exprNode
52
}
53

54
func (n *orNode) evaluate(item interface{}, root interface{}) (bool, error) {
556✔
55
        for _, child := range n.children {
1,486✔
56
                result, err := child.evaluate(item, root)
930✔
57
                if err != nil {
930✔
58
                        return false, err
×
59
                }
×
60
                if result {
1,330✔
61
                        return true, nil
400✔
62
                }
400✔
63
        }
64
        return false, nil
156✔
65
}
66

67
// evaluateSingleCondition evaluates a single filter condition against an item
68
func evaluateSingleCondition(cond filterCondition, item interface{}, root interface{}) (bool, error) {
6,544✔
69
        // Determine the context for field lookup
6,544✔
70
        var context interface{}
6,544✔
71
        if cond.isRoot {
6,616✔
72
                context = root
72✔
73
        } else {
6,544✔
74
                context = item
6,472✔
75
        }
6,472✔
76

77
        // Handle bare existence test for root ($[?$])
78
        if cond.field == "" && cond.operator == "exists" && cond.isRoot {
6,560✔
79
                return root != nil, nil
16✔
80
        }
16✔
81

82
        var value interface{}
6,528✔
83
        var valueErr error
6,528✔
84

6,528✔
85
        // For root references with complex paths (containing * or [), evaluate as JSONPath
6,528✔
86
        if cond.isRoot && (strings.Contains(cond.field, "*") || strings.Contains(cond.field, "[")) {
6,544✔
87
                pathExpr := "$" + cond.field
16✔
88
                results, err := Query(root, pathExpr)
16✔
89
                if err != nil {
16✔
NEW
90
                        return false, nil
×
NEW
91
                }
×
92
                hasResults := len(results) > 0
16✔
93
                switch cond.operator {
16✔
94
                case "exists":
16✔
95
                        return hasResults, nil
16✔
NEW
96
                case "not_exists":
×
NEW
97
                        return !hasResults, nil
×
NEW
98
                default:
×
NEW
99
                        // For comparison operators, use the first result value
×
NEW
100
                        if !hasResults {
×
NEW
101
                                return false, nil
×
NEW
102
                        }
×
NEW
103
                        value = results[0].Value
×
104
                }
105
        } else {
6,512✔
106
                value, valueErr = getFieldValue(context, cond.field)
6,512✔
107
                if valueErr != nil {
7,736✔
108
                        // RFC 9535: when a field is absent, all comparisons return false
1,224✔
109
                        // (absent is not the same as null)
1,224✔
110
                        switch cond.operator {
1,224✔
111
                        case "exists":
1,054✔
112
                                return false, nil
1,054✔
113
                        case "not_exists":
90✔
114
                                return true, nil
90✔
115
                        default:
80✔
116
                                return false, nil
80✔
117
                        }
118
                }
119
        }
120

121
        // Resolve $ and @ references in the comparison value
122
        resolvedValue := resolveFilterValue(cond.value, item, root)
5,288✔
123

5,288✔
124
        switch cond.operator {
5,288✔
125
        case "exists":
1,016✔
126
                // If we got here, the field was found
1,016✔
127
                return true, nil
1,016✔
128
        case "not_exists":
100✔
129
                // If we got here, the field was found (so it exists)
100✔
130
                return false, nil
100✔
131
        case "match":
516✔
132
                // RFC 9535 match() function: match(string, pattern)
516✔
133
                // Uses I-Regexp for full-string matching
516✔
134
                str, ok := value.(string)
516✔
135
                if !ok {
660✔
136
                        return false, nil
144✔
137
                }
144✔
138
                pattern, ok := resolvedValue.(string)
372✔
139
                if !ok {
420✔
140
                        return false, nil
48✔
141
                }
48✔
142
                // Convert I-Regexp to Go regexp
143
                goPattern, err := IRegexpToGoRegexp(pattern)
324✔
144
                if err != nil {
326✔
145
                        return false, nil // Invalid pattern returns false
2✔
146
                }
2✔
147
                // Add anchors for full-string matching
148
                goPattern = "^(" + goPattern + ")$"
322✔
149
                re, err := regexp.Compile(goPattern)
322✔
150
                if err != nil {
322✔
151
                        return false, nil
×
152
                }
×
153
                return re.MatchString(str), nil
322✔
154
        case "search":
656✔
155
                // RFC 9535 search() function: search(string, pattern)
656✔
156
                // Returns true if string contains a match for the pattern
656✔
157
                str, ok := value.(string)
656✔
158
                if !ok {
824✔
159
                        return false, nil
168✔
160
                }
168✔
161
                pattern, ok := resolvedValue.(string)
488✔
162
                if !ok {
536✔
163
                        return false, nil
48✔
164
                }
48✔
165
                // Convert I-Regexp to Go regexp
166
                goPattern, err := IRegexpToGoRegexp(pattern)
440✔
167
                if err != nil {
440✔
168
                        return false, fmt.Errorf("invalid regex pattern: %s", pattern)
×
169
                }
×
170
                re, err := regexp.Compile(goPattern)
440✔
171
                if err != nil {
440✔
172
                        return false, fmt.Errorf("invalid regex pattern: %s", pattern)
×
173
                }
×
174
                return re.MatchString(str), nil
440✔
175
        default:
3,000✔
176
                result, err := compareValues(value, cond.operator, resolvedValue)
3,000✔
177
                if err != nil {
3,146✔
178
                        return false, fmt.Errorf("invalid operator: %s", cond.operator)
146✔
179
                }
146✔
180
                return result, nil
2,854✔
181
        }
182
}
183

184
// resolveFilterValue resolves $ and @ references in filter values
185
func resolveFilterValue(value interface{}, item interface{}, root interface{}) interface{} {
5,288✔
186
        str, ok := value.(string)
5,288✔
187
        if !ok {
7,552✔
188
                return value
2,264✔
189
        }
2,264✔
190

191
        switch str {
3,024✔
192
        case "$":
40✔
193
                // Root reference - return root value
40✔
194
                return root
40✔
195
        case "@":
40✔
196
                // Current element reference - return current item
40✔
197
                return item
40✔
198
        default:
2,944✔
199
                // Check for $.path or @.path references
2,944✔
200
                if strings.HasPrefix(str, "$.") || strings.HasPrefix(str, "$[") {
3,088✔
201
                        // Resolve path from root using JSONPath engine for complex paths
144✔
202
                        if strings.Contains(str, "[") {
144✔
NEW
203
                                // Complex path with brackets - use JSONPath engine
×
NEW
204
                                results, err := Query(root, str)
×
NEW
205
                                if err != nil || len(results) == 0 {
×
NEW
206
                                        return nil
×
NEW
207
                                }
×
NEW
208
                                if len(results) == 1 {
×
NEW
209
                                        return results[0].Value
×
NEW
210
                                }
×
211
                                // Return array of values for multiple results
NEW
212
                                values := make([]interface{}, len(results))
×
NEW
213
                                for i, r := range results {
×
NEW
214
                                        values[i] = r.Value
×
NEW
215
                                }
×
NEW
216
                                return values
×
217
                        }
218
                        // Simple path - use getFieldValue
219
                        path := strings.TrimPrefix(str, "$")
144✔
220
                        if path == "" {
144✔
NEW
221
                                return root
×
NEW
222
                        }
×
223
                        resolved, err := getFieldValue(root, path)
144✔
224
                        if err != nil {
288✔
225
                                return nil
144✔
226
                        }
144✔
NEW
227
                        return resolved
×
228
                }
229
                if strings.HasPrefix(str, "@.") || strings.HasPrefix(str, "@[") {
3,776✔
230
                        // Resolve path from current element using JSONPath engine for complex paths
976✔
231
                        if strings.Contains(str, "[") {
976✔
NEW
232
                                // Complex path with brackets - use JSONPath engine
×
NEW
233
                                // Wrap item in an array to make it accessible as $[0]
×
NEW
234
                                tempRoot := []interface{}{item}
×
NEW
235
                                adjustedPath := "$[0]" + strings.TrimPrefix(str, "@")
×
NEW
236
                                results, err := Query(tempRoot, adjustedPath)
×
NEW
237
                                if err != nil || len(results) == 0 {
×
NEW
238
                                        return nil
×
NEW
239
                                }
×
NEW
240
                                if len(results) == 1 {
×
NEW
241
                                        return results[0].Value
×
NEW
242
                                }
×
243
                                // Return array of values for multiple results
NEW
244
                                values := make([]interface{}, len(results))
×
NEW
245
                                for i, r := range results {
×
NEW
246
                                        values[i] = r.Value
×
NEW
247
                                }
×
NEW
248
                                return values
×
249
                        }
250
                        // Simple path - use getFieldValue
251
                        path := strings.TrimPrefix(str, "@")
976✔
252
                        if path == "" {
976✔
NEW
253
                                return item
×
NEW
254
                        }
×
255
                        resolved, err := getFieldValue(item, path)
976✔
256
                        if err != nil {
984✔
257
                                return nil
8✔
258
                        }
8✔
259
                        return resolved
968✔
260
                }
261
                return value
1,824✔
262
        }
263
}
264

265
// String returns the string representation of a filter condition
266
func (c filterCondition) String() string {
16✔
267
        prefix := "@"
16✔
268
        if c.isRoot {
16✔
NEW
269
                prefix = "$"
×
NEW
270
        }
×
271
        field := strings.TrimPrefix(c.field, "@.")
16✔
272
        field = strings.TrimPrefix(field, "$.")
16✔
273
        switch c.operator {
16✔
274
        case "exists":
×
NEW
275
                if field == "" {
×
NEW
276
                        return prefix
×
NEW
277
                }
×
NEW
278
                return fmt.Sprintf("%s.%s", prefix, field)
×
279
        case "not_exists":
×
NEW
280
                return fmt.Sprintf("!%s.%s", prefix, field)
×
281
        case "match":
×
NEW
282
                return fmt.Sprintf("match(%s.%s, '%v')", prefix, field, c.value)
×
283
        case "search":
×
NEW
284
                return fmt.Sprintf("search(%s.%s, '%v')", prefix, field, c.value)
×
285
        default:
16✔
286
                value := c.value
16✔
287
                if str, ok := value.(string); ok {
20✔
288
                        value = "'" + str + "'"
4✔
289
                }
4✔
290
                return fmt.Sprintf("%s.%s %s %v", prefix, field, c.operator, value)
16✔
291
        }
292
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc