diff --git a/benchmark/README.md b/benchmark/README.md index 905d710..f778bbd 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -11,12 +11,12 @@ Test of accuracy are based off of the expected response based on the original sp ## Command ```bash -go test -bench=. -cpu=1 -benchmem -count=1 -benchtime=1000x +go test -bench=. -cpu=1 -benchmem -count=1 -benchtime=10000x ``` ## Libraries -- `github.com/evilmonkeyinc/jsonpath v0.7.0` +- `github.com/evilmonkeyinc/jsonpath v0.7.2` - `github.com/PaesslerAG/jsonpath v0.1.1` *uses reflection - `github.com/bhmj/jsonslice v1.1.2` *custom parser - `github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852` *uses reflection @@ -35,137 +35,117 @@ goos: darwin goarch: amd64 pkg: github.com/evilmonkeyinc/jsonpath/benchmark cpu: Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz -Benchmark_Comparison/$.store.book[*].author/evilmonkeyinc 10000 21165 ns/op 6496 B/op 188 allocs/op -Benchmark_Comparison/$.store.book[*].author/paesslerAG 10000 16595 ns/op 6944 B/op 153 allocs/op -Benchmark_Comparison/$.store.book[*].author/bhmj 10000 4880 ns/op 1185 B/op 14 allocs/op -Benchmark_Comparison/$.store.book[*].author/oliveagle 10000 17070 ns/op 4784 B/op 147 allocs/op -Benchmark_Comparison/$.store.book[*].author/spyzhov 10000 16361 ns/op 7032 B/op 136 allocs/op -Benchmark_Comparison/$..author/evilmonkeyinc 10000 56820 ns/op 16688 B/op 458 allocs/op -Benchmark_Comparison/$..author/paesslerAG 10000 113776 ns/op 20624 B/op 630 allocs/op -Benchmark_Comparison/$..author/bhmj 10000 33157 ns/op 1553 B/op 27 allocs/op -Benchmark_Comparison/$..author/oliveagle 10000 37042 ns/op 4464 B/op 118 allocs/op +Benchmark_Comparison/$.store.book[*].author/evilmonkeyinc 10000 18869 ns/op 6384 B/op 185 allocs/op +Benchmark_Comparison/$.store.book[*].author/paesslerAG 10000 19311 ns/op 6944 B/op 153 allocs/op +Benchmark_Comparison/$.store.book[*].author/bhmj 10000 7086 ns/op 1185 B/op 14 allocs/op +Benchmark_Comparison/$.store.book[*].author/oliveagle 10000 14569 ns/op 4784 B/op 147 allocs/op +Benchmark_Comparison/$.store.book[*].author/spyzhov 10000 14292 ns/op 7032 B/op 136 allocs/op +Benchmark_Comparison/$..author/evilmonkeyinc 10000 52371 ns/op 12768 B/op 391 allocs/op +Benchmark_Comparison/$..author/paesslerAG 10000 49396 ns/op 20624 B/op 630 allocs/op +Benchmark_Comparison/$..author/bhmj 10000 10053 ns/op 1553 B/op 27 allocs/op +Benchmark_Comparison/$..author/oliveagle 10000 12377 ns/op 4464 B/op 118 allocs/op --- BENCH: Benchmark_Comparison/$..author/oliveagle benchmark_test.go:137: unsupported - benchmark_test.go:137: unsupported -Benchmark_Comparison/$..author/spyzhov 10000 51169 ns/op 8336 B/op 168 allocs/op -Benchmark_Comparison/$.store.*/evilmonkeyinc 10000 43641 ns/op 4929 B/op 130 allocs/op -Benchmark_Comparison/$.store.*/paesslerAG 10000 31748 ns/op 6280 B/op 126 allocs/op -Benchmark_Comparison/$.store.*/bhmj 10000 5370 ns/op 3705 B/op 9 allocs/op -Benchmark_Comparison/$.store.*/oliveagle 10000 20300 ns/op 4480 B/op 118 allocs/op +Benchmark_Comparison/$..author/spyzhov 10000 18027 ns/op 8336 B/op 168 allocs/op +Benchmark_Comparison/$.store.*/evilmonkeyinc 10000 13030 ns/op 4929 B/op 130 allocs/op +Benchmark_Comparison/$.store.*/paesslerAG 10000 13948 ns/op 6280 B/op 126 allocs/op +Benchmark_Comparison/$.store.*/bhmj 10000 5109 ns/op 3705 B/op 9 allocs/op +Benchmark_Comparison/$.store.*/oliveagle 10000 12099 ns/op 4480 B/op 118 allocs/op --- BENCH: Benchmark_Comparison/$.store.*/oliveagle benchmark_test.go:137: unsupported - benchmark_test.go:137: unsupported -Benchmark_Comparison/$.store.*/spyzhov 10000 12892 ns/op 6785 B/op 124 allocs/op -Benchmark_Comparison/$.store..price/evilmonkeyinc 10000 50521 ns/op 15672 B/op 443 allocs/op -Benchmark_Comparison/$.store..price/paesslerAG 10000 60435 ns/op 17400 B/op 515 allocs/op -Benchmark_Comparison/$.store..price/bhmj 10000 12666 ns/op 1192 B/op 28 allocs/op -Benchmark_Comparison/$.store..price/oliveagle 10000 13522 ns/op 4576 B/op 128 allocs/op +Benchmark_Comparison/$.store.*/spyzhov 10000 12331 ns/op 6785 B/op 124 allocs/op +Benchmark_Comparison/$.store..price/evilmonkeyinc 10000 45637 ns/op 12200 B/op 382 allocs/op +Benchmark_Comparison/$.store..price/paesslerAG 10000 42854 ns/op 17400 B/op 515 allocs/op +Benchmark_Comparison/$.store..price/bhmj 10000 8167 ns/op 1192 B/op 28 allocs/op +Benchmark_Comparison/$.store..price/oliveagle 10000 12710 ns/op 4576 B/op 128 allocs/op --- BENCH: Benchmark_Comparison/$.store..price/oliveagle benchmark_test.go:137: unsupported - benchmark_test.go:137: unsupported -Benchmark_Comparison/$.store..price/spyzhov 10000 18554 ns/op 8256 B/op 168 allocs/op -Benchmark_Comparison/$..book[2]/evilmonkeyinc 10000 54938 ns/op 16960 B/op 471 allocs/op -Benchmark_Comparison/$..book[2]/paesslerAG 10000 50810 ns/op 20816 B/op 643 allocs/op -Benchmark_Comparison/$..book[2]/bhmj 10000 10774 ns/op 1257 B/op 16 allocs/op -Benchmark_Comparison/$..book[2]/oliveagle 10000 12317 ns/op 4560 B/op 124 allocs/op +Benchmark_Comparison/$.store..price/spyzhov 10000 17584 ns/op 8256 B/op 168 allocs/op +Benchmark_Comparison/$..book[2]/evilmonkeyinc 10000 49198 ns/op 12864 B/op 399 allocs/op +Benchmark_Comparison/$..book[2]/paesslerAG 10000 62442 ns/op 20816 B/op 643 allocs/op +Benchmark_Comparison/$..book[2]/bhmj 10000 15866 ns/op 1257 B/op 16 allocs/op +Benchmark_Comparison/$..book[2]/oliveagle 10000 22550 ns/op 4560 B/op 124 allocs/op --- BENCH: Benchmark_Comparison/$..book[2]/oliveagle benchmark_test.go:137: unsupported - benchmark_test.go:137: unsupported -Benchmark_Comparison/$..book[2]/spyzhov 10000 18695 ns/op 8296 B/op 166 allocs/op -Benchmark_Comparison/$..book[(@.length-1)]/evilmonkeyinc 10000 129960 ns/op 18000 B/op 542 allocs/op -Benchmark_Comparison/$..book[(@.length-1)]/paesslerAG 10000 18974 ns/op 6576 B/op 140 allocs/op +Benchmark_Comparison/$..book[2]/spyzhov 10000 23252 ns/op 8296 B/op 166 allocs/op +Benchmark_Comparison/$..book[(@.length-1)]/evilmonkeyinc 10000 148979 ns/op 13904 B/op 470 allocs/op +Benchmark_Comparison/$..book[(@.length-1)]/paesslerAG 10000 37214 ns/op 6576 B/op 140 allocs/op --- BENCH: Benchmark_Comparison/$..book[(@.length-1)]/paesslerAG benchmark_test.go:84: unsupported - benchmark_test.go:84: unsupported -Benchmark_Comparison/$..book[(@.length-1)]/bhmj 10000 622.8 ns/op 648 B/op 4 allocs/op +Benchmark_Comparison/$..book[(@.length-1)]/bhmj 10000 872.6 ns/op 648 B/op 4 allocs/op --- BENCH: Benchmark_Comparison/$..book[(@.length-1)]/bhmj benchmark_test.go:102: unsupported - benchmark_test.go:102: unsupported -Benchmark_Comparison/$..book[(@.length-1)]/oliveagle 10000 14319 ns/op 4736 B/op 143 allocs/op +Benchmark_Comparison/$..book[(@.length-1)]/oliveagle 10000 52143 ns/op 4736 B/op 143 allocs/op --- BENCH: Benchmark_Comparison/$..book[(@.length-1)]/oliveagle benchmark_test.go:137: unsupported - benchmark_test.go:137: unsupported -Benchmark_Comparison/$..book[(@.length-1)]/spyzhov 10000 24926 ns/op 9248 B/op 203 allocs/op -Benchmark_Comparison/$..book[-1:]/evilmonkeyinc 10000 127852 ns/op 17200 B/op 486 allocs/op -Benchmark_Comparison/$..book[-1:]/paesslerAG 10000 134288 ns/op 21184 B/op 654 allocs/op -Benchmark_Comparison/$..book[-1:]/bhmj 10000 29420 ns/op 1706 B/op 22 allocs/op +Benchmark_Comparison/$..book[(@.length-1)]/spyzhov 10000 56952 ns/op 9248 B/op 203 allocs/op +Benchmark_Comparison/$..book[-1:]/evilmonkeyinc 10000 83926 ns/op 13104 B/op 414 allocs/op +Benchmark_Comparison/$..book[-1:]/paesslerAG 10000 71105 ns/op 21184 B/op 654 allocs/op +Benchmark_Comparison/$..book[-1:]/bhmj 10000 14475 ns/op 1706 B/op 22 allocs/op --- BENCH: Benchmark_Comparison/$..book[-1:]/bhmj benchmark_test.go:109: found single nested array - benchmark_test.go:109: found single nested array -Benchmark_Comparison/$..book[-1:]/oliveagle 10000 20008 ns/op 4664 B/op 129 allocs/op +Benchmark_Comparison/$..book[-1:]/oliveagle 10000 19190 ns/op 4664 B/op 129 allocs/op --- BENCH: Benchmark_Comparison/$..book[-1:]/oliveagle benchmark_test.go:137: unsupported - benchmark_test.go:137: unsupported -Benchmark_Comparison/$..book[-1:]/spyzhov 10000 63175 ns/op 8376 B/op 170 allocs/op -Benchmark_Comparison/$..book[0,1]/evilmonkeyinc 10000 116278 ns/op 17297 B/op 489 allocs/op -Benchmark_Comparison/$..book[0,1]/paesslerAG 10000 80451 ns/op 21312 B/op 656 allocs/op -Benchmark_Comparison/$..book[0,1]/bhmj 10000 11671 ns/op 2282 B/op 23 allocs/op +Benchmark_Comparison/$..book[-1:]/spyzhov 10000 24713 ns/op 8376 B/op 170 allocs/op +Benchmark_Comparison/$..book[0,1]/evilmonkeyinc 10000 72378 ns/op 13217 B/op 417 allocs/op +Benchmark_Comparison/$..book[0,1]/paesslerAG 10000 58346 ns/op 21312 B/op 656 allocs/op +Benchmark_Comparison/$..book[0,1]/bhmj 10000 12148 ns/op 2282 B/op 23 allocs/op --- BENCH: Benchmark_Comparison/$..book[0,1]/bhmj benchmark_test.go:109: found single nested array - benchmark_test.go:109: found single nested array -Benchmark_Comparison/$..book[0,1]/oliveagle 10000 15306 ns/op 4648 B/op 130 allocs/op +Benchmark_Comparison/$..book[0,1]/oliveagle 10000 14399 ns/op 4648 B/op 130 allocs/op --- BENCH: Benchmark_Comparison/$..book[0,1]/oliveagle benchmark_test.go:137: unsupported - benchmark_test.go:137: unsupported -Benchmark_Comparison/$..book[0,1]/spyzhov 10000 22789 ns/op 8456 B/op 172 allocs/op -Benchmark_Comparison/$..book[:2]/evilmonkeyinc 10000 92287 ns/op 17281 B/op 483 allocs/op -Benchmark_Comparison/$..book[:2]/paesslerAG 10000 89657 ns/op 21248 B/op 656 allocs/op -Benchmark_Comparison/$..book[:2]/bhmj 10000 26255 ns/op 2346 B/op 24 allocs/op +Benchmark_Comparison/$..book[0,1]/spyzhov 10000 45598 ns/op 8456 B/op 172 allocs/op +Benchmark_Comparison/$..book[:2]/evilmonkeyinc 10000 97570 ns/op 13201 B/op 411 allocs/op +Benchmark_Comparison/$..book[:2]/paesslerAG 10000 90545 ns/op 21248 B/op 656 allocs/op +Benchmark_Comparison/$..book[:2]/bhmj 10000 17076 ns/op 2346 B/op 24 allocs/op --- BENCH: Benchmark_Comparison/$..book[:2]/bhmj benchmark_test.go:109: found single nested array - benchmark_test.go:109: found single nested array -Benchmark_Comparison/$..book[:2]/oliveagle 10000 14178 ns/op 4648 B/op 126 allocs/op +Benchmark_Comparison/$..book[:2]/oliveagle 10000 21330 ns/op 4648 B/op 126 allocs/op --- BENCH: Benchmark_Comparison/$..book[:2]/oliveagle benchmark_test.go:137: unsupported - benchmark_test.go:137: unsupported -Benchmark_Comparison/$..book[:2]/spyzhov 10000 37200 ns/op 8392 B/op 171 allocs/op -Benchmark_Comparison/$..book[?(@.isbn)]/evilmonkeyinc 10000 69058 ns/op 20265 B/op 556 allocs/op -Benchmark_Comparison/$..book[?(@.isbn)]/paesslerAG 10000 55980 ns/op 21896 B/op 679 allocs/op -Benchmark_Comparison/$..book[?(@.isbn)]/bhmj 10000 15960 ns/op 2728 B/op 30 allocs/op +Benchmark_Comparison/$..book[:2]/spyzhov 10000 36285 ns/op 8392 B/op 171 allocs/op +Benchmark_Comparison/$..book[?(@.isbn)]/evilmonkeyinc 10000 129392 ns/op 16185 B/op 484 allocs/op +Benchmark_Comparison/$..book[?(@.isbn)]/paesslerAG 10000 79179 ns/op 21896 B/op 679 allocs/op +Benchmark_Comparison/$..book[?(@.isbn)]/bhmj 10000 26555 ns/op 2728 B/op 30 allocs/op --- BENCH: Benchmark_Comparison/$..book[?(@.isbn)]/bhmj benchmark_test.go:109: found single nested array - benchmark_test.go:109: found single nested array -Benchmark_Comparison/$..book[?(@.isbn)]/oliveagle 10000 12832 ns/op 4680 B/op 138 allocs/op +Benchmark_Comparison/$..book[?(@.isbn)]/oliveagle 10000 35661 ns/op 4680 B/op 138 allocs/op --- BENCH: Benchmark_Comparison/$..book[?(@.isbn)]/oliveagle benchmark_test.go:137: unsupported - benchmark_test.go:137: unsupported -Benchmark_Comparison/$..book[?(@.isbn)]/spyzhov 10000 22150 ns/op 9272 B/op 224 allocs/op -Benchmark_Comparison/$..book[?(@.price<10)]/evilmonkeyinc 10000 65729 ns/op 20153 B/op 564 allocs/op -Benchmark_Comparison/$..book[?(@.price<10)]/paesslerAG 10000 15610 ns/op 6576 B/op 140 allocs/op +Benchmark_Comparison/$..book[?(@.isbn)]/spyzhov 10000 28012 ns/op 9272 B/op 224 allocs/op +Benchmark_Comparison/$..book[?(@.price<10)]/evilmonkeyinc 10000 71603 ns/op 16073 B/op 492 allocs/op +Benchmark_Comparison/$..book[?(@.price<10)]/paesslerAG 10000 37617 ns/op 6576 B/op 140 allocs/op --- BENCH: Benchmark_Comparison/$..book[?(@.price<10)]/paesslerAG benchmark_test.go:84: unsupported - benchmark_test.go:84: unsupported -Benchmark_Comparison/$..book[?(@.price<10)]/bhmj 10000 17494 ns/op 2896 B/op 43 allocs/op +Benchmark_Comparison/$..book[?(@.price<10)]/bhmj 10000 22856 ns/op 2896 B/op 43 allocs/op --- BENCH: Benchmark_Comparison/$..book[?(@.price<10)]/bhmj benchmark_test.go:109: found single nested array benchmark_test.go:109: found single nested array -Benchmark_Comparison/$..book[?(@.price<10)]/oliveagle 10000 13628 ns/op 4792 B/op 146 allocs/op +Benchmark_Comparison/$..book[?(@.price<10)]/oliveagle 10000 22944 ns/op 4792 B/op 146 allocs/op --- BENCH: Benchmark_Comparison/$..book[?(@.price<10)]/oliveagle benchmark_test.go:137: unsupported - benchmark_test.go:137: unsupported -Benchmark_Comparison/$..book[?(@.price<10)]/spyzhov 10000 25878 ns/op 10568 B/op 270 allocs/op -Benchmark_Comparison/$..book[?(@.price<$.expensive)]/evilmonkeyinc 10000 73693 ns/op 21449 B/op 628 allocs/op -Benchmark_Comparison/$..book[?(@.price<$.expensive)]/paesslerAG 10000 15426 ns/op 6616 B/op 140 allocs/op +Benchmark_Comparison/$..book[?(@.price<10)]/spyzhov 10000 35844 ns/op 10568 B/op 270 allocs/op +Benchmark_Comparison/$..book[?(@.price<$.expensive)]/evilmonkeyinc 10000 123683 ns/op 17369 B/op 556 allocs/op +Benchmark_Comparison/$..book[?(@.price<$.expensive)]/paesslerAG 10000 16665 ns/op 6616 B/op 140 allocs/op --- BENCH: Benchmark_Comparison/$..book[?(@.price<$.expensive)]/paesslerAG benchmark_test.go:84: unsupported - benchmark_test.go:84: unsupported -Benchmark_Comparison/$..book[?(@.price<$.expensive)]/bhmj 10000 20282 ns/op 2992 B/op 46 allocs/op +Benchmark_Comparison/$..book[?(@.price<$.expensive)]/bhmj 10000 20180 ns/op 2992 B/op 46 allocs/op --- BENCH: Benchmark_Comparison/$..book[?(@.price<$.expensive)]/bhmj benchmark_test.go:109: found single nested array - benchmark_test.go:109: found single nested array -Benchmark_Comparison/$..book[?(@.price<$.expensive)]/oliveagle 10000 14472 ns/op 5080 B/op 164 allocs/op +Benchmark_Comparison/$..book[?(@.price<$.expensive)]/oliveagle 10000 17667 ns/op 5080 B/op 164 allocs/op --- BENCH: Benchmark_Comparison/$..book[?(@.price<$.expensive)]/oliveagle benchmark_test.go:137: unsupported - benchmark_test.go:137: unsupported -Benchmark_Comparison/$..book[?(@.price<$.expensive)]/spyzhov 10000 32897 ns/op 10496 B/op 292 allocs/op -Benchmark_Comparison/$..*/evilmonkeyinc 10000 72445 ns/op 20199 B/op 546 allocs/op -Benchmark_Comparison/$..*/paesslerAG 10000 46927 ns/op 20440 B/op 647 allocs/op -Benchmark_Comparison/$..*/bhmj 10000 25616 ns/op 31209 B/op 69 allocs/op -Benchmark_Comparison/$..*/oliveagle 10000 12465 ns/op 4304 B/op 107 allocs/op +Benchmark_Comparison/$..book[?(@.price<$.expensive)]/spyzhov 10000 29535 ns/op 10496 B/op 292 allocs/op +Benchmark_Comparison/$..*/evilmonkeyinc 10000 66367 ns/op 17863 B/op 496 allocs/op +Benchmark_Comparison/$..*/paesslerAG 10000 47862 ns/op 20440 B/op 647 allocs/op +Benchmark_Comparison/$..*/bhmj 10000 25386 ns/op 31209 B/op 69 allocs/op +Benchmark_Comparison/$..*/oliveagle 10000 12155 ns/op 4304 B/op 107 allocs/op --- BENCH: Benchmark_Comparison/$..*/oliveagle benchmark_test.go:137: unsupported - benchmark_test.go:137: unsupported -Benchmark_Comparison/$..*/spyzhov 10000 22968 ns/op 11519 B/op 220 allocs/op +Benchmark_Comparison/$..*/spyzhov 10000 24140 ns/op 11518 B/op 220 allocs/op PASS -ok github.com/evilmonkeyinc/jsonpath/benchmark 25.365s +ok github.com/evilmonkeyinc/jsonpath/benchmark 24.627s + ``` diff --git a/benchmark/go.mod b/benchmark/go.mod index dd523bc..230ad6e 100644 --- a/benchmark/go.mod +++ b/benchmark/go.mod @@ -5,16 +5,12 @@ go 1.17 require ( github.com/PaesslerAG/jsonpath v0.1.1 github.com/bhmj/jsonslice v1.1.2 - github.com/evilmonkeyinc/jsonpath v0.7.0 + github.com/evilmonkeyinc/jsonpath v0.7.2 github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 github.com/spyzhov/ajson v0.7.0 - github.com/stretchr/testify v1.7.0 ) require ( github.com/PaesslerAG/gval v1.0.0 // indirect github.com/bhmj/xpression v0.9.1 // indirect - github.com/davecgh/go-spew v1.1.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/benchmark/go.sum b/benchmark/go.sum index 7ab49e9..6549fd0 100644 --- a/benchmark/go.sum +++ b/benchmark/go.sum @@ -9,8 +9,8 @@ github.com/bhmj/xpression v0.9.1 h1:N7bX/nWx9oFi/zsiMTx2ehoRApTDAWdQadq/5o2wMGk= github.com/bhmj/xpression v0.9.1/go.mod h1:j9oYmEXJjeL9mrgW1+ZDBKJXnbupsCPGhlO9J5YhS1Q= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/evilmonkeyinc/jsonpath v0.7.0 h1:jHar8KiQNNhobwKpftn5SzqaVGbGbNb+pWm+BaRQiQw= -github.com/evilmonkeyinc/jsonpath v0.7.0/go.mod h1:exI6Yme6vucFk2m/1iVtxPaHGI17Dy9uWfsqbJ4iGHM= +github.com/evilmonkeyinc/jsonpath v0.7.2 h1:QfnIN8jFo3JOQNLkwhuAg8uPJbJz4NcCV+LRPCLLuoQ= +github.com/evilmonkeyinc/jsonpath v0.7.2/go.mod h1:exI6Yme6vucFk2m/1iVtxPaHGI17Dy9uWfsqbJ4iGHM= github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI= github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -20,7 +20,6 @@ github.com/spyzhov/ajson v0.7.0/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzy github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/jsonpath_test.go b/jsonpath_test.go index 82c5b04..6c2a0d1 100644 --- a/jsonpath_test.go +++ b/jsonpath_test.go @@ -8,7 +8,6 @@ import ( ) func Benchmark_Selector(b *testing.B) { - selectors := []string{ "$.store.book[*].author", "$..author", @@ -25,17 +24,62 @@ func Benchmark_Selector(b *testing.B) { "$..*", } - for _, selector := range selectors { - b.Run(fmt.Sprintf("%s", selector), func(b *testing.B) { - var err error - for i := 0; i < b.N; i++ { - _, err = QueryString(selector, sampleDataString) - if err != nil { - b.Error() + b.Run("QueryString", func(b *testing.B) { + for _, selector := range selectors { + b.Run(fmt.Sprintf("%s", selector), func(b *testing.B) { + var err error + for i := 0; i < b.N; i++ { + _, err = QueryString(selector, sampleDataString) + if err != nil { + b.Error() + } } - } - }) - } + }) + } + }) + + compiledSelectors := make([]*Selector, 0) + b.Run("Compile", func(b *testing.B) { + for _, selector := range selectors { + b.Run(fmt.Sprintf("%s", selector), func(b *testing.B) { + var err error + var compiled *Selector + for i := 0; i < b.N; i++ { + compiled, err = Compile(selector) + if err != nil { + b.Error() + } + } + compiledSelectors = append(compiledSelectors, compiled) + }) + } + }) + + b.Run("Selector.QueryString", func(b *testing.B) { + for _, selector := range compiledSelectors { + b.Run(fmt.Sprintf("%s", selector.selector), func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := selector.QueryString(sampleDataString) + if err != nil { + b.Error() + } + } + }) + } + }) + + b.Run("Selector.Query", func(b *testing.B) { + for _, selector := range compiledSelectors { + b.Run(fmt.Sprintf("%s", selector.selector), func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := selector.Query(sampleDataObject) + if err != nil { + b.Error() + } + } + }) + } + }) } // Tests designed after the examples in the specification document diff --git a/token/current_test.go b/token/current_test.go index daf0688..67c7935 100644 --- a/token/current_test.go +++ b/token/current_test.go @@ -21,39 +21,42 @@ func Test_CurrentToken_Type(t *testing.T) { assert.Equal(t, "current", (¤tToken{}).Type()) } -func Test_CurrentToken_Apply(t *testing.T) { - - tests := []*tokenTest{ - { - token: ¤tToken{}, - input: input{ - current: map[string]interface{}{ - "name": "first", - }, +var currentTests = []*tokenTest{ + { + token: ¤tToken{}, + input: input{ + current: map[string]interface{}{ + "name": "first", }, - expected: expected{ - value: map[string]interface{}{ - "name": "first", - }, + }, + expected: expected{ + value: map[string]interface{}{ + "name": "first", }, }, - { - token: ¤tToken{}, - input: input{ - current: map[string]interface{}{ - "name": "first", - }, - tokens: []Token{ - &keyToken{ - key: "name", - }, - }, + }, + { + token: ¤tToken{}, + input: input{ + current: map[string]interface{}{ + "name": "first", }, - expected: expected{ - value: "first", + tokens: []Token{ + &keyToken{ + key: "name", + }, }, }, - } + expected: expected{ + value: "first", + }, + }, +} + +func Test_CurrentToken_Apply(t *testing.T) { + batchTokenTests(t, currentTests) +} - batchTokenTests(t, tests) +func Benchmark_CurrentToken_Apply(b *testing.B) { + batchTokenBenchmarks(b, currentTests) } diff --git a/token/expression_test.go b/token/expression_test.go index affd538..6b384b9 100644 --- a/token/expression_test.go +++ b/token/expression_test.go @@ -72,49 +72,52 @@ func Test_ExpressionToken_Type(t *testing.T) { assert.Equal(t, "expression", (&expressionToken{}).Type()) } -func Test_ExpressionToken_Apply(t *testing.T) { - - tests := []*tokenTest{ - { - token: &expressionToken{}, - input: input{}, - expected: expected{ - err: "invalid expression. is empty", - }, +var expressionTests = []*tokenTest{ + { + token: &expressionToken{}, + input: input{}, + expected: expected{ + err: "invalid expression. is empty", }, - { - token: &expressionToken{ - expression: "any", - compiledExpression: &testCompiledExpression{err: fmt.Errorf("engine error")}, - }, - input: input{}, - expected: expected{ - err: "invalid expression. engine error", - }, + }, + { + token: &expressionToken{ + expression: "any", + compiledExpression: &testCompiledExpression{err: fmt.Errorf("engine error")}, }, - { - token: &expressionToken{ - expression: "any", - compiledExpression: &testCompiledExpression{response: true}, - }, - input: input{}, - expected: expected{ - value: true, - }, + input: input{}, + expected: expected{ + err: "invalid expression. engine error", }, - { - token: &expressionToken{ - expression: "any", - compiledExpression: &testCompiledExpression{response: false}, - }, - input: input{ - tokens: []Token{¤tToken{}}, - }, - expected: expected{ - value: false, - }, + }, + { + token: &expressionToken{ + expression: "any", + compiledExpression: &testCompiledExpression{response: true}, }, - } + input: input{}, + expected: expected{ + value: true, + }, + }, + { + token: &expressionToken{ + expression: "any", + compiledExpression: &testCompiledExpression{response: false}, + }, + input: input{ + tokens: []Token{¤tToken{}}, + }, + expected: expected{ + value: false, + }, + }, +} + +func Test_ExpressionToken_Apply(t *testing.T) { + batchTokenTests(t, expressionTests) +} - batchTokenTests(t, tests) +func Benchmark_ExpressionToken_Apply(b *testing.B) { + batchTokenBenchmarks(b, expressionTests) } diff --git a/token/filter_test.go b/token/filter_test.go index 1746d93..35fa4e2 100644 --- a/token/filter_test.go +++ b/token/filter_test.go @@ -46,415 +46,418 @@ func Test_FilterToken_Type(t *testing.T) { assert.Equal(t, "filter", (&filterToken{}).Type()) } -func Test_FilterToken_Apply(t *testing.T) { - - getNilPointer := func() *filterToken { - return nil - } +func getNilPointer() *sampleStruct { + return nil +} - tests := []*tokenTest{ - { - token: &filterToken{}, - input: input{}, - expected: expected{ - err: "invalid expression. is empty", - }, +var filterTests = []*tokenTest{ + { + token: &filterToken{}, + input: input{}, + expected: expected{ + err: "invalid expression. is empty", }, - { - token: &filterToken{ - expression: "nil current", - compiledExpression: &testCompiledExpression{}, - }, - input: input{}, - expected: expected{ - err: "filter: invalid token target. expected [array map slice] got [nil]", - }, + }, + { + token: &filterToken{ + expression: "nil current", + compiledExpression: &testCompiledExpression{}, }, - { - token: &filterToken{ - expression: "invalid current", - compiledExpression: &testCompiledExpression{}, - }, - input: input{ - current: "string", - }, - expected: expected{ - err: "filter: invalid token target. expected [array map slice] got [string]", - }, + input: input{}, + expected: expected{ + err: "filter: invalid token target. expected [array map slice] got [nil]", }, - { - token: &filterToken{ - expression: "empty array", - compiledExpression: &testCompiledExpression{}, - }, - input: input{ - current: []interface{}{}, - }, - expected: expected{ - value: []interface{}{}, - }, + }, + { + token: &filterToken{ + expression: "invalid current", + compiledExpression: &testCompiledExpression{}, }, - { - token: &filterToken{ - expression: "failed evaluate array", - compiledExpression: &testCompiledExpression{ - err: fmt.Errorf("compiled failed"), - }, - }, - input: input{ - current: [3]interface{}{1, 2, 3}, - }, - expected: expected{ - value: []interface{}{}, - }, + input: input{ + current: "string", }, - { - token: &filterToken{ - expression: "true evaluate array", - compiledExpression: &testCompiledExpression{ - response: true, - }, - }, - input: input{ - current: [3]interface{}{1, 2, 3}, - }, - expected: expected{ - value: []interface{}{1, 2, 3}, - }, + expected: expected{ + err: "filter: invalid token target. expected [array map slice] got [string]", }, - { - token: &filterToken{ - expression: "false evaluate array", - compiledExpression: &testCompiledExpression{ - response: false, - }, - }, - input: input{ - current: [3]interface{}{1, 2, 3}, - }, - expected: expected{ - value: []interface{}{}, - }, + }, + { + token: &filterToken{ + expression: "empty array", + compiledExpression: &testCompiledExpression{}, }, - { - token: &filterToken{ - expression: "empty string evaluate array", - compiledExpression: &testCompiledExpression{ - response: "", - }, - }, - input: input{ - current: [3]interface{}{1, 2, 3}, - }, - expected: expected{ - value: []interface{}{}, - }, + input: input{ + current: []interface{}{}, }, - { - token: &filterToken{ - expression: "non-empty string evaluate array", - compiledExpression: &testCompiledExpression{ - response: "add this", - }, - }, - input: input{ - current: [3]interface{}{1, 2, 3}, - }, - expected: expected{ - value: []interface{}{1, 2, 3}, - }, + expected: expected{ + value: []interface{}{}, }, - { - token: &filterToken{ - expression: "other evaluate array", - compiledExpression: &testCompiledExpression{ - response: 3.14, - }, - }, - input: input{ - current: [3]interface{}{1, 2, 3}, - }, - expected: expected{ - value: []interface{}{1, 2, 3}, + }, + { + token: &filterToken{ + expression: "failed evaluate array", + compiledExpression: &testCompiledExpression{ + err: fmt.Errorf("compiled failed"), }, }, - { - token: &filterToken{ - expression: "empty map", - compiledExpression: &testCompiledExpression{}, - }, - input: input{ - current: map[string]interface{}{}, - }, - expected: expected{ - value: []interface{}{}, - }, + input: input{ + current: [3]interface{}{1, 2, 3}, }, - { - token: &filterToken{ - expression: "failed evaluate map", - compiledExpression: &testCompiledExpression{ - err: fmt.Errorf("compiled failed"), - }, - }, - input: input{ - current: map[string]interface{}{"key": "value"}, - }, - expected: expected{ - value: []interface{}{}, - }, + expected: expected{ + value: []interface{}{}, }, - { - token: &filterToken{ - expression: "true evaluate map", - compiledExpression: &testCompiledExpression{ - response: true, - }, - }, - input: input{ - current: map[string]interface{}{"key": "value"}, - }, - expected: expected{ - value: []interface{}{"value"}, + }, + { + token: &filterToken{ + expression: "true evaluate array", + compiledExpression: &testCompiledExpression{ + response: true, }, }, - { - token: &filterToken{ - expression: "false evaluate map", - compiledExpression: &testCompiledExpression{ - response: false, - }, - }, - input: input{ - current: map[string]interface{}{"key": "value"}, - }, - expected: expected{ - value: []interface{}{}, - }, + input: input{ + current: [3]interface{}{1, 2, 3}, }, - { - token: &filterToken{ - expression: "empty string evaluate map", - compiledExpression: &testCompiledExpression{ - response: "", - }, - }, - input: input{ - current: map[string]interface{}{"key": "value"}, - }, - expected: expected{ - value: []interface{}{}, - }, + expected: expected{ + value: []interface{}{1, 2, 3}, }, - { - token: &filterToken{ - expression: "non-empty string evaluate map", - compiledExpression: &testCompiledExpression{ - response: "add this", - }, - }, - input: input{ - current: map[string]interface{}{"key": "value"}, - }, - expected: expected{ - value: []interface{}{"value"}, + }, + { + token: &filterToken{ + expression: "false evaluate array", + compiledExpression: &testCompiledExpression{ + response: false, }, }, - { - token: &filterToken{ - expression: "other evaluate map", - compiledExpression: &testCompiledExpression{ - response: 3.14, - }, - }, - input: input{ - current: map[string]interface{}{"key": "value"}, - }, - expected: expected{ - value: []interface{}{"value"}, - }, + input: input{ + current: [3]interface{}{1, 2, 3}, }, - { - token: &filterToken{ - expression: "next is index", - compiledExpression: &testCompiledExpression{response: true}, - }, - input: input{ - current: []interface{}{1, 2, 3, 4, 5}, - tokens: []Token{&indexToken{index: 1}}, - }, - expected: expected{ - value: 2, - }, + expected: expected{ + value: []interface{}{}, }, - { - token: &filterToken{ - expression: "next is not index", - compiledExpression: &testCompiledExpression{response: true}, - }, - input: input{ - current: []interface{}{ - map[string]interface{}{"key": "one"}, - map[string]interface{}{"key": "two"}, - map[string]interface{}{"key": "three"}, - }, - tokens: []Token{&keyToken{key: "key"}}, - }, - expected: expected{ - value: []interface{}{"one", "two", "three"}, + }, + { + token: &filterToken{ + expression: "empty string evaluate array", + compiledExpression: &testCompiledExpression{ + response: "", }, }, - { - token: &filterToken{ - expression: "array", - compiledExpression: &testCompiledExpression{ - response: [1]string{"one"}, - }, - }, - input: input{ - current: []interface{}{1, 2, 3}, - }, - expected: expected{ - value: []interface{}{1, 2, 3}, - err: "", - }, + input: input{ + current: [3]interface{}{1, 2, 3}, }, - { - token: &filterToken{ - expression: "slice", - compiledExpression: &testCompiledExpression{ - response: []string{"one"}, - }, - }, - input: input{ - current: []interface{}{1, 2, 3}, - }, - expected: expected{ - value: []interface{}{1, 2, 3}, - err: "", - }, + expected: expected{ + value: []interface{}{}, }, - { - token: &filterToken{ - expression: "empty array", - compiledExpression: &testCompiledExpression{ - response: [0]string{}, - }, - }, - input: input{ - current: []interface{}{1, 2, 3}, - }, - expected: expected{ - value: []interface{}{}, - err: "", + }, + { + token: &filterToken{ + expression: "non-empty string evaluate array", + compiledExpression: &testCompiledExpression{ + response: "add this", }, }, - { - token: &filterToken{ - expression: "map", - compiledExpression: &testCompiledExpression{ - response: map[string]interface{}{"key": "value"}, - }, - }, - input: input{ - current: []interface{}{1, 2, 3}, - }, - expected: expected{ - value: []interface{}{1, 2, 3}, - err: "", - }, + input: input{ + current: [3]interface{}{1, 2, 3}, }, - { - token: &filterToken{ - expression: "empty map", - compiledExpression: &testCompiledExpression{ - response: map[string]interface{}{}, - }, + expected: expected{ + value: []interface{}{1, 2, 3}, + }, + }, + { + token: &filterToken{ + expression: "other evaluate array", + compiledExpression: &testCompiledExpression{ + response: 3.14, }, - input: input{ - current: []interface{}{1, 2, 3}, + }, + input: input{ + current: [3]interface{}{1, 2, 3}, + }, + expected: expected{ + value: []interface{}{1, 2, 3}, + }, + }, + { + token: &filterToken{ + expression: "empty map", + compiledExpression: &testCompiledExpression{}, + }, + input: input{ + current: map[string]interface{}{}, + }, + expected: expected{ + value: []interface{}{}, + }, + }, + { + token: &filterToken{ + expression: "failed evaluate map", + compiledExpression: &testCompiledExpression{ + err: fmt.Errorf("compiled failed"), }, - expected: expected{ - value: []interface{}{}, - err: "", + }, + input: input{ + current: map[string]interface{}{"key": "value"}, + }, + expected: expected{ + value: []interface{}{}, + }, + }, + { + token: &filterToken{ + expression: "true evaluate map", + compiledExpression: &testCompiledExpression{ + response: true, }, }, - { - token: &filterToken{ - expression: "nil pointer", - compiledExpression: &testCompiledExpression{ - response: getNilPointer(), - }, + input: input{ + current: map[string]interface{}{"key": "value"}, + }, + expected: expected{ + value: []interface{}{"value"}, + }, + }, + { + token: &filterToken{ + expression: "false evaluate map", + compiledExpression: &testCompiledExpression{ + response: false, }, - input: input{ - current: []interface{}{1, 2, 3}, + }, + input: input{ + current: map[string]interface{}{"key": "value"}, + }, + expected: expected{ + value: []interface{}{}, + }, + }, + { + token: &filterToken{ + expression: "empty string evaluate map", + compiledExpression: &testCompiledExpression{ + response: "", }, - expected: expected{ - value: []interface{}{}, - err: "", + }, + input: input{ + current: map[string]interface{}{"key": "value"}, + }, + expected: expected{ + value: []interface{}{}, + }, + }, + { + token: &filterToken{ + expression: "non-empty string evaluate map", + compiledExpression: &testCompiledExpression{ + response: "add this", }, }, - { - token: &filterToken{ - expression: "single quotes empty", - compiledExpression: &testCompiledExpression{ - response: "''", - }, + input: input{ + current: map[string]interface{}{"key": "value"}, + }, + expected: expected{ + value: []interface{}{"value"}, + }, + }, + { + token: &filterToken{ + expression: "other evaluate map", + compiledExpression: &testCompiledExpression{ + response: 3.14, }, - input: input{ - current: []interface{}{1, 2, 3}, + }, + input: input{ + current: map[string]interface{}{"key": "value"}, + }, + expected: expected{ + value: []interface{}{"value"}, + }, + }, + { + token: &filterToken{ + expression: "next is index", + compiledExpression: &testCompiledExpression{response: true}, + }, + input: input{ + current: []interface{}{1, 2, 3, 4, 5}, + tokens: []Token{&indexToken{index: 1}}, + }, + expected: expected{ + value: 2, + }, + }, + { + token: &filterToken{ + expression: "next is not index", + compiledExpression: &testCompiledExpression{response: true}, + }, + input: input{ + current: []interface{}{ + map[string]interface{}{"key": "one"}, + map[string]interface{}{"key": "two"}, + map[string]interface{}{"key": "three"}, }, - expected: expected{ - value: []interface{}{}, - err: "", + tokens: []Token{&keyToken{key: "key"}}, + }, + expected: expected{ + value: []interface{}{"one", "two", "three"}, + }, + }, + { + token: &filterToken{ + expression: "array", + compiledExpression: &testCompiledExpression{ + response: [1]string{"one"}, }, }, - { - token: &filterToken{ - expression: "single quotes not empty", - compiledExpression: &testCompiledExpression{ - response: "' '", - }, + input: input{ + current: []interface{}{1, 2, 3}, + }, + expected: expected{ + value: []interface{}{1, 2, 3}, + err: "", + }, + }, + { + token: &filterToken{ + expression: "slice", + compiledExpression: &testCompiledExpression{ + response: []string{"one"}, }, - input: input{ - current: []interface{}{1, 2, 3}, + }, + input: input{ + current: []interface{}{1, 2, 3}, + }, + expected: expected{ + value: []interface{}{1, 2, 3}, + err: "", + }, + }, + { + token: &filterToken{ + expression: "empty array", + compiledExpression: &testCompiledExpression{ + response: [0]string{}, }, - expected: expected{ - value: []interface{}{1, 2, 3}, - err: "", + }, + input: input{ + current: []interface{}{1, 2, 3}, + }, + expected: expected{ + value: []interface{}{}, + err: "", + }, + }, + { + token: &filterToken{ + expression: "map", + compiledExpression: &testCompiledExpression{ + response: map[string]interface{}{"key": "value"}, }, }, - { - token: &filterToken{ - expression: "double quotes empty", - compiledExpression: &testCompiledExpression{ - response: `""`, - }, + input: input{ + current: []interface{}{1, 2, 3}, + }, + expected: expected{ + value: []interface{}{1, 2, 3}, + err: "", + }, + }, + { + token: &filterToken{ + expression: "empty map", + compiledExpression: &testCompiledExpression{ + response: map[string]interface{}{}, }, - input: input{ - current: []interface{}{1, 2, 3}, + }, + input: input{ + current: []interface{}{1, 2, 3}, + }, + expected: expected{ + value: []interface{}{}, + err: "", + }, + }, + { + token: &filterToken{ + expression: "nil pointer", + compiledExpression: &testCompiledExpression{ + response: getNilPointer(), }, - expected: expected{ - value: []interface{}{}, - err: "", + }, + input: input{ + current: []interface{}{1, 2, 3}, + }, + expected: expected{ + value: []interface{}{}, + err: "", + }, + }, + { + token: &filterToken{ + expression: "single quotes empty", + compiledExpression: &testCompiledExpression{ + response: "''", }, }, - { - token: &filterToken{ - expression: "double quotes not empty", - compiledExpression: &testCompiledExpression{ - response: `" "`, - }, + input: input{ + current: []interface{}{1, 2, 3}, + }, + expected: expected{ + value: []interface{}{}, + err: "", + }, + }, + { + token: &filterToken{ + expression: "single quotes not empty", + compiledExpression: &testCompiledExpression{ + response: "' '", }, - input: input{ - current: []interface{}{1, 2, 3}, + }, + input: input{ + current: []interface{}{1, 2, 3}, + }, + expected: expected{ + value: []interface{}{1, 2, 3}, + err: "", + }, + }, + { + token: &filterToken{ + expression: "double quotes empty", + compiledExpression: &testCompiledExpression{ + response: `""`, }, - expected: expected{ - value: []interface{}{1, 2, 3}, - err: "", + }, + input: input{ + current: []interface{}{1, 2, 3}, + }, + expected: expected{ + value: []interface{}{}, + err: "", + }, + }, + { + token: &filterToken{ + expression: "double quotes not empty", + compiledExpression: &testCompiledExpression{ + response: `" "`, }, }, - } + input: input{ + current: []interface{}{1, 2, 3}, + }, + expected: expected{ + value: []interface{}{1, 2, 3}, + err: "", + }, + }, +} + +func Test_FilterToken_Apply(t *testing.T) { + batchTokenTests(t, filterTests) +} - batchTokenTests(t, tests) +func Benchmark_FilterToken_Apply(b *testing.B) { + batchTokenBenchmarks(b, filterTests) } diff --git a/token/index_test.go b/token/index_test.go index 9524dd4..62dbd02 100644 --- a/token/index_test.go +++ b/token/index_test.go @@ -148,226 +148,229 @@ func Test_IndexToken_Type(t *testing.T) { assert.Equal(t, "index", (&indexToken{}).Type()) } -func Test_IndexToken_Apply(t *testing.T) { - - tests := []*tokenTest{ - { - token: &indexToken{index: 0}, - input: input{ - current: nil, - }, - expected: expected{ - err: "index: invalid token target. expected [array slice] got [nil]", - }, +var indexTest = []*tokenTest{ + { + token: &indexToken{index: 0}, + input: input{ + current: nil, }, - { - token: &indexToken{index: 0}, - input: input{ - current: 123, - }, - expected: expected{ - err: "index: invalid token target. expected [array slice] got [int]", - }, + expected: expected{ + err: "index: invalid token target. expected [array slice] got [nil]", }, - { - token: &indexToken{index: 0, allowMap: true}, - input: input{ - current: 123, - }, - expected: expected{ - err: "index: invalid token target. expected [array slice map] got [int]", - }, + }, + { + token: &indexToken{index: 0}, + input: input{ + current: 123, }, - { - token: &indexToken{index: 0, allowString: true}, - input: input{ - current: 123, - }, - expected: expected{ - err: "index: invalid token target. expected [array slice string] got [int]", - }, + expected: expected{ + err: "index: invalid token target. expected [array slice] got [int]", }, - { - token: &indexToken{index: 0, allowMap: true, allowString: true}, - input: input{ - current: 123, - }, - expected: expected{ - err: "index: invalid token target. expected [array slice map string] got [int]", - }, + }, + { + token: &indexToken{index: 0, allowMap: true}, + input: input{ + current: 123, }, - { - token: &indexToken{index: 5}, - input: input{ - current: "Find(X)", - }, - expected: expected{ - err: "index: invalid token target. expected [array slice] got [string]", - }, + expected: expected{ + err: "index: invalid token target. expected [array slice map] got [int]", }, - { - token: &indexToken{index: 5, allowString: true}, - input: input{ - current: "Find(X)", - }, - expected: expected{ - value: "X", - }, + }, + { + token: &indexToken{index: 0, allowString: true}, + input: input{ + current: 123, }, - { - token: &indexToken{index: 0}, - input: input{ - current: [3]string{"one", "two", "three"}, - }, - expected: expected{ - value: "one", - }, + expected: expected{ + err: "index: invalid token target. expected [array slice string] got [int]", }, - { - token: &indexToken{index: 0}, - input: input{ - current: []string{"one", "two", "three"}, - }, - expected: expected{ - value: "one", - }, + }, + { + token: &indexToken{index: 0, allowMap: true, allowString: true}, + input: input{ + current: 123, }, - { - token: &indexToken{index: 2}, - input: input{ - current: []string{"one", "two", "three"}, - }, - expected: expected{ - value: "three", - }, + expected: expected{ + err: "index: invalid token target. expected [array slice map string] got [int]", }, - { - token: &indexToken{index: 4}, - input: input{ - current: []string{"one", "two", "three"}, - }, - expected: expected{ - err: "index: invalid token out of range", - }, + }, + { + token: &indexToken{index: 5}, + input: input{ + current: "Find(X)", }, - { - token: &indexToken{index: 1}, - input: input{ - current: []interface{}{"one", 2, "three"}, - }, - expected: expected{ - value: 2, - }, + expected: expected{ + err: "index: invalid token target. expected [array slice] got [string]", }, - { - token: &indexToken{index: -1}, - input: input{ - current: []interface{}{"one", 2, "three"}, - }, - expected: expected{ - value: "three", - }, + }, + { + token: &indexToken{index: 5, allowString: true}, + input: input{ + current: "Find(X)", }, - { - token: &indexToken{index: -2}, - input: input{ - current: []interface{}{"one", 2, "three"}, - }, - expected: expected{ - value: 2, - }, + expected: expected{ + value: "X", }, - { - token: &indexToken{index: -3}, - input: input{ - current: []interface{}{"one", 2, "three"}, - }, - expected: expected{ - value: "one", - }, + }, + { + token: &indexToken{index: 0}, + input: input{ + current: [3]string{"one", "two", "three"}, }, - { - token: &indexToken{index: -4}, - input: input{ - current: []interface{}{"one", 2, "three"}, - }, - expected: expected{ - err: "index: invalid token out of range", - }, + expected: expected{ + value: "one", }, - { - token: &indexToken{index: 1}, - input: input{ - current: []interface{}{ - map[string]interface{}{ - "name": "one", - "value": 1, - }, - map[string]interface{}{ - "name": "two", - "value": 2, - }, - map[string]interface{}{ - "name": "three", - "value": 3, - }, + }, + { + token: &indexToken{index: 0}, + input: input{ + current: []string{"one", "two", "three"}, + }, + expected: expected{ + value: "one", + }, + }, + { + token: &indexToken{index: 2}, + input: input{ + current: []string{"one", "two", "three"}, + }, + expected: expected{ + value: "three", + }, + }, + { + token: &indexToken{index: 4}, + input: input{ + current: []string{"one", "two", "three"}, + }, + expected: expected{ + err: "index: invalid token out of range", + }, + }, + { + token: &indexToken{index: 1}, + input: input{ + current: []interface{}{"one", 2, "three"}, + }, + expected: expected{ + value: 2, + }, + }, + { + token: &indexToken{index: -1}, + input: input{ + current: []interface{}{"one", 2, "three"}, + }, + expected: expected{ + value: "three", + }, + }, + { + token: &indexToken{index: -2}, + input: input{ + current: []interface{}{"one", 2, "three"}, + }, + expected: expected{ + value: 2, + }, + }, + { + token: &indexToken{index: -3}, + input: input{ + current: []interface{}{"one", 2, "three"}, + }, + expected: expected{ + value: "one", + }, + }, + { + token: &indexToken{index: -4}, + input: input{ + current: []interface{}{"one", 2, "three"}, + }, + expected: expected{ + err: "index: invalid token out of range", + }, + }, + { + token: &indexToken{index: 1}, + input: input{ + current: []interface{}{ + map[string]interface{}{ + "name": "one", + "value": 1, }, - tokens: []Token{ - &keyToken{key: "name"}, + map[string]interface{}{ + "name": "two", + "value": 2, + }, + map[string]interface{}{ + "name": "three", + "value": 3, }, }, - expected: expected{ - value: "two", + tokens: []Token{ + &keyToken{key: "name"}, }, }, - { - token: &indexToken{index: 1}, - input: input{ - current: map[string]interface{}{ - "a": map[string]interface{}{ - "name": "one", - "value": 1, - }, - "c": map[string]interface{}{ - "name": "three", - "value": 3, - }, - "b": map[string]interface{}{ - "name": "two", - "value": 2, - }, + expected: expected{ + value: "two", + }, + }, + { + token: &indexToken{index: 1}, + input: input{ + current: map[string]interface{}{ + "a": map[string]interface{}{ + "name": "one", + "value": 1, + }, + "c": map[string]interface{}{ + "name": "three", + "value": 3, + }, + "b": map[string]interface{}{ + "name": "two", + "value": 2, }, - }, - expected: expected{ - err: "index: invalid token target. expected [array slice] got [map]", }, }, - { - token: &indexToken{index: 1, allowMap: true}, - input: input{ - current: map[string]interface{}{ - "a": map[string]interface{}{ - "name": "one", - "value": 1, - }, - "c": map[string]interface{}{ - "name": "three", - "value": 3, - }, - "b": map[string]interface{}{ - "name": "two", - "value": 2, - }, + expected: expected{ + err: "index: invalid token target. expected [array slice] got [map]", + }, + }, + { + token: &indexToken{index: 1, allowMap: true}, + input: input{ + current: map[string]interface{}{ + "a": map[string]interface{}{ + "name": "one", + "value": 1, }, - }, - expected: expected{ - value: map[string]interface{}{ + "c": map[string]interface{}{ + "name": "three", + "value": 3, + }, + "b": map[string]interface{}{ "name": "two", "value": 2, }, }, }, - } + expected: expected{ + value: map[string]interface{}{ + "name": "two", + "value": 2, + }, + }, + }, +} + +func Test_IndexToken_Apply(t *testing.T) { + batchTokenTests(t, indexTest) +} - batchTokenTests(t, tests) +func Benchmark_IndexToken_Apply(b *testing.B) { + batchTokenBenchmarks(b, indexTest) } diff --git a/token/key_test.go b/token/key_test.go index 34fe60e..90d1f87 100644 --- a/token/key_test.go +++ b/token/key_test.go @@ -40,154 +40,157 @@ func Test_KeyToken_Type(t *testing.T) { assert.Equal(t, "key", (&keyToken{}).Type()) } -func Test_KeyToken_Apply(t *testing.T) { - - tests := []*tokenTest{ - { - token: &keyToken{key: "key"}, - input: input{ - current: nil, - }, - expected: expected{ - value: nil, - err: "key: invalid token target. expected [map] got [nil]", - }, - }, - { - token: &keyToken{key: "key"}, - input: input{ - current: "", - }, - expected: expected{ - value: nil, - err: "key: invalid token target. expected [map] got [string]", - }, - }, - { - token: &keyToken{key: "key"}, - input: input{ - current: map[string]interface{}{ - "key": true, +var keyTests = []*tokenTest{ + { + token: &keyToken{key: "key"}, + input: input{ + current: nil, + }, + expected: expected{ + value: nil, + err: "key: invalid token target. expected [map] got [nil]", + }, + }, + { + token: &keyToken{key: "key"}, + input: input{ + current: "", + }, + expected: expected{ + value: nil, + err: "key: invalid token target. expected [map] got [string]", + }, + }, + { + token: &keyToken{key: "key"}, + input: input{ + current: map[string]interface{}{ + "key": true, + }, + }, + expected: expected{ + value: true, + err: "", + }, + }, + { + token: &keyToken{key: "missing"}, + input: input{ + current: map[string]interface{}{ + "key": true, + }, + }, + expected: expected{ + value: nil, + err: "key: invalid token key 'missing' not found", + }, + }, + { + token: &keyToken{key: "key"}, + input: input{ + current: map[string]interface{}{ + "key": map[string]interface{}{ + "next": "nested target", }, }, - expected: expected{ - value: true, - err: "", - }, - }, - { - token: &keyToken{key: "missing"}, - input: input{ - current: map[string]interface{}{ - "key": true, + tokens: []Token{ + &keyToken{ + key: "next", }, }, - expected: expected{ - value: nil, - err: "key: invalid token key 'missing' not found", - }, }, - { - token: &keyToken{key: "key"}, - input: input{ - current: map[string]interface{}{ - "key": map[string]interface{}{ - "next": "nested target", - }, - }, - tokens: []Token{ - &keyToken{ - key: "next", - }, - }, - }, - expected: expected{ - value: "nested target", - err: "", - }, + expected: expected{ + value: "nested target", + err: "", }, - { - token: &keyToken{key: "key's"}, - input: input{ - current: map[string]interface{}{ - "key's": []interface{}{ - 1, 2, 3, - }, - }, - }, - expected: expected{ - value: []interface{}{ + }, + { + token: &keyToken{key: "key's"}, + input: input{ + current: map[string]interface{}{ + "key's": []interface{}{ 1, 2, 3, }, - err: "", }, }, - { - token: &keyToken{key: "two"}, - input: input{ - current: sampleStruct{ - Two: "two's value", - }, - }, - expected: expected{ - value: "two's value", + expected: expected{ + value: []interface{}{ + 1, 2, 3, }, + err: "", }, - { - token: &keyToken{key: "Five"}, - input: input{ - current: &sampleStruct{ - Five: "value", - }, - }, - expected: expected{ - value: "value", + }, + { + token: &keyToken{key: "two"}, + input: input{ + current: sampleStruct{ + Two: "two's value", }, }, - { - token: &keyToken{key: "two"}, - input: input{ - current: sampleStruct{ - Two: "two's value", - }, - tokens: []Token{ - &indexToken{index: 0, allowString: true}, - }, - }, - expected: expected{ - value: "t", - }, + expected: expected{ + value: "two's value", }, - { - token: &keyToken{key: "two"}, - input: input{ - current: sampleStruct{}, - }, - expected: expected{ - value: "", + }, + { + token: &keyToken{key: "Five"}, + input: input{ + current: &sampleStruct{ + Five: "value", }, }, - { - token: &keyToken{key: "three"}, - input: input{ - current: sampleStruct{ - Four: 100, - }, + expected: expected{ + value: "value", + }, + }, + { + token: &keyToken{key: "two"}, + input: input{ + current: sampleStruct{ + Two: "two's value", }, - expected: expected{ - value: int64(100), + tokens: []Token{ + &indexToken{index: 0, allowString: true}, }, }, - { - token: &keyToken{key: "other"}, - input: input{ - current: sampleStruct{}, - }, - expected: expected{ - err: "key: invalid token key 'other' not found", + expected: expected{ + value: "t", + }, + }, + { + token: &keyToken{key: "two"}, + input: input{ + current: sampleStruct{}, + }, + expected: expected{ + value: "", + }, + }, + { + token: &keyToken{key: "three"}, + input: input{ + current: sampleStruct{ + Four: 100, }, }, - } + expected: expected{ + value: int64(100), + }, + }, + { + token: &keyToken{key: "other"}, + input: input{ + current: sampleStruct{}, + }, + expected: expected{ + err: "key: invalid token key 'other' not found", + }, + }, +} + +func Test_KeyToken_Apply(t *testing.T) { + batchTokenTests(t, keyTests) +} - batchTokenTests(t, tests) +func Benchmark_KeyToken_Apply(b *testing.B) { + batchTokenBenchmarks(b, keyTests) } diff --git a/token/length_test.go b/token/length_test.go index e9f958a..4d7befe 100644 --- a/token/length_test.go +++ b/token/length_test.go @@ -21,105 +21,107 @@ func Test_LengthToken_Type(t *testing.T) { assert.Equal(t, "length", (&lengthToken{}).Type()) } -func Test_LengthToken_Apply(t *testing.T) { - - tests := []*tokenTest{ - { - token: &lengthToken{}, - input: input{ - current: nil, - }, - expected: expected{ - err: "length: invalid token target. expected [array map slice string] got [nil]", - }, +var lengthTests = []*tokenTest{ + { + token: &lengthToken{}, + input: input{ + current: nil, }, - { - token: &lengthToken{}, - input: input{ - current: 1000, - }, - expected: expected{ - err: "length: invalid token target. expected [array map slice string] got [int]", - }, + expected: expected{ + err: "length: invalid token target. expected [array map slice string] got [nil]", }, - { - token: &lengthToken{}, - input: input{ - current: [3]string{"one", "two", "three"}, - }, - expected: expected{ - value: int64(3), - }, + }, + { + token: &lengthToken{}, + input: input{ + current: 1000, }, - { - token: &lengthToken{}, - input: input{ - current: []interface{}{"one", "two", "three", 4, 5}, - }, - expected: expected{ - value: int64(5), - }, + expected: expected{ + err: "length: invalid token target. expected [array map slice string] got [int]", }, - { - token: &lengthToken{}, - input: input{ - current: map[string]int64{ - "one": 1, - "two": 2, - "three": 3, - }, - }, - expected: expected{ - value: int64(3), - }, + }, + { + token: &lengthToken{}, + input: input{ + current: [3]string{"one", "two", "three"}, }, - { - token: &lengthToken{}, - input: input{ - current: map[string]string{ - "one": "1", - "two": "2", - "three": "3", - }, - }, - expected: expected{ - value: int64(3), - }, + expected: expected{ + value: int64(3), }, - { - token: &lengthToken{}, - input: input{ - current: "this is 26 characters long", - }, - expected: expected{ - value: int64(26), - }, + }, + { + token: &lengthToken{}, + input: input{ + current: []interface{}{"one", "two", "three", 4, 5}, }, - { - token: &lengthToken{}, - input: input{ - current: "this is 26 characters long", - tokens: []Token{ - ¤tToken{}, - }, + expected: expected{ + value: int64(5), + }, + }, + { + token: &lengthToken{}, + input: input{ + current: map[string]int64{ + "one": 1, + "two": 2, + "three": 3, }, - expected: expected{ - value: int64(26), + }, + expected: expected{ + value: int64(3), + }, + }, + { + token: &lengthToken{}, + input: input{ + current: map[string]string{ + "one": "1", + "two": "2", + "three": "3", }, }, - { - token: &lengthToken{}, - input: input{ - current: map[string]string{ - "length": "this would be the length", - }, + expected: expected{ + value: int64(3), + }, + }, + { + token: &lengthToken{}, + input: input{ + current: "this is 26 characters long", + }, + expected: expected{ + value: int64(26), + }, + }, + { + token: &lengthToken{}, + input: input{ + current: "this is 26 characters long", + tokens: []Token{ + ¤tToken{}, }, - expected: expected{ - value: "this would be the length", + }, + expected: expected{ + value: int64(26), + }, + }, + { + token: &lengthToken{}, + input: input{ + current: map[string]string{ + "length": "this would be the length", }, }, - } + expected: expected{ + value: "this would be the length", + }, + }, +} - batchTokenTests(t, tests) +func Test_LengthToken_Apply(t *testing.T) { + batchTokenTests(t, lengthTests) +} +func Benchmark_LengthToken_Apply(b *testing.B) { + batchTokenBenchmarks(b, lengthTests) } diff --git a/token/range.go b/token/range.go index e7a4df5..fe4c1f9 100644 --- a/token/range.go +++ b/token/range.go @@ -54,142 +54,6 @@ func (token *rangeToken) Type() string { func (token *rangeToken) Apply(root, current interface{}, next []Token) (interface{}, error) { - var fromInt, toInt, stepInt *int64 - - if token.from != nil { - if script, ok := token.from.(Token); ok { - result, err := script.Apply(root, current, nil) - if err != nil { - return nil, getInvalidTokenError(token.Type(), err) - } - - if result == nil { - err := getUnexpectedExpressionResultNilError(reflect.Int) - return nil, getInvalidTokenError(token.Type(), err) - } - - if intVal, ok := isInteger(result); ok { - tmp := int64(intVal) - fromInt = &tmp - } else { - kind := reflect.TypeOf(result).Kind() - err := getUnexpectedExpressionResultError(kind, reflect.Int) - return nil, getInvalidTokenError(token.Type(), err) - } - } else if intVal, ok := isInteger(token.from); ok { - tmp := int64(intVal) - fromInt = &tmp - } else { - kind := reflect.TypeOf(token.from).Kind() - return nil, getInvalidTokenArgumentError(token.Type(), kind, reflect.Int) - } - } - - if token.to != nil { - if script, ok := token.to.(Token); ok { - result, err := script.Apply(root, current, nil) - if err != nil { - return nil, getInvalidTokenError(token.Type(), err) - } - - if result == nil { - err := getUnexpectedExpressionResultNilError(reflect.Int) - return nil, getInvalidTokenError(token.Type(), err) - } - - if intVal, ok := isInteger(result); ok { - tmp := int64(intVal) - toInt = &tmp - } else { - kind := reflect.TypeOf(result).Kind() - err := getUnexpectedExpressionResultError(kind, reflect.Int) - return nil, getInvalidTokenError(token.Type(), err) - } - } else if intVal, ok := isInteger(token.to); ok { - toInt = &intVal - } else { - kind := reflect.TypeOf(token.to).Kind() - return nil, getInvalidTokenArgumentError(token.Type(), kind, reflect.Int) - } - } - - if token.step != nil { - if script, ok := token.step.(Token); ok { - result, err := script.Apply(root, current, nil) - if err != nil { - return nil, getInvalidTokenError(token.Type(), err) - } - - if result == nil { - err := getUnexpectedExpressionResultNilError(reflect.Int) - return nil, getInvalidTokenError(token.Type(), err) - } - - if intVal, ok := isInteger(result); ok { - tmp := int64(intVal) - stepInt = &tmp - } else { - kind := reflect.TypeOf(result).Kind() - err := getUnexpectedExpressionResultError(kind, reflect.Int) - return nil, getInvalidTokenError(token.Type(), err) - } - } else if intVal, ok := isInteger(token.step); ok { - stepInt = &intVal - } else { - kind := reflect.TypeOf(token.step).Kind() - return nil, getInvalidTokenArgumentError(token.Type(), kind, reflect.Int) - } - } - - rangeResult, err := token.getRange(current, fromInt, toInt, stepInt) - if err != nil { - if isInvalidTokenTargetError(err) { - return nil, err - } - return nil, getInvalidTokenError(token.Type(), err) - } - - if substring, ok := rangeResult.(string); ok { - if len(next) > 0 { - return next[0].Apply(root, substring, next[1:]) - } - return substring, nil - } - - elements := rangeResult.([]interface{}) - - if len(next) > 0 { - nextToken := next[0] - futureTokens := next[1:] - - if indexToken, ok := nextToken.(*indexToken); ok { - // if next is asking for specific index - return indexToken.Apply(current, elements, futureTokens) - } - // any other token type - results := make([]interface{}, 0) - - for _, item := range elements { - result, _ := nextToken.Apply(root, item, futureTokens) - if result != nil { - results = append(results, result) - } - } - - return results, nil - } - - return elements, nil -} - -/** -expected responses -1. nil, error - if there is any errors -2. []interface{}, nil - if an array, map, or slice is processed correctly -3. string, nil - if a string is processed correctly -**/ -func (token *rangeToken) getRange(obj interface{}, start, end, step *int64) (interface{}, error) { - allowedType := []reflect.Kind{ reflect.Array, reflect.Slice, @@ -201,7 +65,7 @@ func (token *rangeToken) getRange(obj interface{}, start, end, step *int64) (int allowedType = append(allowedType, reflect.String) } - objType, objVal := getTypeAndValue(obj) + objType, objVal := getTypeAndValue(current) if objType == nil { return nil, getInvalidTokenTargetNilError( token.Type(), @@ -251,12 +115,15 @@ func (token *rangeToken) getRange(obj interface{}, start, end, step *int64) (int } var from int64 = 0 - if start != nil { - from = *start + if token.from != nil { + var err error + from, err = token.parseArgument(root, current, token.from) + if err != nil { + return nil, err + } if from < 0 { from = length + from } - if from < 0 { from = 0 } @@ -266,8 +133,12 @@ func (token *rangeToken) getRange(obj interface{}, start, end, step *int64) (int } to := length - if end != nil { - to = *end + if token.to != nil { + var err error + to, err = token.parseArgument(root, current, token.to) + if err != nil { + return nil, err + } if to < 0 { to = length + to } @@ -280,52 +151,131 @@ func (token *rangeToken) getRange(obj interface{}, start, end, step *int64) (int } } - var stp int64 = 1 - if step != nil { - stp = *step - if stp == 0 { + var step int64 = 1 + if token.step != nil { + var err error + step, err = token.parseArgument(root, current, token.step) + if err != nil { + return nil, err + } + if step == 0 { return nil, getInvalidTokenOutOfRangeError(token.Type()) } } - array := make([]interface{}, 0) + var nextToken Token + var futureTokens []Token + forEach := false + + if len(next) > 0 { + nextToken = next[0] + futureTokens = next[1:] + + if _, ok := nextToken.(*indexToken); !ok { + forEach = true + } + } + + elements := make([]interface{}, 0) if mapKeys != nil { - if stp < 0 { - for i := to - 1; i >= from; i += stp { + if step < 0 { + for i := to - 1; i >= from; i += step { key := mapKeys[i] - array = append(array, objVal.MapIndex(key).Interface()) + item, add := token.handleNext(root, objVal.MapIndex(key).Interface(), forEach, nextToken, futureTokens) + if add { + elements = append(elements, item) + } } } else { - for i := from; i < to; i += stp { + for i := from; i < to; i += step { key := mapKeys[i] - array = append(array, objVal.MapIndex(key).Interface()) + item, add := token.handleNext(root, objVal.MapIndex(key).Interface(), forEach, nextToken, futureTokens) + if add { + elements = append(elements, item) + } } } } else if isString { substring := "" - if stp < 0 { - for i := to - 1; i >= from; i += stp { + if step < 0 { + for i := to - 1; i >= from; i += step { value := objVal.Index(int(i)).Uint() substring += fmt.Sprintf("%c", value) } } else { - for i := from; i < to; i += stp { + for i := from; i < to; i += step { value := objVal.Index(int(i)).Uint() substring += fmt.Sprintf("%c", value) } } + + if len(next) > 0 { + return next[0].Apply(root, substring, next[1:]) + } + return substring, nil } else { - if stp < 0 { - for i := to - 1; i >= from; i += stp { - array = append(array, objVal.Index(int(i)).Interface()) + if step < 0 { + for i := to - 1; i >= from; i += step { + item, add := token.handleNext(root, objVal.Index(int(i)).Interface(), forEach, nextToken, futureTokens) + if add { + elements = append(elements, item) + } } } else { - for i := from; i < to; i += stp { - array = append(array, objVal.Index(int(i)).Interface()) + for i := from; i < to; i += step { + item, add := token.handleNext(root, objVal.Index(int(i)).Interface(), forEach, nextToken, futureTokens) + if add { + elements = append(elements, item) + } } } } - return array, nil + + if !forEach && nextToken != nil { + return nextToken.Apply(root, elements, futureTokens) + } + + return elements, nil +} + +func (token *rangeToken) handleNext(root, item interface{}, forEach bool, nextToken Token, futureTokens []Token) (interface{}, bool) { + if !forEach { + return item, true + } + val, err := nextToken.Apply(root, item, futureTokens) + if err != nil { + return nil, false + } + if val == nil { + return nil, false + } + return val, true +} + +func (token *rangeToken) parseArgument(root, current interface{}, argument interface{}) (int64, error) { + if script, ok := argument.(Token); ok { + result, err := script.Apply(root, current, nil) + if err != nil { + return 0, getInvalidTokenError(token.Type(), err) + } + + if result == nil { + err := getUnexpectedExpressionResultNilError(reflect.Int) + return 0, getInvalidTokenError(token.Type(), err) + } + if intVal, ok := isInteger(result); ok { + return intVal, nil + } + + kind := reflect.TypeOf(result).Kind() + err = getUnexpectedExpressionResultError(kind, reflect.Int) + return 0, getInvalidTokenError(token.Type(), err) + } else if intVal, ok := isInteger(argument); ok { + return intVal, nil + } + + kind := reflect.TypeOf(argument).Kind() + return 0, getInvalidTokenArgumentError(token.Type(), kind, reflect.Int) } diff --git a/token/range_test.go b/token/range_test.go index acf3258..6b72a25 100644 --- a/token/range_test.go +++ b/token/range_test.go @@ -149,1114 +149,1056 @@ func Test_RangeToken_Type(t *testing.T) { } func Test_RangeToken_Apply(t *testing.T) { + batchTokenTests(t, rangeTests) +} - tests := []*tokenTest{ - { - token: &rangeToken{ - from: nil, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - value: []interface{}{ - "one", - "two", - "three", - }, - }, +func Benchmark_RangeToken_Apply(b *testing.B) { + batchTokenBenchmarks(b, rangeTests) +} + +var rangeTests = []*tokenTest{ + { + token: &rangeToken{ + from: nil, }, - { - token: &rangeToken{ - from: 0, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - value: []interface{}{ - "one", - "two", - "three", - }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - token: &rangeToken{ - from: 1, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - value: []interface{}{ - "two", - "three", - }, + expected: expected{ + value: []interface{}{ + "one", + "two", + "three", }, }, - { - token: &rangeToken{ - from: 1, - to: 2, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - value: []interface{}{ - "two", - }, - }, + }, + { + token: &rangeToken{ + from: 0, }, - { - token: &rangeToken{ - from: 1, - step: 3, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - value: []interface{}{ - "two", - }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - token: &rangeToken{ - from: 0, - to: 3, - step: 2, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - value: []interface{}{ - "one", - "three", - }, + expected: expected{ + value: []interface{}{ + "one", + "two", + "three", }, }, - { - token: &rangeToken{ - from: 0, - to: 2, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - value: []interface{}{ - "one", - "two", - }, - }, + }, + { + token: &rangeToken{ + from: 1, }, - { - token: &rangeToken{ - from: 0, - to: 2, - step: 2, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - value: []interface{}{ - "one", - }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - token: &rangeToken{ - from: 0, - step: 2, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - value: []interface{}{ - "one", - "three", - }, + expected: expected{ + value: []interface{}{ + "two", + "three", }, }, - { - token: &rangeToken{ - from: 0, - to: 100, - }, - input: input{ - current: []interface{}{1, 2, 3}, - }, - expected: expected{ - value: []interface{}{1, 2, 3}, - }, + }, + { + token: &rangeToken{ + from: 1, + to: 2, }, - { - token: &rangeToken{ - from: "string", - to: 2, - step: 2, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - err: "range: invalid token argument. expected [int] got [string]", + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - token: &rangeToken{ - from: 0, - to: "string", - step: 2, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - err: "range: invalid token argument. expected [int] got [string]", + expected: expected{ + value: []interface{}{ + "two", }, }, - { - token: &rangeToken{ - from: 0, - to: 1, - step: "string", - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - err: "range: invalid token argument. expected [int] got [string]", - }, + }, + { + token: &rangeToken{ + from: 1, + step: 3, }, - { - token: &rangeToken{ - from: &expressionToken{ - expression: "", - compiledExpression: &testCompiledExpression{response: ""}, - }, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - err: "range: invalid token invalid expression. is empty", + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - token: &rangeToken{ - from: &expressionToken{ - expression: "'key'", - compiledExpression: &testCompiledExpression{response: "key"}, - }, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - err: "range: invalid token unexpected expression result. expected [int] got [string]", + expected: expected{ + value: []interface{}{ + "two", }, }, - { - token: &rangeToken{ - from: &indexToken{index: 0}, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - err: "range: invalid token unexpected expression result. expected [int] got [string]", - }, + }, + { + token: &rangeToken{ + from: 0, + to: 3, + step: 2, }, - { - token: &rangeToken{ - from: &expressionToken{ - expression: "@.length-1", - compiledExpression: &testCompiledExpression{response: 2}, - }, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - value: []interface{}{ - "three", - }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - token: &rangeToken{ - from: 0, - to: &expressionToken{ - expression: "", - compiledExpression: &testCompiledExpression{response: ""}, - }, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - err: "range: invalid token invalid expression. is empty", + expected: expected{ + value: []interface{}{ + "one", + "three", }, }, - { - token: &rangeToken{ - from: 0, - to: &expressionToken{ - expression: "'key'", - compiledExpression: &testCompiledExpression{response: "key"}, - }, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - err: "range: invalid token unexpected expression result. expected [int] got [string]", - }, + }, + { + token: &rangeToken{ + from: 0, + to: 2, }, - { - token: &rangeToken{ - from: 0, - to: &indexToken{index: 0}, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - err: "range: invalid token unexpected expression result. expected [int] got [string]", + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - token: &rangeToken{ - from: 0, - to: &expressionToken{ - expression: "@.length-2", - compiledExpression: &testCompiledExpression{response: 1}, - }, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - value: []interface{}{ - "one", - }, + expected: expected{ + value: []interface{}{ + "one", + "two", }, }, - { - token: &rangeToken{ - from: 0, - step: &expressionToken{ - expression: "", - compiledExpression: &testCompiledExpression{response: ""}, - }, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - err: "range: invalid token invalid expression. is empty", - }, + }, + { + token: &rangeToken{ + from: 0, + to: 2, + step: 2, }, - { - token: &rangeToken{ - from: 0, - step: &expressionToken{ - expression: "'key'", - compiledExpression: &testCompiledExpression{response: "key"}, - }, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - err: "range: invalid token unexpected expression result. expected [int] got [string]", + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - token: &rangeToken{ - from: 0, - step: &indexToken{index: 0}, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - err: "range: invalid token unexpected expression result. expected [int] got [string]", + expected: expected{ + value: []interface{}{ + "one", }, }, - { - token: &rangeToken{ - from: 0, - step: &expressionToken{ - expression: "@.length-1", - compiledExpression: &testCompiledExpression{response: 2}, - }, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - }, - }, - expected: expected{ - value: []interface{}{ - "one", - "three", - }, - }, + }, + { + token: &rangeToken{ + from: 0, + step: 2, }, - { - token: &rangeToken{ - from: 0, - }, - input: input{ - tokens: []Token{&keyToken{key: "name"}}, - current: []map[string]interface{}{ - { - "name": "one", - }, - { - "name": "two", - }, - { - "name": "three", - }, - { - "name": "four", - }, - { - "name": "five", - }, - }, - }, - expected: expected{ - value: []interface{}{ - "one", - "two", - "three", - "four", - "five", - }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - token: &rangeToken{ - from: 1, - to: -2, - }, - input: input{ - tokens: []Token{&keyToken{key: "name"}}, - current: []map[string]interface{}{ - { - "name": "one", - }, - { - "name": "two", - }, - { - "name": "three", - }, - { - "name": "four", - }, - { - "name": "five", - }, - }, - }, - expected: expected{ - value: []interface{}{ - "two", - "three", - }, + expected: expected{ + value: []interface{}{ + "one", + "three", }, }, - { - token: &rangeToken{from: 10}, - input: input{ - current: "this is a substring", - }, - expected: expected{ - err: "range: invalid token target. expected [array slice] got [string]", - }, + }, + { + token: &rangeToken{ + from: 0, + to: 100, }, - { - token: &rangeToken{from: 10, allowString: true}, - input: input{ - current: "this is a substring", - }, - expected: expected{ - value: "substring", - }, + input: input{ + current: []interface{}{1, 2, 3}, }, - { - token: &rangeToken{from: -9, allowString: true}, - input: input{ - current: "this is a substring", - tokens: []Token{ - &indexToken{index: 0, allowString: true}, - }, - }, - expected: expected{ - value: "s", - }, + expected: expected{ + value: []interface{}{1, 2, 3}, }, - { - token: &rangeToken{from: 1}, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - "four", - }, - tokens: []Token{ - &indexToken{ - index: 0, - }, - }, - }, - expected: expected{ - value: "two", - }, + }, + { + token: &rangeToken{ + from: "string", + to: 2, + step: 2, }, - { - token: &rangeToken{ - from: &expressionToken{ - expression: "nil", - compiledExpression: &testCompiledExpression{response: nil}, - }, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - "four", - }, - }, - expected: expected{ - err: "range: invalid token unexpected expression result. expected [int] got [nil]", + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - token: &rangeToken{ - from: 0, - to: &expressionToken{ - expression: "nil", - compiledExpression: &testCompiledExpression{response: nil}, - }, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - "four", - }, - }, - expected: expected{ - err: "range: invalid token unexpected expression result. expected [int] got [nil]", - }, + expected: expected{ + err: "range: invalid token argument. expected [int] got [string]", }, - { - token: &rangeToken{ - from: 0, - to: 1, - step: &expressionToken{ - expression: "nil", - compiledExpression: &testCompiledExpression{response: nil}, - }, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - "four", - }, - }, - expected: expected{ - err: "range: invalid token unexpected expression result. expected [int] got [nil]", - }, + }, + { + token: &rangeToken{ + from: 0, + to: "string", + step: 2, }, - { - token: &rangeToken{ - from: 0, - to: 1, - }, - input: input{ - current: 123, - }, - expected: expected{ - err: "range: invalid token target. expected [array slice] got [int]", + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - } - - batchTokenTests(t, tests) -} - -func Test_getRange(t *testing.T) { - type input struct { - token *rangeToken - obj interface{} - start, end, step *int64 - } - - type expected struct { - obj interface{} - err string - } - - testArray := []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", "eleven", "twelve", 13} - - intPtr := func(i int64) *int64 { - return &i - } - - tests := []struct { - input input - expected expected - }{ - { - input: input{ - token: &rangeToken{}, - obj: nil, - }, - expected: expected{ - err: "range: invalid token target. expected [array slice] got [nil]", - }, + expected: expected{ + err: "range: invalid token argument. expected [int] got [string]", }, - { - input: input{ - token: &rangeToken{}, - obj: 123, - }, - expected: expected{ - err: "range: invalid token target. expected [array slice] got [int]", - }, + }, + { + token: &rangeToken{ + from: 0, + to: 1, + step: "string", }, - { - input: input{ - token: &rangeToken{allowMap: true}, - obj: 123, - }, - expected: expected{ - err: "range: invalid token target. expected [array slice map] got [int]", + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - input: input{ - token: &rangeToken{allowString: true}, - obj: 123, - }, - expected: expected{ - err: "range: invalid token target. expected [array slice string] got [int]", - }, + expected: expected{ + err: "range: invalid token argument. expected [int] got [string]", }, - { - input: input{ - token: &rangeToken{allowMap: true, allowString: true}, - obj: 123, - }, - expected: expected{ - err: "range: invalid token target. expected [array slice map string] got [int]", + }, + { + token: &rangeToken{ + from: &expressionToken{ + expression: "", + compiledExpression: &testCompiledExpression{response: ""}, }, }, - { - input: input{ - token: &rangeToken{allowString: true}, - obj: "return after this:result text", - start: intPtr(18), - }, - expected: expected{ - obj: "result text", + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - input: input{ - token: &rangeToken{}, - obj: testArray, - start: intPtr(15), - }, - expected: expected{ - obj: []interface{}{}, - }, + expected: expected{ + err: "range: invalid token invalid expression. is empty", }, - { - input: input{ - token: &rangeToken{}, - obj: testArray, - end: intPtr(15), - }, - expected: expected{ - obj: []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", "eleven", "twelve", 13}, + }, + { + token: &rangeToken{ + from: &expressionToken{ + expression: "'key'", + compiledExpression: &testCompiledExpression{response: "key"}, }, }, - { - input: input{ - token: &rangeToken{}, - obj: testArray, - step: intPtr(0), - }, - expected: expected{ - err: "range: invalid token out of range", + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - input: input{ - token: &rangeToken{}, - obj: testArray, + expected: expected{ + err: "range: invalid token unexpected expression result. expected [int] got [string]", + }, + }, + { + token: &rangeToken{ + from: &indexToken{index: 0}, + }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, - expected: expected{ - obj: testArray, + }, + expected: expected{ + err: "range: invalid token unexpected expression result. expected [int] got [string]", + }, + }, + { + token: &rangeToken{ + from: &expressionToken{ + expression: "@.length-1", + compiledExpression: &testCompiledExpression{response: 2}, }, }, - { - input: input{ - token: &rangeToken{}, - obj: testArray, - end: nil, + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, - expected: expected{ - obj: testArray[0:14], + }, + expected: expected{ + value: []interface{}{ + "three", }, }, - { - input: input{ - token: &rangeToken{}, - obj: testArray, - end: intPtr(-1), + }, + { + token: &rangeToken{ + from: 0, + to: &expressionToken{ + expression: "", + compiledExpression: &testCompiledExpression{response: ""}, }, - expected: expected{ - obj: testArray[0:13], + }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - input: input{ - token: &rangeToken{}, - obj: testArray, - end: intPtr(-3), + expected: expected{ + err: "range: invalid token invalid expression. is empty", + }, + }, + { + token: &rangeToken{ + from: 0, + to: &expressionToken{ + expression: "'key'", + compiledExpression: &testCompiledExpression{response: "key"}, }, - expected: expected{ - obj: testArray[0:11], + }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - input: input{ - token: &rangeToken{}, - obj: testArray, - start: intPtr(-3), - end: intPtr(-1), + expected: expected{ + err: "range: invalid token unexpected expression result. expected [int] got [string]", + }, + }, + { + token: &rangeToken{ + from: 0, + to: &indexToken{index: 0}, + }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, - expected: expected{ - obj: testArray[11:13], + }, + expected: expected{ + err: "range: invalid token unexpected expression result. expected [int] got [string]", + }, + }, + { + token: &rangeToken{ + from: 0, + to: &expressionToken{ + expression: "@.length-2", + compiledExpression: &testCompiledExpression{response: 1}, }, }, - { - input: input{ - token: &rangeToken{}, - obj: []string{"one", "two", "three", "four", "five"}, - step: intPtr(2), + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, - expected: expected{ - obj: []interface{}{"one", "three", "five"}, + }, + expected: expected{ + value: []interface{}{ + "one", }, }, - { - input: input{ - token: &rangeToken{}, - obj: []string{"one", "two", "three", "four", "five"}, - start: intPtr(1), - step: intPtr(2), + }, + { + token: &rangeToken{ + from: 0, + step: &expressionToken{ + expression: "", + compiledExpression: &testCompiledExpression{response: ""}, }, - expected: expected{ - obj: []interface{}{"two", "four"}, + }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - input: input{ - token: &rangeToken{}, - obj: []string{"one", "two", "three", "four", "five"}, - start: intPtr(1), - end: intPtr(1), + expected: expected{ + err: "range: invalid token invalid expression. is empty", + }, + }, + { + token: &rangeToken{ + from: 0, + step: &expressionToken{ + expression: "'key'", + compiledExpression: &testCompiledExpression{response: "key"}, }, - expected: expected{ - obj: []interface{}{}, + }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, }, - { - input: input{ - token: &rangeToken{}, - obj: []string{"one", "two", "three", "four", "five"}, - start: intPtr(1), - end: intPtr(2), + expected: expected{ + err: "range: invalid token unexpected expression result. expected [int] got [string]", + }, + }, + { + token: &rangeToken{ + from: 0, + step: &indexToken{index: 0}, + }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, - expected: expected{ - obj: []interface{}{"two"}, + }, + expected: expected{ + err: "range: invalid token unexpected expression result. expected [int] got [string]", + }, + }, + { + token: &rangeToken{ + from: 0, + step: &expressionToken{ + expression: "@.length-1", + compiledExpression: &testCompiledExpression{response: 2}, }, }, - { - input: input{ - token: &rangeToken{allowMap: true}, - obj: map[string]interface{}{ - "b": "bee", - "a": "ae", - "c": "see", - "e": "ee", - "f": "eff", - "g": "gee", - "d": "dee", - }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", }, - expected: expected{ - obj: []interface{}{ - "ae", - "bee", - "see", - "dee", - "ee", - "eff", - "gee", - }, + }, + expected: expected{ + value: []interface{}{ + "one", + "three", }, }, - { - input: input{ - token: &rangeToken{allowMap: true}, - obj: map[string]interface{}{ - "b": "bee", - "a": "ae", - "c": "see", - "e": "ee", - "f": "eff", - "g": "gee", - "d": "dee", + }, + { + token: &rangeToken{ + from: 0, + }, + input: input{ + tokens: []Token{&keyToken{key: "name"}}, + current: []map[string]interface{}{ + { + "name": "one", }, - step: intPtr(2), - }, - expected: expected{ - obj: []interface{}{ - "ae", - "see", - "ee", - "gee", + { + "name": "two", }, - }, - }, - { - input: input{ - token: &rangeToken{allowMap: true}, - obj: map[string]interface{}{ - "b": "bee", - "a": "ae", - "c": "see", - "e": "ee", - "f": "eff", - "g": "gee", - "d": "dee", + { + "name": "three", + }, + { + "name": "four", }, - start: intPtr(1), - end: intPtr(-2), - }, - expected: expected{ - obj: []interface{}{ - "bee", - "see", - "dee", - "ee", + { + "name": "five", }, }, }, - { - input: input{ - token: &rangeToken{}, - obj: []string{"one", "two", "three", "four", "five"}, - step: intPtr(-1), - }, - expected: expected{ - obj: []interface{}{"five", "four", "three", "two", "one"}, + expected: expected{ + value: []interface{}{ + "one", + "two", + "three", + "four", + "five", }, }, - { - input: input{ - token: &rangeToken{allowString: true}, - obj: "abcdef", - step: intPtr(-1), - }, - expected: expected{ - obj: "fedcba", - }, + }, + { + token: &rangeToken{ + from: 1, + to: -2, }, - { - input: input{ - token: &rangeToken{allowMap: true}, - obj: map[string]interface{}{ - "b": "bee", - "a": "ae", - "c": "see", - "e": "ee", - "d": "dee", + input: input{ + tokens: []Token{&keyToken{key: "name"}}, + current: []map[string]interface{}{ + { + "name": "one", + }, + { + "name": "two", + }, + { + "name": "three", + }, + { + "name": "four", + }, + { + "name": "five", }, - step: intPtr(-1), - }, - expected: expected{ - obj: []interface{}{"ee", "dee", "see", "bee", "ae"}, }, }, - { - input: input{ - token: &rangeToken{}, - obj: []string{"one", "two", "three", "four", "five"}, - start: intPtr(1), - end: intPtr(2), - step: intPtr(-1), - }, - expected: expected{ - obj: []interface{}{"two"}, + expected: expected{ + value: []interface{}{ + "two", + "three", }, }, - { - input: input{ - token: &rangeToken{allowString: true}, - obj: "abcdef", - step: intPtr(-1), - start: intPtr(1), - end: intPtr(2), - }, - expected: expected{ - obj: "b", - }, + }, + { + token: &rangeToken{from: 10}, + input: input{ + current: "this is a substring", }, - { - input: input{ - token: &rangeToken{}, - obj: map[string]interface{}{ - "b": "bee", - "a": "ae", - "c": "see", - "e": "ee", - "d": "dee", - }, - start: intPtr(1), - end: intPtr(2), - step: intPtr(-1), - }, - expected: expected{ - err: "range: invalid token target. expected [array slice] got [map]", - }, + expected: expected{ + err: "range: invalid token target. expected [array slice] got [string]", }, - { - input: input{ - token: &rangeToken{allowMap: true}, - obj: map[string]interface{}{ - "b": "bee", - "a": "ae", - "c": "see", - "e": "ee", - "d": "dee", - }, - start: intPtr(1), - end: intPtr(2), - step: intPtr(-1), - }, - expected: expected{ - obj: []interface{}{"bee"}, - }, + }, + { + token: &rangeToken{from: 10, allowString: true}, + input: input{ + current: "this is a substring", }, - { - input: input{ - token: &rangeToken{}, - obj: []string{"one", "two", "three", "four", "five"}, - step: intPtr(-1), - start: intPtr(1), - end: intPtr(5), - }, - expected: expected{ - obj: []interface{}{"five", "four", "three", "two"}, - }, + expected: expected{ + value: "substring", }, - { - input: input{ - token: &rangeToken{}, - obj: "abcdef", - step: intPtr(-1), - start: intPtr(1), - end: intPtr(5), - }, - expected: expected{ - err: "range: invalid token target. expected [array slice] got [string]", + }, + { + token: &rangeToken{from: -9, allowString: true}, + input: input{ + current: "this is a substring", + tokens: []Token{ + &indexToken{index: 0, allowString: true}, }, }, - { - input: input{ - token: &rangeToken{allowString: true}, - obj: "abcdef", - step: intPtr(-1), - start: intPtr(1), - end: intPtr(5), - }, - expected: expected{ - obj: "edcb", - }, + expected: expected{ + value: "s", }, - { - input: input{ - token: &rangeToken{allowMap: true}, - obj: map[string]interface{}{ - "b": "bee", - "a": "ae", - "c": "see", - "e": "ee", - "d": "dee", - }, - step: intPtr(-1), - start: intPtr(1), - end: intPtr(5), + }, + { + token: &rangeToken{from: 1}, + input: input{ + current: []interface{}{ + "one", + "two", + "three", + "four", }, - expected: expected{ - obj: []interface{}{"ee", "dee", "see", "bee"}, + tokens: []Token{ + &indexToken{ + index: 0, + }, }, }, - { - input: input{ - token: &rangeToken{}, - obj: []string{"one", "two", "three", "four", "five"}, - start: intPtr(-10), + expected: expected{ + value: "two", + }, + }, + { + token: &rangeToken{ + from: &expressionToken{ + expression: "nil", + compiledExpression: &testCompiledExpression{response: nil}, }, - expected: expected{ - obj: []interface{}{"one", "two", "three", "four", "five"}, + }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", + "four", }, }, - { - input: input{ - token: &rangeToken{}, - obj: []string{"one", "two", "three", "four", "five"}, - start: intPtr(0), - end: intPtr(-10), + expected: expected{ + err: "range: invalid token unexpected expression result. expected [int] got [nil]", + }, + }, + { + token: &rangeToken{ + from: 0, + to: &expressionToken{ + expression: "nil", + compiledExpression: &testCompiledExpression{response: nil}, }, - expected: expected{ - obj: []interface{}{}, + }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", + "four", }, }, - } - - for idx, test := range tests { - t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { - obj, err := test.input.token.getRange(test.input.obj, test.input.start, test.input.end, test.input.step) - - if test.expected.obj == nil { - assert.Nil(t, obj) - } else { - assert.Equal(t, test.expected.obj, obj) - } - - if test.expected.err == "" { - assert.Nil(t, err) - } else { - assert.EqualError(t, err, test.expected.err) - } - }) - } + expected: expected{ + err: "range: invalid token unexpected expression result. expected [int] got [nil]", + }, + }, + { + token: &rangeToken{ + from: 0, + to: 1, + step: &expressionToken{ + expression: "nil", + compiledExpression: &testCompiledExpression{response: nil}, + }, + }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", + "four", + }, + }, + expected: expected{ + err: "range: invalid token unexpected expression result. expected [int] got [nil]", + }, + }, + { + token: &rangeToken{ + from: 0, + to: 1, + }, + input: input{ + current: 123, + }, + expected: expected{ + err: "range: invalid token target. expected [array slice] got [int]", + }, + }, + { + token: &rangeToken{}, + input: input{ + current: nil, + }, + expected: expected{ + err: "range: invalid token target. expected [array slice] got [nil]", + }, + }, + { + token: &rangeToken{}, + input: input{ + current: 123, + }, + expected: expected{ + err: "range: invalid token target. expected [array slice] got [int]", + }, + }, + { + token: &rangeToken{allowMap: true}, + input: input{ + current: 123, + }, + expected: expected{ + err: "range: invalid token target. expected [array slice map] got [int]", + }, + }, + { + token: &rangeToken{allowString: true}, + input: input{ + current: 123, + }, + expected: expected{ + err: "range: invalid token target. expected [array slice string] got [int]", + }, + }, + { + token: &rangeToken{allowMap: true, allowString: true}, + input: input{ + current: 123, + }, + expected: expected{ + err: "range: invalid token target. expected [array slice map string] got [int]", + }, + }, + { + token: &rangeToken{ + from: 18, + allowString: true, + }, + input: input{ + current: "return after this:result text", + }, + expected: expected{ + value: "result text", + }, + }, + { + token: &rangeToken{from: 15}, + input: input{ + current: []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", "eleven", "twelve", 13}, + }, + expected: expected{ + value: []interface{}{}, + }, + }, + { + token: &rangeToken{to: 15}, + input: input{ + current: []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", "eleven", "twelve", 13}, + }, + expected: expected{ + value: []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", "eleven", "twelve", 13}, + }, + }, + { + token: &rangeToken{step: 0}, + input: input{ + current: []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", "eleven", "twelve", 13}, + }, + expected: expected{ + err: "range: invalid token out of range", + }, + }, + { + token: &rangeToken{}, + input: input{ + current: []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", "eleven", "twelve", 13}, + }, + expected: expected{ + value: []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", "eleven", "twelve", 13}, + }, + }, + { + token: &rangeToken{to: nil}, + input: input{ + current: []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", "eleven", "twelve", 13}, + }, + expected: expected{ + value: []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", "eleven", "twelve", 13}, + }, + }, + { + token: &rangeToken{to: -1}, + input: input{ + current: []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", "eleven", "twelve", 13}, + }, + expected: expected{ + value: []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", "eleven", "twelve"}, + }, + }, + { + token: &rangeToken{to: -3}, + input: input{ + current: []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", "eleven", "twelve", 13}, + }, + expected: expected{ + value: []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten"}, + }, + }, + { + token: &rangeToken{ + from: -3, + to: -1, + }, + input: input{ + current: []interface{}{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", "eleven", "twelve", 13}, + }, + expected: expected{ + value: []interface{}{"eleven", "twelve"}, + }, + }, + { + token: &rangeToken{ + step: 2, + }, + input: input{ + current: []string{"one", "two", "three", "four", "five"}, + }, + expected: expected{ + value: []interface{}{"one", "three", "five"}, + }, + }, + { + token: &rangeToken{from: 1, step: 2}, + input: input{ + current: []string{"one", "two", "three", "four", "five"}, + }, + expected: expected{ + value: []interface{}{"two", "four"}, + }, + }, + { + token: &rangeToken{from: 1, to: 1}, + input: input{ + current: []string{"one", "two", "three", "four", "five"}, + }, + expected: expected{ + value: []interface{}{}, + }, + }, + { + token: &rangeToken{ + from: 1, + to: 2, + }, + input: input{ + current: []string{"one", "two", "three", "four", "five"}, + }, + expected: expected{ + value: []interface{}{"two"}, + }, + }, + { + token: &rangeToken{allowMap: true}, + input: input{ + current: map[string]interface{}{ + "b": "bee", + "a": "ae", + "c": "see", + "e": "ee", + "f": "eff", + "g": "gee", + "d": "dee", + }, + }, + expected: expected{ + value: []interface{}{ + "ae", + "bee", + "see", + "dee", + "ee", + "eff", + "gee", + }, + }, + }, + { + token: &rangeToken{allowMap: true, step: 2}, + input: input{ + current: map[string]interface{}{ + "b": "bee", + "a": "ae", + "c": "see", + "e": "ee", + "f": "eff", + "g": "gee", + "d": "dee", + }, + }, + expected: expected{ + value: []interface{}{ + "ae", + "see", + "ee", + "gee", + }, + }, + }, + { + token: &rangeToken{allowMap: true, from: 1, to: -2}, + input: input{ + current: map[string]interface{}{ + "b": "bee", + "a": "ae", + "c": "see", + "e": "ee", + "f": "eff", + "g": "gee", + "d": "dee", + }, + }, + expected: expected{ + value: []interface{}{ + "bee", + "see", + "dee", + "ee", + }, + }, + }, + { + token: &rangeToken{step: -1}, + input: input{ + current: []string{"one", "two", "three", "four", "five"}, + }, + expected: expected{ + value: []interface{}{"five", "four", "three", "two", "one"}, + }, + }, + { + token: &rangeToken{allowString: true, step: -1}, + input: input{ + current: "abcdef", + }, + expected: expected{ + value: "fedcba", + }, + }, + { + token: &rangeToken{allowMap: true, step: -1}, + input: input{ + current: map[string]interface{}{ + "b": "bee", + "a": "ae", + "c": "see", + "e": "ee", + "d": "dee", + }, + }, + expected: expected{ + value: []interface{}{"ee", "dee", "see", "bee", "ae"}, + }, + }, + { + token: &rangeToken{from: 1, to: 2, step: -1}, + input: input{ + current: []string{"one", "two", "three", "four", "five"}, + }, + expected: expected{ + value: []interface{}{"two"}, + }, + }, + { + token: &rangeToken{allowString: true, from: 1, to: 2, step: -1}, + input: input{ + current: "abcdef", + }, + expected: expected{ + value: "b", + }, + }, + { + token: &rangeToken{from: 1, to: 2, step: -1}, + input: input{ + current: map[string]interface{}{ + "b": "bee", + "a": "ae", + "c": "see", + "e": "ee", + "d": "dee", + }, + }, + expected: expected{ + err: "range: invalid token target. expected [array slice] got [map]", + }, + }, + { + token: &rangeToken{allowMap: true, from: 1, to: 2, step: -1}, + input: input{ + current: map[string]interface{}{ + "b": "bee", + "a": "ae", + "c": "see", + "e": "ee", + "d": "dee", + }, + }, + expected: expected{ + value: []interface{}{"bee"}, + }, + }, + { + token: &rangeToken{from: 1, to: 5, step: -1}, + input: input{ + current: []string{"one", "two", "three", "four", "five"}, + }, + expected: expected{ + value: []interface{}{"five", "four", "three", "two"}, + }, + }, + { + token: &rangeToken{from: 1, to: 5, step: -1}, + input: input{ + current: "abcdef", + }, + expected: expected{ + err: "range: invalid token target. expected [array slice] got [string]", + }, + }, + { + token: &rangeToken{allowString: true, from: 1, to: 5, step: -1}, + input: input{ + current: "abcdef", + }, + expected: expected{ + value: "edcb", + }, + }, + { + token: &rangeToken{allowMap: true, from: 1, to: 5, step: -1}, + input: input{ + current: map[string]interface{}{ + "b": "bee", + "a": "ae", + "c": "see", + "e": "ee", + "d": "dee", + }, + }, + expected: expected{ + value: []interface{}{"ee", "dee", "see", "bee"}, + }, + }, + { + token: &rangeToken{from: -10}, + input: input{ + current: []string{"one", "two", "three", "four", "five"}, + }, + expected: expected{ + value: []interface{}{"one", "two", "three", "four", "five"}, + }, + }, + { + token: &rangeToken{from: 0, to: -10}, + input: input{ + current: []string{"one", "two", "three", "four", "five"}, + }, + expected: expected{ + value: []interface{}{}, + }, + }, + { + token: &rangeToken{}, + input: input{ + current: []string{"one", "two", "three", "four", "five"}, + tokens: []Token{&keyToken{"key"}}, + }, + expected: expected{ + value: []interface{}{}, + }, + }, + { + token: &rangeToken{}, + input: input{ + current: []string{"one", "two", "three", "four", "five"}, + tokens: []Token{&testToken{value: nil}}, + }, + expected: expected{ + value: []interface{}{}, + }, + }, } diff --git a/token/recursive_test.go b/token/recursive_test.go index b5df00d..e5fa1b9 100644 --- a/token/recursive_test.go +++ b/token/recursive_test.go @@ -261,6 +261,29 @@ var recursiveTokenTests = []*tokenTest{ }, }, }, + { + token: &recursiveToken{}, + input: input{ + current: &sampleStruct{ + One: "one", + Two: "two", + Three: 3, + Four: 4, + Five: "five", + Six: "six", + }, + tokens: []Token{¤tToken{}, &wildcardToken{}}, + }, + expected: expected{ + value: []interface{}{ + "one", + "two", + int64(4), + "five", + "six", + }, + }, + }, } func Test_RecursiveToken_Apply(t *testing.T) { diff --git a/token/root_test.go b/token/root_test.go index 633d3db..4b99ed1 100644 --- a/token/root_test.go +++ b/token/root_test.go @@ -21,37 +21,40 @@ func Test_RootToken_Type(t *testing.T) { assert.Equal(t, "root", (&rootToken{}).Type()) } -func Test_RootToken_Apply(t *testing.T) { - - tests := []*tokenTest{ - { - token: &rootToken{}, - input: input{ - root: map[string]interface{}{ - "name": "first", - }, - }, - expected: expected{ - value: map[string]interface{}{ - "name": "first", - }, +var tests = []*tokenTest{ + { + token: &rootToken{}, + input: input{ + root: map[string]interface{}{ + "name": "first", }, }, - { - token: &rootToken{}, - input: input{ - root: map[string]interface{}{ - "name": "first", - }, - tokens: []Token{&keyToken{ - key: "name", - }}, + expected: expected{ + value: map[string]interface{}{ + "name": "first", }, - expected: expected{ - value: "first", + }, + }, + { + token: &rootToken{}, + input: input{ + root: map[string]interface{}{ + "name": "first", }, + tokens: []Token{&keyToken{ + key: "name", + }}, + }, + expected: expected{ + value: "first", }, - } + }, +} +func Test_RootToken_Apply(t *testing.T) { batchTokenTests(t, tests) } + +func Benchmark_RootToken_Apply(b *testing.B) { + batchTokenBenchmarks(b, tests) +} diff --git a/token/script_test.go b/token/script_test.go index f343170..0ff3727 100644 --- a/token/script_test.go +++ b/token/script_test.go @@ -47,73 +47,76 @@ func Test_ScriptToken_Type(t *testing.T) { assert.Equal(t, "script", (&scriptToken{}).Type()) } -func Test_ScriptToken_Apply(t *testing.T) { - - tests := []*tokenTest{ - { - token: &scriptToken{}, - input: input{}, - expected: expected{ - err: "invalid expression. is empty", - }, +var scriptTests = []*tokenTest{ + { + token: &scriptToken{}, + input: input{}, + expected: expected{ + err: "invalid expression. is empty", }, - { - token: &scriptToken{ - expression: "engine error", - compiledExpression: &testCompiledExpression{err: fmt.Errorf("engine error")}, - }, - input: input{}, - expected: expected{ - err: "invalid expression. engine error", - }, + }, + { + token: &scriptToken{ + expression: "engine error", + compiledExpression: &testCompiledExpression{err: fmt.Errorf("engine error")}, }, - { - token: &scriptToken{ - expression: "nil response", - compiledExpression: &testCompiledExpression{response: nil}, - }, - input: input{}, - expected: expected{ - err: "unexpected expression result. expected [int string] got [nil]", - }, + input: input{}, + expected: expected{ + err: "invalid expression. engine error", }, - { - token: &scriptToken{ - expression: "bool response", - compiledExpression: &testCompiledExpression{response: true}, - }, - input: input{}, - expected: expected{ - err: "unexpected expression result. expected [int string] got [bool]", - }, + }, + { + token: &scriptToken{ + expression: "nil response", + compiledExpression: &testCompiledExpression{response: nil}, }, - { - token: &scriptToken{ - expression: "string response", - compiledExpression: &testCompiledExpression{response: "key"}, - }, - input: input{ - current: map[string]interface{}{ - "key": "value", - }, - }, - expected: expected{ - value: "value", - }, + input: input{}, + expected: expected{ + err: "unexpected expression result. expected [int string] got [nil]", }, - { - token: &scriptToken{ - expression: "int response", - compiledExpression: &testCompiledExpression{response: 1}, - }, - input: input{ - current: []string{"one", "two", "three"}, - }, - expected: expected{ - value: "two", + }, + { + token: &scriptToken{ + expression: "bool response", + compiledExpression: &testCompiledExpression{response: true}, + }, + input: input{}, + expected: expected{ + err: "unexpected expression result. expected [int string] got [bool]", + }, + }, + { + token: &scriptToken{ + expression: "string response", + compiledExpression: &testCompiledExpression{response: "key"}, + }, + input: input{ + current: map[string]interface{}{ + "key": "value", }, }, - } + expected: expected{ + value: "value", + }, + }, + { + token: &scriptToken{ + expression: "int response", + compiledExpression: &testCompiledExpression{response: 1}, + }, + input: input{ + current: []string{"one", "two", "three"}, + }, + expected: expected{ + value: "two", + }, + }, +} + +func Test_ScriptToken_Apply(t *testing.T) { + batchTokenTests(t, scriptTests) +} - batchTokenTests(t, tests) +func Benchmark_ScriptToken_Apply(b *testing.B) { + batchTokenBenchmarks(b, scriptTests) } diff --git a/token/token.go b/token/token.go index ca0b16b..0a1ab88 100644 --- a/token/token.go +++ b/token/token.go @@ -15,8 +15,8 @@ support double quotes in keys? // Token represents a component of a JSON Path selector type Token interface { Apply(root, current interface{}, next []Token) (interface{}, error) - Type() string String() string + Type() string } // Tokenize converts a JSON Path selector to a collection of parsable tokens diff --git a/token/token_test.go b/token/token_test.go index beac94c..793c1ce 100644 --- a/token/token_test.go +++ b/token/token_test.go @@ -8,6 +8,17 @@ import ( "github.com/stretchr/testify/assert" ) +type testToken struct { + value interface{} + err error +} + +func (token *testToken) Apply(root, current interface{}, next []Token) (interface{}, error) { + return token.value, token.err +} +func (token *testToken) String() string { return "test" } +func (token *testToken) Type() string { return "test" } + func Test_Parse(t *testing.T) { type input struct { diff --git a/token/union.go b/token/union.go index 1f48e20..3da1d8b 100644 --- a/token/union.go +++ b/token/union.go @@ -65,96 +65,81 @@ func (token *unionToken) Apply(root, current interface{}, next []Token) (interfa indices := make([]int64, 0) for _, arg := range arguments { - if argToken, ok := arg.(Token); ok { - result, err := argToken.Apply(root, current, nil) - if err != nil { - return nil, getInvalidTokenError(token.Type(), err) - } - arg = result - } - - if arg == nil { - return nil, getInvalidTokenArgumentNilError(token.Type(), reflect.Int, reflect.String) + argument, kind, err := token.parseArgument(root, current, arg) + if err != nil { + return nil, err } - if strArg, ok := arg.(string); ok { - keys = append(keys, strArg) + switch kind { + case reflect.String: + keys = append(keys, argument.(string)) if len(indices) > 0 { return nil, getInvalidTokenArgumentError(token.Type(), reflect.String, reflect.Int) } - } else if intArg, ok := isInteger(arg); ok { - indices = append(indices, intArg) + break + case reflect.Int64: + indices = append(indices, argument.(int64)) if len(keys) > 0 { return nil, getInvalidTokenArgumentError(token.Type(), reflect.Int, reflect.String) } - } else { - argType := reflect.TypeOf(arg) - return nil, getInvalidTokenArgumentError(token.Type(), argType.Kind(), reflect.Int, reflect.String) + break } } - var unionValue interface{} - if len(keys) > 0 { - var err error - unionValue, err = token.getUnionByKey(current, keys) - if err != nil { - return nil, getInvalidTokenError(token.Type(), err) - } - } else if len(indices) > 0 { - var err error - unionValue, err = token.getUnionByIndex(current, indices) - if err != nil { - return nil, getInvalidTokenError(token.Type(), err) - } + return token.getUnionByKey(root, current, keys, next) } + return token.getUnionByIndex(root, current, indices, next) +} - if strValue, ok := unionValue.(string); ok { - if len(next) > 0 { - return next[0].Apply(root, strValue, next[1:]) +func (token *unionToken) parseArgument(root, current, argument interface{}) (interface{}, reflect.Kind, error) { + if argToken, ok := argument.(Token); ok { + result, err := argToken.Apply(root, current, nil) + if err != nil { + return nil, reflect.Invalid, getInvalidTokenError(token.Type(), err) } - return strValue, nil + argument = result } - elements := unionValue.([]interface{}) - - if len(next) > 0 { - nextToken := next[0] - futureTokens := next[1:] - - if indexToken, ok := nextToken.(*indexToken); ok { - // if next is asking for specific index - return indexToken.Apply(current, elements, futureTokens) - } - // any other token type - results := make([]interface{}, 0) - - for _, item := range elements { - result, _ := nextToken.Apply(root, item, futureTokens) - if result != nil { - results = append(results, result) - } - } - - return results, nil + if argument == nil { + return nil, reflect.Invalid, getInvalidTokenArgumentNilError(token.Type(), reflect.Int, reflect.String) } - return elements, nil + if strArg, ok := argument.(string); ok { + return strArg, reflect.String, nil + } else if intArg, ok := isInteger(argument); ok { + return intArg, reflect.Int64, nil + } + argType := reflect.TypeOf(argument) + return nil, reflect.Invalid, getInvalidTokenArgumentError(token.Type(), argType.Kind(), reflect.Int, reflect.String) } -func (token *unionToken) getUnionByKey(obj interface{}, keys []string) ([]interface{}, error) { - objType, objVal := getTypeAndValue(obj) +func (token *unionToken) getUnionByKey(root, current interface{}, keys []string, next []Token) (interface{}, error) { + objType, objVal := getTypeAndValue(current) if objType == nil { return nil, getInvalidTokenTargetNilError(token.Type(), reflect.Map) } + var nextToken Token + var futureTokens []Token + forEach := false + + if len(next) > 0 { + nextToken = next[0] + futureTokens = next[1:] + + if _, ok := nextToken.(*indexToken); !ok { + forEach = true + } + } + + elements := make([]interface{}, 0) + switch objType.Kind() { case reflect.Map: mapKeys := objVal.MapKeys() sortMapKeys(mapKeys) - elements := make([]interface{}, 0) - keysMap := make(map[string]reflect.Value) for _, key := range mapKeys { keysMap[key.String()] = key @@ -164,7 +149,10 @@ func (token *unionToken) getUnionByKey(obj interface{}, keys []string) ([]interf for _, requestedKey := range keys { if key, ok := keysMap[requestedKey]; ok { - elements = append(elements, objVal.MapIndex(key).Interface()) + val := objVal.MapIndex(key).Interface() + if item, add := token.handleNext(root, val, forEach, nextToken, futureTokens); add { + elements = append(elements, item) + } } else { missingKeys = append(missingKeys, requestedKey) } @@ -174,17 +162,16 @@ func (token *unionToken) getUnionByKey(obj interface{}, keys []string) ([]interf sort.Strings(missingKeys) return nil, getInvalidTokenKeyNotFoundError(token.Type(), strings.Join(missingKeys, ",")) } - - return elements, nil case reflect.Struct: - elements := make([]interface{}, 0) - keysMap := getStructFields(objVal, false) missingKeys := make([]string, 0) for _, requestedKey := range keys { if field, ok := keysMap[requestedKey]; ok { - elements = append(elements, objVal.FieldByName(field.Name).Interface()) + val := objVal.FieldByName(field.Name).Interface() + if item, add := token.handleNext(root, val, forEach, nextToken, futureTokens); add { + elements = append(elements, item) + } } else { missingKeys = append(missingKeys, requestedKey) } @@ -194,8 +181,6 @@ func (token *unionToken) getUnionByKey(obj interface{}, keys []string) ([]interf sort.Strings(missingKeys) return nil, getInvalidTokenKeyNotFoundError(token.Type(), strings.Join(missingKeys, ",")) } - - return elements, nil default: return nil, getInvalidTokenTargetError( token.Type(), @@ -203,9 +188,15 @@ func (token *unionToken) getUnionByKey(obj interface{}, keys []string) ([]interf reflect.Map, ) } + + if !forEach && nextToken != nil { + return nextToken.Apply(root, elements, futureTokens) + } + + return elements, nil } -func (token *unionToken) getUnionByIndex(obj interface{}, indices []int64) (interface{}, error) { +func (token *unionToken) getUnionByIndex(root, current interface{}, indices []int64, next []Token) (interface{}, error) { allowedType := []reflect.Kind{ reflect.Array, reflect.Slice, @@ -217,7 +208,7 @@ func (token *unionToken) getUnionByIndex(obj interface{}, indices []int64) (inte allowedType = append(allowedType, reflect.String) } - objType, objVal := getTypeAndValue(obj) + objType, objVal := getTypeAndValue(current) if objType == nil { return nil, getInvalidTokenTargetNilError( token.Type(), @@ -225,6 +216,19 @@ func (token *unionToken) getUnionByIndex(obj interface{}, indices []int64) (inte ) } + var nextToken Token + var futureTokens []Token + forEach := false + + if len(next) > 0 { + nextToken = next[0] + futureTokens = next[1:] + + if _, ok := nextToken.(*indexToken); !ok { + forEach = true + } + } + var length int64 var mapKeys []reflect.Value isString := false @@ -282,20 +286,47 @@ func (token *unionToken) getUnionByIndex(obj interface{}, indices []int64) (inte if mapKeys != nil { key := mapKeys[idx] - values = append(values, objVal.MapIndex(key).Interface()) + val := objVal.MapIndex(key).Interface() + if item, add := token.handleNext(root, val, forEach, nextToken, futureTokens); add { + values = append(values, item) + } } else if isString { value := objVal.Index(int(idx)).Interface() if u, ok := value.(uint8); ok { substring += fmt.Sprintf("%c", u) } } else { - values = append(values, objVal.Index(int(idx)).Interface()) + val := objVal.Index(int(idx)).Interface() + if item, add := token.handleNext(root, val, forEach, nextToken, futureTokens); add { + values = append(values, item) + } } } if isString { + if nextToken != nil { + return nextToken.Apply(root, substring, futureTokens) + } return substring, nil } + if !forEach && nextToken != nil { + return nextToken.Apply(root, values, futureTokens) + } + return values, nil } + +func (token *unionToken) handleNext(root, item interface{}, forEach bool, nextToken Token, futureTokens []Token) (interface{}, bool) { + if !forEach { + return item, true + } + val, err := nextToken.Apply(root, item, futureTokens) + if err != nil { + return nil, false + } + if val == nil { + return nil, false + } + return val, true +} diff --git a/token/union_test.go b/token/union_test.go index 902d7c3..b67e7f9 100644 --- a/token/union_test.go +++ b/token/union_test.go @@ -156,289 +156,293 @@ func Test_UnionToken_Type(t *testing.T) { assert.Equal(t, "union", (&unionToken{}).Type()) } -func Test_UnionToken_Apply(t *testing.T) { - - tests := []*tokenTest{ - { - token: &unionToken{}, - input: input{ - current: []interface{}{}, - }, - expected: expected{ - err: "union: invalid token argument. expected [array slice] got [nil]", - }, +var unionTests = []*tokenTest{ + { + token: &unionToken{}, + input: input{ + current: []interface{}{}, }, - { - token: &unionToken{ - arguments: []interface{}{ - &expressionToken{expression: "nil", compiledExpression: &testCompiledExpression{response: nil}}, - }, - }, - input: input{ - current: []interface{}{}, - }, - expected: expected{ - err: "union: invalid token argument. expected [int string] got [nil]", - }, + expected: expected{ + err: "union: invalid token argument. expected [array slice] got [nil]", }, - { - token: &unionToken{ - arguments: []interface{}{"one", 2}, - }, - input: input{ - current: []interface{}{}, - }, - expected: expected{ - err: "union: invalid token argument. expected [string] got [int]", + }, + { + token: &unionToken{ + arguments: []interface{}{ + &expressionToken{expression: "nil", compiledExpression: &testCompiledExpression{response: nil}}, }, }, - { - token: &unionToken{ - arguments: []interface{}{2, "one"}, - }, - input: input{ - current: []interface{}{}, - }, - expected: expected{ - err: "union: invalid token argument. expected [int] got [string]", - }, + input: input{ + current: []interface{}{}, }, - { - token: &unionToken{ - arguments: []interface{}{3.14}, - }, - input: input{ - current: []interface{}{1, 2, 3, 4, 5}, - }, - expected: expected{ - err: "union: invalid token argument. expected [int string] got [float64]", - }, + expected: expected{ + err: "union: invalid token argument. expected [int string] got [nil]", }, - { - token: &unionToken{ - arguments: []interface{}{ - &expressionToken{expression: "", compiledExpression: &testCompiledExpression{response: ""}}, - "one", - }, - }, - input: input{ - current: []interface{}{}, - }, - expected: expected{ - err: "union: invalid token invalid expression. is empty", - }, + }, + { + token: &unionToken{ + arguments: []interface{}{"one", 2}, }, - { - token: &unionToken{ - arguments: []interface{}{ - &expressionToken{expression: "1+1", compiledExpression: &testCompiledExpression{response: 2}}, - "one", - }, - }, - input: input{ - current: []interface{}{}, - }, - expected: expected{ - err: "union: invalid token argument. expected [int] got [string]", - }, + input: input{ + current: []interface{}{}, }, - { - token: &unionToken{ - arguments: []interface{}{ - &expressionToken{expression: "1+1", compiledExpression: &testCompiledExpression{response: 2}}, - 3, - }, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - "four", - }, - }, - expected: expected{ - value: []interface{}{ - "three", - "four", - }, - }, + expected: expected{ + err: "union: invalid token argument. expected [string] got [int]", }, - { - token: &unionToken{ - arguments: []interface{}{ - 0, - 3, - 4, - }, - }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - "four", - }, + }, + { + token: &unionToken{ + arguments: []interface{}{2, "one"}, + }, + input: input{ + current: []interface{}{}, + }, + expected: expected{ + err: "union: invalid token argument. expected [int] got [string]", + }, + }, + { + token: &unionToken{ + arguments: []interface{}{3.14}, + }, + input: input{ + current: []interface{}{1, 2, 3, 4, 5}, + }, + expected: expected{ + err: "union: invalid token argument. expected [int string] got [float64]", + }, + }, + { + token: &unionToken{ + arguments: []interface{}{ + &expressionToken{expression: "", compiledExpression: &testCompiledExpression{response: ""}}, + "one", }, - expected: expected{ - value: []interface{}{"one", "four"}, + }, + input: input{ + current: []interface{}{}, + }, + expected: expected{ + err: "union: invalid token invalid expression. is empty", + }, + }, + { + token: &unionToken{ + arguments: []interface{}{ + &expressionToken{expression: "1+1", compiledExpression: &testCompiledExpression{response: 2}}, + "one", }, }, - { - token: &unionToken{ - arguments: []interface{}{ - 0, - 3, - }, + input: input{ + current: []interface{}{}, + }, + expected: expected{ + err: "union: invalid token argument. expected [int] got [string]", + }, + }, + { + token: &unionToken{ + arguments: []interface{}{ + &expressionToken{expression: "1+1", compiledExpression: &testCompiledExpression{response: 2}}, + 3, }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - "four", - }, + }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", + "four", }, - expected: expected{ - value: []interface{}{ - "one", - "four", - }, + }, + expected: expected{ + value: []interface{}{ + "three", + "four", }, }, - { - token: &unionToken{ - arguments: []interface{}{ - "a", - "d", - }, - allowMap: true, + }, + { + token: &unionToken{ + arguments: []interface{}{ + 0, + 3, + 4, }, - input: input{ - current: map[string]interface{}{ - "a": "one", - "b": "two", - "c": "three", - "d": "four", - }, + }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", + "four", }, - expected: expected{ - value: []interface{}{ - "one", - "four", - }, + }, + expected: expected{ + value: []interface{}{"one", "four"}, + }, + }, + { + token: &unionToken{ + arguments: []interface{}{ + 0, + 3, }, }, - { - token: &unionToken{ - arguments: []interface{}{ - "a", - "d", - "e", - }, - allowMap: true, + input: input{ + current: []interface{}{ + "one", + "two", + "three", + "four", }, - input: input{ - current: map[string]interface{}{ - "a": "one", - "b": "two", - "c": "three", - "d": "four", - }, + }, + expected: expected{ + value: []interface{}{ + "one", + "four", }, - expected: expected{ - value: []interface{}{"one", "four"}, + }, + }, + { + token: &unionToken{ + arguments: []interface{}{ + "a", + "d", }, + allowMap: true, }, - { - token: &unionToken{ - arguments: []interface{}{0, 2, 4}, - allowString: true, + input: input{ + current: map[string]interface{}{ + "a": "one", + "b": "two", + "c": "three", + "d": "four", }, - input: input{ - current: "abcdefghijkl", + }, + expected: expected{ + value: []interface{}{ + "one", + "four", }, - expected: expected{ - value: "ace", + }, + }, + { + token: &unionToken{ + arguments: []interface{}{ + "a", + "d", + "e", }, + allowMap: true, }, - { - token: &unionToken{ - arguments: []interface{}{0, 2, 4}, - allowString: true, + input: input{ + current: map[string]interface{}{ + "a": "one", + "b": "two", + "c": "three", + "d": "four", }, - input: input{ - current: "abcdefghijkl", - tokens: []Token{ - &indexToken{index: 1, allowString: true}, - }, + }, + expected: expected{ + value: []interface{}{"one", "four"}, + }, + }, + { + token: &unionToken{ + arguments: []interface{}{0, 2, 4}, + allowString: true, + }, + input: input{ + current: "abcdefghijkl", + }, + expected: expected{ + value: "ace", + }, + }, + { + token: &unionToken{ + arguments: []interface{}{0, 2, 4}, + allowString: true, + }, + input: input{ + current: "abcdefghijkl", + tokens: []Token{ + &indexToken{index: 1, allowString: true}, }, - expected: expected{ - value: "c", + }, + expected: expected{ + value: "c", + }, + }, + { + token: &unionToken{ + arguments: []interface{}{ + 0, + 3, }, }, - { - token: &unionToken{ - arguments: []interface{}{ - 0, - 3, - }, + input: input{ + current: []interface{}{ + "one", + "two", + "three", + "four", }, - input: input{ - current: []interface{}{ - "one", - "two", - "three", - "four", - }, - tokens: []Token{ - &indexToken{ - index: -1, - }, + tokens: []Token{ + &indexToken{ + index: -1, }, }, - expected: expected{ - value: "four", + }, + expected: expected{ + value: "four", + }, + }, + { + token: &unionToken{ + arguments: []interface{}{ + 0, + 3, }, }, - { - token: &unionToken{ - arguments: []interface{}{ - 0, - 3, - }, + input: input{ + current: []map[string]interface{}{ + {"name": "one"}, + {"name": "two"}, + {"name": "three"}, + {"name": "four"}, }, - input: input{ - current: []map[string]interface{}{ - {"name": "one"}, - {"name": "two"}, - {"name": "three"}, - {"name": "four"}, - }, - tokens: []Token{ - &keyToken{ - key: "name", - }, + tokens: []Token{ + &keyToken{ + key: "name", }, }, - expected: expected{ - value: []interface{}{ - "one", - "four", - }, + }, + expected: expected{ + value: []interface{}{ + "one", + "four", }, }, - } + }, +} +func Test_UnionToken_Apply(t *testing.T) { batchTokenTests(t, tests) } +func Benchmark_UnionToken_Apply(b *testing.B) { + batchTokenBenchmarks(b, tests) +} + func Test_UnionToken_getUnionByIndex(t *testing.T) { type input struct { token *unionToken obj interface{} keys []int64 + next []Token } type expected struct { @@ -655,11 +659,66 @@ func Test_UnionToken_getUnionByIndex(t *testing.T) { obj: []interface{}{"two", "two"}, }, }, + { + input: input{ + token: &unionToken{}, + obj: []string{"one", "two", "three"}, + keys: []int64{1, 1}, + next: []Token{&indexToken{index: 1}}, + }, + expected: expected{ + obj: "two", + }, + }, + { + input: input{ + token: &unionToken{allowString: true}, + obj: "abcdefghijklmnopqrstuvwxyz", + keys: []int64{0, 2, 4}, + next: []Token{&indexToken{index: 2, allowString: true}}, + }, + expected: expected{ + obj: "e", + }, + }, + { + input: input{ + token: &unionToken{}, + obj: []string{"one", "two", "three"}, + keys: []int64{1, 2}, + next: []Token{&testToken{err: fmt.Errorf("fail")}}, + }, + expected: expected{ + obj: []interface{}{}, + }, + }, + { + input: input{ + token: &unionToken{}, + obj: []string{"one", "two", "three"}, + keys: []int64{1, 2}, + next: []Token{&testToken{value: nil}}, + }, + expected: expected{ + obj: []interface{}{}, + }, + }, + { + input: input{ + token: &unionToken{}, + obj: []string{"one", "two", "three"}, + keys: []int64{1, 2}, + next: []Token{&testToken{value: "1"}}, + }, + expected: expected{ + obj: []interface{}{"1", "1"}, + }, + }, } for idx, test := range tests { t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { - obj, err := test.input.token.getUnionByIndex(test.input.obj, test.input.keys) + obj, err := test.input.token.getUnionByIndex(nil, test.input.obj, test.input.keys, test.input.next) if test.expected.obj == nil { assert.Nil(t, obj) @@ -688,10 +747,11 @@ func Test_UnionToken_getUnionByKey(t *testing.T) { token *unionToken obj interface{} keys []string + next []Token } type expected struct { - obj []interface{} + obj interface{} err string } @@ -881,18 +941,72 @@ func Test_UnionToken_getUnionByKey(t *testing.T) { obj: []interface{}{"value", "value", "value"}, }, }, + { + input: input{ + token: &unionToken{}, + obj: map[string]interface{}{ + "a": "value", + }, + keys: []string{"a", "a", "a"}, + next: []Token{&indexToken{index: 1}}, + }, + expected: expected{ + obj: "value", + }, + }, + { + input: input{ + token: &unionToken{}, + obj: map[string]interface{}{ + "a": "value", + }, + keys: []string{"a", "a", "a"}, + next: []Token{&testToken{err: fmt.Errorf("fail")}}, + }, + expected: expected{ + obj: []interface{}{}, + }, + }, + { + input: input{ + token: &unionToken{}, + obj: map[string]interface{}{ + "a": "value", + }, + keys: []string{"a", "a", "a"}, + next: []Token{&testToken{value: nil}}, + }, + expected: expected{ + obj: []interface{}{}, + }, + }, + { + input: input{ + token: &unionToken{}, + obj: map[string]interface{}{ + "a": "value", + }, + keys: []string{"a", "a", "a"}, + next: []Token{&testToken{value: "1"}}, + }, + expected: expected{ + obj: []interface{}{"1", "1", "1"}, + }, + }, } for idx, test := range tests { t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { - obj, err := test.input.token.getUnionByKey(test.input.obj, test.input.keys) + obj, err := test.input.token.getUnionByKey(nil, test.input.obj, test.input.keys, test.input.next) if test.expected.obj == nil { assert.Nil(t, obj) } else { assert.NotNil(t, obj) - if obj != nil { - assert.ElementsMatch(t, test.expected.obj, obj) + if array, ok := obj.([]interface{}); ok { + assert.ElementsMatch(t, test.expected.obj, array) + } else { + assert.Equal(t, test.expected.obj, obj) } } diff --git a/token/wildcard.go b/token/wildcard.go index 193c0a5..3058363 100644 --- a/token/wildcard.go +++ b/token/wildcard.go @@ -31,17 +31,6 @@ func (token *wildcardToken) Apply(root, current interface{}, next []Token) (inte futureTokens = next[1:] } - handleNext := func(item interface{}) (interface{}, bool) { - if nextToken == nil { - return item, true - } - result, _ := nextToken.Apply(root, item, futureTokens) - if result == nil { - return nil, false - } - return result, true - } - objType, objVal := getTypeAndValue(current) if objType == nil { return nil, getInvalidTokenTargetNilError( @@ -56,7 +45,7 @@ func (token *wildcardToken) Apply(root, current interface{}, next []Token) (inte sortMapKeys(keys) for _, kv := range keys { value := objVal.MapIndex(kv).Interface() - if item, add := handleNext(value); add { + if item, add := token.handleNext(root, value, nextToken, futureTokens); add { elements = append(elements, item) } } @@ -65,7 +54,7 @@ func (token *wildcardToken) Apply(root, current interface{}, next []Token) (inte length := objVal.Len() for i := 0; i < length; i++ { value := objVal.Index(i).Interface() - if item, add := handleNext(value); add { + if item, add := token.handleNext(root, value, nextToken, futureTokens); add { elements = append(elements, item) } } @@ -73,7 +62,7 @@ func (token *wildcardToken) Apply(root, current interface{}, next []Token) (inte fields := getStructFields(objVal, true) for _, field := range fields { value := objVal.FieldByName(field.Name).Interface() - if item, add := handleNext(value); add { + if item, add := token.handleNext(root, value, nextToken, futureTokens); add { elements = append(elements, item) } } @@ -88,3 +77,14 @@ func (token *wildcardToken) Apply(root, current interface{}, next []Token) (inte return elements, nil } + +func (token *wildcardToken) handleNext(root, item interface{}, nextToken Token, futureTokens []Token) (interface{}, bool) { + if nextToken == nil { + return item, true + } + result, _ := nextToken.Apply(root, item, futureTokens) + if result == nil { + return nil, false + } + return result, true +}