package query import ( "fmt" "io/ioutil" "sort" "strings" "testing" "time" "github.com/pelletier/go-toml" ) type queryTestNode struct { value interface{} position toml.Position } func valueString(root interface{}) string { result := "" //fmt.Sprintf("%T:", root) switch node := root.(type) { case *Result: items := []string{} for i, v := range node.Values() { items = append(items, fmt.Sprintf("%s:%s", node.Positions()[i].String(), valueString(v))) } sort.Strings(items) result = "[" + strings.Join(items, ", ") + "]" case queryTestNode: result = fmt.Sprintf("%s:%s", node.position.String(), valueString(node.value)) case []interface{}: items := []string{} for _, v := range node { items = append(items, valueString(v)) } sort.Strings(items) result = "[" + strings.Join(items, ", ") + "]" case *toml.Tree: // workaround for unreliable map key ordering items := []string{} for _, k := range node.Keys() { v := node.GetPath([]string{k}) items = append(items, k+":"+valueString(v)) } sort.Strings(items) result = "{" + strings.Join(items, ", ") + "}" case map[string]interface{}: // workaround for unreliable map key ordering items := []string{} for k, v := range node { items = append(items, k+":"+valueString(v)) } sort.Strings(items) result = "{" + strings.Join(items, ", ") + "}" case int64: result += fmt.Sprintf("%d", node) case string: result += "'" + node + "'" case float64: result += fmt.Sprintf("%f", node) case bool: result += fmt.Sprintf("%t", node) case time.Time: result += fmt.Sprintf("'%v'", node) } return result } func assertValue(t *testing.T, result, ref interface{}) { pathStr := valueString(result) refStr := valueString(ref) if pathStr != refStr { t.Errorf("values do not match") t.Log("test:", pathStr) t.Log("ref: ", refStr) } } func assertParseError(t *testing.T, query string, errString string) { _, err := Compile(query) if err == nil { t.Error("error should be non-nil") return } if err.Error() != errString { t.Errorf("error does not match") t.Log("test:", err.Error()) t.Log("ref: ", errString) } } func assertQueryPositions(t *testing.T, tomlDoc string, query string, ref []interface{}) { tree, err := toml.Load(tomlDoc) if err != nil { t.Errorf("Non-nil toml parse error: %v", err) return } q, err := Compile(query) if err != nil { t.Error(err) return } results := q.Execute(tree) assertValue(t, results, ref) } func TestQueryRoot(t *testing.T) { assertQueryPositions(t, "a = 42", "$", []interface{}{ queryTestNode{ map[string]interface{}{ "a": int64(42), }, toml.Position{1, 1}, }, }) } func TestQueryKey(t *testing.T) { assertQueryPositions(t, "[foo]\na = 42", "$.foo.a", []interface{}{ queryTestNode{ int64(42), toml.Position{2, 1}, }, }) } func TestQueryKeyString(t *testing.T) { assertQueryPositions(t, "[foo]\na = 42", "$.foo['a']", []interface{}{ queryTestNode{ int64(42), toml.Position{2, 1}, }, }) } func TestQueryKeyUnicodeString(t *testing.T) { assertQueryPositions(t, "['f𝟘.o']\na = 42", "$['f𝟘.o']['a']", []interface{}{ queryTestNode{ int64(42), toml.Position{2, 1}, }, }) } func TestQueryIndexError1(t *testing.T) { assertParseError(t, "$.foo.a[5", "(1, 10): expected ',' or ']', not ''") } func TestQueryIndexError2(t *testing.T) { assertParseError(t, "$.foo.a[]", "(1, 9): expected union sub expression, not ']', 0") } func TestQueryIndex(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[5]", []interface{}{ queryTestNode{int64(5), toml.Position{2, 1}}, }) } func TestQueryIndexNegative(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[-2]", []interface{}{ queryTestNode{int64(8), toml.Position{2, 1}}, }) } func TestQueryIndexWrong(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[99]", []interface{}{}) } func TestQueryIndexEmpty(t *testing.T) { assertQueryPositions(t, "[foo]\na = []", "$.foo.a[5]", []interface{}{}) } func TestQueryIndexTree(t *testing.T) { assertQueryPositions(t, "[[foo]]\na = [0,1,2,3,4,5,6,7,8,9]\n[[foo]]\nb = 3", "$.foo[1].b", []interface{}{ queryTestNode{int64(3), toml.Position{4, 1}}, }) } func TestQuerySliceError1(t *testing.T) { assertParseError(t, "$.foo.a[3:?]", "(1, 11): expected ']' or ':'") } func TestQuerySliceError2(t *testing.T) { assertParseError(t, "$.foo.a[:::]", "(1, 11): expected ']'") } func TestQuerySliceError3(t *testing.T) { assertParseError(t, "$.foo.a[::0]", "(1, 11): step cannot be zero") } func TestQuerySliceRange(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[:5]", []interface{}{ queryTestNode{int64(0), toml.Position{2, 1}}, queryTestNode{int64(1), toml.Position{2, 1}}, queryTestNode{int64(2), toml.Position{2, 1}}, queryTestNode{int64(3), toml.Position{2, 1}}, queryTestNode{int64(4), toml.Position{2, 1}}, }) } func TestQuerySliceStep(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[0:5:2]", []interface{}{ queryTestNode{int64(0), toml.Position{2, 1}}, queryTestNode{int64(2), toml.Position{2, 1}}, queryTestNode{int64(4), toml.Position{2, 1}}, }) } func TestQuerySliceStartNegative(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[-3:]", []interface{}{ queryTestNode{int64(7), toml.Position{2, 1}}, queryTestNode{int64(8), toml.Position{2, 1}}, queryTestNode{int64(9), toml.Position{2, 1}}, }) } func TestQuerySliceEndNegative(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[:-6]", []interface{}{ queryTestNode{int64(0), toml.Position{2, 1}}, queryTestNode{int64(1), toml.Position{2, 1}}, queryTestNode{int64(2), toml.Position{2, 1}}, queryTestNode{int64(3), toml.Position{2, 1}}, }) } func TestQuerySliceStepNegative(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[::-2]", []interface{}{ queryTestNode{int64(9), toml.Position{2, 1}}, queryTestNode{int64(7), toml.Position{2, 1}}, queryTestNode{int64(5), toml.Position{2, 1}}, queryTestNode{int64(3), toml.Position{2, 1}}, queryTestNode{int64(1), toml.Position{2, 1}}, }) } func TestQuerySliceStartOverRange(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[-99:3]", []interface{}{ queryTestNode{int64(0), toml.Position{2, 1}}, queryTestNode{int64(1), toml.Position{2, 1}}, queryTestNode{int64(2), toml.Position{2, 1}}, }) } func TestQuerySliceStartOverRangeNegative(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[99:7:-1]", []interface{}{ queryTestNode{int64(9), toml.Position{2, 1}}, queryTestNode{int64(8), toml.Position{2, 1}}, }) } func TestQuerySliceEndOverRange(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[7:99]", []interface{}{ queryTestNode{int64(7), toml.Position{2, 1}}, queryTestNode{int64(8), toml.Position{2, 1}}, queryTestNode{int64(9), toml.Position{2, 1}}, }) } func TestQuerySliceEndOverRangeNegative(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[2:-99:-1]", []interface{}{ queryTestNode{int64(2), toml.Position{2, 1}}, queryTestNode{int64(1), toml.Position{2, 1}}, queryTestNode{int64(0), toml.Position{2, 1}}, }) } func TestQuerySliceWrongRange(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[5:3]", []interface{}{}) } func TestQuerySliceWrongRangeNegative(t *testing.T) { assertQueryPositions(t, "[foo]\na = [0,1,2,3,4,5,6,7,8,9]", "$.foo.a[3:5:-1]", []interface{}{}) } func TestQuerySliceEmpty(t *testing.T) { assertQueryPositions(t, "[foo]\na = []", "$.foo.a[5:]", []interface{}{}) } func TestQuerySliceTree(t *testing.T) { assertQueryPositions(t, "[[foo]]\na='nok'\n[[foo]]\na = [0,1,2,3,4,5,6,7,8,9]\n[[foo]]\na='ok'\nb = 3", "$.foo[1:].a", []interface{}{ queryTestNode{ []interface{}{ int64(0), int64(1), int64(2), int64(3), int64(4), int64(5), int64(6), int64(7), int64(8), int64(9)}, toml.Position{4, 1}}, queryTestNode{"ok", toml.Position{6, 1}}, }) } func TestQueryAny(t *testing.T) { assertQueryPositions(t, "[foo.bar]\na=1\nb=2\n[foo.baz]\na=3\nb=4", "$.foo.*", []interface{}{ queryTestNode{ map[string]interface{}{ "a": int64(1), "b": int64(2), }, toml.Position{1, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(3), "b": int64(4), }, toml.Position{4, 1}, }, }) } func TestQueryUnionSimple(t *testing.T) { assertQueryPositions(t, "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6", "$.*[bar,foo]", []interface{}{ queryTestNode{ map[string]interface{}{ "a": int64(1), "b": int64(2), }, toml.Position{1, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(3), "b": int64(4), }, toml.Position{4, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(5), "b": int64(6), }, toml.Position{7, 1}, }, }) } func TestQueryRecursionAll(t *testing.T) { assertQueryPositions(t, "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6", "$..*", []interface{}{ queryTestNode{ map[string]interface{}{ "foo": map[string]interface{}{ "bar": map[string]interface{}{ "a": int64(1), "b": int64(2), }, }, "baz": map[string]interface{}{ "foo": map[string]interface{}{ "a": int64(3), "b": int64(4), }, }, "gorf": map[string]interface{}{ "foo": map[string]interface{}{ "a": int64(5), "b": int64(6), }, }, }, toml.Position{1, 1}, }, queryTestNode{ map[string]interface{}{ "bar": map[string]interface{}{ "a": int64(1), "b": int64(2), }, }, toml.Position{1, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(1), "b": int64(2), }, toml.Position{1, 1}, }, queryTestNode{int64(1), toml.Position{2, 1}}, queryTestNode{int64(2), toml.Position{3, 1}}, queryTestNode{ map[string]interface{}{ "foo": map[string]interface{}{ "a": int64(3), "b": int64(4), }, }, toml.Position{4, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(3), "b": int64(4), }, toml.Position{4, 1}, }, queryTestNode{int64(3), toml.Position{5, 1}}, queryTestNode{int64(4), toml.Position{6, 1}}, queryTestNode{ map[string]interface{}{ "foo": map[string]interface{}{ "a": int64(5), "b": int64(6), }, }, toml.Position{7, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(5), "b": int64(6), }, toml.Position{7, 1}, }, queryTestNode{int64(5), toml.Position{8, 1}}, queryTestNode{int64(6), toml.Position{9, 1}}, }) } func TestQueryRecursionUnionSimple(t *testing.T) { assertQueryPositions(t, "[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6", "$..['foo','bar']", []interface{}{ queryTestNode{ map[string]interface{}{ "bar": map[string]interface{}{ "a": int64(1), "b": int64(2), }, }, toml.Position{1, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(3), "b": int64(4), }, toml.Position{4, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(1), "b": int64(2), }, toml.Position{1, 1}, }, queryTestNode{ map[string]interface{}{ "a": int64(5), "b": int64(6), }, toml.Position{7, 1}, }, }) } func TestQueryFilterFn(t *testing.T) { buff, err := ioutil.ReadFile("../example.toml") if err != nil { t.Error(err) return } assertQueryPositions(t, string(buff), "$..[?(int)]", []interface{}{ queryTestNode{int64(8001), toml.Position{13, 1}}, queryTestNode{int64(8001), toml.Position{13, 1}}, queryTestNode{int64(8002), toml.Position{13, 1}}, queryTestNode{int64(5000), toml.Position{14, 1}}, }) assertQueryPositions(t, string(buff), "$..[?(string)]", []interface{}{ queryTestNode{"TOML Example", toml.Position{3, 1}}, queryTestNode{"Tom Preston-Werner", toml.Position{6, 1}}, queryTestNode{"GitHub", toml.Position{7, 1}}, queryTestNode{"GitHub Cofounder & CEO\nLikes tater tots and beer.", toml.Position{8, 1}}, queryTestNode{"192.168.1.1", toml.Position{12, 1}}, queryTestNode{"10.0.0.1", toml.Position{21, 3}}, queryTestNode{"eqdc10", toml.Position{22, 3}}, queryTestNode{"10.0.0.2", toml.Position{25, 3}}, queryTestNode{"eqdc10", toml.Position{26, 3}}, }) assertQueryPositions(t, string(buff), "$..[?(float)]", []interface{}{ queryTestNode{4e-08, toml.Position{30, 1}}, }) tv, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") assertQueryPositions(t, string(buff), "$..[?(tree)]", []interface{}{ queryTestNode{ map[string]interface{}{ "name": "Tom Preston-Werner", "organization": "GitHub", "bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.", "dob": tv, }, toml.Position{5, 1}, }, queryTestNode{ map[string]interface{}{ "server": "192.168.1.1", "ports": []interface{}{int64(8001), int64(8001), int64(8002)}, "connection_max": int64(5000), "enabled": true, }, toml.Position{11, 1}, }, queryTestNode{ map[string]interface{}{ "alpha": map[string]interface{}{ "ip": "10.0.0.1", "dc": "eqdc10", }, "beta": map[string]interface{}{ "ip": "10.0.0.2", "dc": "eqdc10", }, }, toml.Position{17, 1}, }, queryTestNode{ map[string]interface{}{ "ip": "10.0.0.1", "dc": "eqdc10", }, toml.Position{20, 3}, }, queryTestNode{ map[string]interface{}{ "ip": "10.0.0.2", "dc": "eqdc10", }, toml.Position{24, 3}, }, queryTestNode{ map[string]interface{}{ "data": []interface{}{ []interface{}{"gamma", "delta"}, []interface{}{int64(1), int64(2)}, }, "score": 4e-08, }, toml.Position{28, 1}, }, }) assertQueryPositions(t, string(buff), "$..[?(time)]", []interface{}{ queryTestNode{tv, toml.Position{9, 1}}, }) assertQueryPositions(t, string(buff), "$..[?(bool)]", []interface{}{ queryTestNode{true, toml.Position{15, 1}}, }) }