Skip to content

Commit 037ad50

Browse files
Merge pull request #8 from evilmonkeyinc/fix/symbols-in-expressions
fix: using syntax tokens in expressions
2 parents 987bcce + c738eda commit 037ad50

File tree

5 files changed

+133
-9
lines changed

5 files changed

+133
-9
lines changed

test/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ This implementation would be closer to the 'Scalar consensus' as it does not alw
207207
|`$[?(@.d=={"k":"v"})]`|`[ { "d": { "k": "v" } }, { "d": { "a": "b" } }, { "d": "k" }, { "d": "v" }, { "d": {} }, { "d": [] }, { "d": null }, { "d": -1 }, { "d": 0 }, { "d": 1 }, { "d": "[object Object]" }, { "d": "{\"k\": \"v\"}" }, { "d": "{\"k\":\"v\"}" }, "v" ]`|none|`[]`|:question:|
208208
|`$[?(@.key=="value")]`|`[ {"key": "some"}, {"key": "value"}, {"key": null}, {"key": 0}, {"key": 1}, {"key": -1}, {"key": ""}, {"key": {}}, {"key": []}, {"key": "valuemore"}, {"key": "morevalue"}, {"key": ["value"]}, {"key": {"some": "value"}}, {"key": {"key": "value"}}, {"some": "value"} ]`|`[{"key":"value"}]`|`[{"key":"value"}]`|:white_check_mark:|
209209
|`$[?(@.key=="Motörhead")]`|`[ {"key": "something"}, {"key": "Mot\u00f6rhead"}, {"key": "mot\u00f6rhead"}, {"key": "Motorhead"}, {"key": "Motoo\u0308rhead"}, {"key": "motoo\u0308rhead"} ]`|`[{"key":"Motörhead"}]`|`[{"key":"Motörhead"}]`|:white_check_mark:|
210-
|`$[?(@.key=="hi@example.com")]`|`[ {"key": "some"}, {"key": "value"}, {"key": "hi@example.com"} ]`|`[{"key":"hi@example.com"}]`|`[]`|:no_entry:|
210+
|`$[?(@.key=="hi@example.com")]`|`[ {"key": "some"}, {"key": "value"}, {"key": "hi@example.com"} ]`|`[{"key":"hi@example.com"}]`|`[{"key":"hi@example.com"}]`|:white_check_mark:|
211211
|`$[?(@.key=="some.value")]`|`[ {"key": "some"}, {"key": "value"}, {"key": "some.value"} ]`|`[{"key":"some.value"}]`|`[{"key":"some.value"}]`|:white_check_mark:|
212212
|`$[?(@.key=='value')]`|`[ {"key": "some"}, {"key": "value"} ]`|`[{"key":"value"}]`|`[{"key":"value"}]`|:white_check_mark:|
213213
|`$[?(@.key=="Mot\u00f6rhead")]`|`[ {"key": "something"}, {"key": "Mot\u00f6rhead"}, {"key": "mot\u00f6rhead"}, {"key": "Motorhead"}, {"key": "Motoo\u0308rhead"}, {"key": "motoo\u0308rhead"} ]`|none|`[{"key":"Motörhead"}]`|:question:|

test/filter_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,9 +326,9 @@ var filterTests []testData = []testData{
326326
expectedError: "",
327327
},
328328
{
329-
query: `$[?(@.key=="hi@example.com")]`, // TODO: double quotes in filters
329+
query: `$[?(@.key=="hi@example.com")]`,
330330
data: `[ {"key": "some"}, {"key": "value"}, {"key": "hi@example.com"} ]`,
331-
expected: []interface{}{},
331+
expected: []interface{}{map[string]interface{}{"key": "hi@example.com"}},
332332
consensus: []interface{}{map[string]interface{}{"key": "hi@example.com"}},
333333
expectedError: "",
334334
},

token/expression.go

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,71 @@ func (token *expressionToken) Apply(root, current interface{}, next []Token) (in
4444
return value, nil
4545
}
4646

