12e974f892
* Added public Query interface * Added filter function callback support * Added "script" function callback support Queries are generated via Compile(), which then may be run via Execute() as many times as needed. Much like compiling a regex, this is done to elide the need to re-parse and build the funciton tree for each execution. The distinction between 'filter' and 'script' is borrowed from their syntactic equivalents in jsonpath. Right now, these accept no arguments in the query, and instead merely pass the current node to the callback. Filters return a bool and determine if the node is kept or culled out. 'Scripts' return a string or an in64, which is in turn used in an index or key filter (respectively) on the current node's data. A few callbacks are provided by default, with the ability to add additional callbacks before calling Execute() on a compiled query.
265 lines
5.3 KiB
Go
265 lines
5.3 KiB
Go
package jpath
|
|
|
|
import (
|
|
"fmt"
|
|
. "github.com/pelletier/go-toml"
|
|
"testing"
|
|
)
|
|
|
|
func assertQuery(t *testing.T, toml, query string, ref []interface{}) {
|
|
tree, err := Load(toml)
|
|
if err != nil {
|
|
t.Errorf("Non-nil toml parse error: %v", err)
|
|
return
|
|
}
|
|
results := Compile(query).Execute(tree)
|
|
assertValue(t, results, ref, "((" + query + ")) -> ")
|
|
}
|
|
|
|
func assertValue(t *testing.T, result, ref interface{}, location string) {
|
|
switch node := ref.(type) {
|
|
case []interface{}:
|
|
if resultNode, ok := result.([]interface{}); !ok {
|
|
t.Errorf("{%s} result value not of type %T: %T",
|
|
location, node, resultNode)
|
|
} else {
|
|
if len(node) != len(resultNode) {
|
|
t.Errorf("{%s} lengths do not match: %v vs %v",
|
|
location, node, resultNode)
|
|
} else {
|
|
for i, v := range node {
|
|
assertValue(t, resultNode[i], v, fmt.Sprintf("%s[%d]", location, i))
|
|
}
|
|
}
|
|
}
|
|
case map[string]interface{}:
|
|
if resultNode, ok := result.(*TomlTree); !ok {
|
|
t.Errorf("{%s} result value not of type %T: %T",
|
|
location, node, resultNode)
|
|
} else {
|
|
for k, v := range node {
|
|
assertValue(t, resultNode.GetPath([]string{k}), v, location+"."+k)
|
|
}
|
|
}
|
|
case int64:
|
|
if resultNode, ok := result.(int64); !ok {
|
|
t.Errorf("{%s} result value not of type %T: %T",
|
|
location, node, resultNode)
|
|
} else {
|
|
if node != resultNode {
|
|
t.Errorf("{%s} result value does not match", location)
|
|
}
|
|
}
|
|
case string:
|
|
if resultNode, ok := result.(string); !ok {
|
|
t.Errorf("{%s} result value not of type %T: %T",
|
|
location, node, resultNode)
|
|
} else {
|
|
if node != resultNode {
|
|
t.Errorf("{%s} result value does not match", location)
|
|
}
|
|
}
|
|
default:
|
|
if fmt.Sprintf("%v", node) != fmt.Sprintf("%v", ref) {
|
|
t.Errorf("{%s} result value does not match: %v != %v",
|
|
location, node, ref)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQueryRoot(t *testing.T) {
|
|
assertQuery(t,
|
|
"a = 42",
|
|
"$",
|
|
[]interface{}{
|
|
map[string]interface{}{
|
|
"a": int64(42),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestQueryKey(t *testing.T) {
|
|
assertQuery(t,
|
|
"[foo]\na = 42",
|
|
"$.foo.a",
|
|
[]interface{}{
|
|
int64(42),
|
|
})
|
|
}
|
|
|
|
func TestQueryKeyString(t *testing.T) {
|
|
assertQuery(t,
|
|
"[foo]\na = 42",
|
|
"$.foo['a']",
|
|
[]interface{}{
|
|
int64(42),
|
|
})
|
|
}
|
|
|
|
func TestQueryIndex(t *testing.T) {
|
|
assertQuery(t,
|
|
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
|
"$.foo.a[0]",
|
|
[]interface{}{
|
|
int64(1),
|
|
})
|
|
}
|
|
|
|
func TestQuerySliceRange(t *testing.T) {
|
|
assertQuery(t,
|
|
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
|
"$.foo.a[0:5]",
|
|
[]interface{}{
|
|
int64(1),
|
|
int64(2),
|
|
int64(3),
|
|
int64(4),
|
|
int64(5),
|
|
})
|
|
}
|
|
|
|
func TestQuerySliceStep(t *testing.T) {
|
|
assertQuery(t,
|
|
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
|
"$.foo.a[0:5:2]",
|
|
[]interface{}{
|
|
int64(1),
|
|
int64(3),
|
|
int64(5),
|
|
})
|
|
}
|
|
|
|
func TestQueryAny(t *testing.T) {
|
|
assertQuery(t,
|
|
"[foo.bar]\na=1\nb=2\n[foo.baz]\na=3\nb=4",
|
|
"$.foo.*",
|
|
[]interface{}{
|
|
map[string]interface{}{
|
|
"a": int64(1),
|
|
"b": int64(2),
|
|
},
|
|
map[string]interface{}{
|
|
"a": int64(3),
|
|
"b": int64(4),
|
|
},
|
|
})
|
|
}
|
|
func TestQueryUnionSimple(t *testing.T) {
|
|
assertQuery(t,
|
|
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
|
"$.*[bar,foo]",
|
|
[]interface{}{
|
|
map[string]interface{}{
|
|
"a": int64(1),
|
|
"b": int64(2),
|
|
},
|
|
map[string]interface{}{
|
|
"a": int64(3),
|
|
"b": int64(4),
|
|
},
|
|
map[string]interface{}{
|
|
"a": int64(5),
|
|
"b": int64(6),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestQueryRecursionAll(t *testing.T) {
|
|
assertQuery(t,
|
|
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
|
"$..*",
|
|
[]interface{}{
|
|
map[string]interface{}{
|
|
"bar": map[string]interface{}{
|
|
"a": int64(1),
|
|
"b": int64(2),
|
|
},
|
|
},
|
|
map[string]interface{}{
|
|
"a": int64(1),
|
|
"b": int64(2),
|
|
},
|
|
int64(1),
|
|
int64(2),
|
|
map[string]interface{}{
|
|
"foo": map[string]interface{}{
|
|
"a": int64(3),
|
|
"b": int64(4),
|
|
},
|
|
},
|
|
map[string]interface{}{
|
|
"a": int64(3),
|
|
"b": int64(4),
|
|
},
|
|
int64(3),
|
|
int64(4),
|
|
map[string]interface{}{
|
|
"foo": map[string]interface{}{
|
|
"a": int64(5),
|
|
"b": int64(6),
|
|
},
|
|
},
|
|
map[string]interface{}{
|
|
"a": int64(5),
|
|
"b": int64(6),
|
|
},
|
|
int64(5),
|
|
int64(6),
|
|
})
|
|
}
|
|
|
|
func TestQueryRecursionUnionSimple(t *testing.T) {
|
|
assertQuery(t,
|
|
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
|
"$..['foo','bar']",
|
|
[]interface{}{
|
|
map[string]interface{}{
|
|
"a": int64(1),
|
|
"b": int64(2),
|
|
},
|
|
map[string]interface{}{
|
|
"a": int64(3),
|
|
"b": int64(4),
|
|
},
|
|
map[string]interface{}{
|
|
"a": int64(5),
|
|
"b": int64(6),
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestQueryScriptFnLast(t *testing.T) {
|
|
assertQuery(t,
|
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
|
"$.foo.a[(last)]",
|
|
[]interface{}{
|
|
int64(9),
|
|
})
|
|
}
|
|
|
|
func TestQueryFilterFnOdd(t *testing.T) {
|
|
assertQuery(t,
|
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
|
"$.foo.a[?(odd)]",
|
|
[]interface{}{
|
|
int64(1),
|
|
int64(3),
|
|
int64(5),
|
|
int64(7),
|
|
int64(9),
|
|
})
|
|
}
|
|
|
|
func TestQueryFilterFnEven(t *testing.T) {
|
|
assertQuery(t,
|
|
"[foo]\na = [0,1,2,3,4,5,6,7,8,9]",
|
|
"$.foo.a[?(even)]",
|
|
[]interface{}{
|
|
int64(0),
|
|
int64(2),
|
|
int64(4),
|
|
int64(6),
|
|
int64(8),
|
|
})
|
|
}
|