47+
func getRootTokenIndex(expression string) int {
48+
idx := strings.Index(expression, "$")
49+
if idx < 0 {
50+
return -1
51+
}
52+
if idx+1 >= len(expression) {
53+
// there is no next character
54+
return idx
55+
}
56+
57+
// TODO: this is not accurate enough, we need to properly parse expressions
58+
nextRune := expression[idx+1]
59+
switch nextRune {
60+
case '.', '[', ' ', '-', '+', '/', '%', '>', '<', '=', '!':
61+
// valid root token
62+
return idx
63+
default:
64+
// not a root token
65+
// need to check if there are other $ tokens
66+
subCheck := getRootTokenIndex(expression[idx+1:])
67+
if subCheck > 0 {
68+
return idx + 1 + subCheck
69+
}
70+
return -1
71+
}
72+
}
73+
74+
func getCurrentTokenIndex(expression string) int {
75+
idx := strings.Index(expression, "@")
76+
if idx < 0 {
77+
return -1
78+
}
79+
if idx+1 >= len(expression) {
80+
// there is no next character
81+
return idx
82+
}
83+
84+
// TODO: this is not accurate enough, we need to properly parse expressions
85+
nextRune := expression[idx+1]
86+
switch nextRune {
87+
case '.', '[', ' ', '-', '+', '/', '%', '>', '<', '=', '!':
88+
// valid current token
89+
return idx
90+
default:
91+
// not a current token
92+
// need to check if there are other @ tokens
93+
subCheck := getCurrentTokenIndex(expression[idx+1:])
94+
if subCheck > 0 {
95+
return idx + 1 + subCheck
96+
}
97+
return -1
98+
}
99+
}
100+
47101
// TODO : add extra support
48102
/*
49103
1. regex
50104
*/
51-
52105
func evaluateExpression(root, current interface{}, expression string, options *Options) (interface{}, error) {
53106
if expression == "" {
54107
return nil, getInvalidExpressionEmptyError()
55108
}
56109

57-
rootIndex := strings.Index(expression, "$")
58-
currentIndex := strings.Index(expression, "@")
110+
rootIndex := getRootTokenIndex(expression)
111+
currentIndex := getCurrentTokenIndex(expression)
59112

60113
for rootIndex > -1 || currentIndex > -1 {
61114

@@ -103,8 +156,8 @@ func evaluateExpression(root, current interface{}, expression string, options *O
103156
expression = strings.ReplaceAll(expression, query, new)
104157
}
105158

106-
rootIndex = strings.Index(expression, "$")
107-
currentIndex = strings.Index(expression, "@")
159+
rootIndex = getRootTokenIndex(expression)
160+
currentIndex = getCurrentTokenIndex(expression)
108161
}
109162

110163
expression = strings.TrimSpace(expression)

token/expression_test.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,10 @@ func Test_evaluateExpression(t *testing.T) {
139139
{
140140
input: input{
141141
expression: "@]",
142+
current: true,
142143
},
143144
expected: expected{
144-
err: "invalid expression. unexpected token ']' at index 1",
145+
err: "invalid expression. eval:1:1: illegal character U+0040 '@'",
145146
},
146147
},
147148
{
@@ -520,6 +521,59 @@ func Test_evaluateExpression(t *testing.T) {
520521
value: "'key\\'s'",
521522
},
522523
},
524+
{
525+
input: input{
526+
expression: `@.key=="hi@example.com"`,
527+
current: map[string]interface{}{
528+
"key": "hi@example.com",
529+
},
530+
},
531+
expected: expected{
532+
value: true,
533+
},
534+
},
535+
{
536+
input: input{
537+
expression: `"hi@example.com"==@.key`,
538+
current: map[string]interface{}{
539+
"key": "hi@example.com",
540+
},
541+
},
542+
expected: expected{
543+
value: true,
544+
},
545+
},
546+
{
547+
input: input{
548+
expression: `$.key=="hi@example.com"`,
549+
root: map[string]interface{}{
550+
"key": "hi@example.com",
551+
},
552+
},
553+
expected: expected{
554+
value: true,
555+
},
556+
},
557+
{
558+
input: input{
559+
expression: `"hi@example.com"==$.key`,
560+
root: map[string]interface{}{
561+
"key": "hi@example.com",
562+
},
563+
},
564+
expected: expected{
565+
value: true,
566+
},
567+
},
568+
{
569+
input: input{
570+
expression: `"hi@example.com"==$`,
571+
root: "hi@example.com",
572+
},
573+
expected: expected{
574+
value: true,
575+
},
576+
},
523577
}
524578

525579
for idx, test := range tests {

token/filter_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,23 @@ func Test_FilterToken_Apply(t *testing.T) {
283283
},
284284
},
285285
},
286+
{
287+
token: &filterToken{
288+
expression: `@.key=="hi@example.com"`,
289+
},
290+
input: input{
291+
current: []interface{}{
292+
map[string]interface{}{"key": "some"},
293+
map[string]interface{}{"key": "value"},
294+
map[string]interface{}{"key": "hi@example.com"},
295+
},
296+
},
297+
expected: expected{
298+
value: []interface{}{
299+
map[string]interface{}{"key": "hi@example.com"},
300+
},
301+
},
302+
},
286303
}
287304

288305
batchTokenTests(t, tests)

0 commit comments

Comments
 (0)