From 0d8e10d4a04fa5150ac9a501cdbfb93ddd46b767 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sun, 5 Feb 2023 15:54:50 +0100 Subject: [PATCH 01/35] !feat: Replace engine rmap with engine playmon --- .../internal/constrcheck/bench_test.go | 187 ++ .../internal/constrcheck/constrcheck.go | 1725 +++++++++++++++++ .../internal/constrcheck/constrcheck_test.go | 119 ++ engines/playmon/internal/constrcheck/doc.go | 27 + .../constrcheck/internal/test/test.go | 144 ++ .../constrcheck/internal/union/union.go | 291 +++ .../constrcheck/internal/union/union_test.go | 304 +++ .../constrcheck/internal/unsafe/unsafe.go | 8 + .../internal/constrcheck/iswrongtype.go | 149 ++ .../internal/constrcheck/iswrongtype_test.go | 507 +++++ .../constrcheck/tests/arithmetic_div.yml | 33 + .../constrcheck/tests/arithmetic_mod.yml | 33 + .../constrcheck/tests/arithmetic_mul.yml | 33 + .../constrcheck/tests/arithmetic_sub.yml | 33 + .../tests/equal_strblock_false.todo.yaml | 15 + .../tests/false_arithmetic_add.yml | 27 + .../constrcheck/tests/false_constr_and.yml | 27 + .../constrcheck/tests/false_constr_or.yml | 27 + .../constrcheck/tests/false_equal_arr_int.yml | 39 + .../constrcheck/tests/false_equal_arr_str.yml | 39 + .../constrcheck/tests/false_equal_bool.yml | 21 + .../constrcheck/tests/false_equal_enum.yml | 16 + .../constrcheck/tests/false_equal_float.yml | 15 + .../constrcheck/tests/false_equal_int.yml | 15 + .../constrcheck/tests/false_equal_obj.yml | 35 + .../constrcheck/tests/false_equal_str.yml | 15 + .../tests/false_equal_strblock.yml | 15 + .../constrcheck/tests/false_interface.yml | 40 + .../constrcheck/tests/false_len_array_int.yml | 62 + .../constrcheck/tests/false_len_array_obj.yml | 96 + .../constrcheck/tests/false_len_str_eq.yml | 15 + .../constrcheck/tests/false_len_str_gr.yml | 15 + .../constrcheck/tests/false_len_str_greq.yml | 15 + .../constrcheck/tests/false_len_str_leeq.yml | 15 + .../constrcheck/tests/false_len_str_neq.yml | 15 + .../tests/false_len_strblock_eq.yml | 15 + .../tests/false_len_strblock_gr.yml | 15 + .../tests/false_len_strblock_greq.yml | 15 + .../tests/false_len_strblock_le.yaml | 15 + .../tests/false_len_strblock_le.yml | 15 + .../tests/false_len_strblock_leeq.yml | 15 + .../tests/false_len_strblock_neq.yml | 15 + .../constrcheck/tests/false_logical_equal.yml | 52 + .../tests/false_logical_expr_relational.yml | 39 + .../false_logical_expr_relational_float.yml | 39 + .../tests/false_logical_notequal.yml | 51 + .../constrcheck/tests/false_logical_or_eq.yml | 15 + .../tests/false_logical_or_gr_le.yml | 15 + .../constrcheck/tests/false_map_2d.yml | 47 + .../tests/false_map_array_object.yml | 32 + .../tests/false_map_var_arithmetic.yml | 25 + .../tests/false_not_equal_arr_int.yml | 19 + .../tests/false_not_equal_arr_str.yml | 19 + .../constrcheck/tests/false_not_equal_obj.yml | 36 + .../tests/false_relational_float.yml | 33 + .../tests/false_relational_int.yml | 33 + .../constrcheck/tests/false_union.yml | 33 + .../constrcheck/tests/false_var_bool.yml | 21 + .../constrcheck/tests/false_var_str.yml | 21 + .../constrcheck/tests/false_var_strblock.yml | 21 + .../tests/invalid_obj_duplicate_inputs.yml | 35 + .../invalid_obj_duplicate_inputs_in_any.yml | 24 + .../constrcheck/tests/logical_negation.yml | 46 + .../constrcheck/tests/numeric_negation.yml | 45 + .../constrcheck/tests/true_arithmetic_add.yml | 21 + .../constrcheck/tests/true_constr_and.yml | 27 + .../constrcheck/tests/true_constr_or.yml | 27 + .../constrcheck/tests/true_equal_arr_int.yml | 19 + .../constrcheck/tests/true_equal_arr_str.yml | 19 + .../constrcheck/tests/true_equal_bool.yml | 21 + .../constrcheck/tests/true_equal_enum.yml | 16 + .../constrcheck/tests/true_equal_float.yml | 15 + .../constrcheck/tests/true_equal_int.yml | 15 + .../constrcheck/tests/true_equal_obj.yml | 36 + .../constrcheck/tests/true_equal_str.yml | 15 + .../constrcheck/tests/true_equal_strblock.yml | 15 + .../constrcheck/tests/true_interface.yml | 40 + .../constrcheck/tests/true_len_array_int.yml | 70 + .../constrcheck/tests/true_len_array_obj.yml | 96 + .../constrcheck/tests/true_len_str.yml | 45 + .../constrcheck/tests/true_len_str_eq.yml | 15 + .../constrcheck/tests/true_len_str_gr.yml | 15 + .../constrcheck/tests/true_len_str_greq.yml | 15 + .../constrcheck/tests/true_len_str_le.yml | 15 + .../constrcheck/tests/true_len_str_leeq.yml | 15 + .../constrcheck/tests/true_len_str_neq.yml | 15 + .../constrcheck/tests/true_len_strblock.yml | 45 + .../tests/true_len_strblock_eq.yml | 15 + .../tests/true_len_strblock_gr.yml | 15 + .../tests/true_len_strblock_greq.yml | 15 + .../tests/true_len_strblock_le.yml | 15 + .../tests/true_len_strblock_leeq.yml | 15 + .../tests/true_len_strblock_neq.yml | 15 + .../constrcheck/tests/true_logical_equal.yml | 15 + .../tests/true_logical_expr_relational.yml | 39 + .../true_logical_expr_relational_float.yml | 39 + .../tests/true_logical_notequal.yml | 69 + .../constrcheck/tests/true_logical_or_eq.yml | 15 + .../tests/true_logical_or_gr_le.yml | 22 + .../constrcheck/tests/true_map_2d.yml | 47 + .../tests/true_map_array_object.yml | 32 + .../tests/true_map_var_arithmetic.yml | 32 + .../tests/true_not_equal_arr_int.yml | 46 + .../tests/true_not_equal_arr_str.yml | 46 + .../constrcheck/tests/true_not_equal_obj.yml | 35 + .../tests/true_relational_float.yml | 33 + .../constrcheck/tests/true_relational_int.yml | 33 + .../internal/constrcheck/tests/true_union.yml | 33 + .../constrcheck/tests/true_var_bool.yml | 21 + .../constrcheck/tests/true_var_str.yml | 21 + .../constrcheck/tests/true_var_strblock.yml | 21 + .../tests/var_equal_nonexistent.yml | 24 + .../constrcheck/tests/wrong_type_equal.yml | 58 + .../tests/wrong_type_equal_arrint.yml | 15 + .../tests/wrong_type_equal_bool_f.yml | 15 + .../tests/wrong_type_equal_bool_t.yml | 15 + .../tests/wrong_type_equal_enum.yml | 16 + .../tests/wrong_type_equal_float.yml | 15 + .../tests/wrong_type_equal_int.yml | 15 + .../tests/wrong_type_equal_missing_field.yml | 22 + .../tests/wrong_type_equal_null.yml | 15 + .../tests/wrong_type_equal_str.yml | 16 + .../wrong_type_false_equal_missing_field.yml | 22 + ...ong_type_false_not_equal_missing_field.yml | 22 + .../tests/wrong_type_len_arr_int.yml | 51 + .../tests/wrong_type_len_arr_obj.yml | 52 + .../constrcheck/tests/wrong_type_len_str.yml | 51 + .../constrcheck/tests/wrong_type_map_2d.yml | 47 + .../wrong_type_map_obj_too_many_fields.yml | 38 + .../tests/wrong_type_not_equal.yml | 59 + .../tests/wrong_type_obj_too_many_inputs.yml | 34 + .../tests/wrong_type_relational_float.yml | 33 + engines/playmon/internal/pathscan/pathscan.go | 126 ++ .../internal/pathscan/pathscan_test.go | 88 + engines/playmon/playmon.go | 68 + engines/rmap/pquery/pquery.go | 517 ----- engines/rmap/rmap.go | 1083 ----------- engines/tests/benchmark_test.go | 114 +- engines/tests/engine_test.go | 1406 +++++++------- go.mod | 3 +- go.sum | 5 +- gqlparse/count_values.go | 53 + gqlparse/count_values_test.go | 391 ++++ gqlparse/getvallen.go | 51 + gqlparse/getvallen_test.go | 176 ++ gqlparse/gqlparse.go | 38 +- gqlparse/internal/graph/graph.go | 2 +- gqlparse/scan_values.go | 63 + gqlparse/scan_values_in_arrays.go | 88 + gqlparse/scan_values_in_arrays_test.go | 371 ++++ gqlparse/scan_values_test.go | 465 +++++ utilities/atoi/atoi.go | 53 + utilities/atoi/atoi_test.go | 118 ++ utilities/stack/stack.go | 55 +- utilities/stack/stack_test.go | 21 +- 155 files changed, 9884 insertions(+), 2405 deletions(-) create mode 100644 engines/playmon/internal/constrcheck/bench_test.go create mode 100644 engines/playmon/internal/constrcheck/constrcheck.go create mode 100644 engines/playmon/internal/constrcheck/constrcheck_test.go create mode 100644 engines/playmon/internal/constrcheck/doc.go create mode 100644 engines/playmon/internal/constrcheck/internal/test/test.go create mode 100644 engines/playmon/internal/constrcheck/internal/union/union.go create mode 100644 engines/playmon/internal/constrcheck/internal/union/union_test.go create mode 100644 engines/playmon/internal/constrcheck/internal/unsafe/unsafe.go create mode 100644 engines/playmon/internal/constrcheck/iswrongtype.go create mode 100644 engines/playmon/internal/constrcheck/iswrongtype_test.go create mode 100644 engines/playmon/internal/constrcheck/tests/arithmetic_div.yml create mode 100644 engines/playmon/internal/constrcheck/tests/arithmetic_mod.yml create mode 100644 engines/playmon/internal/constrcheck/tests/arithmetic_mul.yml create mode 100644 engines/playmon/internal/constrcheck/tests/arithmetic_sub.yml create mode 100644 engines/playmon/internal/constrcheck/tests/equal_strblock_false.todo.yaml create mode 100644 engines/playmon/internal/constrcheck/tests/false_arithmetic_add.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_constr_and.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_constr_or.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_equal_arr_int.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_equal_arr_str.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_equal_bool.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_equal_enum.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_equal_float.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_equal_int.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_equal_obj.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_equal_str.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_equal_strblock.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_interface.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_len_array_int.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_len_array_obj.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_len_str_eq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_len_str_gr.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_len_str_greq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_len_str_leeq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_len_str_neq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_len_strblock_eq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_len_strblock_gr.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_len_strblock_greq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_len_strblock_le.yaml create mode 100644 engines/playmon/internal/constrcheck/tests/false_len_strblock_le.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_len_strblock_leeq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_len_strblock_neq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_logical_equal.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_logical_expr_relational.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_logical_expr_relational_float.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_logical_notequal.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_logical_or_eq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_logical_or_gr_le.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_map_2d.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_map_array_object.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_map_var_arithmetic.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_not_equal_arr_int.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_not_equal_arr_str.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_not_equal_obj.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_relational_float.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_relational_int.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_union.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_var_bool.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_var_str.yml create mode 100644 engines/playmon/internal/constrcheck/tests/false_var_strblock.yml create mode 100644 engines/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs.yml create mode 100644 engines/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs_in_any.yml create mode 100644 engines/playmon/internal/constrcheck/tests/logical_negation.yml create mode 100644 engines/playmon/internal/constrcheck/tests/numeric_negation.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_arithmetic_add.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_constr_and.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_constr_or.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_equal_arr_int.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_equal_arr_str.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_equal_bool.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_equal_enum.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_equal_float.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_equal_int.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_equal_obj.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_equal_str.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_equal_strblock.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_interface.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_len_array_int.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_len_array_obj.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_len_str.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_len_str_eq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_len_str_gr.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_len_str_greq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_len_str_le.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_len_str_leeq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_len_str_neq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_len_strblock.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_len_strblock_eq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_len_strblock_gr.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_len_strblock_greq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_len_strblock_le.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_len_strblock_leeq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_len_strblock_neq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_logical_equal.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_logical_expr_relational.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_logical_expr_relational_float.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_logical_notequal.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_logical_or_eq.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_logical_or_gr_le.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_map_2d.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_map_array_object.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_map_var_arithmetic.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_not_equal_arr_int.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_not_equal_arr_str.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_not_equal_obj.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_relational_float.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_relational_int.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_union.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_var_bool.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_var_str.yml create mode 100644 engines/playmon/internal/constrcheck/tests/true_var_strblock.yml create mode 100644 engines/playmon/internal/constrcheck/tests/var_equal_nonexistent.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_equal.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_equal_arrint.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_equal_bool_f.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_equal_bool_t.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_equal_enum.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_equal_float.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_equal_int.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_equal_missing_field.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_equal_null.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_equal_str.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_false_equal_missing_field.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_false_not_equal_missing_field.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_len_arr_int.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_len_arr_obj.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_len_str.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_map_2d.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_map_obj_too_many_fields.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_not_equal.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_obj_too_many_inputs.yml create mode 100644 engines/playmon/internal/constrcheck/tests/wrong_type_relational_float.yml create mode 100644 engines/playmon/internal/pathscan/pathscan.go create mode 100644 engines/playmon/internal/pathscan/pathscan_test.go create mode 100644 engines/playmon/playmon.go delete mode 100644 engines/rmap/pquery/pquery.go delete mode 100644 engines/rmap/rmap.go create mode 100644 gqlparse/count_values.go create mode 100644 gqlparse/count_values_test.go create mode 100644 gqlparse/getvallen.go create mode 100644 gqlparse/getvallen_test.go create mode 100644 gqlparse/scan_values.go create mode 100644 gqlparse/scan_values_in_arrays.go create mode 100644 gqlparse/scan_values_in_arrays_test.go create mode 100644 gqlparse/scan_values_test.go create mode 100644 utilities/atoi/atoi.go create mode 100644 utilities/atoi/atoi_test.go diff --git a/engines/playmon/internal/constrcheck/bench_test.go b/engines/playmon/internal/constrcheck/bench_test.go new file mode 100644 index 0000000..b5a8b46 --- /dev/null +++ b/engines/playmon/internal/constrcheck/bench_test.go @@ -0,0 +1,187 @@ +package constrcheck_test + +import ( + "strconv" + "strings" + "testing" + + "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck" + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/gqlscan" + "github.com/graph-guard/gqt/v4" + "github.com/vektah/gqlparser/v2" + "github.com/vektah/gqlparser/v2/ast" +) + +var GB bool + +func Token(t gqlscan.Token, value string) gqlparse.Token { + v := []byte(nil) + if value != "" { + v = []byte(value) + } + return gqlparse.Token{ID: t, Value: v} +} + +func BenchmarkSimple(b *testing.B) { + fn := makeBenchmarkFn( + b, + `type Query { f(a:Int!):Int! }`, + `query { f(a:42) }`, + "Query.f|a", + Token(gqlscan.TokenInt, "42"), + ) + + b.ResetTimer() + for n := 0; n < b.N; n++ { + if GB = fn(); !GB { + b.Fatal("unexpected result: ", GB) + } + } +} + +func BenchmarkSimpleArray(b *testing.B) { + fn := makeBenchmarkFn( + b, + `type Query { f(a:[Int!]!):Int! }`, + `query { f(a:[1,2,3,4,5]) }`, + "Query.f|a", + Token(gqlscan.TokenArr, ""), + Token(gqlscan.TokenInt, "1"), + Token(gqlscan.TokenInt, "2"), + Token(gqlscan.TokenInt, "3"), + Token(gqlscan.TokenInt, "4"), + Token(gqlscan.TokenInt, "5"), + Token(gqlscan.TokenArrEnd, ""), + ) + + b.ResetTimer() + for n := 0; n < b.N; n++ { + if GB = fn(); !GB { + b.Fatal("unexpected result: ", GB) + } + } +} + +func BenchmarkBigArray(b *testing.B) { + items := 1024 + + tokens := make([]gqlparse.Token, items+2) + tokens[0] = Token(gqlscan.TokenArr, "") + tokens[len(tokens)-1] = Token(gqlscan.TokenArrEnd, "") + for i := 1; i <= items; i++ { + tokens[i] = Token(gqlscan.TokenInt, strconv.Itoa(i)) + } + + var tmpl strings.Builder + tmpl.WriteString(`query { f(a:[`) + for i := 1; i <= items; i++ { + tmpl.WriteString(strconv.Itoa(i)) + if i != items { + tmpl.WriteByte(',') + } + } + tmpl.WriteString(`]) }`) + + fn := makeBenchmarkFn( + b, + `type Query { f(a:[Int!]!):Int! }`, + tmpl.String(), + "Query.f|a", + tokens..., + ) + + b.ResetTimer() + for n := 0; n < b.N; n++ { + if GB = fn(); !GB { + b.Fatal("unexpected result: ", GB) + } + } +} + +func BenchmarkObject(b *testing.B) { + fn := makeBenchmarkFn( + b, + ` + type Query { f(object:Object!):Int } + input Object { subobject: SubObject! } + input SubObject { array: [ArrayObject!]! } + input ArrayObject { name: String!, index: Int! } + `, + `query { f(object:{ + subobject: { + array: [{ + name: "first", index: 0 + }, { + name: "second", index: 1 + }] + } + })}`, + "Query.f|object", + Token(gqlscan.TokenObj, ""), + Token(gqlscan.TokenObjField, "subobject"), + Token(gqlscan.TokenObj, ""), + Token(gqlscan.TokenObjField, "array"), + Token(gqlscan.TokenArr, ""), + + Token(gqlscan.TokenObj, ""), + Token(gqlscan.TokenObjField, "name"), + Token(gqlscan.TokenStr, "first"), + Token(gqlscan.TokenObjField, "index"), + Token(gqlscan.TokenInt, "0"), + Token(gqlscan.TokenObjEnd, ""), + + Token(gqlscan.TokenObj, ""), + Token(gqlscan.TokenObjField, "name"), + Token(gqlscan.TokenStr, "second"), + Token(gqlscan.TokenObjField, "index"), + Token(gqlscan.TokenInt, "1"), + Token(gqlscan.TokenObjEnd, ""), + + Token(gqlscan.TokenArrEnd, ""), + Token(gqlscan.TokenObjEnd, ""), + Token(gqlscan.TokenObjEnd, ""), + ) + + b.ResetTimer() + for n := 0; n < b.N; n++ { + if GB = fn(); !GB { + b.Fatal("unexpected result: ", GB) + } + } +} + +func makeBenchmarkFn( + b interface{ Fatal(...any) }, + schema, template, path string, tokens ...gqlparse.Token, +) func() bool { + s, err := gqlparser.LoadSchema(&ast.Source{ + Name: "schema.graphqls", + Input: schema, + }) + if err != nil { + b.Fatal(err) + } + + p, err := gqt.NewParser([]gqt.Source{{ + Name: "schema.graphqls", + Content: schema, + }}) + if err != nil { + b.Fatal(err) + } + + opr, _, errs := p.Parse([]byte(template)) + if errs != nil { + b.Fatal(errs) + } + + m := constrcheck.New(opr, s) + + inputs := map[string][]gqlparse.Token{path: tokens} + + return func() bool { + m.Init(inputs) + return m.Check(path) + } +} diff --git a/engines/playmon/internal/constrcheck/constrcheck.go b/engines/playmon/internal/constrcheck/constrcheck.go new file mode 100644 index 0000000..429aaa8 --- /dev/null +++ b/engines/playmon/internal/constrcheck/constrcheck.go @@ -0,0 +1,1725 @@ +package constrcheck + +import ( + "fmt" + "math" + "strconv" + "strings" + + "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck/internal/union" + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/utilities/atoi" + "github.com/graph-guard/gqlscan" + "github.com/graph-guard/gqt/v4" + gqlast "github.com/vektah/gqlparser/v2/ast" +) + +// Enum is a GraphQL enum value. +type Enum string + +// Checker is a constraint checker instance. +// Before calling Check, make sure you initialize the Checker using Init. +type Checker struct { + operation *gqt.Operation + schema *gqlast.Schema + checkers map[string]check + + inputs map[string][]gqlparse.Token + + inputValue []gqlparse.Token + checkedValue []gqlparse.Token + stack []union.Union +} + +// // Paths executes fn for every path the checker is aware of. +// // Returns true immediatelly if any call to fn returns true. +// func (m *Checker) Paths(fn func(string) (stop bool)) (stopped bool) { +// for p := range m.checkers { +// if fn(p) { +// return true +// } +// } +// return false +// } + +func (m *Checker) stackTopType() union.Type { + return m.stack[len(m.stack)-1].Type() +} + +func (m *Checker) popStack() union.Union { + t := m.stack[len(m.stack)-1] + m.stack = m.stack[:len(m.stack)-1] + return t +} + +func (m *Checker) popStackArrays() (left, right []union.Union) { + if m.stack[len(m.stack)-1].Type() != union.TypeArray { + return + } + i := len(m.stack) - 2 + var c int +LOOP_R: + for l := 1; i > 0; i, c = i-1, c+1 { + switch m.stack[i].Type() { + case union.TypeArray: + l++ + case union.TypeArrayEnd: + l-- + if l < 1 { + break LOOP_R + } + } + } + right = m.stack[i+1 : len(m.stack)-1] + + i-- + iv := i + if m.stack[i].Type() != union.TypeArray { + return + } + c = 0 + i-- +LOOP_L: + for l := 1; i > 0; i, c = i-1, c+1 { + switch m.stack[i].Type() { + case union.TypeArray: + l++ + case union.TypeArrayEnd: + l-- + if l < 1 { + break LOOP_L + } + } + } + + left = m.stack[i+1 : iv] + m.stack = m.stack[:i] + return left, right +} + +func (m *Checker) pushStackArray() { + m.stack = append(m.stack, union.Array()) +} + +func (m *Checker) pushStackArrayEnd() { + m.stack = append(m.stack, union.ArrayEnd()) +} + +func (m *Checker) pushStackInt(v int32) { + m.stack = append(m.stack, union.Int(v)) +} + +func (m *Checker) pushStackFloat(v float64) { + m.stack = append(m.stack, union.Float(v)) +} + +func (m *Checker) pushStackBool(v bool) { + if v { + m.stack = append(m.stack, union.True()) + return + } + m.stack = append(m.stack, union.False()) +} + +// Init initializes the checker with inputs to validate a request. +// Init must be used before making calls to Check in the context of a request. +func (m *Checker) Init(inputs map[string][]gqlparse.Token) { + m.inputs = inputs +} + +// Check returns true if the value for the given path is accepted, +// otherwise returns false. +func (m *Checker) Check(path string) bool { + m.stack = m.stack[:0] + c := m.checkers[path] + if c == nil { + return false + } + v := m.inputs[path] + m.inputValue, m.checkedValue = v, v + return c(m) +} + +// check is a constraint check function which returns true +// if the input value matches the constraint and can be accepted, +// otherwise returns false. +type check func(*Checker) (match bool) + +// resolveExpr resolves expression e into a union and pushes it onto the stack. +func (m *Checker) resolveExpr(e gqt.Expression) union.Type { + switch e := e.(type) { + case *gqt.Array: + m.pushStackArrayEnd() + for i := len(e.Items) - 1; i >= 0; i-- { + m.resolveExpr(e.Items[i].(*gqt.ConstrEquals).Value) + } + m.pushStackArray() + return union.TypeArray + case *gqt.Variable: + p := MakePath(e.Declaration.Parent) + if s := m.inputs[p]; s != nil { + m.stack = append(m.stack, union.Tokens(s)) + return union.TypeTokens + } + m.stack = append(m.stack, union.Null()) + return union.TypeNull + case *gqt.Number: + if i, ok := e.Int(); ok { + m.stack = append(m.stack, union.Int(int32(i))) + return union.TypeInt + } + f, _ := e.Float() + m.stack = append(m.stack, union.Float(f)) + return union.TypeFloat + case *gqt.True: + m.stack = append(m.stack, union.True()) + return union.TypeBoolean + case *gqt.False: + m.stack = append(m.stack, union.False()) + return union.TypeBoolean + case *gqt.Enum: + m.stack = append(m.stack, union.Enum(e.Value)) + return union.TypeEnum + case *gqt.String: + m.stack = append(m.stack, union.String(e.Value)) + return union.TypeString + case *gqt.Null: + return union.TypeNull + case *gqt.ExprAddition: + m.resolveExpr(e.AddendLeft) + m.resolveExpr(e.AddendRight) + r := m.popStack() + l := m.popStack() + if l.Type() == union.TypeInt && r.Type() == union.TypeInt { + l, _ := l.Int() + r, _ := r.Int() + m.pushStackInt(l + r) + return union.TypeInt + } + { + l, _ := l.Float() + r, _ := r.Float() + m.pushStackFloat(l + r) + } + return union.TypeFloat + case *gqt.ExprSubtraction: + m.resolveExpr(e.Minuend) + m.resolveExpr(e.Subtrahend) + r := m.popStack() + l := m.popStack() + if l.Type() == union.TypeInt && r.Type() == union.TypeInt { + l, _ := l.Int() + r, _ := r.Int() + m.pushStackInt(l - r) + return union.TypeInt + } + { + l, _ := l.Float() + r, _ := r.Float() + m.pushStackFloat(l - r) + } + return union.TypeFloat + case *gqt.ExprMultiplication: + m.resolveExpr(e.Multiplicant) + m.resolveExpr(e.Multiplicator) + r := m.popStack() + l := m.popStack() + if l.Type() == union.TypeInt && r.Type() == union.TypeInt { + l, _ := l.Int() + r, _ := r.Int() + m.pushStackInt(l * r) + return union.TypeInt + } + { + l, _ := l.Float() + r, _ := r.Float() + m.pushStackFloat(l * r) + } + return union.TypeFloat + case *gqt.ExprDivision: + m.resolveExpr(e.Dividend) + m.resolveExpr(e.Divisor) + r := m.popStack() + l := m.popStack() + if l.Type() == union.TypeInt && r.Type() == union.TypeInt { + l, _ := l.Int() + r, _ := r.Int() + m.pushStackInt(l / r) + return union.TypeInt + } + { + l, _ := l.Float() + r, _ := r.Float() + m.pushStackFloat(l / r) + } + return union.TypeFloat + case *gqt.ExprModulo: + m.resolveExpr(e.Dividend) + m.resolveExpr(e.Divisor) + r := m.popStack() + l := m.popStack() + if l.Type() == union.TypeInt && r.Type() == union.TypeInt { + l, _ := l.Int() + r, _ := r.Int() + m.pushStackInt(l % r) + return union.TypeInt + } + { + l, _ := l.Float() + r, _ := r.Float() + m.pushStackFloat(math.Mod(l, r)) + } + return union.TypeFloat + case *gqt.ExprNumericNegation: + m.resolveExpr(e.Expression) + i := m.popStack() + switch i.Type() { + case union.TypeFloat: + i, _ := i.Float() + m.pushStackFloat(-i) + return union.TypeFloat + case union.TypeInt: + i, _ := i.Int() + m.pushStackInt(-i) + return union.TypeInt + case union.TypeTokens: + switch i.Tokens()[0].ID { + case gqlscan.TokenInt: + i := atoi.MustI32(i.Tokens()[0].Value) + m.pushStackInt(-i) + return union.TypeInt + case gqlscan.TokenFloat: + f := atoi.MustF64(i.Tokens()[0].Value) + m.pushStackFloat(-f) + return union.TypeFloat + } + panic(fmt.Errorf("unexpected token type: %q", i.Tokens()[0].ID.String())) + } + panic(fmt.Errorf("unexpected union type: %q", i.Type().String())) + case *gqt.ExprEqual: + m.resolveExpr(e.Left) + m.resolveExpr(e.Right) + equal := false + if m.stackTopType() == union.TypeArray { + left, right := m.popStackArrays() + equal = unionsEqual(left, right) + } else { + r := m.popStack() + l := m.popStack() + equal = union.Equal(l, r) + } + m.pushStackBool(equal) + return union.TypeBoolean + case *gqt.ExprNotEqual: + m.resolveExpr(e.Left) + m.resolveExpr(e.Right) + notEqual := false + if m.stackTopType() == union.TypeArray { + left, right := m.popStackArrays() + notEqual = !unionsEqual(left, right) + } else { + r := m.popStack() + l := m.popStack() + notEqual = !union.Equal(l, r) + } + m.pushStackBool(notEqual) + return union.TypeBoolean + case *gqt.ExprLogicalNegation: + m.resolveExpr(e.Expression) + u := m.popStack() + b, _ := u.Bool() + m.pushStackBool(!b) + return union.TypeBoolean + case *gqt.ExprGreater: + m.resolveExpr(e.Left) + m.resolveExpr(e.Right) + r := m.popStack() + l := m.popStack() + if l.Type() == union.TypeInt && r.Type() == union.TypeInt { + l, _ := l.Int() + r, _ := r.Int() + m.pushStackBool(l > r) + } else { + l, _ := l.Float() + r, _ := r.Float() + m.pushStackBool(l > r) + } + return union.TypeBoolean + case *gqt.ExprLess: + m.resolveExpr(e.Left) + m.resolveExpr(e.Right) + r := m.popStack() + l := m.popStack() + if l.Type() == union.TypeInt && r.Type() == union.TypeInt { + l, _ := l.Int() + r, _ := r.Int() + m.pushStackBool(l < r) + } else { + l, _ := l.Float() + r, _ := r.Float() + m.pushStackBool(l < r) + } + return union.TypeBoolean + case *gqt.ExprGreaterOrEqual: + m.resolveExpr(e.Left) + m.resolveExpr(e.Right) + r := m.popStack() + l := m.popStack() + if l.Type() == union.TypeInt && r.Type() == union.TypeInt { + l, _ := l.Int() + r, _ := r.Int() + m.pushStackBool(l >= r) + } else { + l, _ := l.Float() + r, _ := r.Float() + m.pushStackBool(l >= r) + } + return union.TypeBoolean + case *gqt.ExprLessOrEqual: + m.resolveExpr(e.Left) + m.resolveExpr(e.Right) + r := m.popStack() + l := m.popStack() + if l.Type() == union.TypeInt && r.Type() == union.TypeInt { + l, _ := l.Int() + r, _ := r.Int() + m.pushStackBool(l <= r) + } else { + l, _ := l.Float() + r, _ := r.Float() + m.pushStackBool(l <= r) + } + return union.TypeBoolean + case *gqt.ExprLogicalOr: + for _, x := range e.Expressions { + m.resolveExpr(x) + u := m.popStack() + b, _ := u.Bool() + if b { + m.pushStackBool(true) + return union.TypeBoolean + } + } + m.pushStackBool(false) + return union.TypeBoolean + case *gqt.ExprLogicalAnd: + for _, x := range e.Expressions { + m.resolveExpr(x) + u := m.popStack() + b, _ := u.Bool() + if !b { + m.pushStackBool(false) + return union.TypeBoolean + } + } + m.pushStackBool(true) + return union.TypeBoolean + case *gqt.ExprParentheses: + return m.resolveExpr(e.Expression) + } + panic(fmt.Errorf("unhandled value expression type: %T", e)) +} + +// New creates a constraint checker instance for each path of o. +func New(o *gqt.Operation, s *gqlast.Schema) *Checker { + m := &Checker{ + operation: o, + schema: s, + checkers: make(map[string]check), + stack: make([]union.Union, 1024), + } + + inputTypes := map[string]*gqlast.Type{} + if s != nil { + traverse(o, func(e gqt.Expression) (stop, skipChildren bool) { + switch e := e.(type) { + case *gqt.SelectionField: + for _, a := range e.Arguments { + if a.Def == nil { + continue + } + p := MakePath(a) + inputTypes[p] = a.Def.Type + } + case *gqt.ObjectField: + if e.Def == nil { + break + } + p := MakePath(e) + inputTypes[p] = e.Def.Type + } + return false, false + }) + } + + traverse(o, func(e gqt.Expression) (stop, skipChildren bool) { + switch e := e.(type) { + case *gqt.SelectionField: + for _, a := range e.Arguments { + var expect *gqlast.Type + if a.Def != nil { + expect = a.Def.Type + } + if fn := makeCheck(a.Constraint, expect, s); fn != nil { + p := MakePath(a) + m.checkers[p] = fn + } + } + } + return false, false // Continue traversal + }) + + return m +} + +func makeCheck( + e gqt.Expression, + expect *gqlast.Type, + schema *gqlast.Schema, +) check { + switch e := e.(type) { + case *gqt.ConstrAny: + return func(m *Checker) (match bool) { + if wrong, _ := isWrongType(expect, m.checkedValue, schema); wrong { + return false + } + + // Make sure the value is semantically valid. + return !gqlparse.ScanValuesInArrays(m.checkedValue, func(t []gqlparse.Token) (stop bool) { + if t[0].ID != gqlscan.TokenObj { + return false + } + t = t[1:] + checkedFields := map[string]struct{}{} + for t[0].ID != gqlscan.TokenObjEnd { + fieldName := t[0].Value + if _, ok := checkedFields[string(fieldName)]; ok { + // Duplicate field! Invalid object value. + return true + } + checkedFields[string(fieldName)] = struct{}{} + t = t[1:] + } + return false + }) + } + case *gqt.ExprParentheses: + return makeCheck(e.Expression, expect, schema) + case *gqt.ExprLogicalOr: + exprCheckers := make([]check, len(e.Expressions)) + for i, e := range e.Expressions { + exprCheckers[i] = makeCheck(e, expect, schema) + } + return func(m *Checker) (match bool) { + r := m.checkedValue + for _, e := range exprCheckers { + if e(m) { + return true + } + // Reset value to start checking from the same index + m.checkedValue = r + } + return false + } + case *gqt.ExprLogicalAnd: + exprCheckers := make([]check, len(e.Expressions)) + for i, e := range e.Expressions { + exprCheckers[i] = makeCheck(e, expect, schema) + } + return func(m *Checker) (match bool) { + r := m.checkedValue + for _, e := range exprCheckers { + if !e(m) { + return false + } + // Reset value to start checking from the same index + m.checkedValue = r + } + return true + } + case *gqt.ConstrGreater: + return func(m *Checker) (match bool) { + if m.expectOrNum(expect) { + return false + } + m.resolveExpr(e.Value) + u := m.popStack() + switch m.checkedValue[0].ID { + case gqlscan.TokenInt: + i := atoi.MustI32(m.checkedValue[0].Value) + if u, ok := u.Int(); ok != union.ValueNone { + match = i > u + break + } + u, _ := u.Float() + match = float64(i) > u + case gqlscan.TokenFloat: + i := atoi.MustF64(m.checkedValue[0].Value) + u, _ := u.Float() + match = i > u + } + m.checkedValue = m.checkedValue[1:] + return match + } + case *gqt.ConstrGreaterOrEqual: + return func(m *Checker) (match bool) { + if m.expectOrNum(expect) { + return false + } + m.resolveExpr(e.Value) + u := m.popStack() + switch m.checkedValue[0].ID { + case gqlscan.TokenInt: + i := atoi.MustI32(m.checkedValue[0].Value) + if u, ok := u.Int(); ok != union.ValueNone { + match = i >= u + break + } + u, _ := u.Float() + match = float64(i) >= u + case gqlscan.TokenFloat: + i := atoi.MustF64(m.checkedValue[0].Value) + u, _ := u.Float() + match = i >= u + } + m.checkedValue = m.checkedValue[1:] + return match + } + case *gqt.ConstrLess: + return func(m *Checker) (match bool) { + if m.expectOrNum(expect) { + return false + } + m.resolveExpr(e.Value) + u := m.popStack() + switch m.checkedValue[0].ID { + case gqlscan.TokenInt: + i := atoi.MustI32(m.checkedValue[0].Value) + if u, ok := u.Int(); ok != union.ValueNone { + match = i < u + break + } + u, _ := u.Float() + match = float64(i) < u + case gqlscan.TokenFloat: + i := atoi.MustF64(m.checkedValue[0].Value) + u, _ := u.Float() + match = i < u + } + m.checkedValue = m.checkedValue[1:] + return match + } + case *gqt.ConstrLessOrEqual: + return func(m *Checker) (match bool) { + if m.expectOrNum(expect) { + return false + } + m.resolveExpr(e.Value) + u := m.popStack() + switch m.checkedValue[0].ID { + case gqlscan.TokenInt: + i := atoi.MustI32(m.checkedValue[0].Value) + if u, ok := u.Int(); ok != union.ValueNone { + match = i <= u + break + } + u, _ := u.Float() + match = float64(i) <= u + case gqlscan.TokenFloat: + i := atoi.MustF64(m.checkedValue[0].Value) + u, _ := u.Float() + match = i <= u + } + m.checkedValue = m.checkedValue[1:] + return match + } + case *gqt.ConstrEquals: + switch v := e.Value.(type) { + case *gqt.Array: + return makeEqArray(v, expect, schema) + case *gqt.Object: + return makeEqObject(v, expect, schema) + case *gqt.Number: + return makeEqNumber(v, expect, schema) + case *gqt.String: + return makeEqString(v, expect, schema) + case *gqt.Enum: + return makeEqEnum(v, expect, schema) + case *gqt.Null: + return makeEqNull(expect, schema) + case *gqt.False: + return makeEqBool(false, expect, schema) + case *gqt.True: + return makeEqBool(true, expect, schema) + } + // Expression + return makeEqExpression(e.Value, expect, schema) + case *gqt.ConstrNotEquals: + switch v := e.Value.(type) { + case *gqt.Array: + return makeNotEqArray(v, expect, schema) + case *gqt.Object: + return makeNotEqObject(v, expect, schema) + case *gqt.Number: + return makeNotEqNumber(v, expect, schema) + case *gqt.String: + return makeNotEqString(v, expect, schema) + case *gqt.Enum: + return makeNotEqEnum(v, expect, schema) + case *gqt.Null: + return makeNotEqNull(expect, schema) + case *gqt.False: + return makeNotEqBool(false, expect, schema) + case *gqt.True: + return makeNotEqBool(true, expect, schema) + } + fn := makeEqExpression(e.Value, expect, schema) + return func(m *Checker) (match bool) { + return !fn(m) + } + case *gqt.ConstrLenEquals: + return func(m *Checker) (match bool) { + if m.expectOrHasLen(expect) { + return false + } + + var length int + switch m.checkedValue[0].ID { + case gqlscan.TokenArr: + var tokensRead int + length, tokensRead = gqlparse.CountValuesUntil( + m.checkedValue[1:], + gqlscan.TokenArrEnd, + ) + m.checkedValue = m.checkedValue[tokensRead:] + case gqlscan.TokenStr: + length = len(m.checkedValue[0].Value) + case gqlscan.TokenStrBlock: + length = len(m.checkedValue[0].Value) + } + + m.resolveExpr(e.Value) + u := m.popStack() + if u, ok := u.Int(); ok != union.ValueNone { + return int32(length) == u + } + if u, ok := u.Float(); ok != union.ValueNone { + return float64(length) == u + } + panic(fmt.Errorf("unexpected value type: %s", u.Type().String())) + } + case *gqt.ConstrLenNotEquals: + return func(m *Checker) (match bool) { + if m.expectOrHasLen(expect) { + return false + } + + var length int + switch m.checkedValue[0].ID { + case gqlscan.TokenArr: + var tokensRead int + length, tokensRead = gqlparse.CountValuesUntil( + m.checkedValue[1:], + gqlscan.TokenArrEnd, + ) + m.checkedValue = m.checkedValue[tokensRead:] + case gqlscan.TokenStr: + length = len(m.checkedValue[0].Value) + case gqlscan.TokenStrBlock: + length = len(m.checkedValue[0].Value) + } + + m.resolveExpr(e.Value) + u := m.popStack() + if u, ok := u.Int(); ok != union.ValueNone { + return int32(length) != u + } + if u, ok := u.Float(); ok != union.ValueNone { + return float64(length) != u + } + panic(fmt.Errorf("unexpected value type: %s", u.Type().String())) + } + case *gqt.ConstrLenGreater: + return func(m *Checker) (match bool) { + if m.expectOrHasLen(expect) { + return false + } + + var length int + switch m.checkedValue[0].ID { + case gqlscan.TokenArr: + var tokensRead int + length, tokensRead = gqlparse.CountValuesUntil( + m.checkedValue[1:], + gqlscan.TokenArrEnd, + ) + m.checkedValue = m.checkedValue[tokensRead:] + case gqlscan.TokenStr: + length = len(m.checkedValue[0].Value) + case gqlscan.TokenStrBlock: + length = len(m.checkedValue[0].Value) + } + + m.resolveExpr(e.Value) + u := m.popStack() + if u, ok := u.Int(); ok != union.ValueNone { + return int32(length) > u + } + if u, ok := u.Float(); ok != union.ValueNone { + return float64(length) > u + } + panic(fmt.Errorf("unexpected value type: %s", u.Type().String())) + } + case *gqt.ConstrLenGreaterOrEqual: + return func(m *Checker) (match bool) { + if m.expectOrHasLen(expect) { + return false + } + + var length int + switch m.checkedValue[0].ID { + case gqlscan.TokenArr: + var tokensRead int + length, tokensRead = gqlparse.CountValuesUntil( + m.checkedValue[1:], + gqlscan.TokenArrEnd, + ) + m.checkedValue = m.checkedValue[tokensRead:] + case gqlscan.TokenStr: + length = len(m.checkedValue[0].Value) + case gqlscan.TokenStrBlock: + length = len(m.checkedValue[0].Value) + } + + m.resolveExpr(e.Value) + u := m.popStack() + if u, ok := u.Int(); ok != union.ValueNone { + return int32(length) >= u + } + if u, ok := u.Float(); ok != union.ValueNone { + return float64(length) >= u + } + panic(fmt.Errorf("unexpected value type: %s", u.Type().String())) + } + case *gqt.ConstrLenLess: + return func(m *Checker) (match bool) { + if m.expectOrHasLen(expect) { + return false + } + + var length int + switch m.checkedValue[0].ID { + case gqlscan.TokenArr: + var tokensRead int + length, tokensRead = gqlparse.CountValuesUntil( + m.checkedValue[1:], + gqlscan.TokenArrEnd, + ) + m.checkedValue = m.checkedValue[tokensRead:] + case gqlscan.TokenStr: + length = len(m.checkedValue[0].Value) + case gqlscan.TokenStrBlock: + length = len(m.checkedValue[0].Value) + } + + m.resolveExpr(e.Value) + u := m.popStack() + if u, ok := u.Int(); ok != union.ValueNone { + return int32(length) < u + } + if u, ok := u.Float(); ok != union.ValueNone { + return float64(length) < u + } + panic(fmt.Errorf("unexpected value type: %s", u.Type().String())) + } + case *gqt.ConstrLenLessOrEqual: + return func(m *Checker) (match bool) { + if m.expectOrHasLen(expect) { + return false + } + + var length int + switch m.checkedValue[0].ID { + case gqlscan.TokenArr: + var tokensRead int + length, tokensRead = gqlparse.CountValuesUntil( + m.checkedValue[1:], + gqlscan.TokenArrEnd, + ) + m.checkedValue = m.checkedValue[tokensRead:] + case gqlscan.TokenStr: + length = len(m.checkedValue[0].Value) + case gqlscan.TokenStrBlock: + length = len(m.checkedValue[0].Value) + } + + m.resolveExpr(e.Value) + u := m.popStack() + if u, ok := u.Int(); ok != union.ValueNone { + return int32(length) <= u + } + if u, ok := u.Float(); ok != union.ValueNone { + return float64(length) <= u + } + panic(fmt.Errorf("unexpected value type: %s", u.Type().String())) + } + case *gqt.ConstrMap: + var expectItem *gqlast.Type + if expect != nil { + expectItem = expect.Elem + } + itemCheck := makeCheck(e.Constraint, expectItem, schema) + return func(m *Checker) (match bool) { + if wrong, _ := isWrongType(expect, m.checkedValue, schema); wrong { + return false + } + for m.checkedValue[0].ID != gqlscan.TokenArr { + return false + } + m.checkedValue = m.checkedValue[1:] + for m.checkedValue[0].ID != gqlscan.TokenArrEnd { + if !itemCheck(m) { + return false + } + } + m.checkedValue = m.checkedValue[1:] + return true + } + } + return nil +} + +// traverse returns true after BFS-traversing the entire tree under e +// calling onExpression for every discovered expression. +func traverse( + e gqt.Expression, + onExpression func(gqt.Expression) (stop, skipChildren bool), +) (stopped bool) { + stack := make([]gqt.Expression, 0, 64) + push := func(expression gqt.Expression) { + stack = append(stack, expression) + } + + push(e) + for len(stack) > 0 { + top := stack[len(stack)-1] + stack = stack[:len(stack)-1] + stop, skipChildren := onExpression(top) + if stop { + return true + } else if skipChildren { + continue + } + + switch e := top.(type) { + case *gqt.Operation: + for _, f := range e.Selections { + push(f) + } + case *gqt.SelectionInlineFrag: + for _, f := range e.Selections { + push(f) + } + case *gqt.SelectionField: + for _, f := range e.Selections { + push(f) + } + for _, a := range e.Arguments { + push(a) + } + case *gqt.SelectionMax: + for _, f := range e.Options.Selections { + push(f) + } + case *gqt.Argument: + push(e.Constraint) + case *gqt.ConstrEquals: + push(e.Value) + case *gqt.ConstrNotEquals: + push(e.Value) + case *gqt.ConstrGreater: + push(e.Value) + case *gqt.ConstrGreaterOrEqual: + push(e.Value) + case *gqt.ConstrLess: + push(e.Value) + case *gqt.ConstrLessOrEqual: + push(e.Value) + case *gqt.ConstrLenEquals: + push(e.Value) + case *gqt.ConstrLenNotEquals: + push(e.Value) + case *gqt.ConstrLenGreater: + push(e.Value) + case *gqt.ConstrLenLess: + push(e.Value) + case *gqt.ConstrLenGreaterOrEqual: + push(e.Value) + case *gqt.ConstrLenLessOrEqual: + push(e.Value) + case *gqt.ConstrMap: + push(e.Constraint) + case *gqt.ExprParentheses: + push(e.Expression) + case *gqt.ExprEqual: + push(e.Left) + push(e.Right) + case *gqt.ExprNotEqual: + push(e.Left) + push(e.Right) + case *gqt.ExprLogicalNegation: + push(e.Expression) + case *gqt.ExprNumericNegation: + push(e.Expression) + case *gqt.ExprLogicalOr: + for _, e := range e.Expressions { + push(e) + } + case *gqt.ExprLogicalAnd: + for _, e := range e.Expressions { + push(e) + } + case *gqt.ExprAddition: + push(e.AddendLeft) + push(e.AddendRight) + case *gqt.ExprSubtraction: + push(e.Minuend) + push(e.Subtrahend) + case *gqt.ExprMultiplication: + push(e.Multiplicant) + push(e.Multiplicator) + case *gqt.ExprDivision: + push(e.Dividend) + push(e.Divisor) + case *gqt.ExprModulo: + push(e.Dividend) + push(e.Divisor) + case *gqt.ExprGreater: + push(e.Left) + push(e.Right) + case *gqt.ExprGreaterOrEqual: + push(e.Left) + push(e.Right) + case *gqt.ExprLess: + push(e.Left) + push(e.Right) + case *gqt.ExprLessOrEqual: + push(e.Left) + push(e.Right) + case *gqt.Array: + for _, i := range e.Items { + push(i) + } + case *gqt.Object: + for _, f := range e.Fields { + push(f) + } + case *gqt.ObjectField: + push(e.Constraint) + case *gqt.Number, *gqt.True, *gqt.False, *gqt.Null, + *gqt.Enum, *gqt.String, *gqt.ConstrAny, *gqt.Variable: + default: + panic(fmt.Errorf("unhandled type: %T", top)) + } + } + return false +} + +// Designation creates a textual explanation for the given expression. +func Designation(e gqt.Expression) string { + switch e := e.(type) { + case *gqt.ConstrAny: + return "can be any value" + case *gqt.ConstrEquals: + return "must be equal " + Designation(e.Value) + case *gqt.ConstrNotEquals: + return "must not be equal " + Designation(e.Value) + case *gqt.ConstrLenEquals: + return "length must be equal " + Designation(e.Value) + case *gqt.ConstrLenNotEquals: + return "length must not be equal " + Designation(e.Value) + case *gqt.ConstrLenLess: + return "length must be less than " + Designation(e.Value) + case *gqt.ConstrLenGreater: + return "length must be greater than " + Designation(e.Value) + case *gqt.ConstrLenLessOrEqual: + return "length must be less than or equal " + Designation(e.Value) + case *gqt.ConstrLenGreaterOrEqual: + return "length must be greater than or equal " + Designation(e.Value) + case *gqt.ConstrMap: + return "each item: " + Designation(e.Constraint) + case *gqt.ConstrLess: + return "must be less than " + Designation(e.Value) + case *gqt.ConstrGreater: + return "must be greater than " + Designation(e.Value) + case *gqt.ConstrLessOrEqual: + return "must be less than or equal " + Designation(e.Value) + case *gqt.ConstrGreaterOrEqual: + return "must be greater than or equal " + Designation(e.Value) + case *gqt.ExprParentheses: + return "(" + Designation(e.Expression) + ")" + case *gqt.ExprAddition: + return Designation(e.AddendLeft) + " + " + Designation(e.AddendRight) + case *gqt.ExprSubtraction: + return Designation(e.Minuend) + " - " + Designation(e.Subtrahend) + case *gqt.ExprMultiplication: + return Designation(e.Multiplicant) + " * " + Designation(e.Multiplicator) + case *gqt.ExprDivision: + return Designation(e.Dividend) + " / " + Designation(e.Divisor) + case *gqt.ExprModulo: + return Designation(e.Dividend) + " % " + Designation(e.Divisor) + case *gqt.ExprEqual: + return Designation(e.Left) + " equal " + Designation(e.Right) + case *gqt.ExprNotEqual: + return Designation(e.Left) + " not equal " + Designation(e.Right) + case *gqt.ExprGreater: + return Designation(e.Left) + " greater than " + Designation(e.Right) + case *gqt.ExprLess: + return Designation(e.Left) + " less than " + Designation(e.Right) + case *gqt.ExprLessOrEqual: + return Designation(e.Left) + " less than or equal " + Designation(e.Right) + case *gqt.ExprGreaterOrEqual: + return Designation(e.Left) + " greater than or equal " + Designation(e.Right) + case *gqt.ExprLogicalNegation: + return "not(" + Designation(e.Expression) + ")" + case *gqt.ExprNumericNegation: + return "negative(" + Designation(e.Expression) + ")" + case *gqt.Number: + i, ok := e.Int() + if ok { + return strconv.FormatInt(int64(i), 10) + } + f, _ := e.Float() + return fmt.Sprintf("%f", f) + case *gqt.String: + return fmt.Sprintf("%q", e.Value) + case *gqt.True: + return "true" + case *gqt.False: + return "false" + case *gqt.Null: + return "null" + case *gqt.Array: + var b strings.Builder + b.WriteByte('[') + for i, v := range e.Items { + if i > 0 { + b.WriteString(" ,") + } + fmt.Fprintf(&b, "%d: %v", i, Designation(v)) + } + b.WriteByte(']') + return b.String() + case *gqt.Enum: + return e.Value + case *gqt.Variable: + return MakePath(e.Declaration.Parent) + case *gqt.ExprLogicalOr: + var b strings.Builder + for i, v := range e.Expressions { + if i > 0 { + b.WriteString(", or ") + } + b.WriteString(Designation(v)) + } + return b.String() + case *gqt.ExprLogicalAnd: + var b strings.Builder + for i, v := range e.Expressions { + if i > 0 { + b.WriteString(", and ") + } + b.WriteString(Designation(v)) + } + return b.String() + case *gqt.Object: + var b strings.Builder + b.WriteByte('{') + for i, v := range e.Fields { + if i > 0 { + b.WriteString(" ,") + } + fmt.Fprintf(&b, "%q: %v", v.Name.Name, Designation(v.Constraint)) + } + b.WriteByte('}') + return b.String() + } + panic(fmt.Sprintf("unhandled expression: %T", e)) +} + +// MakePath generates a path for the given expression. +// For example the path "Query.a1.a2|arg" denotes the argument "arg" +// of the field "a2" inside the field "a1" of the Query type. +func MakePath(e gqt.Expression) string { + var p []gqt.Expression // Reversed path + for ; e != nil; e = e.GetParent() { + p = append(p, e) + } + var s strings.Builder + for i := len(p) - 1; i >= 0; i-- { + switch v := p[i].(type) { + case *gqt.SelectionInlineFrag: + _ = s.WriteByte('&') + _, _ = s.WriteString(v.TypeCondition.TypeName) + case *gqt.SelectionField: + _ = s.WriteByte('.') + _, _ = s.WriteString(v.Name.Name) + case *gqt.ObjectField: + _ = s.WriteByte('/') + _, _ = s.WriteString(v.Name.Name) + case *gqt.Argument: + _ = s.WriteByte('|') + _, _ = s.WriteString(v.Name.Name) + case *gqt.Operation: + _, _ = s.WriteString(v.Type.String()) + } + } + return s.String() +} + +func (m *Checker) expectOrNum(expect *gqlast.Type) (wrongType bool) { + if expect != nil { + wrongType, _ = isWrongType(expect, m.checkedValue, m.schema) + return wrongType + } + return m.checkedValue[0].ID != gqlscan.TokenInt && + m.checkedValue[0].ID != gqlscan.TokenFloat +} + +func (m *Checker) expectOrInt(expect *gqlast.Type) (wrongType bool) { + if expect != nil { + wrongType, _ = isWrongType(expect, m.checkedValue, m.schema) + return wrongType + } + return m.checkedValue[0].ID != gqlscan.TokenInt +} + +func (m *Checker) expectOrFloat(expect *gqlast.Type) (wrongType bool) { + if expect != nil { + wrongType, _ = isWrongType(expect, m.checkedValue, m.schema) + return wrongType + } + return m.checkedValue[0].ID != gqlscan.TokenFloat +} + +func (m *Checker) expectOrString(expect *gqlast.Type) (wrongType bool) { + if expect != nil { + wrongType, _ = isWrongType(expect, m.checkedValue, m.schema) + return wrongType + } + return m.checkedValue[0].ID != gqlscan.TokenStr && + m.checkedValue[0].ID != gqlscan.TokenStrBlock +} + +func (m *Checker) expectOrEnum(expect *gqlast.Type) (wrongType bool) { + if expect != nil { + wrongType, _ = isWrongType(expect, m.checkedValue, m.schema) + return wrongType + } + return m.checkedValue[0].ID != gqlscan.TokenEnumVal +} + +func (m *Checker) expectOrBool(expect *gqlast.Type) (wrongType bool) { + if expect != nil { + wrongType, _ = isWrongType(expect, m.checkedValue, m.schema) + return wrongType + } + return m.checkedValue[0].ID != gqlscan.TokenTrue && + m.checkedValue[0].ID != gqlscan.TokenFalse +} + +func (m *Checker) expectOrHasLen( + expect *gqlast.Type, +) (wrongType bool) { + if expect != nil { + wrongType, _ = isWrongType(expect, m.checkedValue, m.schema) + return wrongType + } + return m.checkedValue[0].ID != gqlscan.TokenArr && + m.checkedValue[0].ID != gqlscan.TokenStr && + m.checkedValue[0].ID != gqlscan.TokenStrBlock +} + +func makeEqArray( + v *gqt.Array, + expect *gqlast.Type, + schema *gqlast.Schema, +) check { + checks := make([]check, len(v.Items)) + for i := 0; i < len(v.Items); i++ { + var expect *gqlast.Type + if expect != nil { + expect = expect.Elem + } + checks[i] = makeCheck(v.Items[i], expect, schema) + } + return func(m *Checker) (match bool) { + if m.checkedValue[0].ID != gqlscan.TokenArr { + return false + } + m.checkedValue = m.checkedValue[1:] + c := 0 + for ; m.checkedValue[0].ID != gqlscan.TokenArrEnd; c++ { + if c >= len(checks) { + return false + } + if !checks[c](m) { + return false + } + } + if c != len(checks) { + return false + } + m.checkedValue = m.checkedValue[1:] + return true + } +} + +func makeNotEqArray( + v *gqt.Array, + expect *gqlast.Type, + schema *gqlast.Schema, +) check { + checks := make([]check, len(v.Items)) + for i := 0; i < len(v.Items); i++ { + var expect *gqlast.Type + if expect != nil { + expect = expect.Elem + } + checks[i] = makeCheck(v.Items[i], expect, schema) + } + return func(m *Checker) (match bool) { + if m.checkedValue[0].ID != gqlscan.TokenArr { + return false + } + m.checkedValue = m.checkedValue[1:] + c := 0 + for ; m.checkedValue[0].ID != gqlscan.TokenArrEnd; c++ { + if c >= len(checks) { + // Skip all following values to return correct tokenRead + for m.checkedValue[0].ID != gqlscan.TokenArrEnd { + l := gqlparse.GetValLen(m.checkedValue) + m.checkedValue = m.checkedValue[l:] + } + return true + } + if !checks[c](m) { + // Skip all following values to return correct tokenRead + for m.checkedValue[0].ID != gqlscan.TokenArrEnd { + l := gqlparse.GetValLen(m.checkedValue) + m.checkedValue = m.checkedValue[l:] + } + return true + } + } + m.checkedValue = m.checkedValue[1:] + // All item checks were matched, finally check length + return c != len(checks) + } +} + +func makeEqNumber( + v *gqt.Number, + expect *gqlast.Type, + schema *gqlast.Schema, +) check { + if i, ok := v.Int(); ok { + return func(m *Checker) (match bool) { + if m.expectOrInt(expect) { + return false + } + a := atoi.MustI32(m.checkedValue[0].Value) + m.checkedValue = m.checkedValue[1:] + return int32(i) == a + } + } + f, _ := v.Float() + return func(m *Checker) (match bool) { + if m.expectOrFloat(expect) { + return false + } + a := atoi.MustF64(m.checkedValue[0].Value) + m.checkedValue = m.checkedValue[1:] + return f == a + } +} + +func makeNotEqNumber( + v *gqt.Number, + expect *gqlast.Type, + schema *gqlast.Schema, +) check { + if i, ok := v.Int(); ok { + return func(m *Checker) (match bool) { + if m.expectOrInt(expect) { + return false + } + a := atoi.MustI32(m.checkedValue[0].Value) + m.checkedValue = m.checkedValue[1:] + return int32(i) != a + } + } + f, _ := v.Float() + return func(m *Checker) (match bool) { + if m.expectOrFloat(expect) { + return false + } + a := atoi.MustF64(m.checkedValue[0].Value) + m.checkedValue = m.checkedValue[1:] + return f != a + } +} + +func makeEqString( + v *gqt.String, + expect *gqlast.Type, + schema *gqlast.Schema, +) check { + return func(m *Checker) (match bool) { + if m.expectOrString(expect) { + return false + } + t := m.checkedValue[0] + m.checkedValue = m.checkedValue[1:] + switch t.ID { + case gqlscan.TokenStr: + return v.Value == string(t.Value) + case gqlscan.TokenStrBlock: + return v.Value == string(t.Value) + } + return false + } +} + +func makeNotEqString( + v *gqt.String, + expect *gqlast.Type, + schema *gqlast.Schema, +) check { + return func(m *Checker) (match bool) { + if m.expectOrString(expect) { + return false + } + t := m.checkedValue[0] + m.checkedValue = m.checkedValue[1:] + switch t.ID { + case gqlscan.TokenStr: + return v.Value != string(t.Value) + case gqlscan.TokenStrBlock: + return v.Value != string(t.Value) + } + return false + } +} + +func makeEqEnum( + v *gqt.Enum, + expect *gqlast.Type, + schema *gqlast.Schema, +) check { + return func(m *Checker) (match bool) { + if m.expectOrEnum(expect) { + return false + } + b := m.checkedValue[0].Value + m.checkedValue = m.checkedValue[1:] + return v.Value == string(b) + } +} + +func makeNotEqEnum( + v *gqt.Enum, + expect *gqlast.Type, + schema *gqlast.Schema, +) check { + return func(m *Checker) (match bool) { + if m.expectOrEnum(expect) { + return false + } + b := m.checkedValue[0].Value + m.checkedValue = m.checkedValue[1:] + return v.Value != string(b) + } +} + +func makeEqNull( + expect *gqlast.Type, + schema *gqlast.Schema, +) check { + return func(m *Checker) (match bool) { + if wrong, _ := isWrongType(expect, m.checkedValue, schema); wrong { + return false + } + t := m.checkedValue[0].ID + m.checkedValue = m.checkedValue[1:] + return t == gqlscan.TokenNull + } +} + +func makeNotEqNull( + expect *gqlast.Type, + schema *gqlast.Schema, +) check { + return func(m *Checker) (match bool) { + if wrong, _ := isWrongType(expect, m.checkedValue, schema); wrong { + return false + } + t := m.checkedValue[0].ID + m.checkedValue = m.checkedValue[1:] + return t != gqlscan.TokenNull + } +} + +func makeEqBool( + value bool, + expect *gqlast.Type, + schema *gqlast.Schema, +) check { + v := gqlscan.TokenFalse + if value { + v = gqlscan.TokenTrue + } + return func(m *Checker) (match bool) { + if m.expectOrBool(expect) { + return false + } + t := m.checkedValue[0].ID + m.checkedValue = m.checkedValue[1:] + return t == v + } +} + +func makeNotEqBool( + value bool, + expect *gqlast.Type, + schema *gqlast.Schema, +) check { + v := gqlscan.TokenFalse + if value { + v = gqlscan.TokenTrue + } + return func(m *Checker) (match bool) { + if m.expectOrBool(expect) { + return false + } + t := m.checkedValue[0].ID + m.checkedValue = m.checkedValue[1:] + return t != v + } +} + +func makeEqObject( + v *gqt.Object, + expect *gqlast.Type, + schema *gqlast.Schema, +) check { + checks := make(map[string]check, len(v.Fields)) + fieldChecked := make(map[string]bool, len(v.Fields)) + var requiredChecks int + for _, v := range v.Fields { + var expect *gqlast.Type + if v.Def != nil { + expect = v.Def.Type + if expect.NonNull { + requiredChecks++ + } + } + checks[v.Name.Name] = makeCheck(v.Constraint, expect, schema) + fieldChecked[v.Name.Name] = false + } + return func(m *Checker) (match bool) { + if wrong, _ := isWrongType(expect, m.checkedValue, schema); wrong { + return false + } + if m.checkedValue[0].ID != gqlscan.TokenObj { + return false + } + m.checkedValue = m.checkedValue[1:] + + // Reset check status + for k := range fieldChecked { + fieldChecked[k] = false + } + + c := 0 + for ; m.checkedValue[0].ID != gqlscan.TokenObjEnd; c++ { + if c >= len(checks) { + return false + } + + check := checks[string(m.checkedValue[0].Value)] + if check == nil { + // Unknown field, wrong object type + return false + } + + if fieldChecked[string(m.checkedValue[0].Value)] { + // Field provided twice, invalid object + return false + } + fieldChecked[string(m.checkedValue[0].Value)] = true + + m.checkedValue = m.checkedValue[1:] + + if !check(m) { + return false + } + } + if requiredChecks < 1 && c != len(checks) || c < requiredChecks { + // Not all required fields were provided, wrong object type + return false + } + m.checkedValue = m.checkedValue[1:] + return true + } +} + +func makeNotEqObject( + v *gqt.Object, + expect *gqlast.Type, + schema *gqlast.Schema, +) check { + checks := make(map[string]check, len(v.Fields)) + fieldChecked := make(map[string]bool, len(v.Fields)) + var requiredChecks int + for _, v := range v.Fields { + var expect *gqlast.Type + if v.Def != nil { + expect = v.Def.Type + if expect.NonNull { + requiredChecks++ + } + } + checks[v.Name.Name] = makeCheck(v.Constraint, expect, schema) + fieldChecked[v.Name.Name] = false + } + return func(m *Checker) (match bool) { + if wrong, _ := isWrongType(expect, m.checkedValue, schema); wrong { + return false + } + if m.checkedValue[0].ID != gqlscan.TokenObj { + return false + } + m.checkedValue = m.checkedValue[1:] + + // Reset check status + for k := range fieldChecked { + fieldChecked[k] = false + } + + c := 0 + for ; m.checkedValue[0].ID != gqlscan.TokenObjEnd; c++ { + if c >= len(checks) { + return false + } + + check := checks[string(m.checkedValue[0].Value)] + if check == nil { + // Unknown field, wrong object type + return false + } + + if fieldChecked[string(m.checkedValue[0].Value)] { + // Field provided twice, invalid object + return false + } + fieldChecked[string(m.checkedValue[0].Value)] = true + + m.checkedValue = m.checkedValue[1:] + + if check(m) { + // Don't return just yet as we're not sure whether the + // object type was correct. + match = true + } + } + if requiredChecks < 1 && c != len(checks) || c < requiredChecks { + // Not all required fields were provided, wrong object type + return false + } + m.checkedValue = m.checkedValue[1:] + return !match + } +} + +func makeEqExpression( + v gqt.Expression, + expect *gqlast.Type, + schema *gqlast.Schema, +) check { + return func(m *Checker) (match bool) { + if wrong, _ := isWrongType(expect, m.checkedValue, schema); wrong { + return false + } + m.resolveExpr(v) + u := m.popStack() + + switch u.Type() { + case union.TypeNull: + return m.checkedValue[0].ID == gqlscan.TokenNull + case union.TypeBoolean: + b, _ := u.Bool() + if b { + return m.checkedValue[0].ID == gqlscan.TokenTrue + } + return m.checkedValue[0].ID == gqlscan.TokenFalse + case union.TypeInt: + if m.checkedValue[0].ID == gqlscan.TokenInt { + u, _ := u.Int() + return u == atoi.MustI32(m.checkedValue[0].Value) + } + return false + case union.TypeFloat: + if m.checkedValue[0].ID == gqlscan.TokenFloat { + u, _ := u.Float() + return u == atoi.MustF64(m.checkedValue[0].Value) + } + return false + case union.TypeString: + if m.checkedValue[0].ID == gqlscan.TokenStr { + u, _ := u.String() + return u == string(m.checkedValue[0].Value) + } + return false + case union.TypeEnum: + if m.checkedValue[0].ID == gqlscan.TokenEnumVal { + u, _ := u.Enum() + return u == string(m.checkedValue[0].Value) + } + return false + case union.TypeTokens: + if len(u.Tokens()) != len(m.checkedValue) { + return false + } + for i := range m.checkedValue { + if m.checkedValue[i].ID != u.Tokens()[i].ID { + return false + } + switch m.checkedValue[i].ID { + case gqlscan.TokenStrBlock: + if string(m.checkedValue[i].Value) != string(u.Tokens()[i].Value) { + return false + } + default: + if string(m.checkedValue[i].Value) != string(u.Tokens()[i].Value) { + return false + } + } + } + } + + return true + } +} + +func unionsEqual(a, b []union.Union) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if !union.Equal(a[i], b[i]) { + return false + } + } + return true +} diff --git a/engines/playmon/internal/constrcheck/constrcheck_test.go b/engines/playmon/internal/constrcheck/constrcheck_test.go new file mode 100644 index 0000000..7c64fea --- /dev/null +++ b/engines/playmon/internal/constrcheck/constrcheck_test.go @@ -0,0 +1,119 @@ +package constrcheck_test + +import ( + "embed" + "io/fs" + "sort" + "strings" + "testing" + + "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck" + "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck/internal/test" + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/gqt/v4" + "github.com/stretchr/testify/require" + "github.com/vektah/gqlparser/v2" + gqlast "github.com/vektah/gqlparser/v2/ast" +) + +//go:embed tests +var testsFS embed.FS + +func TestCheck(t *testing.T) { + d, err := fs.ReadDir(testsFS, "tests") + require.NoError(t, err) + + for _, do := range d { + fileName := do.Name() + if do.IsDir() { + t.Run(fileName, func(t *testing.T) { + t.Skipf("ignoring directory %q", fileName) + }) + continue + } + if !strings.HasSuffix(fileName, ".yml") { + t.Run(fileName, func(t *testing.T) { + t.Skipf("ignoring file %q", fileName) + }) + continue + } + + t.Run(strings.TrimSuffix(fileName, ".yml"), func(t *testing.T) { + ts, err := test.Parse(testsFS, fileName) + require.NoError(t, err) + var withschema, schemaless *gqt.Operation + var schemaAST *gqlast.Schema + { + p, err := gqt.NewParser([]gqt.Source{ + {Name: "schema.graphqls", Content: ts.Schema}, + }) + require.NoError(t, err, "unexpected error in schema") + opr, _, errs := p.Parse([]byte(ts.Template)) + require.Len(t, errs, 0, "unexpected errors: %#v", errs) + withschema = opr + + schemaAST, err = gqlparser.LoadSchema(&gqlast.Source{ + Name: "schema.graphqls", Input: ts.Schema, + }) + require.NoError(t, err) + } + { + opr, _, errs := gqt.Parse([]byte(ts.Template)) + require.Len(t, errs, 0, "unexpected errors: %#v", errs) + schemaless = opr + } + + run := func(t *testing.T, opr *gqt.Operation) { + t.Helper() + c := constrcheck.New(opr, schemaAST) + + paths := make([]string, 0, len(ts.Inputs)) + for path := range ts.Inputs { + paths = append(paths, path) + } + sort.Strings(paths) + + inputs := make(map[string][]gqlparse.Token, len(ts.Inputs)) + for path, v := range ts.Inputs { + inputs[path] = v.Tokens + } + + c.Init(inputs) + for _, path := range paths { + if len(ts.Inputs[path].Tokens) < 1 { + t.Errorf("missing test value for path %q", path) + continue + } + + if ts.Inputs[path].Match == nil { + i := ts.Inputs[path] + t := false + i.Match = &t + ts.Inputs[path] = i + } + if ts.Inputs[path].MatchSchemaless == nil { + i := ts.Inputs[path] + i.MatchSchemaless = i.Match + ts.Inputs[path] = i + } + + actual := c.Check(path) + if opr.Def != nil { + require.Equal( + t, *ts.Inputs[path].Match, actual, + "checking %q", path, + ) + } else { + require.Equal( + t, *ts.Inputs[path].MatchSchemaless, actual, + "checking %q", path, + ) + } + } + } + + t.Run("schema", func(t *testing.T) { run(t, withschema) }) + t.Run("schemaless", func(t *testing.T) { run(t, schemaless) }) + }) + } +} diff --git a/engines/playmon/internal/constrcheck/doc.go b/engines/playmon/internal/constrcheck/doc.go new file mode 100644 index 0000000..1814a94 --- /dev/null +++ b/engines/playmon/internal/constrcheck/doc.go @@ -0,0 +1,27 @@ +// package constrcheck provides function Make which +// creates constraint checker functions for each input in a GQT operation. +// Example: +// +// query { +// f( +// a: * +// b=$v: > 10 +// c: [...["a", != "b"]] +// d: len < (4+2) * $v +// ) +// } +// +// The above template will produce 3 constraint checker functions because +// there is no need to check argument "a" since it accepts any value. +// Argument "b" will accept Int values greater 10; +// argument "c" will accept arrays where every item is an array +// that contains exactly 2 items, first of which must be equal String("a") +// and second must not be equal String("b"); +// argument "d" will accept String values (or arrays depending on schema type) +// the length of which is less than (4+2) multiplied by +// the value of argument "b". +// Constraint checker functions will be returned as a map of path -> function. +// and require argument inputs to be passed as map[string]any, where +// int32, float64, string, Enum, bool values as well as (nested) slices of any +// of the mentioned types are matched. +package constrcheck diff --git a/engines/playmon/internal/constrcheck/internal/test/test.go b/engines/playmon/internal/constrcheck/internal/test/test.go new file mode 100644 index 0000000..747eb22 --- /dev/null +++ b/engines/playmon/internal/constrcheck/internal/test/test.go @@ -0,0 +1,144 @@ +package test + +import ( + "bytes" + "embed" + "fmt" + "path/filepath" + + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/gqlscan" + "gopkg.in/yaml.v3" +) + +type T struct { + Schema string `yaml:"schema"` + Template string `yaml:"template"` + Inputs map[string]Input `yaml:"inputs"` +} + +type Input struct { + Match *bool `yaml:"match"` + MatchSchemaless *bool `yaml:"match-schemaless"` + Tokens Tokens `yaml:"tokens"` +} + +type Tokens []gqlparse.Token + +func (t *Tokens) UnmarshalYAML(value *yaml.Node) error { + for _, pair := range value.Content { + if len(pair.Content) < 1 { + return fmt.Errorf("invalid tokens dictionary") + } + tp, err := parseTokenType(pair.Content[0].Value) + if err != nil { + return err + } + *t = append(*t, gqlparse.Token{ID: tp, Value: []byte(pair.Content[1].Value)}) + } + return nil +} + +func Parse(fs embed.FS, fileName string) (ts T, err error) { + f, err := fs.ReadFile(filepath.Join("tests", fileName)) + if err != nil { + return ts, fmt.Errorf("reading YAML test file: %w", err) + } + + d := yaml.NewDecoder(bytes.NewReader(f)) + d.KnownFields(true) + if err := d.Decode(&ts); err != nil { + return ts, fmt.Errorf("parsing YAML test definition: %w", err) + } + + // Make sure each input has only one value + for path, i := range ts.Inputs { + if len(i.Tokens) < 1 { + return ts, fmt.Errorf("%q: missing input value", path) + } + } + + return ts, nil +} + +func parseTokenType(name string) (gqlscan.Token, error) { + switch name { + case "DefQry": + return gqlscan.TokenDefQry, nil + case "DefMut": + return gqlscan.TokenDefMut, nil + case "DefSub": + return gqlscan.TokenDefSub, nil + case "DefFrag": + return gqlscan.TokenDefFrag, nil + case "OprName": + return gqlscan.TokenOprName, nil + case "DirName": + return gqlscan.TokenDirName, nil + case "VarList": + return gqlscan.TokenVarList, nil + case "VarListEnd": + return gqlscan.TokenVarListEnd, nil + case "ArgList": + return gqlscan.TokenArgList, nil + case "ArgListEnd": + return gqlscan.TokenArgListEnd, nil + case "Set": + return gqlscan.TokenSet, nil + case "SetEnd": + return gqlscan.TokenSetEnd, nil + case "FragTypeCond": + return gqlscan.TokenFragTypeCond, nil + case "FragName": + return gqlscan.TokenFragName, nil + case "FragInline": + return gqlscan.TokenFragInline, nil + case "NamedSpread": + return gqlscan.TokenNamedSpread, nil + case "FieldAlias": + return gqlscan.TokenFieldAlias, nil + case "Field": + return gqlscan.TokenField, nil + case "ArgName": + return gqlscan.TokenArgName, nil + case "EnumVal": + return gqlscan.TokenEnumVal, nil + case "Arr": + return gqlscan.TokenArr, nil + case "ArrEnd": + return gqlscan.TokenArrEnd, nil + case "Str": + return gqlscan.TokenStr, nil + case "StrBlock": + return gqlscan.TokenStrBlock, nil + case "Int": + return gqlscan.TokenInt, nil + case "Float": + return gqlscan.TokenFloat, nil + case "True": + return gqlscan.TokenTrue, nil + case "False": + return gqlscan.TokenFalse, nil + case "Null": + return gqlscan.TokenNull, nil + case "VarName": + return gqlscan.TokenVarName, nil + case "VarTypeName": + return gqlscan.TokenVarTypeName, nil + case "VarTypeArr": + return gqlscan.TokenVarTypeArr, nil + case "VarTypeArrEnd": + return gqlscan.TokenVarTypeArrEnd, nil + case "VarTypeNotNull": + return gqlscan.TokenVarTypeNotNull, nil + case "VarRef": + return gqlscan.TokenVarRef, nil + case "Obj": + return gqlscan.TokenObj, nil + case "ObjEnd": + return gqlscan.TokenObjEnd, nil + case "ObjField": + return gqlscan.TokenObjField, nil + } + return 0, fmt.Errorf("unknown token type: %q", name) +} diff --git a/engines/playmon/internal/constrcheck/internal/union/union.go b/engines/playmon/internal/constrcheck/internal/union/union.go new file mode 100644 index 0000000..8c88cdf --- /dev/null +++ b/engines/playmon/internal/constrcheck/internal/union/union.go @@ -0,0 +1,291 @@ +package union + +import ( + "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck/internal/unsafe" + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/utilities/atoi" + "github.com/graph-guard/gqlscan" +) + +type Type int8 + +const ( + _ Type = iota + TypeNull + TypeTokens + TypeString + TypeEnum + TypeFloat + TypeInt + TypeBoolean + TypeArray + TypeArrayEnd +) + +func Array() Union { return Union{unionType: TypeArray} } +func ArrayEnd() Union { return Union{unionType: TypeArrayEnd} } +func Null() Union { return Union{unionType: TypeNull} } +func Int(v int32) Union { return Union{unionType: TypeInt, i: v} } +func Float(v float64) Union { return Union{unionType: TypeFloat, f: v} } +func String(v string) Union { return Union{unionType: TypeString, s: v} } +func Enum(v string) Union { return Union{unionType: TypeEnum, s: v} } +func Tokens(v []gqlparse.Token) Union { return Union{unionType: TypeTokens, t: v} } +func True() Union { return Union{unionType: TypeBoolean, i: 1} } +func False() Union { return Union{unionType: TypeBoolean, i: 0} } + +func (t Type) String() string { + switch t { + case TypeNull: + return "Null" + case TypeTokens: + return "Tokens" + case TypeString: + return "String" + case TypeEnum: + return "Enum" + case TypeFloat: + return "Float" + case TypeInt: + return "Int" + case TypeBoolean: + return "Boolean" + case TypeArray: + return "array" + case TypeArrayEnd: + return "array_end" + } + return "" +} + +// Union represents any of Float, Int, String, Enum, Boolean +type Union struct { + t []gqlparse.Token + s string + f float64 + i int32 + unionType Type +} + +// Value defines whether a value could be extracted (>ValueNone) +// and how it was extracted. +type Value int8 + +const ( + // ValueNone that there is no value. + ValueNone Value = iota - 1 + // ValueConv indicates that the value matched exactly. + ValueExact + // ValueConv indicates that the value was converted. + ValueConv + // ValueInf indicates that the value was inferred. + ValueInf + // ValueInfConv indicates that the value was inferred and converted. + ValueInfConv +) + +// Type returns the type of the value the union is holding, +// or 0 if the union has zero value. +func (u *Union) Type() Type { return u.unionType } + +// Float returns a float64 value or ValueNone if +// the union is storing a value of a different type. +func (u *Union) Float() (float64, Value) { + switch u.unionType { + case TypeFloat: + return u.f, ValueExact + case TypeInt: + return float64(u.i), ValueConv + case TypeTokens: + switch u.t[0].ID { + case gqlscan.TokenInt: + return float64(atoi.MustI32(u.t[0].Value)), ValueInfConv + case gqlscan.TokenFloat: + return atoi.MustF64(u.t[0].Value), ValueInf + } + } + return 0, ValueNone +} + +// Int returns an int32 value or ValueNone if +// the union is storing a value of a different type. +func (u *Union) Int() (value int32, ok Value) { + switch u.unionType { + case TypeInt: + return u.i, ValueExact + case TypeTokens: + if u.t[0].ID == gqlscan.TokenInt { + return atoi.MustI32(u.t[0].Value), ValueInf + } + } + return 0, ValueNone +} + +// Enum returns an enum value or ValueNone if +// the union is storing a value of a different type. +func (u *Union) Enum() (value string, ok Value) { + switch u.unionType { + case TypeEnum: + return u.s, ValueExact + case TypeTokens: + if u.t[0].ID == gqlscan.TokenEnumVal { + return unsafe.B2S(u.t[0].Value), ValueInf + } + } + return "", ValueNone +} + +// Bool returns a boolean value or ValueNone if +// the union is storing a value of a different type. +func (u *Union) Bool() (value bool, ok Value) { + if u.unionType == TypeBoolean { + return u.i > 0, ValueExact + } else if u.unionType == TypeTokens { + switch u.t[0].ID { + case gqlscan.TokenTrue: + return true, ValueInf + case gqlscan.TokenFalse: + return false, ValueInf + } + } + return false, ValueNone +} + +// String returns a string value or ValueNone if +// the union is storing a value of a different type. +func (u *Union) String() (value string, ok Value) { + if u.unionType == TypeString { + return u.s, ValueExact + } else if u.unionType == TypeTokens { + switch u.t[0].ID { + case gqlscan.TokenStr: + return unsafe.B2S(u.t[0].Value), ValueInf + case gqlscan.TokenStrBlock: + panic("todo") + // return unsafe.B2S(u.t[0].Value), true + } + } + return "", ValueNone +} + +// Tokens returns the tokens slice or nil if +// the union is storing a value of a different type. +func (u *Union) Tokens() (value []gqlparse.Token) { + if u.unionType == TypeTokens { + return u.t + } + return nil +} + +// IsNull returns true if the union represents a null value. +func (u *Union) IsNull() bool { + return u.unionType == TypeNull || + u.unionType == TypeTokens && u.t[0].ID == gqlscan.TokenNull +} + +func Equal(l, r Union) bool { + if l.unionType == TypeArray && r.unionType == TypeArray || + l.unionType == TypeArrayEnd && r.unionType == TypeArrayEnd { + return true + } + if l.IsNull() && r.IsNull() { + return true + } + if l, v := l.Bool(); v != ValueNone { + r, v := r.Bool() + return v != ValueNone && l == r + } + if l, v := l.Int(); v != ValueNone { + r, v := r.Int() + return v != ValueNone && l == r + } + if l, v := l.Float(); v != ValueNone { + r, v := r.Float() + return v != ValueNone && l == r + } + if l, v := l.String(); v != ValueNone { + r, v := r.String() + return v != ValueNone && l == r + } + if l, v := l.Enum(); v != ValueNone { + r, v := r.Enum() + return v != ValueNone && l == r + } + + if r.unionType != TypeTokens || len(l.t) != len(r.t) { + return false + } + for i := range r.t { + if l.t[i].ID != r.t[i].ID || + string(l.t[i].Value) != string(r.t[i].Value) { + return false + } + } + return l.unionType != TypeArray && l.unionType != TypeArrayEnd + + /* OLD IMPLEMENTATION */ + // switch l.unionType { + // case TypeInt: + // switch r.unionType { + // case TypeInt: + // return l.i == r.i + // case TypeFloat: + // return l.Float() == r.f + // case TypeTokens: + // switch r.t[0].Type { + // case gqlscan.TokenInt: + // return l.i == atoi.MustI32(r.t[0].Value) + // case gqlscan.TokenFloat: + // return l.Float() == atoi.MustF64(r.t[0].Value) + // } + // } + // case TypeFloat: + // switch r.unionType { + // case TypeInt: + // return l.f == r.Float() + // case TypeFloat: + // return l.f == r.f + // case TypeTokens: + // switch r.t[0].Type { + // case gqlscan.TokenInt: + // return l.f == float64(atoi.MustI32(r.t[0].Value)) + // case gqlscan.TokenFloat: + // return l.f == atoi.MustF64(r.t[0].Value) + // } + // } + // case TypeEnum: + // return l.Enum() == r.Enum() + // case TypeBoolean: + // return l.Bool() == r.Bool() + // case TypeNull: + // return r.unionType == TypeNull + // case TypeString: + // switch r.unionType { + // case TypeString: + // return l.s == r.s + // case TypeTokens: + // switch r.t[0].Type { + // case gqlscan.TokenStr: + // return l.s == string(r.t[0].Value) + // case gqlscan.TokenStrBlock: + // panic("todo") + // } + // } + // case TypeTokens: + // if r.unionType != TypeTokens { + // return false + // } + // if len(l.t) != len(r.t) { + // return false + // } + // for i := range r.t { + // if l.t[i].Type != r.t[i].Type { + // return false + // } + // if string(l.t[i].Value) != string(r.t[i].Value) { + // return false + // } + // } + // return true + // } + // return false +} diff --git a/engines/playmon/internal/constrcheck/internal/union/union_test.go b/engines/playmon/internal/constrcheck/internal/union/union_test.go new file mode 100644 index 0000000..d7a66bc --- /dev/null +++ b/engines/playmon/internal/constrcheck/internal/union/union_test.go @@ -0,0 +1,304 @@ +package union_test + +import ( + "testing" + + "github.com/graph-guard/constrcheck/internal/token" + "github.com/graph-guard/constrcheck/internal/union" + "github.com/graph-guard/gqlscan" + "github.com/stretchr/testify/require" +) + +func TestAreUnionsEqual(t *testing.T) { + for _, tt := range []struct { + Name string + Left union.Union + Right union.Union + Expect bool + }{ + { + Name: "same_type_int_equal", + Left: union.Int(42), + Right: union.Int(42), + Expect: true, + }, + { + Name: "same_type_int_diff", + Left: union.Int(42), + Right: union.Int(43), + Expect: false, + }, + + { + Name: "same_type_float_equal", + Left: union.Float(3.14), + Right: union.Float(3.14), + Expect: true, + }, + { + Name: "same_type_float_diff", + Left: union.Float(3.14), + Right: union.Float(3.15), + Expect: false, + }, + + { + Name: "same_type_bool_equal", + Left: union.True(), + Right: union.True(), + Expect: true, + }, + { + Name: "same_type_bool_diff", + Left: union.True(), + Right: union.False(), + Expect: false, + }, + + { + Name: "same_type_string_equal", + Left: union.String("okay"), + Right: union.String("okay"), + Expect: true, + }, + { + Name: "same_type_string_diff", + Left: union.String("okay"), + Right: union.String("!okay"), + Expect: false, + }, + + { + Name: "same_type_enum_equal", + Left: union.Enum("red"), + Right: union.Enum("red"), + Expect: true, + }, + { + Name: "same_type_enum_diff", + Left: union.Enum("red"), + Right: union.Enum("redd"), + Expect: false, + }, + + { + Name: "same_type_null_equal", + Left: union.Null(), + Right: union.Null(), + Expect: true, + }, + { + Name: "same_type_null_diff", + Left: union.Null(), + Right: union.Int(42), + Expect: false, + }, + + { + Name: "same_type_tokens_int_equal", + Left: union.Tokens([]token.Token{ + {Type: gqlscan.TokenInt, Value: []byte("42")}, + }), + Right: union.Tokens([]token.Token{ + {Type: gqlscan.TokenInt, Value: []byte("42")}, + }), + Expect: true, + }, + { + Name: "same_type_tokens_int_diff", + Left: union.Tokens([]token.Token{ + {Type: gqlscan.TokenInt, Value: []byte("42")}, + }), + Right: union.Tokens([]token.Token{ + {Type: gqlscan.TokenInt, Value: []byte("43")}, + }), + Expect: false, + }, + + { + Name: "inf_int_tokens", + Left: union.Int(42), + Right: union.Tokens([]token.Token{ + {Type: gqlscan.TokenInt, Value: []byte("42")}, + }), + Expect: true, + }, + { + Name: "inf_tokens_int", + Left: union.Tokens([]token.Token{ + {Type: gqlscan.TokenInt, Value: []byte("42")}, + }), + Right: union.Int(42), + Expect: true, + }, + + { + Name: "inf_float_tokens", + Left: union.Float(3.1415), + Right: union.Tokens([]token.Token{ + {Type: gqlscan.TokenFloat, Value: []byte("3.1415")}, + }), + Expect: true, + }, + { + Name: "inf_tokens_float", + Left: union.Tokens([]token.Token{ + {Type: gqlscan.TokenFloat, Value: []byte("3.1415")}, + }), + Right: union.Float(3.1415), + Expect: true, + }, + + { + Name: "inf_string_tokens", + Left: union.String("okay"), + Right: union.Tokens([]token.Token{ + {Type: gqlscan.TokenStr, Value: []byte("okay")}, + }), + Expect: true, + }, + { + Name: "inf_tokens_string", + Left: union.Tokens([]token.Token{ + {Type: gqlscan.TokenStr, Value: []byte("okay")}, + }), + Right: union.String("okay"), + Expect: true, + }, + + { + Name: "inf_enum_tokens", + Left: union.Enum("red"), + Right: union.Tokens([]token.Token{ + {Type: gqlscan.TokenEnumVal, Value: []byte("red")}, + }), + Expect: true, + }, + { + Name: "inf_tokens_enum", + Left: union.Tokens([]token.Token{ + {Type: gqlscan.TokenEnumVal, Value: []byte("red")}, + }), + Right: union.Enum("red"), + Expect: true, + }, + + { + Name: "inf_true_tokens", + Left: union.True(), + Right: union.Tokens([]token.Token{ + {Type: gqlscan.TokenTrue}, + }), + Expect: true, + }, + { + Name: "inf_tokens_true", + Left: union.Tokens([]token.Token{ + {Type: gqlscan.TokenTrue}, + }), + Right: union.True(), + Expect: true, + }, + + { + Name: "inf_false_tokens", + Left: union.False(), + Right: union.Tokens([]token.Token{ + {Type: gqlscan.TokenFalse}, + }), + Expect: true, + }, + { + Name: "inf_tokens_false", + Left: union.Tokens([]token.Token{ + {Type: gqlscan.TokenFalse}, + }), + Right: union.False(), + Expect: true, + }, + + { + Name: "inf_null_tokens", + Left: union.Null(), + Right: union.Tokens([]token.Token{ + {Type: gqlscan.TokenNull}, + }), + Expect: true, + }, + { + Name: "inf_tokens_null", + Left: union.Tokens([]token.Token{ + {Type: gqlscan.TokenNull}, + }), + Right: union.Null(), + Expect: true, + }, + + { + Name: "equal_tokens_array", + Left: union.Tokens([]token.Token{ + {Type: gqlscan.TokenArr}, + {Type: gqlscan.TokenInt, Value: []byte("1")}, + {Type: gqlscan.TokenInt, Value: []byte("23")}, + {Type: gqlscan.TokenArr}, + }), + Right: union.Tokens([]token.Token{ + {Type: gqlscan.TokenArr}, + {Type: gqlscan.TokenInt, Value: []byte("1")}, + {Type: gqlscan.TokenInt, Value: []byte("23")}, + {Type: gqlscan.TokenArr}, + }), + Expect: true, + }, + { + Name: "not_equal_tokens_array", + Left: union.Tokens([]token.Token{ + {Type: gqlscan.TokenArr}, + {Type: gqlscan.TokenInt, Value: []byte("1")}, + {Type: gqlscan.TokenInt, Value: []byte("23")}, + {Type: gqlscan.TokenArr}, + }), + Right: union.Tokens([]token.Token{ + {Type: gqlscan.TokenArr}, + {Type: gqlscan.TokenInt, Value: []byte("23")}, + {Type: gqlscan.TokenInt, Value: []byte("1")}, + {Type: gqlscan.TokenArr}, + }), + Expect: false, + }, + { + Name: "not_equal_tokens_array_empty", + Left: union.Tokens([]token.Token{ + {Type: gqlscan.TokenArr}, + {Type: gqlscan.TokenInt, Value: []byte("1")}, + {Type: gqlscan.TokenInt, Value: []byte("23")}, + {Type: gqlscan.TokenArr}, + }), + Right: union.Tokens([]token.Token{ + {Type: gqlscan.TokenArr}, + {Type: gqlscan.TokenArr}, + }), + Expect: false, + }, + + { + Name: "inf_tokens_null", + Left: union.Tokens([]token.Token{ + {Type: gqlscan.TokenNull}, + }), + Right: union.Null(), + Expect: true, + }, + + { + Name: "diff_type_int_float", + Left: union.Int(42), + Right: union.Float(42), + Expect: false, + }, + } { + t.Run(tt.Name, func(t *testing.T) { + require.Equal(t, tt.Expect, union.Equal(tt.Left, tt.Right)) + }) + } +} diff --git a/engines/playmon/internal/constrcheck/internal/unsafe/unsafe.go b/engines/playmon/internal/constrcheck/internal/unsafe/unsafe.go new file mode 100644 index 0000000..5e05f78 --- /dev/null +++ b/engines/playmon/internal/constrcheck/internal/unsafe/unsafe.go @@ -0,0 +1,8 @@ +package unsafe + +import "unsafe" + +// B2S returns a string representation of b. +func B2S(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} diff --git a/engines/playmon/internal/constrcheck/iswrongtype.go b/engines/playmon/internal/constrcheck/iswrongtype.go new file mode 100644 index 0000000..e04584d --- /dev/null +++ b/engines/playmon/internal/constrcheck/iswrongtype.go @@ -0,0 +1,149 @@ +package constrcheck + +import ( + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/gqlscan" + gqlast "github.com/vektah/gqlparser/v2/ast" +) + +// isWrongType returns true if the value represented in input +// doesn't correspond to the type defined by expect. +func isWrongType( + expect *gqlast.Type, + input []gqlparse.Token, + schema *gqlast.Schema, +) (bool, []gqlparse.Token) { + var def *gqlast.Definition + if expect != nil { + def = schema.Types[expect.NamedType] + } + + switch input[0].ID { + case gqlscan.TokenNull: + if expect == nil { + return false, input[1:] + } + return expect.NonNull, input[1:] + case gqlscan.TokenArr: + if expect != nil { + if expect.Elem == nil { + return true, nil + } + expect = expect.Elem + } + input = input[1:] + for { + if input[0].ID == gqlscan.TokenArrEnd { + input = input[1:] + return false, input + } + var wrongType bool + wrongType, input = isWrongType(expect, input, schema) + if wrongType { + return true, nil + } + } + case gqlscan.TokenTrue, gqlscan.TokenFalse: + if expect == nil || (def != nil && def.Kind == gqlast.Scalar && + def.Name != "ID" && + def.Name != "Int" && + def.Name != "Float" && + def.Name != "String") { + return false, input[1:] + } + return expect.NamedType != "Boolean", input[1:] + case gqlscan.TokenInt: + if expect == nil || (def != nil && def.Kind == gqlast.Scalar && + def.Name != "ID" && + def.Name != "String" && + def.Name != "Boolean") { + return false, input[1:] + } + return expect.NamedType != "Int" && + expect.NamedType != "Float", input[1:] + case gqlscan.TokenFloat: + if expect == nil || (def != nil && def.Kind == gqlast.Scalar && + def.Name != "ID" && + def.Name != "Int" && + def.Name != "String" && + def.Name != "Boolean") { + return false, input[1:] + } + return expect.NamedType != "Float", input[1:] + case gqlscan.TokenStr, gqlscan.TokenStrBlock: + if expect == nil || (def != nil && def.Kind == gqlast.Scalar && + def.Name != "Int" && + def.Name != "Float" && + def.Name != "Boolean") { + return false, input[1:] + } + return expect.NamedType != "String" && + expect.NamedType != "ID", input[1:] + case gqlscan.TokenEnumVal: + if def == nil || def.Kind == gqlast.Scalar && + def.Name != "ID" && + def.Name != "Int" && + def.Name != "Float" && + def.Name != "String" && + def.Name != "Boolean" { + // No expectation or custom scalar. + return false, input[1:] + } + for i := range def.EnumValues { + if def.EnumValues[i].Name == string(input[0].Value) { + return false, input[1:] + } + } + return true, nil + case gqlscan.TokenObj: + if def == nil || def.Kind == gqlast.Scalar { + // No expectation or custom scalar type. + input = input[1:] + SKIP_OBJECT: + for levelObj := 1; ; input = input[1:] { + switch input[0].ID { + case gqlscan.TokenObj: + levelObj++ + case gqlscan.TokenObjEnd: + levelObj-- + if levelObj < 1 { + input = input[1:] + break SKIP_OBJECT + } + } + } + return false, input + } else if def.Kind != gqlast.InputObject { + return true, nil + } + + input = input[1:] + SCAN_OBJECT: + for len(input) > 0 { + if input[0].ID == gqlscan.TokenObjEnd { + input = input[1:] + break SCAN_OBJECT + } + + field := input[0] + for i := range def.Fields { + if def.Fields[i].Name == string(field.Value) { + // Check field value + var wrongType bool + wrongType, input = isWrongType( + def.Fields[i].Type, input[1:], schema, + ) + if wrongType { + return true, nil + } + continue SCAN_OBJECT + } + } + // Field not found in expected input object type + return true, nil + } + return false, input + } + + return true, nil +} diff --git a/engines/playmon/internal/constrcheck/iswrongtype_test.go b/engines/playmon/internal/constrcheck/iswrongtype_test.go new file mode 100644 index 0000000..a85a39f --- /dev/null +++ b/engines/playmon/internal/constrcheck/iswrongtype_test.go @@ -0,0 +1,507 @@ +package constrcheck + +import ( + "testing" + + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/gqlscan" + "github.com/stretchr/testify/require" + gqlparser "github.com/vektah/gqlparser/v2" + gqlast "github.com/vektah/gqlparser/v2/ast" +) + +func TestIsWrongType_False(t *testing.T) { + for _, tt := range []struct { + Name string + Schema string + ExpectType *gqlast.Type + Input []gqlparse.Token + }{ + { + Name: "int", + Schema: `type Query { x:Int }`, + ExpectType: &gqlast.Type{NamedType: "Int"}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenInt, Value: []byte("42")}, + }, + }, + { + Name: "float", + Schema: `type Query { x:Int }`, + ExpectType: &gqlast.Type{NamedType: "Float"}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenFloat, Value: []byte("3.14")}, + }, + }, + { + Name: "string", + Schema: `type Query { x:Int }`, + ExpectType: &gqlast.Type{NamedType: "String"}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenStr, Value: []byte("text")}, + }, + }, + { + Name: "string_block", + Schema: `type Query { x:Int }`, + ExpectType: &gqlast.Type{NamedType: "String"}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenStrBlock}, + }, + }, + { + Name: "id", + Schema: `type Query { x:Int }`, + ExpectType: &gqlast.Type{NamedType: "ID"}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenStr, Value: []byte("someid")}, + }, + }, + { + Name: "bool_true", + Schema: `type Query { x:Int }`, + ExpectType: &gqlast.Type{NamedType: "Boolean"}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenTrue}, + }, + }, + { + Name: "bool_false", + Schema: `type Query { x:Int }`, + ExpectType: &gqlast.Type{NamedType: "Boolean"}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenFalse}, + }, + }, + { + Name: "enum", + Schema: `enum Color {red}`, + ExpectType: &gqlast.Type{NamedType: "Color"}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenEnumVal, Value: []byte("red")}, + }, + }, + { + Name: "null", + Schema: `type Query { x:Int }`, + ExpectType: &gqlast.Type{NamedType: "Int", NonNull: false}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenNull}, + }, + }, + { + Name: "array_int", + Schema: `type Query { x:Int }`, + ExpectType: &gqlast.Type{Elem: &gqlast.Type{ + NamedType: "Int", + NonNull: true, + }}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenInt, Value: []byte("0")}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenInt, Value: []byte("100500")}, + {ID: gqlscan.TokenArrEnd}, + }, + }, + { + Name: "array_int_empty", + Schema: `type Query { x:Int }`, + ExpectType: &gqlast.Type{Elem: &gqlast.Type{ + NamedType: "Int", + NonNull: true, + }}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArrEnd}, + }, + }, + { + Name: "array_int_null", + Schema: `type Query { x:Int }`, + ExpectType: &gqlast.Type{Elem: &gqlast.Type{ + NamedType: "Int", + NonNull: false, + }}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenNull}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenNull}, + {ID: gqlscan.TokenInt, Value: []byte("0")}, + {ID: gqlscan.TokenArrEnd}, + }, + }, + { + Name: "input_object_1_field", + Schema: `input InputObject { x: Int! }`, + ExpectType: &gqlast.Type{ + NamedType: "InputObject", + }, + Input: []gqlparse.Token{ + // {x:0} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("x")}, + {ID: gqlscan.TokenInt, Value: []byte("0")}, + {ID: gqlscan.TokenObjEnd}, + }, + }, + { + Name: "input_object_3_fields", + Schema: `input InputObject { x: Int! y:String! z:Boolean }`, + ExpectType: &gqlast.Type{ + NamedType: "InputObject", + }, + Input: []gqlparse.Token{ + // {x:0, y:"text" z:null} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("x")}, + {ID: gqlscan.TokenInt, Value: []byte("0")}, + {ID: gqlscan.TokenObjField, Value: []byte("y")}, + {ID: gqlscan.TokenStr, Value: []byte("text")}, + {ID: gqlscan.TokenObjField, Value: []byte("z")}, + {ID: gqlscan.TokenNull}, + {ID: gqlscan.TokenObjEnd}, + }, + }, + { + Name: "nested_input_object", + Schema: ` + input InputObject { x: O! y:String! z:Boolean } + input O { i: Int! } + `, + ExpectType: &gqlast.Type{ + NamedType: "InputObject", + }, + Input: []gqlparse.Token{ + // {x:{i:42}, y:"text" z:null} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("x")}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("i")}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObjField, Value: []byte("y")}, + {ID: gqlscan.TokenStr, Value: []byte("text")}, + {ID: gqlscan.TokenObjField, Value: []byte("z")}, + {ID: gqlscan.TokenNull}, + {ID: gqlscan.TokenObjEnd}, + }, + }, + { + Name: "2d_array_nested_input_objects", + Schema: ` + input InputObject { x: O! } + input O { i: Int! } + `, + ExpectType: &gqlast.Type{ + Elem: &gqlast.Type{ + Elem: &gqlast.Type{ + NamedType: "InputObject", + }, + }, + }, + Input: []gqlparse.Token{ + // [[{x:{i:42}}]] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("x")}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("i")}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenArrEnd}, + }, + }, + { + Name: "custom_scalar_int", + Schema: `scalar Custom`, + ExpectType: &gqlast.Type{ + NamedType: "Custom", + }, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenInt, Value: []byte("0")}, + }, + }, + { + Name: "custom_scalar_string", + Schema: `scalar Custom`, + ExpectType: &gqlast.Type{ + NamedType: "Custom", + }, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenStr, Value: []byte("custom")}, + }, + }, + { + Name: "custom_scalar_string_block", + Schema: `scalar Custom`, + ExpectType: &gqlast.Type{ + NamedType: "Custom", + }, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenStrBlock}, + }, + }, + { + Name: "custom_scalar_enum", + Schema: `scalar Custom`, + ExpectType: &gqlast.Type{ + NamedType: "Custom", + }, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenEnumVal, Value: []byte("customenum")}, + }, + }, + { + Name: "custom_scalar_boolean_true", + Schema: `scalar Custom`, + ExpectType: &gqlast.Type{ + NamedType: "Custom", + }, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenTrue}, + }, + }, + { + Name: "custom_scalar_boolean_false", + Schema: `scalar Custom`, + ExpectType: &gqlast.Type{ + NamedType: "Custom", + }, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenFalse}, + }, + }, + { + Name: "custom_scalar_object", + Schema: `scalar Custom`, + ExpectType: &gqlast.Type{ + NamedType: "Custom", + }, + Input: []gqlparse.Token{ + // {x:{y:[0]}} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("x")}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("y")}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenInt, Value: []byte("0")}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObjEnd}, + }, + }, + { + Name: "tst", + Schema: ` + type Query { f(object:Object!):Int } + input Object { subobject: SubObject! } + input SubObject { array: [ArrayObject!]! } + input ArrayObject { name: String!, index: Int! } + `, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("subobject")}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("array")}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("name")}, + {ID: gqlscan.TokenStr, Value: []byte("first")}, + {ID: gqlscan.TokenObjField, Value: []byte("index")}, + {ID: gqlscan.TokenInt, Value: []byte("0")}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("name")}, + {ID: gqlscan.TokenStr, Value: []byte("second")}, + {ID: gqlscan.TokenObjField, Value: []byte("index")}, + {ID: gqlscan.TokenInt, Value: []byte("1")}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObjEnd}, + }, + }, + { + Name: "expect_float_get_int", + Schema: `type Query { x:Int }`, + ExpectType: &gqlast.Type{NamedType: "Float"}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenInt, Value: []byte("42")}, + }, + }, + } { + t.Run(tt.Name, func(t *testing.T) { + var s *gqlast.Schema + if tt.Schema != "" { + var err error + s, err = gqlparser.LoadSchema(&gqlast.Source{ + Name: "schema.graphqls", + Input: tt.Schema, + }) + require.NoError(t, err) + } else { + require.Nil( + t, tt.ExpectType, + "type expectations are always schema-aware", + ) + } + + r, _ := isWrongType(tt.ExpectType, tt.Input, s) + require.False(t, r) + }) + } +} + +func TestIsWrongType_True(t *testing.T) { + for _, tt := range []struct { + Name string + Schema string + ExpectType *gqlast.Type + Input []gqlparse.Token + }{ + { + Name: "expect_non-null_int_get_null", + Schema: `type Query { x:Int }`, + ExpectType: &gqlast.Type{NamedType: "Int", NonNull: true}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenNull}, + }, + }, + { + Name: "expect_int_get_float", + Schema: `type Query { x:Int }`, + ExpectType: &gqlast.Type{NamedType: "Int"}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenFloat, Value: []byte("3.14")}, + }, + }, + { + Name: "expect_int_get_enum", + Schema: ` + type Query { x:Int } + enum Color { red } + `, + ExpectType: &gqlast.Type{NamedType: "Int"}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenEnumVal, Value: []byte("red")}, + }, + }, + { + Name: "expect_enum_get_int", + Schema: ` + type Query { x:Int } + enum Color { red } + `, + ExpectType: &gqlast.Type{NamedType: "Color"}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenInt, Value: []byte("42")}, + }, + }, + { + Name: "expect_boolean_get_enum", + Schema: `type Query { x:Int }`, + ExpectType: &gqlast.Type{NamedType: "Boolean"}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenEnumVal, Value: []byte("tru")}, + }, + }, + { + Name: "expect_enum_get_boolean", + Schema: `enum Color { red green blue }`, + ExpectType: &gqlast.Type{NamedType: "Color"}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenTrue}, + }, + }, + { + Name: "expect_enum_get_wrong_enum", + Schema: ` + enum Color { red green blue } + enum Fruit { banana orange apple } + `, + ExpectType: &gqlast.Type{NamedType: "Color"}, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenEnumVal, Value: []byte("orange")}, + }, + }, + { + Name: "expect_object_get_str_block", + Schema: `input InputObject { x: Int! }`, + ExpectType: &gqlast.Type{ + NamedType: "InputObject", + }, + Input: []gqlparse.Token{ + // """not an object""" + {ID: gqlscan.TokenStrBlock}, + }, + }, + { + Name: "expect_different_object_missing_fields", + Schema: `input InputObject { x: Int! }`, + ExpectType: &gqlast.Type{ + NamedType: "InputObject", + }, + Input: []gqlparse.Token{ + // {y:42} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("y")}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenObjEnd}, + }, + }, + { + Name: "expect_different_object_superfluous_fields", + Schema: `input InputObject { x: Int! }`, + ExpectType: &gqlast.Type{ + NamedType: "InputObject", + }, + Input: []gqlparse.Token{ + // {x:42, y:43} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("x")}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenObjField, Value: []byte("y")}, + {ID: gqlscan.TokenInt, Value: []byte("43")}, + {ID: gqlscan.TokenObjEnd}, + }, + }, + { + Name: "expect_different_field_type", + Schema: `input InputObject { x: Int! }`, + ExpectType: &gqlast.Type{ + NamedType: "InputObject", + }, + Input: []gqlparse.Token{ + // {x:3.14} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("x")}, + {ID: gqlscan.TokenFloat, Value: []byte("3.14")}, + {ID: gqlscan.TokenObjEnd}, + }, + }, + } { + t.Run(tt.Name, func(t *testing.T) { + var s *gqlast.Schema + if tt.Schema != "" { + var err error + s, err = gqlparser.LoadSchema(&gqlast.Source{ + Name: "schema.graphqls", + Input: tt.Schema, + }) + require.NoError(t, err) + } else { + require.Nil( + t, tt.ExpectType, + "type expectations are always schema-aware", + ) + } + r, _ := isWrongType(tt.ExpectType, tt.Input, s) + require.True(t, r) + }) + } +} diff --git a/engines/playmon/internal/constrcheck/tests/arithmetic_div.yml b/engines/playmon/internal/constrcheck/tests/arithmetic_div.yml new file mode 100644 index 0000000..a2a5789 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/arithmetic_div.yml @@ -0,0 +1,33 @@ +schema: > + type Query {f( + t: Float! + f: Float! + t2: Float + f2: Float + ):Int} + +template: > + query {f( + t: 3.14 / 3.14, + f: 3.14 / 3.14, + t2: 3.14 / 2, + f2: 3.14 / 2, + )} + +inputs: + Query.f|t: + match: true + tokens: + - Float: 1 + Query.f|f: + match: false + tokens: + - Float: 1.5 + Query.f|t2: + match: true + tokens: + - Float: 1.57 + Query.f|f2: + match: false + tokens: + - Float: 1.58 diff --git a/engines/playmon/internal/constrcheck/tests/arithmetic_mod.yml b/engines/playmon/internal/constrcheck/tests/arithmetic_mod.yml new file mode 100644 index 0000000..c9a0e54 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/arithmetic_mod.yml @@ -0,0 +1,33 @@ +schema: > + type Query {f( + t: Float! + f: Float! + t2: Float + f2: Float + ):Int} + +template: > + query {f( + t: 12.5 % 5.5, + f: 12.5 % 5.5, + t2: 20.5 % 10, + f2: 20.5 % 10, + )} + +inputs: + Query.f|t: + match: true + tokens: + - Float: 1.5 + Query.f|f: + match: false + tokens: + - Float: 15 + Query.f|t2: + match: true + tokens: + - Float: 0.5 + Query.f|f2: + match: false + tokens: + - Float: 5 diff --git a/engines/playmon/internal/constrcheck/tests/arithmetic_mul.yml b/engines/playmon/internal/constrcheck/tests/arithmetic_mul.yml new file mode 100644 index 0000000..4bde2e5 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/arithmetic_mul.yml @@ -0,0 +1,33 @@ +schema: > + type Query {f( + t: Float! + f: Float! + t2: Float + f2: Float + ):Int} + +template: > + query {f( + t: 3.14 * 3.14, + f: 3.14 * 3.14, + t2: 3.14 * -1, + f2: 3.14 * -1, + )} + +inputs: + Query.f|t: + match: true + tokens: + - Float: 9.8596 + Query.f|f: + match: false + tokens: + - Float: 9.8597 + Query.f|t2: + match: true + tokens: + - Float: -3.14 + Query.f|f2: + match: false + tokens: + - Float: -3 diff --git a/engines/playmon/internal/constrcheck/tests/arithmetic_sub.yml b/engines/playmon/internal/constrcheck/tests/arithmetic_sub.yml new file mode 100644 index 0000000..7137984 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/arithmetic_sub.yml @@ -0,0 +1,33 @@ +schema: > + type Query {f( + t: Float! + f: Float! + t2: Float + f2: Float + ):Int} + +template: > + query {f( + t: 3.14 - 2.14, + f: 3.14 - 2.14, + t2: 12 - 1.1, + f2: 12 - 1.1, + )} + +inputs: + Query.f|t: + match: true + tokens: + - Float: 1 + Query.f|f: + match: false + tokens: + - Float: 1.0001 + Query.f|t2: + match: true + tokens: + - Float: 10.9 + Query.f|f2: + match: false + tokens: + - Float: 10.91 diff --git a/engines/playmon/internal/constrcheck/tests/equal_strblock_false.todo.yaml b/engines/playmon/internal/constrcheck/tests/equal_strblock_false.todo.yaml new file mode 100644 index 0000000..4aea73e --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/equal_strblock_false.todo.yaml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + string: String! + ):Int} + +template: > + query {f( + string: "text", + )} + +inputs: + Query.f|string: + match: false + tokens: + - StrBlock: "tex" diff --git a/engines/playmon/internal/constrcheck/tests/false_arithmetic_add.yml b/engines/playmon/internal/constrcheck/tests/false_arithmetic_add.yml new file mode 100644 index 0000000..804d080 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_arithmetic_add.yml @@ -0,0 +1,27 @@ +schema: > + type Query {f( + a: Float! + b: Float! + c: Float + ):Int} + +template: > + query {f( + a: 3.14 + 3.14, + b: 12 + 1.1, + c: 3 + 1, + )} + +inputs: + Query.f|a: + match: false + tokens: + - Float: 6.281 + Query.f|b: + match: false + tokens: + - Float: 13 + Query.f|c: + match: false + tokens: + - Null: diff --git a/engines/playmon/internal/constrcheck/tests/false_constr_and.yml b/engines/playmon/internal/constrcheck/tests/false_constr_and.yml new file mode 100644 index 0000000..7339d60 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_constr_and.yml @@ -0,0 +1,27 @@ +schema: > + type Query {f( + a: Int! + b: Int! + c: Int! + ):Int} + +template: > + query {f( + a: >0 && <10, + b: >0 && <10, + c: >0 && <10 && != 5, + )} + +inputs: + Query.f|a: + match: false + tokens: + - Int: 10 + Query.f|b: + match: false + tokens: + - Int: 0 + Query.f|c: + match: false + tokens: + - Int: 5 diff --git a/engines/playmon/internal/constrcheck/tests/false_constr_or.yml b/engines/playmon/internal/constrcheck/tests/false_constr_or.yml new file mode 100644 index 0000000..4303476 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_constr_or.yml @@ -0,0 +1,27 @@ +schema: > + type Query {f( + a: Int! + b: Int! + c: Int! + ):Int} + +template: > + query {f( + a: 1 || 0, + b: 1 || 0, + c: 1 || 0 || 2, + )} + +inputs: + Query.f|a: + match: false + tokens: + - Int: 42 + Query.f|b: + match: false + tokens: + - Int: 42 + Query.f|c: + match: false + tokens: + - Int: 42 diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_arr_int.yml b/engines/playmon/internal/constrcheck/tests/false_equal_arr_int.yml new file mode 100644 index 0000000..13fb514 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_equal_arr_int.yml @@ -0,0 +1,39 @@ +schema: > + type Query {f( + ints1: [Int!]! + ints2: [Int!]! + ints3: [Int!]! + ):Int} + +template: > + query {f( + ints1: [1,2,3], + ints2: [1,2,3], + ints3: [1,2,3], + )} + +inputs: + Query.f|ints1: + match: false + tokens: + - Arr: + - Int: 1 + - Int: 2 + - ArrEnd: + Query.f|ints2: + match: false + tokens: + - Arr: + - Int: 1 + - Int: 2 + - Int: 3 + - Int: 4 + - ArrEnd: + Query.f|ints3: + match: false + tokens: + - Arr: + - Int: 3 + - Int: 2 + - Int: 1 + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_arr_str.yml b/engines/playmon/internal/constrcheck/tests/false_equal_arr_str.yml new file mode 100644 index 0000000..eb19bd7 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_equal_arr_str.yml @@ -0,0 +1,39 @@ +schema: > + type Query {f( + strs1: [String!]! + strs2: [String!]! + strs3: [String!]! + ):String} + +template: > + query {f( + strs1: ["1","2","3"], + strs2: ["1","2","3"], + strs3: ["1","2","3"], + )} + +inputs: + Query.f|strs1: + match: false + tokens: + - Arr: + - Str: "1" + - Str: "2" + - ArrEnd: + Query.f|strs2: + match: false + tokens: + - Arr: + - Str: "1" + - Str: "2" + - Str: "3" + - Str: "4" + - ArrEnd: + Query.f|strs3: + match: false + tokens: + - Arr: + - Str: "3" + - Str: "2" + - Str: "1" + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_bool.yml b/engines/playmon/internal/constrcheck/tests/false_equal_bool.yml new file mode 100644 index 0000000..d2c2734 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_equal_bool.yml @@ -0,0 +1,21 @@ +schema: > + type Query {f( + bool_t: Boolean! + bool_f: Boolean! + ):Int} + +template: > + query {f( + bool_t: true, + bool_f: false, + )} + +inputs: + Query.f|bool_t: + match: false + tokens: + - False: + Query.f|bool_f: + match: false + tokens: + - True: diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_enum.yml b/engines/playmon/internal/constrcheck/tests/false_equal_enum.yml new file mode 100644 index 0000000..62835db --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_equal_enum.yml @@ -0,0 +1,16 @@ +schema: > + type Query {f( + enum: Color! + ):Int} + enum Color { red green blue } + +template: > + query {f( + enum: green, + )} + +inputs: + Query.f|enum: + match: false + tokens: + - EnumVal: "red" diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_float.yml b/engines/playmon/internal/constrcheck/tests/false_equal_float.yml new file mode 100644 index 0000000..f42df8b --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_equal_float.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + float: Float! + ):Int} + +template: > + query {f( + float: 3.14159, + )} + +inputs: + Query.f|float: + match: false + tokens: + - Float: 3.1415 diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_int.yml b/engines/playmon/internal/constrcheck/tests/false_equal_int.yml new file mode 100644 index 0000000..1004b4f --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_equal_int.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + int: Int! + ):Int} + +template: > + query {f( + int: 42, + )} + +inputs: + Query.f|int: + match: false + tokens: + - Int: 142 diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_obj.yml b/engines/playmon/internal/constrcheck/tests/false_equal_obj.yml new file mode 100644 index 0000000..181a56e --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_equal_obj.yml @@ -0,0 +1,35 @@ +schema: > + type Query {f( + a: A! + ):Int} + input A { b: B! } + input B { + s: String! + i: Int! + optional: Boolean + } + +template: > + query {f( + a: { + b: { + s: "okay", + i: 42, + optional: * + } + } + )} + +inputs: + Query.f|a: + match: false + tokens: # {b:{s:"okay",i:24}} + - Obj: + - ObjField: "b" + - Obj: + - ObjField: "s" + - Str: "okay" + - ObjField: "i" + - Int: 24 # mismatch + - ObjEnd: + - ObjEnd: diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_str.yml b/engines/playmon/internal/constrcheck/tests/false_equal_str.yml new file mode 100644 index 0000000..5037036 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_equal_str.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + string: String! + ):Int} + +template: > + query {f( + string: "text", + )} + +inputs: + Query.f|string: + match: false + tokens: + - Str: "tex" diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_strblock.yml b/engines/playmon/internal/constrcheck/tests/false_equal_strblock.yml new file mode 100644 index 0000000..4aea73e --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_equal_strblock.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + string: String! + ):Int} + +template: > + query {f( + string: "text", + )} + +inputs: + Query.f|string: + match: false + tokens: + - StrBlock: "tex" diff --git a/engines/playmon/internal/constrcheck/tests/false_interface.yml b/engines/playmon/internal/constrcheck/tests/false_interface.yml new file mode 100644 index 0000000..76e38a7 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_interface.yml @@ -0,0 +1,40 @@ +schema: > + type Query { + iface: Iface! + } + interface Iface { + i(x:Int!):Int! + } + type Foo implements Iface { + i(x:Int!):Int! + } + type Bar implements Iface { + i(x:Int!):Int! + } + +template: > + query { + iface { + i(x:>42) + ... on Foo { + i(x:>20) + } + ... on Bar { + i(x:>30) + } + } + } + +inputs: + Query.iface.i|x: + match: false + tokens: + - Int: 42 + Query.iface&Foo.i|x: + match: false + tokens: + - Int: 20 + Query.iface&Bar.i|x: + match: false + tokens: + - Int: 30 diff --git a/engines/playmon/internal/constrcheck/tests/false_len_array_int.yml b/engines/playmon/internal/constrcheck/tests/false_len_array_int.yml new file mode 100644 index 0000000..27b43ee --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_len_array_int.yml @@ -0,0 +1,62 @@ +schema: > + type Query { f( + eq:[Int]! + neq:[Int]! + gr:[Int]! + le:[Int]! + greq:[Int]! + leeq:[Int]! + ):Int } + +template: > + query { f( + eq: len 0, + neq: len != 0, + gr: len > 1, + le: len < 2, + greq: len >= 3, + leeq: len <= 4, + ) } + +inputs: + Query.f|eq: + match: false + tokens: # len diff: +1 + - Arr: + - Int: 1 + - ArrEnd: + Query.f|neq: + match: false + tokens: # len eq + - Arr: + - ArrEnd: + Query.f|gr: + match: false + tokens: # len diff: -1 + - Arr: + - Int: 1 + - ArrEnd: + Query.f|le: + match: false + tokens: # len diff: +1 + - Arr: + - Int: 1 + - Int: 2 + - ArrEnd: + Query.f|greq: + match: false + tokens: # len diff: -1 + - Arr: + - Int: 1 + - Int: 2 + - ArrEnd: + Query.f|leeq: + match: false + tokens: # len diff: +1 + - Arr: + - Int: 1 + - Int: 2 + - Int: 3 + - Int: 4 + - Int: 5 + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/false_len_array_obj.yml b/engines/playmon/internal/constrcheck/tests/false_len_array_obj.yml new file mode 100644 index 0000000..bbdf9d7 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_len_array_obj.yml @@ -0,0 +1,96 @@ +schema: > + type Query { f( + eq:[I!]! + neq:[I!]! + gr:[I!]! + le:[I!]! + greq:[I!]! + leeq:[I!]! + ):Int } + input I { x:Int! } + +template: > + query { f( + eq: len 0, + neq: len != 0, + gr: len > 1, + le: len < 2, + greq: len >= 3, + leeq: len <= 4, + ) } + +inputs: + Query.f|eq: + match: false + tokens: # len diff: +1 + - Arr: + - Obj: + - ObjField: "x" + - Int: 1 + - ObjEnd: + - ArrEnd: + Query.f|neq: + match: false + tokens: # len eq + - Arr: + - ArrEnd: + Query.f|gr: + match: false + tokens: # len diff: -1 + - Arr: + - Obj: + - ObjField: "x" + - Int: 1 + - ObjEnd: + - ArrEnd: + Query.f|le: + match: false + tokens: # len diff: +1 + - Arr: + - Obj: + - ObjField: "x" + - Int: 1 + - ObjEnd: + - Obj: + - ObjField: "x" + - Int: 2 + - ObjEnd: + - ArrEnd: + Query.f|greq: + match: false + tokens: # len diff: -1 + - Arr: + - Obj: + - ObjField: "x" + - Int: 1 + - ObjEnd: + - Obj: + - ObjField: "x" + - Int: 2 + - ObjEnd: + - ArrEnd: + Query.f|leeq: + match: false + tokens: # len diff: +1 + - Arr: + - Obj: + - ObjField: "x" + - Int: 1 + - ObjEnd: + - Obj: + - ObjField: "x" + - Int: 2 + - ObjEnd: + - Obj: + - ObjField: "x" + - Int: 3 + - ObjEnd: + - Obj: + - ObjField: "x" + - Int: 4 + - ObjEnd: + - Obj: + - ObjField: "x" + - Int: 5 + - ObjEnd: + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/false_len_str_eq.yml b/engines/playmon/internal/constrcheck/tests/false_len_str_eq.yml new file mode 100644 index 0000000..9c9f7bb --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_len_str_eq.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + eq: String! + ):Int } + +template: > + query { f( + eq: len 0, + ) } + +inputs: + Query.f|eq: + match: false + tokens: + - Str: "1" # diff: +1 diff --git a/engines/playmon/internal/constrcheck/tests/false_len_str_gr.yml b/engines/playmon/internal/constrcheck/tests/false_len_str_gr.yml new file mode 100644 index 0000000..c07a147 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_len_str_gr.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + gr: String! + ):Int } + +template: > + query { f( + gr: len > 1, + ) } + +inputs: + Query.f|gr: + match: false + tokens: + - Str: "1" # diff: -1 diff --git a/engines/playmon/internal/constrcheck/tests/false_len_str_greq.yml b/engines/playmon/internal/constrcheck/tests/false_len_str_greq.yml new file mode 100644 index 0000000..6c7f65f --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_len_str_greq.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + greq: String! + ):Int } + +template: > + query { f( + greq: len >= 3, + ) } + +inputs: + Query.f|greq: + match: false + tokens: + - Str: "12" # diff: -1 diff --git a/engines/playmon/internal/constrcheck/tests/false_len_str_leeq.yml b/engines/playmon/internal/constrcheck/tests/false_len_str_leeq.yml new file mode 100644 index 0000000..1db9dc0 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_len_str_leeq.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + leeq: String! + ):Int } + +template: > + query { f( + leeq: len <= 4, + ) } + +inputs: + Query.f|leeq: + match: false + tokens: + - Str: "12345" # diff: +1 diff --git a/engines/playmon/internal/constrcheck/tests/false_len_str_neq.yml b/engines/playmon/internal/constrcheck/tests/false_len_str_neq.yml new file mode 100644 index 0000000..0511463 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_len_str_neq.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + neq: String! + ):Int } + +template: > + query { f( + neq: len != 0, + ) } + +inputs: + Query.f|neq: + match: false + tokens: + - Str: "" # eq diff --git a/engines/playmon/internal/constrcheck/tests/false_len_strblock_eq.yml b/engines/playmon/internal/constrcheck/tests/false_len_strblock_eq.yml new file mode 100644 index 0000000..a1a5105 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_len_strblock_eq.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + eq: String! + ):Int } + +template: > + query { f( + eq: len 0, + ) } + +inputs: + Query.f|eq: + match: false + tokens: + - StrBlock: "1" # diff: +1 diff --git a/engines/playmon/internal/constrcheck/tests/false_len_strblock_gr.yml b/engines/playmon/internal/constrcheck/tests/false_len_strblock_gr.yml new file mode 100644 index 0000000..5d6d5f0 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_len_strblock_gr.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + gr: String! + ):Int } + +template: > + query { f( + gr: len > 1, + ) } + +inputs: + Query.f|gr: + match: false + tokens: + - StrBlock: "1" # diff: -1 diff --git a/engines/playmon/internal/constrcheck/tests/false_len_strblock_greq.yml b/engines/playmon/internal/constrcheck/tests/false_len_strblock_greq.yml new file mode 100644 index 0000000..09186da --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_len_strblock_greq.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + greq: String! + ):Int } + +template: > + query { f( + greq: len >= 3, + ) } + +inputs: + Query.f|greq: + match: false + tokens: + - StrBlock: "12" # diff: -1 diff --git a/engines/playmon/internal/constrcheck/tests/false_len_strblock_le.yaml b/engines/playmon/internal/constrcheck/tests/false_len_strblock_le.yaml new file mode 100644 index 0000000..f1d8f0e --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_len_strblock_le.yaml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + le: String! + ):Int } + +template: > + query { f( + le: len < 2, + ) } + +inputs: + Query.f|le: + match: false + tokens: + - StrBlock: "12" # diff: +1 diff --git a/engines/playmon/internal/constrcheck/tests/false_len_strblock_le.yml b/engines/playmon/internal/constrcheck/tests/false_len_strblock_le.yml new file mode 100644 index 0000000..f1d8f0e --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_len_strblock_le.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + le: String! + ):Int } + +template: > + query { f( + le: len < 2, + ) } + +inputs: + Query.f|le: + match: false + tokens: + - StrBlock: "12" # diff: +1 diff --git a/engines/playmon/internal/constrcheck/tests/false_len_strblock_leeq.yml b/engines/playmon/internal/constrcheck/tests/false_len_strblock_leeq.yml new file mode 100644 index 0000000..970a5d9 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_len_strblock_leeq.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + leeq: String! + ):Int } + +template: > + query { f( + leeq: len <= 4, + ) } + +inputs: + Query.f|leeq: + match: false + tokens: + - StrBlock: "12345" # diff: +1 diff --git a/engines/playmon/internal/constrcheck/tests/false_len_strblock_neq.yml b/engines/playmon/internal/constrcheck/tests/false_len_strblock_neq.yml new file mode 100644 index 0000000..ed00d3e --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_len_strblock_neq.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + neq: String! + ):Int } + +template: > + query { f( + neq: len != 0, + ) } + +inputs: + Query.f|neq: + match: false + tokens: + - StrBlock: "" # eq diff --git a/engines/playmon/internal/constrcheck/tests/false_logical_equal.yml b/engines/playmon/internal/constrcheck/tests/false_logical_equal.yml new file mode 100644 index 0000000..f399597 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_logical_equal.yml @@ -0,0 +1,52 @@ +schema: > + type Query {f( + ints: Boolean! + floats: Boolean! + strings: Boolean! + enums: Boolean! + bools1: Boolean! + bools2: Boolean! + arrs: Boolean! + ):Int} + enum Color { red green blue } + +template: > + query {f( + ints: 10 == 11, + floats: 3.14 == 3.145, + strings: "foo" == "fo", + enums: red == green, + bools1: true == false, + bools2: false == true, + arrs: [1,2] == [1,2,3], + )} + +inputs: + Query.f|ints: + match: false + tokens: + - True: + Query.f|floats: + match: false + tokens: + - True: + Query.f|strings: + match: false + tokens: + - True: + Query.f|enums: + match: false + tokens: + - True: + Query.f|bools1: + match: false + tokens: + - True: + Query.f|bools2: + match: false + tokens: + - True: + Query.f|arrs: + match: false + tokens: + - True: diff --git a/engines/playmon/internal/constrcheck/tests/false_logical_expr_relational.yml b/engines/playmon/internal/constrcheck/tests/false_logical_expr_relational.yml new file mode 100644 index 0000000..2bb15b9 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_logical_expr_relational.yml @@ -0,0 +1,39 @@ +schema: > + type Query {f( + i: Int! + gr: Boolean! + le: Boolean! + greq: Boolean! + leeq: Boolean! + ):Int} + +template: > + query {f( + i=$v: 2, + gr: $v > 2, + le: $v < 2, + greq: $v >= 2, + leeq: $v <= 2, + )} + +inputs: + Query.f|i: + match: true + tokens: + - Int: 2 + Query.f|gr: + match: false + tokens: + - True: + Query.f|le: + match: false + tokens: + - True: + Query.f|greq: + match: false + tokens: + - False: + Query.f|leeq: + match: false + tokens: + - False: diff --git a/engines/playmon/internal/constrcheck/tests/false_logical_expr_relational_float.yml b/engines/playmon/internal/constrcheck/tests/false_logical_expr_relational_float.yml new file mode 100644 index 0000000..5bde5d6 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_logical_expr_relational_float.yml @@ -0,0 +1,39 @@ +schema: > + type Query {f( + i: Float! + gr: Boolean! + le: Boolean! + greq: Boolean! + leeq: Boolean! + ):Int} + +template: > + query {f( + i=$v: 3.14, + gr: $v > 3.14, + le: $v < 3.14, + greq: $v >= 3.14, + leeq: $v <= 3.14, + )} + +inputs: + Query.f|i: + match: true + tokens: + - Float: 3.14 + Query.f|gr: + match: false + tokens: + - True: + Query.f|le: + match: false + tokens: + - True: + Query.f|greq: + match: false + tokens: + - False: + Query.f|leeq: + match: false + tokens: + - False: diff --git a/engines/playmon/internal/constrcheck/tests/false_logical_notequal.yml b/engines/playmon/internal/constrcheck/tests/false_logical_notequal.yml new file mode 100644 index 0000000..ebd63c6 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_logical_notequal.yml @@ -0,0 +1,51 @@ +schema: > + type Query {f( + ints: Boolean! + strs: Boolean! + floats: Boolean! + arrays_int0: Boolean! + arrays_int1: Boolean! + arrays_strs: Boolean! + arrays_3d: Boolean! + ):Int} + +template: > + query {f( + ints: 11 != 11, + strs: "a" != "a", + floats: 3.14 != 3.14, + arrays_int0: [] != [], + arrays_int1: [1,2] != [1,2], + arrays_strs: ["okay"] != ["okay"], + arrays_3d: [[[]]] != [[[]]], + )} + +inputs: + Query.f|ints: + match: false + tokens: + - True: + Query.f|strs: + match: false + tokens: + - True: + Query.f|floats: + match: false + tokens: + - True: + Query.f|arrays_int0: + match: false + tokens: + - True: + Query.f|arrays_int1: + match: false + tokens: + - True: + Query.f|arrays_strs: + match: false + tokens: + - True: + Query.f|arrays_2d: + match: false + tokens: + - True: diff --git a/engines/playmon/internal/constrcheck/tests/false_logical_or_eq.yml b/engines/playmon/internal/constrcheck/tests/false_logical_or_eq.yml new file mode 100644 index 0000000..64491b7 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_logical_or_eq.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + x: Int! + ):Int} + +template: > + query {f( + x: 1 || 2, + )} + +inputs: + Query.f|x: + match: false + tokens: + - Int: 3 diff --git a/engines/playmon/internal/constrcheck/tests/false_logical_or_gr_le.yml b/engines/playmon/internal/constrcheck/tests/false_logical_or_gr_le.yml new file mode 100644 index 0000000..9797e6f --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_logical_or_gr_le.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + x: Int! + ):Int} + +template: > + query {f( + x: <10 || >20, + )} + +inputs: + Query.f|x: + match: false + tokens: + - Int: 15 diff --git a/engines/playmon/internal/constrcheck/tests/false_map_2d.yml b/engines/playmon/internal/constrcheck/tests/false_map_2d.yml new file mode 100644 index 0000000..53fc714 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_map_2d.yml @@ -0,0 +1,47 @@ +schema: > + type Query { f( + a: [[Int!]!]! + b: [[Int!]!]! + empty: [[Int!]!]! + ):Int } + +template: > + query { f( + a: [...[...>20]], + b: [...[...>20]], + empty: [...[...>20]], + ) } + +inputs: + Query.f|a: + match: false + tokens: + - Arr: + - Arr: + - Int: 6 + - Int: 7 + - Int: 8 + - ArrEnd: + - ArrEnd: + Query.f|b: + match: false + tokens: + - Arr: + + - Arr: + - Int: 10 + - ArrEnd: + + - Arr: + - Int: 12 + - ArrEnd: + + - Arr: + - ArrEnd: + + - ArrEnd: + Query.f|empty: + match: true + tokens: + - Arr: + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/false_map_array_object.yml b/engines/playmon/internal/constrcheck/tests/false_map_array_object.yml new file mode 100644 index 0000000..a6d8b92 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_map_array_object.yml @@ -0,0 +1,32 @@ +schema: > + type Query { f( + a: [I!]! + ):Int } + input I { + x: Float! + y: Float! + } + +template: > + query { f( + a: [ ...{x: > 0, y: > 0} ], + ) } + +inputs: + Query.f|a: + match: false + tokens: + - Arr: + - Obj: + - ObjField: "x" + - Float: 1 + - ObjField: "y" + - Float: 1 + - ObjEnd: + - Obj: + - ObjField: "x" + - Float: 0.1 + - ObjField: "y" + - Float: -1 # mismatch + - ObjEnd: + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/false_map_var_arithmetic.yml b/engines/playmon/internal/constrcheck/tests/false_map_var_arithmetic.yml new file mode 100644 index 0000000..4fc8ef0 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_map_var_arithmetic.yml @@ -0,0 +1,25 @@ +schema: > + type Query { f( + v:Int! + nonempty:[Int!]! + ):Int } + +template: > + query { f( + v=$v: *, + nonempty: [... > ($v + 2 * 200 / 10 % 7) - 3], + ) } + +inputs: + Query.f|v: + match: true + tokens: + - Int: 2 + Query.f|nonempty: + match: false + tokens: # [20,10,3] # not > 4 + - Arr: + - Int: 20 + - Int: 10 + - Int: 3 + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/false_not_equal_arr_int.yml b/engines/playmon/internal/constrcheck/tests/false_not_equal_arr_int.yml new file mode 100644 index 0000000..e0d4fdd --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_not_equal_arr_int.yml @@ -0,0 +1,19 @@ +schema: > + type Query {f( + ints: [Int!]! + ):Int} + +template: > + query {f( + ints: != [1,2,3], + )} + +inputs: + Query.f|ints: + match: false + tokens: # [1,2,3] + - Arr: + - Int: 1 + - Int: 2 + - Int: 3 + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/false_not_equal_arr_str.yml b/engines/playmon/internal/constrcheck/tests/false_not_equal_arr_str.yml new file mode 100644 index 0000000..bb0b645 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_not_equal_arr_str.yml @@ -0,0 +1,19 @@ +schema: > + type Query {f( + strs: [String!]! + ):String} + +template: > + query {f( + strs: != ["1","2","3"], + )} + +inputs: + Query.f|strs: + match: false + tokens: + - Arr: + - Str: "1" + - Str: "2" + - Str: "3" + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/false_not_equal_obj.yml b/engines/playmon/internal/constrcheck/tests/false_not_equal_obj.yml new file mode 100644 index 0000000..93663ff --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_not_equal_obj.yml @@ -0,0 +1,36 @@ +schema: > + type Query {f( + a: A! + ):Int} + input A { b: B! } + input B { + s: String! + i: Int! + optional: Boolean + } + +template: > + query {f( + a: != { + b: { + s: "okay", + i: 42, + optional: * + } + } + )} + +inputs: + Query.f|a: + match: false + match-schemaless: true + tokens: + - Obj: + - ObjField: "b" + - Obj: + - ObjField: "s" + - Str: "okay" + - ObjField: "i" + - Int: 42 + - ObjEnd: + - ObjEnd: diff --git a/engines/playmon/internal/constrcheck/tests/false_relational_float.yml b/engines/playmon/internal/constrcheck/tests/false_relational_float.yml new file mode 100644 index 0000000..53d194f --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_relational_float.yml @@ -0,0 +1,33 @@ +schema: > + type Query { f( + gr: Float! + le: Float! + greq: Float! + leeq: Float! + ):Int } + +template: > + query { f( + gr: > 1.1, + le: < 2.22, + greq: >= 3.333, + leeq: <= 4.4444, + ) } + +inputs: + Query.f|gr: + match: false + tokens: + - Float: 1.1 # diff: 0 + Query.f|le: + match: false + tokens: + - Float: 2.22 # diff: 0 + Query.f|greq: + match: false + tokens: + - Float: 3.332 # diff: -0.001 + Query.f|leeq: + match: false + tokens: + - Float: 4.4445 # diff: +0.0001 diff --git a/engines/playmon/internal/constrcheck/tests/false_relational_int.yml b/engines/playmon/internal/constrcheck/tests/false_relational_int.yml new file mode 100644 index 0000000..bc60d36 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_relational_int.yml @@ -0,0 +1,33 @@ +schema: > + type Query { f( + gr: Int! + le: Int! + greq: Int! + leeq: Int! + ):Int } + +template: > + query { f( + gr: > 1, + le: < 20, + greq: >= 300, + leeq: <= 4000, + ) } + +inputs: + Query.f|gr: + match: false + tokens: + - Int: 1 # diff: -1 + Query.f|le: + match: false + tokens: + - Int: 20 # diff: +1 + Query.f|greq: + match: false + tokens: + - Int: 299 # diff: -1 + Query.f|leeq: + match: false + tokens: + - Int: 4001 # diff: +1 diff --git a/engines/playmon/internal/constrcheck/tests/false_union.yml b/engines/playmon/internal/constrcheck/tests/false_union.yml new file mode 100644 index 0000000..3cf463a --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_union.yml @@ -0,0 +1,33 @@ +schema: > + type Query { + u: U! + } + union U = Foo | Bar + type Foo { + i(x:Int!):Int! + } + type Bar { + i(x:Int!):Int! + } + +template: > + query { + u { + ... on Foo { + i(x:>20) + } + ... on Bar { + i(x:>30) + } + } + } + +inputs: + Query.u&Foo.i|x: + match: false + tokens: + - Int: 20 + Query.u&Bar.i|x: + match: false + tokens: + - Int: 30 diff --git a/engines/playmon/internal/constrcheck/tests/false_var_bool.yml b/engines/playmon/internal/constrcheck/tests/false_var_bool.yml new file mode 100644 index 0000000..f7bfb97 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_var_bool.yml @@ -0,0 +1,21 @@ +schema: > + type Query { f( + a: Boolean! + b: Boolean! + ):Int } + +template: > + query { f( + a = $a: false, + b: (true && true) && $a, + ) } + +inputs: + Query.f|a: + match: true + tokens: + - False: + Query.f|b: + match: false + tokens: + - True: diff --git a/engines/playmon/internal/constrcheck/tests/false_var_str.yml b/engines/playmon/internal/constrcheck/tests/false_var_str.yml new file mode 100644 index 0000000..350edd4 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_var_str.yml @@ -0,0 +1,21 @@ +schema: > + type Query { f( + a: String! + b: String! + ):Int } + +template: > + query { f( + a = $a: "okay", + b: $a, + ) } + +inputs: + Query.f|a: + match: true + tokens: + - Str: "okay" + Query.f|b: + match: false + tokens: + - Str: "not okay" diff --git a/engines/playmon/internal/constrcheck/tests/false_var_strblock.yml b/engines/playmon/internal/constrcheck/tests/false_var_strblock.yml new file mode 100644 index 0000000..a7934c7 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/false_var_strblock.yml @@ -0,0 +1,21 @@ +schema: > + type Query { f( + a: String! + b: String! + ):Int } + +template: > + query { f( + a = $a: "okay", + b: $a, + ) } + +inputs: + Query.f|a: + match: true + tokens: + - StrBlock: "okay" + Query.f|b: + match: false + tokens: + - StrBlock: "not okay" diff --git a/engines/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs.yml b/engines/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs.yml new file mode 100644 index 0000000..5b4722b --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs.yml @@ -0,0 +1,35 @@ +schema: > + type Query { f( + eq: I! + neq: I! + ):Int } + input I { + x: Int! + y: Int! + } + +template: > + query { f( + eq: {x: *, y: *}, + neq: != {x: *, y: *}, + ) } + +inputs: + Query.f|ie: + match: false + tokens: # {x:42,x:42} + - Obj: + - ObjField: "x" + - Int: 42 + - ObjField: "x" + - Int: 42 + - ObjEnd: + Query.f|in: + match: false + tokens: # {x:42,x:42} + - Obj: + - ObjField: "i" + - Int: 42 + - ObjField: "x" + - Int: 42 + - ObjEnd: diff --git a/engines/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs_in_any.yml b/engines/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs_in_any.yml new file mode 100644 index 0000000..1deee31 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs_in_any.yml @@ -0,0 +1,24 @@ +schema: > + type Query { f( + any: I! + ):Int } + input I { + x: Int! + y: Int! + } + +template: > + query { f( + any: *, + ) } + +inputs: + Query.f|any: + match: false + tokens: # {x:42,x:42} + - Obj: + - ObjField: "x" + - Int: 42 + - ObjField: "x" + - Int: 42 + - ObjEnd: diff --git a/engines/playmon/internal/constrcheck/tests/logical_negation.yml b/engines/playmon/internal/constrcheck/tests/logical_negation.yml new file mode 100644 index 0000000..a6ea1bf --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/logical_negation.yml @@ -0,0 +1,46 @@ +schema: > + type Query { f( + neg_t: Boolean! + neg_f: Boolean! + + var_t: Boolean! + var_f: Boolean! + neg_var_t: Boolean! + neg_var_f: Boolean! + ):Int } + +template: > + query { f( + neg_t: !true, + neg_f: !false, + var_t=$var_t: true, + var_f=$var_f: false, + neg_var_t: !$var_t, + neg_var_f: !$var_f, + ) } + +inputs: + Query.f|neg_t: + match: true + tokens: + - False: + Query.f|neg_f: + match: true + tokens: + - True: + Query.f|var_t: + match: true + tokens: + - True: + Query.f|var_f: + match: true + tokens: + - False: + Query.f|neg_var_t: + match: true + tokens: + - False: + Query.f|neg_var_f: + match: true + tokens: + - True: diff --git a/engines/playmon/internal/constrcheck/tests/numeric_negation.yml b/engines/playmon/internal/constrcheck/tests/numeric_negation.yml new file mode 100644 index 0000000..329c441 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/numeric_negation.yml @@ -0,0 +1,45 @@ +schema: > + type Query { f( + neg_i: Int! + neg_f: Float! + var_i: Int! + var_f: Float! + neg_var_i: Int! + neg_var_f: Float! + ):Int } + +template: > + query { f( + neg_i: -(3), + neg_f: -(3.14), + var_i=$var_i: 3, + var_f=$var_f: 3.14, + neg_var_i: -$var_i, + neg_var_f: -$var_f, + ) } + +inputs: + Query.f|neg_i: + match: true + tokens: + - Int: -3 + Query.f|neg_f: + match: true + tokens: + - Float: -3.14 + Query.f|var_i: + match: true + tokens: + - Int: 3 + Query.f|var_f: + match: true + tokens: + - Float: 3.14 + Query.f|neg_var_i: + match: true + tokens: + - Int: -3 + Query.f|neg_var_f: + match: true + tokens: + - Float: -3.14 diff --git a/engines/playmon/internal/constrcheck/tests/true_arithmetic_add.yml b/engines/playmon/internal/constrcheck/tests/true_arithmetic_add.yml new file mode 100644 index 0000000..afff359 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_arithmetic_add.yml @@ -0,0 +1,21 @@ +schema: > + type Query {f( + a: Float! + b: Float + ):Int} + +template: > + query {f( + a: 3.14 + 3.14, + b: 12 + 1.1, + )} + +inputs: + Query.f|a: + match: true + tokens: + - Float: 6.28 + Query.f|b: + match: true + tokens: + - Float: 13.1 diff --git a/engines/playmon/internal/constrcheck/tests/true_constr_and.yml b/engines/playmon/internal/constrcheck/tests/true_constr_and.yml new file mode 100644 index 0000000..710c37d --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_constr_and.yml @@ -0,0 +1,27 @@ +schema: > + type Query {f( + a: Int! + b: Int! + c: Int! + ):Int} + +template: > + query {f( + a: >0 && <10, + b: >0 && <10, + c: >0 && <10 && != 5, + )} + +inputs: + Query.f|a: + match: true + tokens: + - Int: 4 + Query.f|b: + match: true + tokens: + - Int: 4 + Query.f|c: + match: true + tokens: + - Int: 4 diff --git a/engines/playmon/internal/constrcheck/tests/true_constr_or.yml b/engines/playmon/internal/constrcheck/tests/true_constr_or.yml new file mode 100644 index 0000000..dd5a5e7 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_constr_or.yml @@ -0,0 +1,27 @@ +schema: > + type Query {f( + a: Int! + b: Int! + c: Int! + ):Int} + +template: > + query {f( + a: 1 || 0, + b: 1 || 0, + c: 1 || 0 || 2, + )} + +inputs: + Query.f|a: + match: true + tokens: + - Int: 1 + Query.f|b: + match: true + tokens: + - Int: 0 + Query.f|c: + match: true + tokens: + - Int: 2 diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_arr_int.yml b/engines/playmon/internal/constrcheck/tests/true_equal_arr_int.yml new file mode 100644 index 0000000..2097c24 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_equal_arr_int.yml @@ -0,0 +1,19 @@ +schema: > + type Query {f( + ints: [Int!]! + ):Int} + +template: > + query {f( + ints: [1,2,3], + )} + +inputs: + Query.f|ints: + match: true + tokens: + - Arr: + - Int: 1 + - Int: 2 + - Int: 3 + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_arr_str.yml b/engines/playmon/internal/constrcheck/tests/true_equal_arr_str.yml new file mode 100644 index 0000000..4e82b61 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_equal_arr_str.yml @@ -0,0 +1,19 @@ +schema: > + type Query {f( + strs: [String!]! + ):String} + +template: > + query {f( + strs: ["1","2","3"], + )} + +inputs: + Query.f|strs: + match: true + tokens: + - Arr: + - Str: "1" + - Str: "2" + - Str: "3" + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_bool.yml b/engines/playmon/internal/constrcheck/tests/true_equal_bool.yml new file mode 100644 index 0000000..527eb30 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_equal_bool.yml @@ -0,0 +1,21 @@ +schema: > + type Query {f( + bool_t: Boolean! + bool_f: Boolean! + ):Int} + +template: > + query {f( + bool_t: true, + bool_f: false, + )} + +inputs: + Query.f|bool_t: + match: true + tokens: + - True: + Query.f|bool_f: + match: true + tokens: + - False: diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_enum.yml b/engines/playmon/internal/constrcheck/tests/true_equal_enum.yml new file mode 100644 index 0000000..8bf8c7e --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_equal_enum.yml @@ -0,0 +1,16 @@ +schema: > + type Query {f( + enum: Color! + ):Int} + enum Color { red green blue } + +template: > + query {f( + enum: green, + )} + +inputs: + Query.f|enum: + match: true + tokens: + - EnumVal: "green" diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_float.yml b/engines/playmon/internal/constrcheck/tests/true_equal_float.yml new file mode 100644 index 0000000..3a51283 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_equal_float.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + float: Float! + ):Int} + +template: > + query {f( + float: 3.14159, + )} + +inputs: + Query.f|float: + match: true + tokens: + - Float: 3.14159 diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_int.yml b/engines/playmon/internal/constrcheck/tests/true_equal_int.yml new file mode 100644 index 0000000..8e3d96b --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_equal_int.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + int: Int! + ):Int} + +template: > + query {f( + int: 42, + )} + +inputs: + Query.f|int: + match: true + tokens: + - Int: 42 diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_obj.yml b/engines/playmon/internal/constrcheck/tests/true_equal_obj.yml new file mode 100644 index 0000000..9822bc8 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_equal_obj.yml @@ -0,0 +1,36 @@ +schema: > + type Query {f( + a: A! + ):Int} + input A { b: B! } + input B { + s: String! + i: Int! + optional: Boolean + } + +template: > + query {f( + a: { + b: { + s: "okay", + i: 42, + optional: * + } + } + )} + +inputs: + Query.f|a: + match: true + match-schemaless: false + tokens: # {b:{s:"okay",i:42}} + - Obj: + - ObjField: "b" + - Obj: + - ObjField: "s" + - Str: "okay" + - ObjField: "i" + - Int: 42 + - ObjEnd: + - ObjEnd: diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_str.yml b/engines/playmon/internal/constrcheck/tests/true_equal_str.yml new file mode 100644 index 0000000..4731de2 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_equal_str.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + string: String! + ):Int} + +template: > + query {f( + string: "text", + )} + +inputs: + Query.f|string: + match: true + tokens: + - Str: "text" diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_strblock.yml b/engines/playmon/internal/constrcheck/tests/true_equal_strblock.yml new file mode 100644 index 0000000..07c2a0a --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_equal_strblock.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + string: String! + ):Int} + +template: > + query {f( + string: "text", + )} + +inputs: + Query.f|string: + match: true + tokens: + - StrBlock: "text" diff --git a/engines/playmon/internal/constrcheck/tests/true_interface.yml b/engines/playmon/internal/constrcheck/tests/true_interface.yml new file mode 100644 index 0000000..b23122c --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_interface.yml @@ -0,0 +1,40 @@ +schema: > + type Query { + iface: Iface! + } + interface Iface { + i(x:Int!):Int! + } + type Foo implements Iface { + i(x:Int!):Int! + } + type Bar implements Iface { + i(x:Int!):Int! + } + +template: > + query { + iface { + i(x:>42) + ... on Foo { + i(x:>20) + } + ... on Bar { + i(x:>30) + } + } + } + +inputs: + Query.iface.i|x: + match: true + tokens: + - Int: 43 + Query.iface&Foo.i|x: + match: true + tokens: + - Int: 21 + Query.iface&Bar.i|x: + match: true + tokens: + - Int: 31 diff --git a/engines/playmon/internal/constrcheck/tests/true_len_array_int.yml b/engines/playmon/internal/constrcheck/tests/true_len_array_int.yml new file mode 100644 index 0000000..c607bf7 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_len_array_int.yml @@ -0,0 +1,70 @@ +schema: > + type Query { f( + eq:[Int]! + neq:[Int]! + gr:[Int]! + le:[Int]! + greq:[Int]! + leeq:[Int]! + ):Int } + +template: > + query { f( + eq: len 0, + neq: len != 0, + gr: len > 1, + le: len < 2, + greq: len >= 3, + leeq: len <= 4, + ) } + +inputs: + Query.f|eq: + match: true + tokens: # len eq + - Arr: + - ArrEnd: + Query.f|neq: + match: true + tokens: # len not eq + - Arr: + - Int: 1 + - Int: 2 + - Int: 3 + - Int: 4 + - Int: 5 + - Int: 6 + - Int: 7 + - Int: 8 + - Int: 9 + - ArrEnd: + Query.f|gr: + match: true + tokens: # len gr + - Arr: + - Int: 1 + - Int: 2 + - ArrEnd: + Query.f|le: + match: true + tokens: # len le + - Arr: + - Int: 1 + - ArrEnd: + Query.f|greq: + match: true + tokens: # len eq + - Arr: + - Int: 1 + - Int: 2 + - Int: 3 + - ArrEnd: + Query.f|leeq: + match: true + tokens: # len eq + - Arr: + - Int: 1 + - Int: 2 + - Int: 3 + - Int: 4 + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/true_len_array_obj.yml b/engines/playmon/internal/constrcheck/tests/true_len_array_obj.yml new file mode 100644 index 0000000..08b6128 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_len_array_obj.yml @@ -0,0 +1,96 @@ +schema: > + type Query { f( + eq:[I!]! + neq:[I!]! + gr:[I!]! + le:[I!]! + greq:[I!]! + leeq:[I!]! + ):Int } + input I { x:Int! } + +template: > + query { f( + eq: len 0, + neq: len != 0, + gr: len > 1, + le: len < 2, + greq: len >= 3, + leeq: len <= 4, + ) } + +inputs: + Query.f|eq: + match: true + tokens: # len eq + - Arr: + - ArrEnd: + Query.f|neq: + match: true + tokens: # len not eq + - Arr: + - Obj: + - ObjField: "x" + - Int: 1 + - ObjEnd: + - ArrEnd: + Query.f|gr: + match: true + tokens: # len gr + - Arr: + - Obj: + - ObjField: "x" + - Int: 1 + - ObjEnd: + - Obj: + - ObjField: "x" + - Int: 2 + - ObjEnd: + - ArrEnd: + Query.f|le: + match: true + tokens: # len le + - Arr: + - Obj: + - ObjField: "x" + - Int: 1 + - ObjEnd: + - ArrEnd: + Query.f|greq: + match: true + tokens: # len eq + - Arr: + - Obj: + - ObjField: "x" + - Int: 1 + - ObjEnd: + - Obj: + - ObjField: "x" + - Int: 2 + - ObjEnd: + - Obj: + - ObjField: "x" + - Int: 3 + - ObjEnd: + - ArrEnd: + Query.f|leeq: + match: true + tokens: # len eq + - Arr: + - Obj: + - ObjField: "x" + - Int: 1 + - ObjEnd: + - Obj: + - ObjField: "x" + - Int: 2 + - ObjEnd: + - Obj: + - ObjField: "x" + - Int: 3 + - ObjEnd: + - Obj: + - ObjField: "x" + - Int: 4 + - ObjEnd: + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/true_len_str.yml b/engines/playmon/internal/constrcheck/tests/true_len_str.yml new file mode 100644 index 0000000..8a1c0ee --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_len_str.yml @@ -0,0 +1,45 @@ +schema: > + type Query { f( + eq:String! + neq:String! + gr:String! + le:String! + greq:String! + leeq:String! + ):Int } + +template: > + query { f( + eq: len 0, + neq: len != 0, + gr: len > 1, + le: len < 2, + greq: len >= 3, + leeq: len <= 4, + ) } + +inputs: + Query.f|eq: + match: true + tokens: + - Str: "" # eq + Query.f|neq: + match: true + tokens: + - Str: "123456789" # not eq + Query.f|gr: + match: true + tokens: + - Str: "12" # gr + Query.f|le: + match: true + tokens: + - Str: "1" # le + Query.f|greq: + match: true + tokens: + - Str: "123" # eq + Query.f|leeq: + match: true + tokens: + - Str: "1234" # eq diff --git a/engines/playmon/internal/constrcheck/tests/true_len_str_eq.yml b/engines/playmon/internal/constrcheck/tests/true_len_str_eq.yml new file mode 100644 index 0000000..091760c --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_len_str_eq.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + eq: String! + ):Int } + +template: > + query { f( + eq: len 0, + ) } + +inputs: + Query.f|eq: + match: true + tokens: + - Str: "" # eq diff --git a/engines/playmon/internal/constrcheck/tests/true_len_str_gr.yml b/engines/playmon/internal/constrcheck/tests/true_len_str_gr.yml new file mode 100644 index 0000000..e498c98 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_len_str_gr.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + gr: String! + ):Int } + +template: > + query { f( + gr: len > 1, + ) } + +inputs: + Query.f|gr: + match: true + tokens: + - Str: "12" # gr diff --git a/engines/playmon/internal/constrcheck/tests/true_len_str_greq.yml b/engines/playmon/internal/constrcheck/tests/true_len_str_greq.yml new file mode 100644 index 0000000..31206c7 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_len_str_greq.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + greq: String! + ):Int } + +template: > + query { f( + greq: len >= 3, + ) } + +inputs: + Query.f|greq: + match: true + tokens: + - Str: "123" # eq diff --git a/engines/playmon/internal/constrcheck/tests/true_len_str_le.yml b/engines/playmon/internal/constrcheck/tests/true_len_str_le.yml new file mode 100644 index 0000000..66b966a --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_len_str_le.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + le: String! + ):Int } + +template: > + query { f( + le: len < 2, + ) } + +inputs: + Query.f|le: + match: true + tokens: + - Str: "1" # le diff --git a/engines/playmon/internal/constrcheck/tests/true_len_str_leeq.yml b/engines/playmon/internal/constrcheck/tests/true_len_str_leeq.yml new file mode 100644 index 0000000..500f47d --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_len_str_leeq.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + leeq: String! + ):Int } + +template: > + query { f( + leeq: len <= 4, + ) } + +inputs: + Query.f|leeq: + match: true + tokens: + - Str: "1234" # eq diff --git a/engines/playmon/internal/constrcheck/tests/true_len_str_neq.yml b/engines/playmon/internal/constrcheck/tests/true_len_str_neq.yml new file mode 100644 index 0000000..70bb3db --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_len_str_neq.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + neq: String! + ):Int } + +template: > + query { f( + neq: len != 0, + ) } + +inputs: + Query.f|neq: + match: true + tokens: + - Str: "123456789" # not eq diff --git a/engines/playmon/internal/constrcheck/tests/true_len_strblock.yml b/engines/playmon/internal/constrcheck/tests/true_len_strblock.yml new file mode 100644 index 0000000..4269858 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_len_strblock.yml @@ -0,0 +1,45 @@ +schema: > + type Query { f( + eq:String! + neq:String! + gr:String! + le:String! + greq:String! + leeq:String! + ):Int } + +template: > + query { f( + eq: len 0, + neq: len != 0, + gr: len > 1, + le: len < 2, + greq: len >= 3, + leeq: len <= 4, + ) } + +inputs: + Query.f|eq: + match: true + tokens: + - StrBlock: "" # eq + Query.f|neq: + match: true + tokens: + - StrBlock: "123456789" # not eq + Query.f|gr: + match: true + tokens: + - StrBlock: "12" # gr + Query.f|le: + match: true + tokens: + - StrBlock: "1" # le + Query.f|greq: + match: true + tokens: + - StrBlock: "123" # eq + Query.f|leeq: + match: true + tokens: + - StrBlock: "1234" # eq diff --git a/engines/playmon/internal/constrcheck/tests/true_len_strblock_eq.yml b/engines/playmon/internal/constrcheck/tests/true_len_strblock_eq.yml new file mode 100644 index 0000000..ea55ea6 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_len_strblock_eq.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + eq: String! + ):Int } + +template: > + query { f( + eq: len 0, + ) } + +inputs: + Query.f|eq: + match: true + tokens: + - StrBlock: "" # eq diff --git a/engines/playmon/internal/constrcheck/tests/true_len_strblock_gr.yml b/engines/playmon/internal/constrcheck/tests/true_len_strblock_gr.yml new file mode 100644 index 0000000..cade3ee --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_len_strblock_gr.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + gr: String! + ):Int } + +template: > + query { f( + gr: len > 1, + ) } + +inputs: + Query.f|gr: + match: true + tokens: + - StrBlock: "12" # gr diff --git a/engines/playmon/internal/constrcheck/tests/true_len_strblock_greq.yml b/engines/playmon/internal/constrcheck/tests/true_len_strblock_greq.yml new file mode 100644 index 0000000..e0b7053 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_len_strblock_greq.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + greq: String! + ):Int } + +template: > + query { f( + greq: len >= 3, + ) } + +inputs: + Query.f|greq: + match: true + tokens: + - StrBlock: "123" # eq diff --git a/engines/playmon/internal/constrcheck/tests/true_len_strblock_le.yml b/engines/playmon/internal/constrcheck/tests/true_len_strblock_le.yml new file mode 100644 index 0000000..1f99bd9 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_len_strblock_le.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + le: String! + ):Int } + +template: > + query { f( + le: len < 2, + ) } + +inputs: + Query.f|le: + match: true + tokens: + - StrBlock: "1" # le diff --git a/engines/playmon/internal/constrcheck/tests/true_len_strblock_leeq.yml b/engines/playmon/internal/constrcheck/tests/true_len_strblock_leeq.yml new file mode 100644 index 0000000..d74c0e2 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_len_strblock_leeq.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + leeq: String! + ):Int } + +template: > + query { f( + leeq: len <= 4, + ) } + +inputs: + Query.f|leeq: + match: true + tokens: + - StrBlock: "1234" # eq diff --git a/engines/playmon/internal/constrcheck/tests/true_len_strblock_neq.yml b/engines/playmon/internal/constrcheck/tests/true_len_strblock_neq.yml new file mode 100644 index 0000000..1f631dc --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_len_strblock_neq.yml @@ -0,0 +1,15 @@ +schema: > + type Query { f( + neq: String! + ):Int } + +template: > + query { f( + neq: len != 0, + ) } + +inputs: + Query.f|neq: + match: true + tokens: + - StrBlock: "123456789" # not eq diff --git a/engines/playmon/internal/constrcheck/tests/true_logical_equal.yml b/engines/playmon/internal/constrcheck/tests/true_logical_equal.yml new file mode 100644 index 0000000..5dcc11f --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_logical_equal.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + x: Boolean! + ):Int} + +template: > + query {f( + x: != (10 == 10), + )} + +inputs: + Query.f|x: + match: true + tokens: + - False: diff --git a/engines/playmon/internal/constrcheck/tests/true_logical_expr_relational.yml b/engines/playmon/internal/constrcheck/tests/true_logical_expr_relational.yml new file mode 100644 index 0000000..93e9c5c --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_logical_expr_relational.yml @@ -0,0 +1,39 @@ +schema: > + type Query {f( + i: Int! + gr: Boolean! + le: Boolean! + greq: Boolean! + leeq: Boolean! + ):Int} + +template: > + query {f( + i=$v: 2, + gr: $v > 2, + le: $v < 2, + greq: $v >= 2, + leeq: $v <= 2, + )} + +inputs: + Query.f|i: + match: true + tokens: + - Int: 2 + Query.f|gr: + match: true + tokens: + - False: + Query.f|le: + match: true + tokens: + - False: + Query.f|greq: + match: true + tokens: + - True: + Query.f|leeq: + match: true + tokens: + - True: diff --git a/engines/playmon/internal/constrcheck/tests/true_logical_expr_relational_float.yml b/engines/playmon/internal/constrcheck/tests/true_logical_expr_relational_float.yml new file mode 100644 index 0000000..bd12c71 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_logical_expr_relational_float.yml @@ -0,0 +1,39 @@ +schema: > + type Query {f( + i: Float! + gr: Boolean! + le: Boolean! + greq: Boolean! + leeq: Boolean! + ):Int} + +template: > + query {f( + i=$v: 3.14, + gr: $v > 3.14, + le: $v < 3.14, + greq: $v >= 3.14, + leeq: $v <= 3.14, + )} + +inputs: + Query.f|i: + match: true + tokens: + - Float: 3.14 + Query.f|gr: + match: true + tokens: + - False: + Query.f|le: + match: true + tokens: + - False: + Query.f|greq: + match: true + tokens: + - True: + Query.f|leeq: + match: true + tokens: + - True: diff --git a/engines/playmon/internal/constrcheck/tests/true_logical_notequal.yml b/engines/playmon/internal/constrcheck/tests/true_logical_notequal.yml new file mode 100644 index 0000000..5c6df77 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_logical_notequal.yml @@ -0,0 +1,69 @@ +schema: > + type Query {f( + ints: Boolean! + strs: Boolean! + floats: Boolean! + arrays_int0: Boolean! + arrays_int1: Boolean! + arrays_int2: Boolean! + arrays_strs: Boolean! + arrays_2d: Boolean! + arrays_2d_diff: Boolean! + arrays_3d_diff: Boolean! + ):Int} + +template: > + query {f( + ints: 10 != 11, + strs: "a" != "b", + floats: 3.1415 != 3.14, + arrays_int0: [] != [1], + arrays_int1: [1,2] != [1], + arrays_int2: [1,2] != [1,3], + arrays_strs: [] != ["okay"], + arrays_2d: [[]] != [[], []], + arrays_2d_diff: [[],[2]] != [[],[3]], + arrays_3d_diff: [[[],[2]]] != [[[],[3]]], + )} + +inputs: + Query.f|ints: + match: true + tokens: + - True: + Query.f|strs: + match: true + tokens: + - True: + Query.f|floats: + match: true + tokens: + - True: + Query.f|arrays_int0: + match: true + tokens: + - True: + Query.f|arrays_int1: + match: true + tokens: + - True: + Query.f|arrays_int2: + match: true + tokens: + - True: + Query.f|arrays_strs: + match: true + tokens: + - True: + Query.f|arrays_2d: + match: true + tokens: + - True: + Query.f|arrays_2d_diff: + match: true + tokens: + - True: + Query.f|arrays_3d_diff: + match: true + tokens: + - True: diff --git a/engines/playmon/internal/constrcheck/tests/true_logical_or_eq.yml b/engines/playmon/internal/constrcheck/tests/true_logical_or_eq.yml new file mode 100644 index 0000000..33b3bb9 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_logical_or_eq.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + x: Int! + ):Int} + +template: > + query {f( + x: 1 || 2, + )} + +inputs: + Query.f|x: + match: true + tokens: + - Int: 1 diff --git a/engines/playmon/internal/constrcheck/tests/true_logical_or_gr_le.yml b/engines/playmon/internal/constrcheck/tests/true_logical_or_gr_le.yml new file mode 100644 index 0000000..ec0e12d --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_logical_or_gr_le.yml @@ -0,0 +1,22 @@ +schema: > + type Query {f( + a: Int! + b: Int! + ):Int} + +template: > + query {f( + a: <10 || >20, + b: <10 || >20, + )} + +inputs: + Query.f|a: + match: true + tokens: + - Int: 5 + Query.f|b: + match: true + tokens: + - Int: 25 + diff --git a/engines/playmon/internal/constrcheck/tests/true_map_2d.yml b/engines/playmon/internal/constrcheck/tests/true_map_2d.yml new file mode 100644 index 0000000..d72cf51 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_map_2d.yml @@ -0,0 +1,47 @@ +schema: > + type Query { f( + a: [[Int!]!]! + b: [[Int!]!]! + empty: [[Int!]!]! + ):Int } + +template: > + query { f( + a: [...[...>5]], + b: [...[...>5]], + empty: [...[...>5]], + ) } + +inputs: + Query.f|a: + match: true + tokens: + - Arr: + - Arr: + - Int: 6 + - Int: 7 + - Int: 8 + - ArrEnd: + - ArrEnd: + Query.f|b: + match: true + tokens: + - Arr: + + - Arr: + - Int: 10 + - ArrEnd: + + - Arr: + - Int: 12 + - ArrEnd: + + - Arr: + - ArrEnd: + + - ArrEnd: + Query.f|empty: + match: true + tokens: + - Arr: + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/true_map_array_object.yml b/engines/playmon/internal/constrcheck/tests/true_map_array_object.yml new file mode 100644 index 0000000..a37a0a6 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_map_array_object.yml @@ -0,0 +1,32 @@ +schema: > + type Query { f( + a: [I!]! + ):Int } + input I { + x: Float! + y: Float! + } + +template: > + query { f( + a: [ ...{x: > 0, y: > 0} ], + ) } + +inputs: + Query.f|a: + match: true + tokens: + - Arr: + - Obj: + - ObjField: "x" + - Float: 1 + - ObjField: "y" + - Float: 1 + - ObjEnd: + - Obj: + - ObjField: "x" + - Float: 0.1 + - ObjField: "y" + - Float: 4.1 + - ObjEnd: + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/true_map_var_arithmetic.yml b/engines/playmon/internal/constrcheck/tests/true_map_var_arithmetic.yml new file mode 100644 index 0000000..3b6e8c6 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_map_var_arithmetic.yml @@ -0,0 +1,32 @@ +schema: > + type Query { f( + v:Int! + nonempty:[Int!]! + empty:[Int!]! + ):Int } + +template: > + query { f( + v=$v: *, + nonempty: [... > ($v + 2 * 200 / 10 % 7) - 3], + empty: [... > ($v + 2 * 200 / 10 % 7) - 3], + ) } + +inputs: + Query.f|v: + match: true + tokens: + - Int: 2 + Query.f|nonempty: + match: true + tokens: + - Arr: + - Int: 5 + - Int: 6 + - Int: 7 + - ArrEnd: + Query.f|empty: + match: true + tokens: + - Arr: + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/true_not_equal_arr_int.yml b/engines/playmon/internal/constrcheck/tests/true_not_equal_arr_int.yml new file mode 100644 index 0000000..6d8b1e5 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_not_equal_arr_int.yml @@ -0,0 +1,46 @@ +schema: > + type Query {f( + intsDiff: [Int!]! + intsShort: [Int!]! + intsLong: [Int!]! + intsEmpty: [Int!]! + ):Int} + +template: > + query {f( + intsDiff: != [1,2,3], + intsShort: != [1,2,3], + intsLong: != [1,2,3], + intsEmpty: != [1,2,3], + )} + +inputs: + Query.f|intsDiff: + match: true + tokens: + - Arr: + - Int: 1 + - Int: 2 + - Int: 5 + - ArrEnd: + Query.f|intsShort: + match: true + tokens: + - Arr: + - Int: 1 + - Int: 2 + - ArrEnd: + Query.f|intsLong: + match: true + tokens: + - Arr: + - Int: 1 + - Int: 2 + - Int: 3 + - Int: 3 + - ArrEnd: + Query.f|intsEmpty: + match: true + tokens: + - Arr: + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/true_not_equal_arr_str.yml b/engines/playmon/internal/constrcheck/tests/true_not_equal_arr_str.yml new file mode 100644 index 0000000..d8f3c73 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_not_equal_arr_str.yml @@ -0,0 +1,46 @@ +schema: > + type Query {f( + strsDiff: [String!]! + strsShort: [String!]! + strsLong: [String!]! + strsEmpty: [String!]! + ):Int} + +template: > + query {f( + strsDiff: != ["1","2","3"], + strsShort: != ["1","2","3"], + strsLong: != ["1","2","3"], + strsEmpty: != ["1","2","3"], + )} + +inputs: + Query.f|strsDiff: + match: true + tokens: + - Arr: + - Str: "1" + - Str: "2" + - Str: "5" + - ArrEnd: + Query.f|strsShort: + match: true + tokens: + - Arr: + - Str: "1" + - Str: "2" + - ArrEnd: + Query.f|strsLong: + match: true + tokens: + - Arr: + - Str: "1" + - Str: "2" + - Str: "3" + - Str: "3" + - ArrEnd: + Query.f|strsEmpty: + match: true + tokens: + - Arr: + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/true_not_equal_obj.yml b/engines/playmon/internal/constrcheck/tests/true_not_equal_obj.yml new file mode 100644 index 0000000..b748f77 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_not_equal_obj.yml @@ -0,0 +1,35 @@ +schema: > + type Query {f( + a: A! + ):Int} + input A { b: B! } + input B { + s: String! + i: Int! + optional: Boolean + } + +template: > + query {f( + a: != { + b: { + s: "okay", + i: 42, + optional: * + } + } + )} + +inputs: + Query.f|a: + match: true + tokens: # {b:{s:"okay",i:24}} + - Obj: + - ObjField: "b" + - Obj: + - ObjField: "s" + - Str: "okay" + - ObjField: "i" + - Int: 24 + - ObjEnd: + - ObjEnd: diff --git a/engines/playmon/internal/constrcheck/tests/true_relational_float.yml b/engines/playmon/internal/constrcheck/tests/true_relational_float.yml new file mode 100644 index 0000000..5622540 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_relational_float.yml @@ -0,0 +1,33 @@ +schema: > + type Query { f( + gr: Float! + le: Float! + greq: Float! + leeq: Float! + ):Int } + +template: > + query { f( + gr: > 1.1, + le: < 2.22, + greq: >= 3.333, + leeq: <= 4.4444, + ) } + +inputs: + Query.f|gr: + match: true + tokens: + - Float: 1.11 # gr + Query.f|le: + match: true + tokens: + - Float: 2.21 # le + Query.f|greq: + match: true + tokens: + - Float: 3.333 # eq + Query.f|leeq: + match: true + tokens: + - Float: 4.4444 # eq diff --git a/engines/playmon/internal/constrcheck/tests/true_relational_int.yml b/engines/playmon/internal/constrcheck/tests/true_relational_int.yml new file mode 100644 index 0000000..f49faba --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_relational_int.yml @@ -0,0 +1,33 @@ +schema: > + type Query { f( + gr: Int! + le: Int! + greq: Int! + leeq: Int! + ):Int } + +template: > + query { f( + gr: > 1, + le: < 20, + greq: >= 300, + leeq: <= 4000, + ) } + +inputs: + Query.f|gr: + match: true + tokens: + - Int: 2 + Query.f|le: + match: true + tokens: + - Int: 19 + Query.f|greq: + match: true + tokens: + - Int: 300 + Query.f|leeq: + match: true + tokens: + - Int: 4000 diff --git a/engines/playmon/internal/constrcheck/tests/true_union.yml b/engines/playmon/internal/constrcheck/tests/true_union.yml new file mode 100644 index 0000000..627964a --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_union.yml @@ -0,0 +1,33 @@ +schema: > + type Query { + u: U! + } + union U = Foo | Bar + type Foo { + i(x:Int!):Int! + } + type Bar { + i(x:Int!):Int! + } + +template: > + query { + u { + ... on Foo { + i(x:>20) + } + ... on Bar { + i(x:>30) + } + } + } + +inputs: + Query.u&Foo.i|x: + match: true + tokens: + - Int: 21 + Query.u&Bar.i|x: + match: true + tokens: + - Int: 31 diff --git a/engines/playmon/internal/constrcheck/tests/true_var_bool.yml b/engines/playmon/internal/constrcheck/tests/true_var_bool.yml new file mode 100644 index 0000000..4cf48fb --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_var_bool.yml @@ -0,0 +1,21 @@ +schema: > + type Query { f( + a: Boolean! + b: Boolean! + ):Int } + +template: > + query { f( + a = $a: true, + b: (true && true) && $a, + ) } + +inputs: + Query.f|a: + match: true + tokens: + - True: + Query.f|b: + match: true + tokens: + - True: diff --git a/engines/playmon/internal/constrcheck/tests/true_var_str.yml b/engines/playmon/internal/constrcheck/tests/true_var_str.yml new file mode 100644 index 0000000..b136895 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_var_str.yml @@ -0,0 +1,21 @@ +schema: > + type Query { f( + a: String! + b: String! + ):Int } + +template: > + query { f( + a = $a: "okay", + b: $a, + ) } + +inputs: + Query.f|a: + match: true + tokens: + - Str: "okay" + Query.f|b: + match: true + tokens: + - Str: "okay" diff --git a/engines/playmon/internal/constrcheck/tests/true_var_strblock.yml b/engines/playmon/internal/constrcheck/tests/true_var_strblock.yml new file mode 100644 index 0000000..ec2ebfa --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/true_var_strblock.yml @@ -0,0 +1,21 @@ +schema: > + type Query { f( + a: String! + b: String! + ):Int } + +template: > + query { f( + a = $a: "okay", + b: $a, + ) } + +inputs: + Query.f|a: + match: true + tokens: + - StrBlock: "okay" + Query.f|b: + match: true + tokens: + - StrBlock: "okay" diff --git a/engines/playmon/internal/constrcheck/tests/var_equal_nonexistent.yml b/engines/playmon/internal/constrcheck/tests/var_equal_nonexistent.yml new file mode 100644 index 0000000..3dd9b03 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/var_equal_nonexistent.yml @@ -0,0 +1,24 @@ +# Make sure that missing values are treated as `null`. +schema: > + type Query { f( + v: Int + a: Int + b: Int + ):Int } + +template: > + query { f( + v = $v: *, + a: $v, + b: != $v, + ) } + +inputs: + Query.f|a: + match: true + tokens: + - Null: + Query.f|b: + match: true + tokens: + - Int: 42 diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_equal.yml new file mode 100644 index 0000000..5953b97 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_equal.yml @@ -0,0 +1,58 @@ +schema: > + type Query {f( + int: Int! + float: Float! + string: String! + bool_t: Boolean! + bool_f: Boolean! + enum: TestEnum! + arrint: [Int!]! + nullable: Int + ):Int} + enum TestEnum { something } + +template: > + query {f( + int: 42, + float: 3.14159, + string: "something", + bool_t: true, + bool_f: false, + enum: something, + arrint: [1,2,3], + nullable: null + )} + +inputs: + Query.f|int: + match: false + tokens: + - Str: "wrong type" + Query.f|float: + match: false + tokens: + - Str: "wrong type" + Query.f|string: + match: false + tokens: + - EnumVal: "something" + Query.f|bool_t: + match: false + tokens: + - Str: "wrong type" + Query.f|bool_f: + match: false + tokens: + - Str: "wrong type" + Query.f|enum: + match: false + tokens: + - Str: "wrong type" + Query.f|arrint: + match: false + tokens: + - Str: "wrong type" + Query.f|nullable: + match: false + tokens: + - Str: "wrong type" diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_arrint.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_arrint.yml new file mode 100644 index 0000000..2f0c73e --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_arrint.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + arrint: [Int!]! + ):Int} + +template: > + query {f( + arrint: [1,2,3], + )} + +inputs: + Query.f|arrint: + match: false + tokens: + - Str: "[1,2,3]" diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_bool_f.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_bool_f.yml new file mode 100644 index 0000000..0ba67b7 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_bool_f.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + bool_f: Boolean! + ):Int} + +template: > + query {f( + bool_f: false, + )} + +inputs: + Query.f|bool_f: + match: false + tokens: + - Str: "false" diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_bool_t.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_bool_t.yml new file mode 100644 index 0000000..b699450 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_bool_t.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + bool_t: Boolean! + ):Int} + +template: > + query {f( + bool_t: true, + )} + +inputs: + Query.f|bool_t: + match: false + tokens: + - Str: "true" diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_enum.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_enum.yml new file mode 100644 index 0000000..8b7a880 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_enum.yml @@ -0,0 +1,16 @@ +schema: > + type Query {f( + enum: TestEnum! + ):Int} + enum TestEnum { something } + +template: > + query {f( + enum: something, + )} + +inputs: + Query.f|enum: + match: false + tokens: + - Str: "something" # not an enum value diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_float.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_float.yml new file mode 100644 index 0000000..a402ca3 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_float.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + float: Float! + ):Int} + +template: > + query {f( + float: 3.14159, + )} + +inputs: + Query.f|float: + match: false + tokens: + - Str: "3.14159" diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_int.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_int.yml new file mode 100644 index 0000000..c030f18 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_int.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + int: Int! + ):Int} + +template: > + query {f( + int: 42, + )} + +inputs: + Query.f|int: + match: false + tokens: + - Str: "42" diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_missing_field.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_missing_field.yml new file mode 100644 index 0000000..22f17ba --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_missing_field.yml @@ -0,0 +1,22 @@ +schema: > + type Query { f( + in: In + ):Int } + input In { + i: Int! + x: Int! + } + +template: > + query { f( + in: {i: 42, x: 42} + ) } + +inputs: + Query.f|in: + match: false + tokens: + - Obj: + - ObjField: "i" + - Int: 42 + - ObjEnd: diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_null.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_null.yml new file mode 100644 index 0000000..a688ef2 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_null.yml @@ -0,0 +1,15 @@ +schema: > + type Query {f( + nullable: Int + ):Int} + +template: > + query {f( + nullable: null + )} + +inputs: + Query.f|nullable: + match: false + tokens: + - Str: "null" diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_str.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_str.yml new file mode 100644 index 0000000..4d6b8be --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_equal_str.yml @@ -0,0 +1,16 @@ +schema: > + type Query {f( + string: String! + ):Int} + enum TestEnum { something } + +template: > + query {f( + string: "something", + )} + +inputs: + Query.f|string: + match: false + tokens: + - EnumVal: "something" diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_false_equal_missing_field.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_false_equal_missing_field.yml new file mode 100644 index 0000000..d1df458 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_false_equal_missing_field.yml @@ -0,0 +1,22 @@ +schema: > + type Query { f( + in: In + ):Int } + input In { + i: Int! + x: Int! + } + +template: > + query { f( + in: {i: 42, x: 42} + ) } + +inputs: + Query.f|in: + match: false + tokens: # {i:42} + - Obj: + - ObjField: "i" + - Int: 42 + - ObjEnd: diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_false_not_equal_missing_field.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_false_not_equal_missing_field.yml new file mode 100644 index 0000000..dfbfab3 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_false_not_equal_missing_field.yml @@ -0,0 +1,22 @@ +schema: > + type Query { f( + in: In + ):Int } + input In { + i: Int! + x: Int! + } + +template: > + query { f( + in: != {i: 42, x: 42} + ) } + +inputs: + Query.f|in: + match: false + tokens: # {i:42} + - Obj: + - ObjField: "i" + - Int: 42 + - ObjEnd: diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_len_arr_int.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_len_arr_int.yml new file mode 100644 index 0000000..b291d78 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_len_arr_int.yml @@ -0,0 +1,51 @@ +schema: > + type Query { f( + eq:[Int!]! + neq:[Int!]! + gr:[Int!]! + le:[Int!]! + greq:[Int!]! + leeq:[Int!]! + ):Int } + enum Color { red } + +template: > + query { f( + eq: len 0, + neq: len != 0, + gr: len > 1, + le: len < 2, + greq: len >= 3, + leeq: len <= 4, + ) } + +inputs: + Query.f|eq: + match: false + tokens: + - Int: 42 + Query.f|neq: + match: false + match-schemaless: true + tokens: + - Arr: + - Str: "a" + - Str: "b" + - Str: "c" + - ArrEnd: + Query.f|gr: + match: false + tokens: + - EnumVal: "red" + Query.f|le: + match: false + tokens: + - True: + Query.f|greq: + match: false + tokens: + - False: + Query.f|leeq: + match: false + tokens: + - Null: diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_len_arr_obj.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_len_arr_obj.yml new file mode 100644 index 0000000..5fefa33 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_len_arr_obj.yml @@ -0,0 +1,52 @@ +schema: > + type Query { f( + eq:[I!]! + neq:[I!]! + gr:[I!]! + le:[I!]! + greq:[I!]! + leeq:[I!]! + ):Int } + enum Color { red } + input I { x:Int } + +template: > + query { f( + eq: len 0, + neq: len != 0, + gr: len > 1, + le: len < 2, + greq: len >= 3, + leeq: len <= 4, + ) } + +inputs: + Query.f|eq: + match: false + tokens: + - Int: 42 + Query.f|neq: + match: false + match-schemaless: true + tokens: + - Arr: + - Str: "a" + - Str: "b" + - Str: "c" + - ArrEnd: + Query.f|gr: + match: false + tokens: + - EnumVal: "red" + Query.f|le: + match: false + tokens: + - True: + Query.f|greq: + match: false + tokens: + - False: + Query.f|leeq: + match: false + tokens: + - Null: diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_len_str.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_len_str.yml new file mode 100644 index 0000000..513ecbd --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_len_str.yml @@ -0,0 +1,51 @@ +schema: > + type Query { f( + eq:String! + neq:String! + gr:String! + le:String! + greq:String! + leeq:String! + ):Int } + enum Color { red } + +template: > + query { f( + eq: len 0, + neq: len != 0, + gr: len > 1, + le: len < 2, + greq: len >= 3, + leeq: len <= 4, + ) } + +inputs: + Query.f|eq: + match: false + tokens: + - Int: 42 + Query.f|neq: + match: false + match-schemaless: true + tokens: + - Arr: + - Int: 1 + - Int: 2 + - Int: 3 + - ArrEnd: + Query.f|gr: + match: false + tokens: + - EnumVal: "red" + Query.f|le: + match: false + tokens: + - True: + Query.f|greq: + match: false + tokens: + - False: + Query.f|leeq: + match: false + tokens: + - Null: diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_map_2d.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_map_2d.yml new file mode 100644 index 0000000..a14536d --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_map_2d.yml @@ -0,0 +1,47 @@ +schema: > + type Query { f( + a: [[Int!]!]! + b: [[Int!]!]! + empty: [[Int!]!]! + ):Int } + +template: > + query { f( + a: [...[...>0]], + b: [...[...>0]], + empty: [...[...>0]], + ) } + +inputs: + Query.f|a: + match: false + tokens: + - Arr: + - Int: 6 + - Int: 7 + - Int: 8 + - ArrEnd: + Query.f|b: + match: false + tokens: + - Arr: + + - Arr: + - Int: 10 + - ArrEnd: + + - Null: + + - Arr: + - ArrEnd: + + - ArrEnd: + Query.f|empty: + match: false + tokens: + - Arr: + - Arr: + - Arr: + - ArrEnd: + - ArrEnd: + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_map_obj_too_many_fields.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_map_obj_too_many_fields.yml new file mode 100644 index 0000000..2cb6abc --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_map_obj_too_many_fields.yml @@ -0,0 +1,38 @@ +schema: > + type Query { f( + ae: [I!]! + an: [I!]! + ):Int } + input I { + i: Int! + } + +template: > + query { f( + ae: [ ... {i: 42} ], + an: [... != {i: 42} ], + ) } + +inputs: + Query.f|ae: + match: false + tokens: + - Arr: + - Obj: + - ObjField: "i" + - Int: 42 + - ObjField: "inexistent" + - Int: 42 + - ObjEnd: + - ArrEnd: + Query.f|an: + match: false + tokens: + - Arr: + - Obj: + - ObjField: "i" + - Int: 42 + - ObjField: "inexistent" + - Int: 42 + - ObjEnd: + - ArrEnd: diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_not_equal.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_not_equal.yml new file mode 100644 index 0000000..9d5eb93 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_not_equal.yml @@ -0,0 +1,59 @@ +schema: > + type Query {f( + int: Int! + float: Float! + str: String! + bool_t: Boolean! + bool_f: Boolean! + enum: TestEnum! + arrint: [Int!]! + nullable: Int + ):Int} + enum TestEnum { something } + +template: > + query {f( + int: != 42, + float: != 3.14159, + str: != "something", + bool_t: != true, + bool_f: != false, + enum: != something, + arrint: != [1,2,3], + nullable: != null, + )} + +inputs: + Query.f|int: + match: false + tokens: + - Str: "wrong type" + Query.f|float: + match: false + tokens: + - Str: "wrong type" + Query.f|str: + match: false + tokens: + - EnumVal: "something" + Query.f|bool_t: + match: false + tokens: + - Str: "wrong type" + Query.f|bool_f: + match: false + tokens: + - Str: "wrong type" + Query.f|enum: + match: false + tokens: + - Str: "wrong type" + Query.f|arrint: + match: false + tokens: + - Str: "wrong type" + Query.f|nullable: + match: false + match-schemaless: true + tokens: + - Str: "wrong type" diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_obj_too_many_inputs.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_obj_too_many_inputs.yml new file mode 100644 index 0000000..d4a6993 --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_obj_too_many_inputs.yml @@ -0,0 +1,34 @@ +schema: > + type Query { f( + ie: I! + in: I! + ):Int } + input I { + i: Int! + } + +template: > + query { f( + ie: {i: 42}, + in: != {i: 42}, + ) } + +inputs: + Query.f|ie: + match: false + tokens: # {i:42,inexistent:42} + - Obj: + - ObjField: "i" + - Int: 42 + - ObjField: "inexistent" + - Int: 42 + - ObjEnd: + Query.f|in: + match: false + tokens: # {i:42,inexistent:42} + - Obj: + - ObjField: "i" + - Int: 42 + - ObjField: "inexistent" + - Int: 42 + - ObjEnd: diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_relational_float.yml b/engines/playmon/internal/constrcheck/tests/wrong_type_relational_float.yml new file mode 100644 index 0000000..ba2c04a --- /dev/null +++ b/engines/playmon/internal/constrcheck/tests/wrong_type_relational_float.yml @@ -0,0 +1,33 @@ +schema: > + type Query { f( + gr: Float! + le: Float! + greq: Float! + leeq: Float! + ):Int } + +template: > + query { f( + gr: > 1.1, + le: < 2.22, + greq: >= 3.333, + leeq: <= 4.4444, + ) } + +inputs: + Query.f|gr: + match: false + tokens: + - Str: "wrong type" + Query.f|le: + match: false + tokens: + - Str: "wrong type" + Query.f|greq: + match: false + tokens: + - Str: "wrong type" + Query.f|leeq: + match: false + tokens: + - Str: "wrong type" diff --git a/engines/playmon/internal/pathscan/pathscan.go b/engines/playmon/internal/pathscan/pathscan.go new file mode 100644 index 0000000..c260fc1 --- /dev/null +++ b/engines/playmon/internal/pathscan/pathscan.go @@ -0,0 +1,126 @@ +// Package pathscan provides a scanner that traverses token slices and +// finds all paths of leaf nodes in a GraphQL operation. +// +// Consider the following example: +// +// query { +// foo { +// bar { +// burr(x:4) +// } +// baz { +// buzz(b:5, a:null, c:true) +// ... on Kraz { +// fraz +// graz(argument:{i:"foo",i2:"bar"}) { +// lum +// } +// } +// } +// } +// mazz +// } +// +// The above query contains 7 leafs with the following paths: +// +// - Q.foo.bar.burr|x +// - Q.foo.baz.buzz|b +// - Q.foo.baz.buzz|a +// - Q.foo.baz.buzz|c +// - Q.foo.baz&Kraz.fraz +// - Q.foo.baz&Kraz.graz|argument +// - Q.foo.baz&Kraz.graz.lum +// - Q.mazz +package pathscan + +import ( + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/utilities/stack" + gqlscan "github.com/graph-guard/gqlscan" +) + +type PathScanner struct { + pathBuf []byte + stack stack.Stack[int] +} + +func New(preallocateStack, preallocatePathBuffer int) *PathScanner { + return &PathScanner{ + pathBuf: make([]byte, 0, preallocatePathBuffer), + stack: stack.New[int](preallocateStack), + } +} + +// Magic identifier and divider bytes +const ( + elemQuery = 'Q' + elemMutation = 'M' + elemSubscription = 'S' + divField = '.' + divArg = '|' + divTypeCond = '&' +) + +func (s *PathScanner) Scan(tokens []gqlparse.Token, onPath func([]byte) (stop bool)) { + s.pathBuf = s.pathBuf[:0] + s.stack.Reset() + for i, level := 0, 1; i < len(tokens); i++ { + switch tokens[i].ID { + case gqlscan.TokenDefQry: + s.stackPushByte(elemQuery) + case gqlscan.TokenDefMut: + s.stackPushByte(elemMutation) + case gqlscan.TokenDefSub: + s.stackPushByte(elemSubscription) + case gqlscan.TokenSet: + level++ + case gqlscan.TokenSetEnd: + level-- + s.stackPop() + case gqlscan.TokenFragInline: + if level > s.stack.Len() { + s.stackPushWithDiv(divTypeCond, tokens[i].Value) + break + } + s.stackPop() + s.stackPushWithDiv(divTypeCond, tokens[i].Value) + case gqlscan.TokenField: + if level > s.stack.Len() { + s.stackPushWithDiv(divField, tokens[i].Value) + } else { + s.stackPop() + s.stackPushWithDiv(divField, tokens[i].Value) + } + if t := tokens[i+1].ID; t == gqlscan.TokenArgList || + t == gqlscan.TokenSet { + continue + } + if onPath(s.pathBuf) { + return + } + case gqlscan.TokenArgName: + s.stackPushWithDiv(divArg, tokens[i].Value) + if onPath(s.pathBuf) { + return + } + s.stackPop() + } + } +} + +func (s *PathScanner) stackPushByte(element byte) { + s.stack.Push(1) + s.pathBuf = append(s.pathBuf, element) +} + +func (s *PathScanner) stackPushWithDiv(div byte, element []byte) { + s.stack.Push(1 + len(element)) + s.pathBuf = append(s.pathBuf, div) + s.pathBuf = append(s.pathBuf, element...) +} + +func (s *PathScanner) stackPop() { + t := s.stack.Top() + s.pathBuf = s.pathBuf[:len(s.pathBuf)-t] + s.stack.Pop() +} diff --git a/engines/playmon/internal/pathscan/pathscan_test.go b/engines/playmon/internal/pathscan/pathscan_test.go new file mode 100644 index 0000000..e7739fb --- /dev/null +++ b/engines/playmon/internal/pathscan/pathscan_test.go @@ -0,0 +1,88 @@ +package pathscan_test + +import ( + "testing" + + "github.com/graph-guard/ggproxy/engines/playmon/internal/pathscan" + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/stretchr/testify/require" +) + +func TestPathScan(t *testing.T) { + for _, tt := range []struct { + Name string + Input string + ExpectedPaths []string + }{ + { + Name: "query", + Input: `query { + foo { + bar { + burr(x:4) + } + baz { + ... on Kraz { + fraz + graz(argument:{i:"foo",i2:"bar"}) { + lum + } + } + buzz(b:5, a:null, c:true) + ... on Guz { + guz + } + } + } + mazz + }`, + ExpectedPaths: []string{ + "Q.foo.bar.burr|x", + "Q.foo.baz&Kraz.fraz", + "Q.foo.baz&Kraz.graz|argument", + "Q.foo.baz&Kraz.graz.lum", + "Q.foo.baz.buzz|b", + "Q.foo.baz.buzz|a", + "Q.foo.baz.buzz|c", + "Q.foo.baz&Guz.guz", + "Q.mazz", + }, + }, + { + Name: "mutation_with_vars", + Input: `mutation($baz:String="baz"){foo(bar:$baz)}`, + ExpectedPaths: []string{ + "M.foo|bar", + }, + }, + { + Name: "subscription", + Input: `subscription{foo}`, + ExpectedPaths: []string{ + "S.foo", + }, + }, + } { + t.Run(tt.Name, func(t *testing.T) { + ps := pathscan.New(0, 0) + p := gqlparse.NewParser() + var actualPaths []string + p.Parse( + []byte(tt.Input), nil, nil, + func( + varValues [][]gqlparse.Token, + operation, selectionSet []gqlparse.Token, + ) { + ps.Scan(operation, func(b []byte) (stop bool) { + actualPaths = append(actualPaths, string(b)) + return false + }) + }, + func(err error) { + t.Fatalf("unexpected GraphQL parsing error: %v", err) + }, + ) + require.Equal(t, tt.ExpectedPaths, actualPaths) + }) + } +} diff --git a/engines/playmon/playmon.go b/engines/playmon/playmon.go new file mode 100644 index 0000000..a37af62 --- /dev/null +++ b/engines/playmon/playmon.go @@ -0,0 +1,68 @@ +package playmon + +import ( + "fmt" + + "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck" + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/utilities/bitmask" + "github.com/graph-guard/gqlscan" + "github.com/graph-guard/gqt/v4" +) + +type Engine struct { + constrchecker *constrcheck.Checker + currentMask *bitmask.Set + maskByPath map[string]*bitmask.Set + templatesByName map[string]*Template +} + +type Template struct { + Name string + RawGQTTemplateSource []byte + GQTOpr *gqt.Operation + ConstraintChecker *constrcheck.Checker +} + +// New expects templates to be initialized and valid. +func New(templates ...*Template) *Engine { + e := &Engine{ + maskByPath: make(map[string]*bitmask.Set), + templatesByName: make(map[string]*Template, len(templates)), + } + for i := range templates { + n := templates[i].Name + if _, ok := e.templatesByName[n]; ok { + panic(fmt.Errorf("redeclared template: %q", n)) + } + if templates[i].GQTOpr == nil { + panic(fmt.Errorf("uninitialized template, "+ + "missing GQT operation for path %q", n)) + } + if templates[i].ConstraintChecker == nil { + panic(fmt.Errorf("uninitialized template, "+ + "missing constraint checker for path %q", n)) + } + e.templatesByName[n] = templates[i] + } + return e +} + +// Match returns the ID of the first matching template or "" if none was matched. +func (e *Engine) Match( + variableValues [][]gqlparse.Token, + queryType gqlscan.Token, + selectionSet []gqlparse.Token, +) (id string) { + panic("todo") +} + +// MatchAll calls fn for every matching template. +func (e *Engine) MatchAll( + variableValues [][]gqlparse.Token, + queryType gqlscan.Token, + selectionSet []gqlparse.Token, + fn func(id string), +) { + panic("todo") +} diff --git a/engines/rmap/pquery/pquery.go b/engines/rmap/pquery/pquery.go deleted file mode 100644 index d6a8a66..0000000 --- a/engines/rmap/pquery/pquery.go +++ /dev/null @@ -1,517 +0,0 @@ -package pquery - -import ( - "fmt" - "io" - "strconv" - - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/ggproxy/utilities/container/amap" - "github.com/graph-guard/ggproxy/utilities/container/hamap" - "github.com/graph-guard/ggproxy/utilities/stack" - "github.com/graph-guard/ggproxy/utilities/unsafe" - "github.com/graph-guard/ggproxy/utilities/xxhash" - "github.com/graph-guard/gqlscan" -) - -type pathTerminal struct{} -type selectTerminal struct{} -type argumentsTerminal struct{} -type argumentPathTerminal struct{} -type objectTerminal struct{} - -var specialFields []string = []string{"__typename"} - -// QueryPart is a structure of query pash hash and value, -// when united represents a parted query. -// Used for a template fast search by rmap. -type QueryPart struct { - ArgLeafIdx int - Hash uint64 - Value any -} - -// Maker is a meta structure to store the runtime data and the hash seed. -type Maker struct { - mstack *stack.Stack[any] - pstack *stack.Stack[xxhash.Hash] - qmap *amap.Map[uint64, bool] - usedStack *stack.Stack[any] - arrayPool *stack.Stack[*[]any] - mapPool *stack.Stack[*hamap.Map[string, any]] - seed uint64 -} - -// NewMaker creates a new instance of Maker. -// Accepts a hash seed. -func NewMaker(seed uint64) *Maker { - return &Maker{ - mstack: stack.New[any](256), - pstack: stack.New[xxhash.Hash](256), - qmap: amap.New[uint64, bool](256), - mapPool: stack.New[*hamap.Map[string, any]](128), - arrayPool: stack.New[*[]any](128), - usedStack: stack.New[any](128), - seed: seed, - } -} - -// ParseQuery parses query into QueryParts. -// Accepts a token list. -// QueryParts are accessible through the fn function. -func (m *Maker) ParseQuery( - variableValues [][]gqlparse.Token, - queryType gqlscan.Token, - selectionSet []gqlparse.Token, - fn func(qp QueryPart) (stop bool), -) { - m.mstack.Reset() - m.pstack.Reset() - m.qmap.Reset() - - var pathHash uint64 - var insideArray, argLeafIdx int = 0, -1 - var lastObjField string - - switch queryType { - case gqlscan.TokenDefQry: - path := xxhash.New(m.seed) - xxhash.Write(&path, "query") - m.pstack.Push(path) - case gqlscan.TokenDefMut: - path := xxhash.New(m.seed) - xxhash.Write(&path, "mutation") - m.pstack.Push(path) - default: - panic(fmt.Errorf("unsupported query type: %v", queryType)) - } - - for tokenIdx, token := range selectionSet { - if ix := token.VariableIndex(); ix > -1 { - value := variableValues[ix] - for _, token := range value { - switch token.ID { - case gqlscan.TokenArr: - insideArray++ - var arr *[]any - if m.arrayPool.Len() > 0 { - arr = m.arrayPool.Pop() - } else { - arr = &[]any{} - } - m.usedStack.Push(arr) - - switch t := m.mstack.Top(); t := t.(type) { - case *[]any: - *t = append(*t, arr) - case *hamap.Map[string, any]: - t.Set(lastObjField, arr) - } - m.mstack.Push(arr) - case gqlscan.TokenObj: - if insideArray == 0 { - path := m.pstack.Top() - xxhash.Write(&path, ".") - m.pstack.Push(path) - m.mstack.Push(objectTerminal{}) - } else { - var obj *hamap.Map[string, any] - if m.mapPool.Len() > 0 { - obj = m.mapPool.Pop() - } else { - obj = hamap.New[string, any](64, nil) - } - m.usedStack.Push(obj) - - switch t := m.mstack.Top(); t := t.(type) { - case *[]any: - *t = append(*t, obj) - case *hamap.Map[string, any]: - t.Set(lastObjField, obj) - } - m.mstack.Push(obj) - } - case gqlscan.TokenObjField: - if insideArray == 0 { - t := m.pstack.Top() - xxhash.Write(&t, token.Value) - m.pstack.Push(t) - m.mstack.Push(pathTerminal{}) - } else { - lastObjField = unsafe.B2S(token.Value) - } - case gqlscan.TokenStr, gqlscan.TokenEnumVal, gqlscan.TokenInt, - gqlscan.TokenFloat, gqlscan.TokenTrue, gqlscan.TokenFalse, gqlscan.TokenNull: - var val any - var err error - switch token.ID { - case gqlscan.TokenStr, gqlscan.TokenEnumVal: - val = token.Value - case gqlscan.TokenInt: - val, err = strconv.ParseInt(unsafe.B2S(token.Value), 10, 64) - if err != nil { - panic(err) - } - case gqlscan.TokenFloat: - val, err = strconv.ParseFloat(unsafe.B2S(token.Value), 64) - if err != nil { - panic(err) - } - case gqlscan.TokenTrue: - val = true - case gqlscan.TokenFalse: - val = false - } - if insideArray == 0 { - switch t := m.mstack.Top(); t.(type) { - case pathTerminal: - m.mstack.Pop() - path := m.pstack.Pop() - pathHash = path.Sum64() - if _, ok := m.qmap.Get(pathHash); !ok { - argLeafIdx++ - m.qmap.Set(pathHash, true) - if fn(QueryPart{ArgLeafIdx: argLeafIdx, Hash: pathHash, Value: val}) { - return - } - } - } - } else { - switch t := m.mstack.Top(); t := t.(type) { - case *[]any: - *t = append(*t, val) - case *hamap.Map[string, any]: - t.Set(lastObjField, val) - } - } - case gqlscan.TokenArrEnd, gqlscan.TokenObjEnd: - if token.ID == gqlscan.TokenArrEnd { - insideArray-- - } - for { - switch t := m.mstack.Top(); t.(type) { - case objectTerminal: - m.mstack.Pop() - m.pstack.Pop() - m.mstack.Pop() - m.pstack.Pop() - case *[]any, *hamap.Map[string, any]: - el := m.mstack.Pop() - path := m.pstack.Top() - if insideArray == 0 { - pathHash = path.Sum64() - switch elt := el.(type) { - case *[]any, *hamap.Map[string, any]: - if _, ok := m.qmap.Get(pathHash); !ok { - argLeafIdx++ - m.qmap.Set(pathHash, true) - if fn(QueryPart{ArgLeafIdx: argLeafIdx, Hash: pathHash, Value: elt}) { - return - } - } - } - m.mstack.Pop() - m.pstack.Pop() - } - } - break - } - } - } - continue - } - - switch token.ID { - case gqlscan.TokenField, gqlscan.TokenFragInline: - switch t := m.mstack.Top(); t.(type) { - case argumentPathTerminal: - m.mstack.Pop() - m.pstack.Pop() - } - switch t := m.mstack.Top(); t.(type) { - case pathTerminal: - m.mstack.Pop() - path := m.pstack.Pop() - pathHash = path.Sum64() - if _, ok := m.qmap.Get(pathHash); !ok { - if !inSlice(specialFields, unsafe.B2S(selectionSet[tokenIdx-1].Value)) { - m.qmap.Set(pathHash, true) - if fn(QueryPart{ArgLeafIdx: -1, Hash: pathHash, Value: nil}) { - return - } - } - } - } - path := m.pstack.Top() - if token.ID == gqlscan.TokenFragInline { - xxhash.Write(&path, "|") - } - xxhash.Write(&path, token.Value) - m.pstack.Push(path) - m.mstack.Push(pathTerminal{}) - case gqlscan.TokenArgList: - m.mstack.PopPush(argumentPathTerminal{}) - path := m.pstack.Top() - xxhash.Write(&path, ".") - m.pstack.Push(path) - m.mstack.Push(argumentsTerminal{}) - case gqlscan.TokenSet: - path := m.pstack.Top() - xxhash.Write(&path, ".") - m.pstack.Push(path) - m.mstack.Push(selectTerminal{}) - case gqlscan.TokenArgName: - path := m.pstack.Top() - xxhash.Write(&path, token.Value) - m.pstack.Push(path) - m.mstack.Push(pathTerminal{}) - case gqlscan.TokenArr: - insideArray++ - var arr *[]any - if m.arrayPool.Len() > 0 { - arr = m.arrayPool.Pop() - } else { - arr = &[]any{} - } - m.usedStack.Push(arr) - - switch t := m.mstack.Top(); t := t.(type) { - case *[]any: - *t = append(*t, arr) - case *hamap.Map[string, any]: - t.Set(lastObjField, arr) - } - m.mstack.Push(arr) - case gqlscan.TokenObj: - if insideArray == 0 { - path := m.pstack.Top() - xxhash.Write(&path, ".") - m.pstack.Push(path) - m.mstack.Push(objectTerminal{}) - } else { - var obj *hamap.Map[string, any] - if m.mapPool.Len() > 0 { - obj = m.mapPool.Pop() - } else { - obj = hamap.New[string, any](64, nil) - } - m.usedStack.Push(obj) - - switch t := m.mstack.Top(); t := t.(type) { - case *[]any: - *t = append(*t, obj) - case *hamap.Map[string, any]: - t.Set(lastObjField, obj) - } - m.mstack.Push(obj) - } - case gqlscan.TokenObjField: - if insideArray == 0 { - t := m.pstack.Top() - xxhash.Write(&t, token.Value) - m.pstack.Push(t) - m.mstack.Push(pathTerminal{}) - } else { - lastObjField = unsafe.B2S(token.Value) - } - case gqlscan.TokenStr, gqlscan.TokenEnumVal, gqlscan.TokenInt, - gqlscan.TokenFloat, gqlscan.TokenTrue, gqlscan.TokenFalse, gqlscan.TokenNull: - var val any - var err error - switch token.ID { - case gqlscan.TokenStr, gqlscan.TokenEnumVal: - val = token.Value - case gqlscan.TokenNull: - val = nil - case gqlscan.TokenInt: - val, err = strconv.ParseInt(unsafe.B2S(token.Value), 10, 64) - if err != nil { - panic(err) - } - case gqlscan.TokenFloat: - val, err = strconv.ParseFloat(unsafe.B2S(token.Value), 64) - if err != nil { - panic(err) - } - case gqlscan.TokenTrue: - val = true - case gqlscan.TokenFalse: - val = false - } - if insideArray == 0 { - switch t := m.mstack.Top(); t.(type) { - case pathTerminal: - m.mstack.Pop() - path := m.pstack.Pop() - pathHash = path.Sum64() - if _, ok := m.qmap.Get(pathHash); !ok { - argLeafIdx++ - m.qmap.Set(pathHash, true) - if fn(QueryPart{ArgLeafIdx: argLeafIdx, Hash: pathHash, Value: val}) { - return - } - } - } - } else { - switch t := m.mstack.Top(); t := t.(type) { - case *[]any: - *t = append(*t, val) - case *hamap.Map[string, any]: - t.Set(lastObjField, val) - } - } - case gqlscan.TokenArrEnd, gqlscan.TokenSetEnd, - gqlscan.TokenArgListEnd, gqlscan.TokenObjEnd: - if token.ID == gqlscan.TokenArgListEnd { - argLeafIdx = -1 - } - if token.ID == gqlscan.TokenArrEnd { - insideArray-- - } - for { - switch t := m.mstack.Top(); t.(type) { - case pathTerminal: - m.mstack.Pop() - path := m.pstack.Pop() - pathHash = path.Sum64() - if _, ok := m.qmap.Get(pathHash); !ok { - m.qmap.Set(pathHash, true) - if fn(QueryPart{ArgLeafIdx: -1, Hash: pathHash, Value: nil}) { - return - } - } - continue - case argumentPathTerminal: - m.mstack.Pop() - m.pstack.Pop() - continue - case argumentsTerminal: - m.mstack.Pop() - m.pstack.Pop() - case selectTerminal, objectTerminal: - m.mstack.Pop() - m.pstack.Pop() - m.mstack.Pop() - m.pstack.Pop() - case *[]any, *hamap.Map[string, any]: - el := m.mstack.Pop() - path := m.pstack.Top() - if insideArray == 0 { - pathHash = path.Sum64() - switch elt := el.(type) { - case *[]any, *hamap.Map[string, any]: - if _, ok := m.qmap.Get(pathHash); !ok { - argLeafIdx++ - m.qmap.Set(pathHash, true) - if fn(QueryPart{ArgLeafIdx: argLeafIdx, Hash: pathHash, Value: elt}) { - return - } - } - } - m.mstack.Pop() - m.pstack.Pop() - } - } - break - } - } - } - - for m.usedStack.Len() > 0 { - switch el := m.usedStack.Pop().(type) { - case *[]any: - *el = (*el)[:0] - m.arrayPool.Push(el) - case *hamap.Map[string, any]: - el.Reset() - m.mapPool.Push(el) - } - } -} - -// PrintNSpaces prints n spaces in a row -func PrintNSpaces(w io.Writer, n uint) { - for i := uint(0); i < n; i++ { - _, _ = w.Write([]byte(" ")) - } -} - -// Print prints out the QueryPart -func (qp QueryPart) Print(w io.Writer) { - qp.print(w, 0) -} - -func (qp QueryPart) print(w io.Writer, indent uint) { - PrintNSpaces(w, indent) - fmt.Fprintf(w, "%d:", qp.Hash) - switch vt := qp.Value.(type) { - case *[]any: - _, _ = w.Write([]byte("\n")) - printArr(*vt, w, indent+2) - case *hamap.Map[string, any]: - _, _ = w.Write([]byte("\n")) - printObj(*vt, w, indent+2) - default: - if qp.Value != nil { - _, _ = w.Write([]byte(" ")) - if s, ok := qp.Value.([]byte); ok { - fmt.Fprintln(w, string(s)) - } else { - fmt.Fprintln(w, qp.Value) - } - } else { - _, _ = w.Write([]byte("\n")) - } - } -} - -func printArr(arr []any, w io.Writer, indent uint) { - for _, v := range arr { - PrintNSpaces(w, indent) - _, _ = w.Write([]byte("-:\n")) - switch vt := v.(type) { - case *[]any: - printArr(*vt, w, indent+2) - case *hamap.Map[string, any]: - printObj(*vt, w, indent+2) - default: - PrintNSpaces(w, indent+2) - if s, ok := v.([]byte); ok { - fmt.Fprintln(w, string(s)) - } else { - fmt.Fprintln(w, v) - } - } - } -} - -func printObj(obj hamap.Map[string, any], w io.Writer, indent uint) { - obj.Visit(func(key string, value any) (stop bool) { - PrintNSpaces(w, indent) - fmt.Fprintf(w, "%s:\n", key) - switch vt := value.(type) { - case *[]any: - printArr(*vt, w, indent+2) - case *hamap.Map[string, any]: - printObj(*vt, w, indent+2) - default: - PrintNSpaces(w, indent+2) - if s, ok := value.([]byte); ok { - fmt.Fprintln(w, string(s)) - } else { - fmt.Fprintln(w, value) - } - } - return false - }) -} - -func inSlice[T comparable](a []T, e T) bool { - for _, el := range a { - if el == e { - return true - } - } - - return false -} diff --git a/engines/rmap/rmap.go b/engines/rmap/rmap.go deleted file mode 100644 index e6a18a0..0000000 --- a/engines/rmap/rmap.go +++ /dev/null @@ -1,1083 +0,0 @@ -package rmap - -import ( - "bytes" - "errors" - "fmt" - "io" - - "github.com/graph-guard/ggproxy/engines/rmap/pquery" - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/ggproxy/utilities/bitmask" - "github.com/graph-guard/ggproxy/utilities/container/amap" - "github.com/graph-guard/ggproxy/utilities/container/hamap" - "github.com/graph-guard/ggproxy/utilities/xxhash" - "github.com/graph-guard/gqlscan" - "github.com/graph-guard/gqt" -) - -var ErrHashCollision = errors.New("hash collsision") - -const ( - maxRand = 32768 - maxAttempts = 32 -) - -// RulesMap is a graphql query to a template fast search structure. -type RulesMap struct { - seed uint64 - mask *bitmask.Set - rejected *bitmask.Set - qmake pquery.Maker - matchCounter *amap.Map[int, int] - combinations []int - combinationCounters []int - rules map[uint64][]Variant - hashedPaths map[uint64]string - templateIDs []string -} - -// Combination is a auxiliary "max" block structure. -type Combination struct { - Index int - Depth int - RuleIndex int -} - -// Variant is an auxiliary RulesNode structure. -type Variant struct { - Condition bool - Constraint Constraint - Mask *bitmask.Set - Value any - Combinations []Combination -} - -// Elem is an auxiliary Variant structure. -type Elem struct { - Constraint Constraint - Value any -} - -// Array is an auxiliary array structure. -type Array []Elem - -// Object is an auxiliary map structure. -type Object map[string]Elem - -// ConstraintInterface is a generic interface for constraints. -type ConstraintInterface interface { - Key() string - Content() gqt.Constraint - gqt.InputConstraint | gqt.ObjectField -} - -// Equal checks two Elems for equality. -func (e Elem) Equal(x Elem) bool { - if e.Constraint == x.Constraint { - switch ve := e.Value.(type) { - case Elem: - switch vx := x.Value.(type) { - case Elem: - return ve.Equal(vx) - } - case Array: - switch vx := x.Value.(type) { - case Array: - return ve.Equal(vx) - } - case Object: - switch vx := x.Value.(type) { - case Object: - return ve.Equal(vx) - } - case []byte: - switch vx := x.Value.(type) { - case []byte: - return bytes.Equal(ve, vx) - } - default: - return e.Value == x.Value - } - } - - return false -} - -// Equal checks two Arrays for equality. -func (arr Array) Equal(x Array) bool { - if len(arr) != len(x) { - return false - } - - for i := 0; i < len(x); i++ { - if !arr[i].Equal(x[i]) { - return false - } - } - - return true -} - -// Equal checks two Objects for equality. -func (obj Object) Equal(x Object) bool { - if len(obj) != len(x) { - return false - } - - for k, e := range x { - v, ok := obj[k] - if !ok { - return false - } - if !v.Equal(e) { - return false - } - } - - return true -} - -// New creates a new instance of RulesMap. -// Accepts a rules list and a hash seed. -func New(rules map[string]gqt.Doc, seed uint64) (*RulesMap, error) { - rm := &RulesMap{ - seed: seed, - mask: bitmask.New(), - rejected: bitmask.New(), - qmake: *pquery.NewMaker(seed), - matchCounter: amap.New[int, int](0), - combinations: []int{}, - combinationCounters: []int{}, - rules: map[uint64][]Variant{}, - hashedPaths: map[uint64]string{}, - } - var attempt int - var err error - - rm.templateIDs = make([]string, 0, len(rules)) - for id := range rules { - rm.templateIDs = append(rm.templateIDs, id) - } - - for attempt < maxAttempts { - for index, id := range rm.templateIDs { - rule := rules[id] - m := bitmask.New(index) - if rule.Query != nil { - err = buildRulesMapSelections( - rm, rule.Query, nil, m, "query", index, 0, - ) - } - if rule.Mutation != nil { - err = buildRulesMapSelections( - rm, rule.Mutation, nil, m, "mutation", index, 0, - ) - } - if rule.Subscription != nil { - panic("subscriptions are not yet supported") - } - if err == ErrHashCollision { - rm = &RulesMap{ - seed: seed, - mask: bitmask.New(), - rejected: bitmask.New(), - qmake: *pquery.NewMaker(seed), - matchCounter: amap.New[int, int](0), - combinations: []int{}, - combinationCounters: []int{}, - rules: map[uint64][]Variant{}, - hashedPaths: map[uint64]string{}, - } - attempt++ - break - } - } - if err != ErrHashCollision { - break - } - } - - if err != nil { - return nil, err - } - - return rm, nil -} - -func buildRulesMapSelections( - rm *RulesMap, - selections []gqt.Selection, - dependencies []uint64, - mask *bitmask.Set, - path string, - ruleIdx int, - combinationDepth int, -) error { - for _, selection := range selections { - switch selection := selection.(type) { - case gqt.SelectionField: - selPath := path + "." + selection.Name - if len(selection.Selections) == 0 && len(selection.InputConstraints) == 0 { - h := xxhash.New(rm.seed) - xxhash.Write(&h, selPath) - pathHash := h.Sum64() - - v := Variant{ - Mask: mask, - Combinations: []Combination{}, - } - if combinationDepth > 0 { - v.Combinations = append( - v.Combinations, - Combination{len(rm.combinations) - 1, combinationDepth - 1, ruleIdx}, - ) - } - if _, ok := (*rm).rules[pathHash]; ok { - (*rm).rules[pathHash] = mergeVariants((*rm).rules[pathHash], v) - } else { - if v, ok := rm.hashedPaths[pathHash]; !ok { - rm.hashedPaths[pathHash] = selPath - } else { - if v != selPath { - return ErrHashCollision - } - } - (*rm).rules[pathHash] = []Variant{v} - } - } else { - var leafs []uint64 - var err error - if len(selection.InputConstraints) > 0 { - leafs, err = buildRulesMapConstraints( - rm, selection.InputConstraints, dependencies, mask, selPath, true, ruleIdx, combinationDepth, - ) - if err != nil { - return err - } - } - if len(selection.Selections) > 0 { - if len(selection.InputConstraints) > 0 { - combinationDepth = 0 - } - err = buildRulesMapSelections( - rm, selection.Selections, append(leafs, dependencies...), mask, selPath, ruleIdx, combinationDepth, - ) - if err != nil { - return err - } - } - } - case gqt.SelectionInlineFragment: - selPath := path + ".|" + selection.TypeName - if err := buildRulesMapSelections( - rm, selection.Selections, dependencies, mask, selPath, ruleIdx, combinationDepth, - ); err != nil { - return err - } - case gqt.ConstraintCombine: - rm.combinations = append(rm.combinations, int(selection.MaxItems)) - rm.combinationCounters = append(rm.combinationCounters, 0) - if err := buildRulesMapSelections( - rm, selection.Items, dependencies, mask, path, ruleIdx, combinationDepth+1, - ); err != nil { - return err - } - } - } - - return nil -} - -func buildRulesMapConstraints[T ConstraintInterface]( - rm *RulesMap, - constraints []T, - dependencies []uint64, - mask *bitmask.Set, - path string, - condition bool, - ruleIdx int, - combinationDepth int, -) ([]uint64, error) { - var leafs []uint64 - for _, constraint := range constraints { - var cv any - var cid Constraint - cond := condition - - cid, cv = ConstraintIdAndValue(constraint.Content()) - if cid == ConstraintValNotEqual { - cond = !cond - } - - conPath := path + "." + constraint.Key() - switch cv := cv.(type) { - case gqt.ValueObject: - l, err := buildRulesMapConstraints( - rm, cv.Fields, dependencies, mask, conPath, cond, ruleIdx, combinationDepth, - ) - if err != nil { - return nil, err - } - leafs = append(leafs, l...) - default: - h := xxhash.New(rm.seed) - xxhash.Write(&h, conPath) - pathHash := h.Sum64() - leafs = append(leafs, pathHash) - if _, ok := (*rm).rules[pathHash]; !ok { - if v, ok := rm.hashedPaths[pathHash]; !ok { - rm.hashedPaths[pathHash] = conPath - } else { - if v != conPath { - return nil, ErrHashCollision - } - } - } - - c := []Combination{} - if combinationDepth > 0 { - c = append( - c, - Combination{len(rm.combinations) - 1, combinationDepth - 1, ruleIdx}, - ) - } - switch cid { - case ConstraintMap: - (*rm).rules[pathHash] = mergeVariants((*rm).rules[pathHash], Variant{ - Condition: cond, - Constraint: cid, - Mask: mask, - Value: buildRulesMapConstraintsElem(cv), - Combinations: c, - }) - case ConstraintOr, ConstraintAnd: - (*rm).rules[pathHash] = mergeVariants((*rm).rules[pathHash], Variant{ - Condition: cond, - Constraint: cid, - Mask: mask, - Value: buildRulesMapConstraintsArray(cv.([]gqt.Constraint)), - Combinations: c, - }) - default: - switch cv := cv.(type) { - case gqt.ValueArray: - (*rm).rules[pathHash] = mergeVariants((*rm).rules[pathHash], Variant{ - Condition: cond, - Constraint: cid, - Mask: mask, - Value: buildRulesMapConstraintsArray(cv.Items), - Combinations: c, - }) - case gqt.EnumValue: - (*rm).rules[pathHash] = mergeVariants((*rm).rules[pathHash], Variant{ - Condition: cond, - Constraint: cid, - Mask: mask, - Value: []byte(cv), - Combinations: c, - }) - default: - (*rm).rules[pathHash] = mergeVariants((*rm).rules[pathHash], Variant{ - Condition: cond, - Constraint: cid, - Mask: mask, - Value: cv, - Combinations: c, - }) - } - } - } - } - - return leafs, nil -} - -func buildRulesMapConstraintsElem(constraint gqt.Constraint) (el Elem) { - var cv any - var cid Constraint - - cid, cv = ConstraintIdAndValue(constraint) - - switch cid { - case ConstraintMap: - el = Elem{ - Constraint: cid, - Value: buildRulesMapConstraintsElem(cv), - } - case ConstraintOr, ConstraintAnd: - el = Elem{ - Constraint: cid, - Value: buildRulesMapConstraintsArray(cv.([]gqt.Constraint)), - } - default: - switch cv := cv.(type) { - case gqt.ValueObject: - el = Elem{ - Constraint: cid, - Value: buildRulesMapConstraintsObject(cv.Fields), - } - case gqt.ValueArray: - el = Elem{ - Constraint: cid, - Value: buildRulesMapConstraintsArray(cv.Items), - } - case gqt.EnumValue: - el = Elem{ - Constraint: cid, - Value: []byte(cv), - } - default: - el = Elem{ - Constraint: cid, - Value: cv, - } - } - } - - return -} - -func buildRulesMapConstraintsArray( - constraints []gqt.Constraint, -) (arr Array) { - for _, constraint := range constraints { - arr = append(arr, buildRulesMapConstraintsElem(constraint)) - } - - return -} - -func buildRulesMapConstraintsObject( - constraints []gqt.ObjectField, -) (obj Object) { - obj = Object{} - for _, constraint := range constraints { - obj[constraint.Name] = buildRulesMapConstraintsElem(constraint.Value) - } - - return -} - -func mergeVariants(variants []Variant, x Variant) []Variant { - merge := func(i int) { - variants[i].Mask = variants[i].Mask.Or(x.Mask) - variants[i].Combinations = append(variants[i].Combinations, x.Combinations...) - } - - for i := 0; i < len(variants); i++ { - if variants[i].Constraint == x.Constraint { - switch vt := variants[i].Value.(type) { - case Elem: - switch xt := x.Value.(type) { - case Elem: - if vt.Equal(xt) { - merge(i) - return variants - } - } - case Array: - switch xt := x.Value.(type) { - case Array: - if vt.Equal(xt) { - merge(i) - return variants - } - } - default: - if xb, ok := x.Value.([]byte); ok { - if bytes.Equal(variants[i].Value.([]byte), xb) { - merge(i) - return variants - } - } else if variants[i].Value == x.Value { - merge(i) - return variants - } - } - } - } - variants = append(variants, x) - - return variants -} - -// ConstraintIdAndValue returns constraint Id and Value. -func ConstraintIdAndValue(c gqt.Constraint) (Constraint, any) { - switch c := c.(type) { - case gqt.ConstraintOr: - return ConstraintOr, c.Constraints - case gqt.ConstraintAnd: - return ConstraintAnd, c.Constraints - case gqt.ConstraintMap: - return ConstraintMap, c.Constraint - case gqt.ConstraintAny: - return ConstraintAny, nil - case gqt.ConstraintValEqual: - if s, ok := c.Value.(string); ok { - return ConstraintValEqual, []byte(s) - } - return ConstraintValEqual, c.Value - case gqt.ConstraintValNotEqual: - if s, ok := c.Value.(string); ok { - return ConstraintValNotEqual, []byte(s) - } - return ConstraintValNotEqual, c.Value - case gqt.ConstraintValGreater: - return ConstraintValGreater, c.Value - case gqt.ConstraintValLess: - return ConstraintValLess, c.Value - case gqt.ConstraintValGreaterOrEqual: - return ConstraintValGreaterOrEqual, c.Value - case gqt.ConstraintValLessOrEqual: - return ConstraintValLessOrEqual, c.Value - case gqt.ConstraintBytelenEqual: - return ConstraintBytelenEqual, c.Value - case gqt.ConstraintBytelenNotEqual: - return ConstraintBytelenNotEqual, c.Value - case gqt.ConstraintBytelenGreater: - return ConstraintBytelenGreater, c.Value - case gqt.ConstraintBytelenLess: - return ConstraintBytelenLess, c.Value - case gqt.ConstraintBytelenGreaterOrEqual: - return ConstraintBytelenGreaterOrEqual, c.Value - case gqt.ConstraintBytelenLessOrEqual: - return ConstraintBytelenLessOrEqual, c.Value - case gqt.ConstraintLenEqual: - return ConstraintLenEqual, c.Value - case gqt.ConstraintLenNotEqual: - return ConstraintLenNotEqual, c.Value - case gqt.ConstraintLenGreater: - return ConstraintLenGreater, c.Value - case gqt.ConstraintLenLess: - return ConstraintLenLess, c.Value - case gqt.ConstraintLenGreaterOrEqual: - return ConstraintLenGreaterOrEqual, c.Value - case gqt.ConstraintLenLessOrEqual: - return ConstraintLenLessOrEqual, c.Value - default: - return ConstraintUnknown, nil - } -} - -// Match returns the ID of the first matching template or "" if none was matched. -func (rm *RulesMap) Match( - variableValues [][]gqlparse.Token, - queryType gqlscan.Token, - selectionSet []gqlparse.Token, -) (id string) { - rm.FindMatch(variableValues, queryType, selectionSet, func(mask *bitmask.Set) { - if mask.Size() > 0 { - mask.Visit(func(n int) (skip bool) { - id = rm.templateIDs[n] - return true - }) - } - }) - return id -} - -// MatchAll calls fn for every matching template. -func (rm *RulesMap) MatchAll( - variableValues [][]gqlparse.Token, - queryType gqlscan.Token, - selectionSet []gqlparse.Token, - fn func(id string), -) { - rm.FindMatch(variableValues, queryType, selectionSet, func(mask *bitmask.Set) { - mask.VisitAll(func(n int) { - fn(rm.templateIDs[n]) - }) - }) -} - -// FindMatch matches query to the rules. -func (rm *RulesMap) FindMatch( - variableValues [][]gqlparse.Token, - queryType gqlscan.Token, - selectionSet []gqlparse.Token, - fn func(mask *bitmask.Set), -) { - var qpCount int - rm.matchCounter.Reset() - rm.mask.Reset() - rm.rejected.Reset() - memset(rm.combinationCounters, 0) - rm.qmake.ParseQuery(variableValues, queryType, selectionSet, func(qp pquery.QueryPart) (stop bool) { - qpCount++ - if rn, ok := rm.rules[qp.Hash]; ok { - if len(rn) > 0 { - var match bool - for _, v := range rn { - if len(v.Combinations) > 0 { - if qp.ArgLeafIdx < 1 { - var depth int - for _, c := range v.Combinations { - if rm.combinationCounters[c.Index] == 0 { - depth = c.Depth - } - for i := c.Index - depth; i <= c.Index; i++ { - rm.combinationCounters[i]++ - if rm.combinations[i] < rm.combinationCounters[i] { - rm.rejected.Add(c.RuleIndex) - } - } - } - } - } - - if v.Compare(qp.Value) { - match = true - rm.mask.SetOr(rm.mask, v.Mask) - v.Mask.Visit(func(x int) (skip bool) { - rm.matchCounter.SetFn(x, 1, func(value *int) { *value++ }) - return false - }) - } - } - if !match { - rm.mask.Reset() - return true - } - } - } else { - rm.mask.Reset() - return true - } - - return false - }) - for _, el := range rm.matchCounter.A { - if el.Value < qpCount { - rm.rejected.Add(el.Key) - } - } - rm.mask.SetAndNot(rm.mask, rm.rejected) - - if rm.mask.Empty() { - rm.mask.Reset() - } - - fn(rm.mask) -} - -// CompareValues compares two values according to the provided constraint. -func CompareValues(constraint Constraint, a any, b any) bool { - switch constraint { - case None, ConstraintAny: - return true - case ConstraintValEqual: - if b, ok := b.([]byte); ok { - return bytes.Equal(b, a.([]byte)) - } - return b == a - case ConstraintValNotEqual: - if b, ok := b.([]byte); ok { - return !bytes.Equal(b, a.([]byte)) - } - return b != a - case ConstraintValGreater, ConstraintValLess, - ConstraintValGreaterOrEqual, ConstraintValLessOrEqual: - switch vala := a.(type) { - case int64: - valb, ok := b.(int64) - if !ok { - return false - } - switch constraint { - case ConstraintValGreater: - return valb > vala - case ConstraintValLess: - return valb < vala - case ConstraintValGreaterOrEqual: - return valb >= vala - case ConstraintValLessOrEqual: - return valb <= vala - } - case float64: - valb, ok := b.(float64) - if !ok { - return false - } - switch constraint { - case ConstraintValGreater: - return valb > vala - case ConstraintValLess: - return valb < vala - case ConstraintValGreaterOrEqual: - return valb >= vala - case ConstraintValLessOrEqual: - return valb <= vala - } - } - case ConstraintBytelenEqual, ConstraintBytelenNotEqual, - ConstraintBytelenGreater, ConstraintBytelenLess, - ConstraintBytelenGreaterOrEqual, ConstraintBytelenLessOrEqual: - vala, ok := a.(uint) - if !ok { - return false - } - valb, ok := b.([]byte) - if !ok { - return false - } - switch constraint { - case ConstraintBytelenEqual: - return len(valb) == int(vala) - case ConstraintBytelenNotEqual: - return len(valb) != int(vala) - case ConstraintBytelenGreater: - return len(valb) > int(vala) - case ConstraintBytelenLess: - return len(valb) < int(vala) - case ConstraintBytelenGreaterOrEqual: - return len(valb) >= int(vala) - case ConstraintBytelenLessOrEqual: - return len(valb) <= int(vala) - } - case ConstraintLenEqual, ConstraintLenNotEqual, - ConstraintLenGreater, ConstraintLenLess, - ConstraintLenGreaterOrEqual, ConstraintLenLessOrEqual: - ca, ok := a.(uint) - if !ok { - return false - } - bi, ok := b.(*[]any) - if !ok { - return false - } - switch constraint { - case ConstraintLenEqual: - return len(*bi) == int(ca) - case ConstraintLenNotEqual: - return len(*bi) != int(ca) - case ConstraintLenGreater: - return len(*bi) > int(ca) - case ConstraintLenLess: - return len(*bi) < int(ca) - case ConstraintLenGreaterOrEqual: - return len(*bi) >= int(ca) - case ConstraintLenLessOrEqual: - return len(*bi) <= int(ca) - } - default: - panic(fmt.Errorf("wrong constraint-type pair; constraint: %d", constraint)) - } - - return true -} - -// Compare checks two Varians for equality. -func (v Variant) Compare(x any) bool { - switch v.Constraint { - case ConstraintMap: - switch xt := x.(type) { - case *[]any: - for _, el := range *xt { - switch vt := v.Value.(type) { - case Elem: - if !vt.Compare(el) { - return false - } - } - } - return true - } - case ConstraintOr: - for _, el := range v.Value.(Array) { - if el.Compare(x) { - return true - } - } - return false - case ConstraintAnd: - for _, el := range v.Value.(Array) { - if !el.Compare(x) { - return false - } - } - return true - default: - neq := v.Constraint == ConstraintValNotEqual - switch vt := v.Value.(type) { - case Array: - switch xt := x.(type) { - case *[]any: - return vt.Compare(*xt) != neq - } - default: - return CompareValues(v.Constraint, v.Value, x) - } - } - - return false -} - -// Compare checks two Elems for equality. -func (e Elem) Compare(x any) bool { - switch e.Constraint { - case ConstraintMap: - switch xt := x.(type) { - case *[]any: - for _, el := range *xt { - switch et := e.Value.(type) { - case Elem: - return et.Compare(el) - } - } - } - case ConstraintOr: - for _, el := range e.Value.(Array) { - if el.Compare(x) { - return true - } - } - return false - case ConstraintAnd: - for _, el := range e.Value.(Array) { - if !el.Compare(x) { - return false - } - } - return true - default: - neq := e.Constraint == ConstraintValNotEqual - switch et := e.Value.(type) { - case Array: - switch xt := x.(type) { - case *[]any: - return et.Compare(*xt) != neq - } - case Object: - switch xt := x.(type) { - case *hamap.Map[string, any]: - return et.Compare(xt) != neq - } - default: - return CompareValues(e.Constraint, e.Value, x) - } - } - - return false -} - -// Compare checks two Arrays for equality. -func (arr Array) Compare(x []any) bool { - if len(arr) != len(x) { - return false - } - for i := 0; i < len(x); i++ { - if !arr[i].Compare(x[i]) { - return false - } - } - - return true -} - -// Compare checks two Objects for equality. -func (obj Object) Compare(x *hamap.Map[string, any]) (eq bool) { - var v Elem - eq = true - if len(obj) != x.Len() { - return false - } - x.Visit(func(key string, value any) (stop bool) { - v, eq = obj[key] - if !eq { - return true - } - eq = v.Compare(value) - return !eq - }) - - return -} - -// PrintNSpaces prints n spaces in a row. -func PrintNSpaces(w io.Writer, n uint) { - for i := uint(0); i < n; i++ { - _, _ = w.Write([]byte(" ")) - } -} - -// Print prints out the RulesMap object. -func (rm *RulesMap) Print(w io.Writer) { - rm.print(w, 0) -} - -func (rm *RulesMap) print(w io.Writer, indent uint) { - for hash, rn := range rm.rules { - fmt.Fprintf(w, "%d", hash) - _, _ = w.Write([]byte(":")) - if len(rn) > 0 { - _, _ = w.Write([]byte("\n")) - for _, v := range rn { - v.print(w, indent+4) - } - } - } -} - -func (v *Variant) print(w io.Writer, indent uint) { - PrintNSpaces(w, indent) - _, _ = w.Write(append([]byte(ConstraintLookup[v.Constraint]), []byte(": ")...)) - v.Mask.Visit(func(x int) (skip bool) { - fmt.Fprintf(w, "%d", x) - return false - }) - _, _ = w.Write([]byte("\n")) - if v.Value != nil { - switch v := v.Value.(type) { - case Elem: - v.print(w, indent+2) - case Array: - v.print(w, indent+2) - default: - PrintNSpaces(w, indent+2) - if s, ok := v.([]byte); ok { - fmt.Fprintln(w, string(s)) - } else { - fmt.Fprintln(w, v) - } - } - } -} - -func (e *Elem) print(w io.Writer, indent uint) { - PrintNSpaces(w, indent) - _, _ = w.Write(append([]byte(ConstraintLookup[e.Constraint]), []byte(":\n")...)) - switch v := e.Value.(type) { - case Elem: - v.print(w, indent+2) - case Array: - v.print(w, indent+2) - case Object: - v.print(w, indent+2) - default: - PrintNSpaces(w, indent+2) - if s, ok := v.([]byte); ok { - fmt.Fprintln(w, string(s)) - } else { - fmt.Fprintln(w, v) - } - } -} - -func (arr *Array) print(w io.Writer, indent uint) { - for _, el := range *arr { - PrintNSpaces(w, indent) - _, _ = w.Write([]byte("-:\n")) - PrintNSpaces(w, indent+2) - _, _ = w.Write(append([]byte(ConstraintLookup[el.Constraint]), []byte(":\n")...)) - switch v := el.Value.(type) { - case Elem: - v.print(w, indent+4) - case Array: - v.print(w, indent+4) - case Object: - v.print(w, indent+4) - default: - PrintNSpaces(w, indent+4) - if s, ok := v.([]byte); ok { - fmt.Fprintln(w, string(s)) - } else { - fmt.Fprintln(w, v) - } - } - } -} - -func (obj *Object) print(w io.Writer, indent uint) { - for k, el := range *obj { - PrintNSpaces(w, indent) - _, _ = w.Write(append([]byte(k), []byte(":\n")...)) - PrintNSpaces(w, indent+2) - _, _ = w.Write(append([]byte(ConstraintLookup[el.Constraint]), []byte(":\n")...)) - switch v := el.Value.(type) { - case Elem: - v.print(w, indent+4) - case Array: - v.print(w, indent+4) - case Object: - v.print(w, indent+4) - default: - PrintNSpaces(w, indent+4) - if s, ok := v.([]byte); ok { - fmt.Fprintln(w, string(s)) - } else { - fmt.Fprintln(w, v) - } - } - } -} - -func memset[T comparable](a []T, v T) { - if len(a) == 0 { - return - } - a[0] = v - for i := 1; i < len(a); i *= 2 { - copy(a[i:], a[:i]) - } -} - -// Constraint is a constraint simplified abstraction. -type Constraint uint16 - -const ( - None Constraint = iota - ConstraintUnknown - ConstraintOr - ConstraintAnd - ConstraintAny - ConstraintMap - ConstraintTypeEqual - ConstraintTypeNotEqual - ConstraintValEqual - ConstraintValNotEqual - ConstraintValGreater - ConstraintValLess - ConstraintValGreaterOrEqual - ConstraintValLessOrEqual - ConstraintBytelenEqual - ConstraintBytelenNotEqual - ConstraintBytelenGreater - ConstraintBytelenLess - ConstraintBytelenGreaterOrEqual - ConstraintBytelenLessOrEqual - ConstraintLenEqual - ConstraintLenNotEqual - ConstraintLenGreater - ConstraintLenLess - ConstraintLenGreaterOrEqual - ConstraintLenLessOrEqual -) - -var ConstraintLookup = map[Constraint]string{ - None: "NoConstraint", - ConstraintOr: "ConstraintOr", - ConstraintAnd: "ConstraintAnd", - ConstraintAny: "ConstraintAny", - ConstraintMap: "ConstraintMap", - ConstraintTypeEqual: "ConstraintTypeEqual", - ConstraintTypeNotEqual: "ConstraintTypeNotEqual", - ConstraintValEqual: "ConstraintValEqual", - ConstraintValNotEqual: "ConstraintValNotEqual", - ConstraintValGreater: "ConstraintValGreater", - ConstraintValLess: "ConstraintValLess", - ConstraintValGreaterOrEqual: "ConstraintValGreaterOrEqual", - ConstraintValLessOrEqual: "ConstraintValLessOrEqual", - ConstraintBytelenEqual: "ConstraintBytelenEqual", - ConstraintBytelenNotEqual: "ConstraintBytelenNotEqual", - ConstraintBytelenGreater: "ConstraintBytelenGreater", - ConstraintBytelenLess: "ConstraintBytelenLess", - ConstraintBytelenGreaterOrEqual: "ConstraintBytelenGreaterOrEqual", - ConstraintBytelenLessOrEqual: "ConstraintBytelenLessOrEqual", - ConstraintLenEqual: "ConstraintLenEqual", - ConstraintLenNotEqual: "ConstraintLenNotEqual", - ConstraintLenGreater: "ConstraintLenGreater", - ConstraintLenLess: "ConstraintLenLess", - ConstraintLenGreaterOrEqual: "ConstraintLenGreaterOrEqual", - ConstraintLenLessOrEqual: "ConstraintLenLessOrEqual", -} diff --git a/engines/tests/benchmark_test.go b/engines/tests/benchmark_test.go index 7aa7233..cab2c07 100644 --- a/engines/tests/benchmark_test.go +++ b/engines/tests/benchmark_test.go @@ -1,58 +1,60 @@ package engine_test -import ( - "embed" - _ "embed" - "fmt" - "testing" - - "github.com/graph-guard/ggproxy/engines/rmap" - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/gqt" -) - -var N int - -//go:embed assets/benchassets -var benchassets embed.FS - -var GS string - -func BenchmarkPartedQuery(b *testing.B) { - templates := readTestAssets(benchassets, "assets/benchassets", "templates")[0].Templates - rules := make(map[string]gqt.Doc, len(templates)) - for _, r := range templates { - rules[r.ID] = r.Document - } - rm, _ := rmap.New(rules, 0) - - for _, td := range readTestAssets(benchassets, "assets/benchassets", "bench_") { - b.Run(td.ID, func(b *testing.B) { - p := gqlparse.NewParser() - query := []byte(td.Query) - operationName := []byte(td.OperationName) - variables := []byte(td.Variables) - b.ResetTimer() - - for n := 0; n < b.N; n++ { - p.Parse( - query, operationName, variables, - func( - varVals [][]gqlparse.Token, - operation []gqlparse.Token, - selectionSet []gqlparse.Token, - ) { - rm.MatchAll( - varVals, - operation[0].ID, - selectionSet, - func(id string) { GS = id }, - ) - }, func(err error) { - panic(fmt.Errorf("unexpected error: %w", err)) - }, - ) - } - }) - } -} +// TODO: either reimplement the benchmark or make it playmon compatible. + +// import ( +// "embed" +// _ "embed" +// "fmt" +// "testing" + +// "github.com/graph-guard/ggproxy/engines/rmap" +// "github.com/graph-guard/ggproxy/gqlparse" +// "github.com/graph-guard/gqt" +// ) + +// var N int + +// //go:embed assets/benchassets +// var benchassets embed.FS + +// var GS string + +// func BenchmarkPartedQuery(b *testing.B) { +// templates := readTestAssets(benchassets, "assets/benchassets", "templates")[0].Templates +// rules := make(map[string]gqt.Doc, len(templates)) +// for _, r := range templates { +// rules[r.ID] = r.Document +// } +// rm, _ := rmap.New(rules, 0) + +// for _, td := range readTestAssets(benchassets, "assets/benchassets", "bench_") { +// b.Run(td.ID, func(b *testing.B) { +// p := gqlparse.NewParser() +// query := []byte(td.Query) +// operationName := []byte(td.OperationName) +// variables := []byte(td.Variables) +// b.ResetTimer() + +// for n := 0; n < b.N; n++ { +// p.Parse( +// query, operationName, variables, +// func( +// varVals [][]gqlparse.Token, +// operation []gqlparse.Token, +// selectionSet []gqlparse.Token, +// ) { +// rm.MatchAll( +// varVals, +// operation[0].ID, +// selectionSet, +// func(id string) { GS = id }, +// ) +// }, func(err error) { +// panic(fmt.Errorf("unexpected error: %w", err)) +// }, +// ) +// } +// }) +// } +// } diff --git a/engines/tests/engine_test.go b/engines/tests/engine_test.go index 16b35cd..04b034c 100644 --- a/engines/tests/engine_test.go +++ b/engines/tests/engine_test.go @@ -1,729 +1,729 @@ package engine_test -import ( - "bytes" - "embed" - _ "embed" - "fmt" - "io" - "io/fs" - "path/filepath" - "strings" - "testing" +// TODO: either reimplement the tests or make them playmon compatible. - "github.com/graph-guard/ggproxy/config" - "github.com/graph-guard/ggproxy/config/metadata" - "github.com/graph-guard/ggproxy/engines/rmap" - "github.com/graph-guard/ggproxy/engines/rmap/pquery" - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/ggproxy/utilities/container/hamap" - "github.com/graph-guard/ggproxy/utilities/xxhash" - "github.com/graph-guard/gqt" - "github.com/stretchr/testify/require" - "gopkg.in/yaml.v3" -) +// import ( +// "bytes" +// "embed" +// _ "embed" +// "fmt" +// "io" +// "io/fs" +// "path/filepath" +// "strings" +// "testing" -func TestConstraintIdAndValue(t *testing.T) { - for _, td := range []struct { - input gqt.Constraint - id rmap.Constraint - value any - err error - }{ - { - input: gqt.ConstraintMap{ - Constraint: new(gqt.Constraint), - }, - id: rmap.ConstraintMap, - value: new(gqt.Constraint), - }, - { - input: gqt.ConstraintAny{}, - id: rmap.ConstraintAny, - value: nil, - }, - { - input: gqt.ConstraintValEqual{ - Value: gqt.ValueObject{ - Fields: []gqt.ObjectField{ - { - Name: "a", - Value: gqt.ConstraintValLessOrEqual{ - Value: 42.0, - }, - }, - }, - }, - }, - id: rmap.ConstraintValEqual, - value: gqt.ValueObject{ - Fields: []gqt.ObjectField{ - { - Name: "a", - Value: gqt.ConstraintValLessOrEqual{ - Value: 42.0, - }, - }, - }, - }, - }, - { - input: gqt.ConstraintValGreater{ - Value: 42.0, - }, - id: rmap.ConstraintValGreater, - value: 42.0, - }, - { - input: gqt.ConstraintValLess{ - Value: 42.0, - }, - id: rmap.ConstraintValLess, - value: 42.0, - }, - { - input: gqt.ConstraintValGreaterOrEqual{ - Value: 69.0, - }, - id: rmap.ConstraintValGreaterOrEqual, - value: 69.0, - }, - { - input: gqt.ConstraintValLessOrEqual{ - Value: 69.0, - }, - id: rmap.ConstraintValLessOrEqual, - value: 69.0, - }, - { - input: gqt.ConstraintBytelenEqual{ - Value: 1984, - }, - id: rmap.ConstraintBytelenEqual, - value: uint(1984), - }, - { - input: gqt.ConstraintBytelenNotEqual{ - Value: 1984, - }, - id: rmap.ConstraintBytelenNotEqual, - value: uint(1984), - }, - { - input: gqt.ConstraintBytelenGreater{ - Value: 282, - }, - id: rmap.ConstraintBytelenGreater, - value: uint(282), - }, - { - input: gqt.ConstraintBytelenLess{ - Value: 282, - }, - id: rmap.ConstraintBytelenLess, - value: uint(282), - }, - { - input: gqt.ConstraintBytelenGreaterOrEqual{ - Value: 27015, - }, - id: rmap.ConstraintBytelenGreaterOrEqual, - value: uint(27015), - }, - { - input: gqt.ConstraintBytelenLessOrEqual{ - Value: 27015, - }, - id: rmap.ConstraintBytelenLessOrEqual, - value: uint(27015), - }, - { - input: gqt.ConstraintLenEqual{ - Value: 997, - }, - id: rmap.ConstraintLenEqual, - value: uint(997), - }, - { - input: gqt.ConstraintLenNotEqual{ - Value: 997, - }, - id: rmap.ConstraintLenNotEqual, - value: uint(997), - }, - { - input: gqt.ConstraintLenGreater{ - Value: 47, - }, - id: rmap.ConstraintLenGreater, - value: uint(47), - }, - { - input: gqt.ConstraintLenLess{ - Value: 47, - }, - id: rmap.ConstraintLenLess, - value: uint(47), - }, - { - input: gqt.ConstraintLenGreaterOrEqual{ - Value: 404, - }, - id: rmap.ConstraintLenGreaterOrEqual, - value: uint(404), - }, - { - input: gqt.ConstraintLenLessOrEqual{ - Value: 404, - }, - id: rmap.ConstraintLenLessOrEqual, - value: uint(404), - }, - } { - t.Run("", func(t *testing.T) { - id, value := rmap.ConstraintIdAndValue(td.input) - require.Equal(t, td.id, id) - require.Equal(t, td.value, value) - }) - } -} +// "github.com/graph-guard/ggproxy/config" +// "github.com/graph-guard/ggproxy/config/metadata" +// "github.com/graph-guard/ggproxy/gqlparse" +// "github.com/graph-guard/ggproxy/utilities/container/hamap" +// "github.com/graph-guard/ggproxy/utilities/xxhash" +// "github.com/graph-guard/gqt" +// "github.com/stretchr/testify/require" +// "gopkg.in/yaml.v3" +// ) -//go:embed assets/testassets -var testassets embed.FS +// func TestConstraintIdAndValue(t *testing.T) { +// for _, td := range []struct { +// input gqt.Constraint +// id rmap.Constraint +// value any +// err error +// }{ +// { +// input: gqt.ConstraintMap{ +// Constraint: new(gqt.Constraint), +// }, +// id: rmap.ConstraintMap, +// value: new(gqt.Constraint), +// }, +// { +// input: gqt.ConstraintAny{}, +// id: rmap.ConstraintAny, +// value: nil, +// }, +// { +// input: gqt.ConstraintValEqual{ +// Value: gqt.ValueObject{ +// Fields: []gqt.ObjectField{ +// { +// Name: "a", +// Value: gqt.ConstraintValLessOrEqual{ +// Value: 42.0, +// }, +// }, +// }, +// }, +// }, +// id: rmap.ConstraintValEqual, +// value: gqt.ValueObject{ +// Fields: []gqt.ObjectField{ +// { +// Name: "a", +// Value: gqt.ConstraintValLessOrEqual{ +// Value: 42.0, +// }, +// }, +// }, +// }, +// }, +// { +// input: gqt.ConstraintValGreater{ +// Value: 42.0, +// }, +// id: rmap.ConstraintValGreater, +// value: 42.0, +// }, +// { +// input: gqt.ConstraintValLess{ +// Value: 42.0, +// }, +// id: rmap.ConstraintValLess, +// value: 42.0, +// }, +// { +// input: gqt.ConstraintValGreaterOrEqual{ +// Value: 69.0, +// }, +// id: rmap.ConstraintValGreaterOrEqual, +// value: 69.0, +// }, +// { +// input: gqt.ConstraintValLessOrEqual{ +// Value: 69.0, +// }, +// id: rmap.ConstraintValLessOrEqual, +// value: 69.0, +// }, +// { +// input: gqt.ConstraintBytelenEqual{ +// Value: 1984, +// }, +// id: rmap.ConstraintBytelenEqual, +// value: uint(1984), +// }, +// { +// input: gqt.ConstraintBytelenNotEqual{ +// Value: 1984, +// }, +// id: rmap.ConstraintBytelenNotEqual, +// value: uint(1984), +// }, +// { +// input: gqt.ConstraintBytelenGreater{ +// Value: 282, +// }, +// id: rmap.ConstraintBytelenGreater, +// value: uint(282), +// }, +// { +// input: gqt.ConstraintBytelenLess{ +// Value: 282, +// }, +// id: rmap.ConstraintBytelenLess, +// value: uint(282), +// }, +// { +// input: gqt.ConstraintBytelenGreaterOrEqual{ +// Value: 27015, +// }, +// id: rmap.ConstraintBytelenGreaterOrEqual, +// value: uint(27015), +// }, +// { +// input: gqt.ConstraintBytelenLessOrEqual{ +// Value: 27015, +// }, +// id: rmap.ConstraintBytelenLessOrEqual, +// value: uint(27015), +// }, +// { +// input: gqt.ConstraintLenEqual{ +// Value: 997, +// }, +// id: rmap.ConstraintLenEqual, +// value: uint(997), +// }, +// { +// input: gqt.ConstraintLenNotEqual{ +// Value: 997, +// }, +// id: rmap.ConstraintLenNotEqual, +// value: uint(997), +// }, +// { +// input: gqt.ConstraintLenGreater{ +// Value: 47, +// }, +// id: rmap.ConstraintLenGreater, +// value: uint(47), +// }, +// { +// input: gqt.ConstraintLenLess{ +// Value: 47, +// }, +// id: rmap.ConstraintLenLess, +// value: uint(47), +// }, +// { +// input: gqt.ConstraintLenGreaterOrEqual{ +// Value: 404, +// }, +// id: rmap.ConstraintLenGreaterOrEqual, +// value: uint(404), +// }, +// { +// input: gqt.ConstraintLenLessOrEqual{ +// Value: 404, +// }, +// id: rmap.ConstraintLenLessOrEqual, +// value: uint(404), +// }, +// } { +// t.Run("", func(t *testing.T) { +// id, value := rmap.ConstraintIdAndValue(td.input) +// require.Equal(t, td.id, id) +// require.Equal(t, td.value, value) +// }) +// } +// } -type QueryModel struct { - Query string `yaml:"query"` - OperationName string `yaml:"operationName"` - Variables string `yaml:"variables"` - Expect []string `yaml:"expect"` -} +// //go:embed assets/testassets +// var testassets embed.FS -type MatchTest struct { - ID string - *QueryModel - Templates []*config.Template -} +// type QueryModel struct { +// Query string `yaml:"query"` +// OperationName string `yaml:"operationName"` +// Variables string `yaml:"variables"` +// Expect []string `yaml:"expect"` +// } -func readTestAsset( - filesystem fs.FS, path string, -) ( - query *QueryModel, templates []*config.Template, -) { - test, err := fs.ReadDir(filesystem, path) - if err != nil { - panic(err) - } +// type MatchTest struct { +// ID string +// *QueryModel +// Templates []*config.Template +// } - for _, f := range test { - if f.IsDir() { - continue - } - fn := f.Name() - fp := filepath.Join(path, f.Name()) - if strings.HasSuffix(fn, ".gqt") { - id := strings.ToLower(fn[:len(fn)-len(filepath.Ext(fn))]) - src, err := filesystem.Open(fp) - if err != nil { - panic(err) - } - b, err := io.ReadAll(src) - if err != nil { - panic(err) - } +// func readTestAsset( +// filesystem fs.FS, path string, +// ) ( +// query *QueryModel, templates []*config.Template, +// ) { +// test, err := fs.ReadDir(filesystem, path) +// if err != nil { +// panic(err) +// } - meta, template, err := metadata.Parse(b) - if err != nil { - panic(err) - } - doc, errParser := gqt.Parse(template) - if errParser.IsErr() { - panic(errParser) - } +// for _, f := range test { +// if f.IsDir() { +// continue +// } +// fn := f.Name() +// fp := filepath.Join(path, f.Name()) +// if strings.HasSuffix(fn, ".gqt") { +// id := strings.ToLower(fn[:len(fn)-len(filepath.Ext(fn))]) +// src, err := filesystem.Open(fp) +// if err != nil { +// panic(err) +// } +// b, err := io.ReadAll(src) +// if err != nil { +// panic(err) +// } - templates = append(templates, &config.Template{ - ID: id, - Source: template, - Document: doc, - Name: meta.Name, - Tags: meta.Tags, - }) - } - if strings.HasSuffix(fn, ".yml") || strings.HasSuffix(fn, ".yaml") { - src, err := filesystem.Open(fp) - if err != nil { - panic(err) - } - d := yaml.NewDecoder(src) - d.KnownFields(true) - err = d.Decode(&query) - if err != nil { - panic(err) - } - } - } +// meta, template, err := metadata.Parse(b) +// if err != nil { +// panic(err) +// } +// doc, errParser := gqt.Parse(template) +// if errParser.IsErr() { +// panic(errParser) +// } - return -} +// templates = append(templates, &config.Template{ +// ID: id, +// Source: template, +// Document: doc, +// Name: meta.Name, +// Tags: meta.Tags, +// }) +// } +// if strings.HasSuffix(fn, ".yml") || strings.HasSuffix(fn, ".yaml") { +// src, err := filesystem.Open(fp) +// if err != nil { +// panic(err) +// } +// d := yaml.NewDecoder(src) +// d.KnownFields(true) +// err = d.Decode(&query) +// if err != nil { +// panic(err) +// } +// } +// } -func readTestAssets(filesystem fs.FS, path, prefix string) (assets []*MatchTest) { - root, err := fs.ReadDir(filesystem, path) - if err != nil { - panic(err) - } - for _, testDir := range root { - if !testDir.IsDir() { - continue - } - testDirName := testDir.Name() - testDirPath := filepath.Join(path, testDirName) - if !strings.HasPrefix(testDirName, prefix) { - continue - } +// return +// } - query, templates := readTestAsset(filesystem, testDirPath) - assets = append(assets, &MatchTest{ - ID: testDirName, - QueryModel: query, - Templates: templates, - }) - } +// func readTestAssets(filesystem fs.FS, path, prefix string) (assets []*MatchTest) { +// root, err := fs.ReadDir(filesystem, path) +// if err != nil { +// panic(err) +// } +// for _, testDir := range root { +// if !testDir.IsDir() { +// continue +// } +// testDirName := testDir.Name() +// testDirPath := filepath.Join(path, testDirName) +// if !strings.HasPrefix(testDirName, prefix) { +// continue +// } - return -} +// query, templates := readTestAsset(filesystem, testDirPath) +// assets = append(assets, &MatchTest{ +// ID: testDirName, +// QueryModel: query, +// Templates: templates, +// }) +// } -func TestMatchAllPartedQuery(t *testing.T) { - for _, td := range readTestAssets(testassets, "assets/testassets", "test_") { - t.Run(td.ID, func(t *testing.T) { - rules := make(map[string]gqt.Doc, len(td.Templates)) - for _, r := range td.Templates { - rules[r.ID] = r.Document - } +// return +// } - p := gqlparse.NewParser() - rm, _ := rmap.New(rules, 0) +// func TestMatchAllPartedQuery(t *testing.T) { +// for _, td := range readTestAssets(testassets, "assets/testassets", "test_") { +// t.Run(td.ID, func(t *testing.T) { +// rules := make(map[string]gqt.Doc, len(td.Templates)) +// for _, r := range td.Templates { +// rules[r.ID] = r.Document +// } - p.Parse( - []byte(td.Query), - []byte(td.OperationName), - []byte(td.Variables), - func( - varVals [][]gqlparse.Token, - operation []gqlparse.Token, - selectionSet []gqlparse.Token, - ) { - actual := []string{} - rm.MatchAll( - varVals, - operation[0].ID, - selectionSet, - func(id string) { - actual = append(actual, id) - }, - ) - require.Len(t, actual, len(td.Expect)) - for _, e := range td.Expect { - require.Contains(t, actual, e) - } - }, - func(err error) { - t.Fatalf("unexpected error: %v", err) - }, - ) - }) - } -} +// p := gqlparse.NewParser() +// rm, _ := rmap.New(rules, 0) -func TestPrintPartedQuery(t *testing.T) { - for _, td := range []struct { - template string - expect string - }{ - { - template: ` - query { - a( - a_0: val = 0 - ) - } - `, - expect: fmt.Sprintf(`%d: - ConstraintValEqual: 0 - 0 -`, Hash("query.a.a_0")), - }, - { - template: ` - query { - a( - a_0: val = "a" - ) - } - `, - expect: fmt.Sprintf(`%d: - ConstraintValEqual: 0 - a -`, Hash("query.a.a_0")), - }, - { - template: ` - query { - a( - a_0: val = { - a_00: val = [val = 1, val = 2] - } - ) - } - `, - expect: fmt.Sprintf(`%d: - ConstraintValEqual: 0 - -: - ConstraintValEqual: - 1 - -: - ConstraintValEqual: - 2 -`, Hash("query.a.a_0.a_00")), - }, - { - template: ` - query { - a( - a_0: val = [ ... val = [ ... val <= 0 ] ] - ) - } - `, - expect: fmt.Sprintf(`%d: - ConstraintMap: 0 - ConstraintMap: - ConstraintValLessOrEqual: - 0 -`, Hash("query.a.a_0")), - }, - { - template: ` - query { - a( - a_0: val = [ - val = { - a_000: val > 5 - } - val = { - a_010: val = [val = 0, val = 1] - } - ] - ) - } - `, - expect: fmt.Sprintf(`%d: - ConstraintValEqual: 0 - -: - ConstraintValEqual: - a_000: - ConstraintValGreater: - 5 - -: - ConstraintValEqual: - a_010: - ConstraintValEqual: - -: - ConstraintValEqual: - 0 - -: - ConstraintValEqual: - 1 -`, Hash("query.a.a_0")), - }, - } { - t.Run("", func(t *testing.T) { - b := new(bytes.Buffer) +// p.Parse( +// []byte(td.Query), +// []byte(td.OperationName), +// []byte(td.Variables), +// func( +// varVals [][]gqlparse.Token, +// operation []gqlparse.Token, +// selectionSet []gqlparse.Token, +// ) { +// actual := []string{} +// rm.MatchAll( +// varVals, +// operation[0].ID, +// selectionSet, +// func(id string) { +// actual = append(actual, id) +// }, +// ) +// require.Len(t, actual, len(td.Expect)) +// for _, e := range td.Expect { +// require.Contains(t, actual, e) +// } +// }, +// func(err error) { +// t.Fatalf("unexpected error: %v", err) +// }, +// ) +// }) +// } +// } - rd, err := gqt.Parse([]byte(td.template)) - require.False(t, err.IsErr()) - rm, _ := rmap.New(map[string]gqt.Doc{ - "rd": rd, - }, 0) - rm.Print(b) +// func TestPrintPartedQuery(t *testing.T) { +// for _, td := range []struct { +// template string +// expect string +// }{ +// { +// template: ` +// query { +// a( +// a_0: val = 0 +// ) +// } +// `, +// expect: fmt.Sprintf(`%d: +// ConstraintValEqual: 0 +// 0 +// `, Hash("query.a.a_0")), +// }, +// { +// template: ` +// query { +// a( +// a_0: val = "a" +// ) +// } +// `, +// expect: fmt.Sprintf(`%d: +// ConstraintValEqual: 0 +// a +// `, Hash("query.a.a_0")), +// }, +// { +// template: ` +// query { +// a( +// a_0: val = { +// a_00: val = [val = 1, val = 2] +// } +// ) +// } +// `, +// expect: fmt.Sprintf(`%d: +// ConstraintValEqual: 0 +// -: +// ConstraintValEqual: +// 1 +// -: +// ConstraintValEqual: +// 2 +// `, Hash("query.a.a_0.a_00")), +// }, +// { +// template: ` +// query { +// a( +// a_0: val = [ ... val = [ ... val <= 0 ] ] +// ) +// } +// `, +// expect: fmt.Sprintf(`%d: +// ConstraintMap: 0 +// ConstraintMap: +// ConstraintValLessOrEqual: +// 0 +// `, Hash("query.a.a_0")), +// }, +// { +// template: ` +// query { +// a( +// a_0: val = [ +// val = { +// a_000: val > 5 +// } +// val = { +// a_010: val = [val = 0, val = 1] +// } +// ] +// ) +// } +// `, +// expect: fmt.Sprintf(`%d: +// ConstraintValEqual: 0 +// -: +// ConstraintValEqual: +// a_000: +// ConstraintValGreater: +// 5 +// -: +// ConstraintValEqual: +// a_010: +// ConstraintValEqual: +// -: +// ConstraintValEqual: +// 0 +// -: +// ConstraintValEqual: +// 1 +// `, Hash("query.a.a_0")), +// }, +// } { +// t.Run("", func(t *testing.T) { +// b := new(bytes.Buffer) - require.Equal(t, td.expect, b.String()) - }) - } -} +// rd, err := gqt.Parse([]byte(td.template)) +// require.False(t, err.IsErr()) +// rm, _ := rmap.New(map[string]gqt.Doc{ +// "rd": rd, +// }, 0) +// rm.Print(b) -func Hash(s string) uint64 { - h := xxhash.New(0) - xxhash.Write(&h, s) - return h.Sum64() -} +// require.Equal(t, td.expect, b.String()) +// }) +// } +// } -func TestNewQueryPart(t *testing.T) { - for _, td := range []struct { - query string - operationName string - variablesJSON string - expect []pquery.QueryPart - }{ - { - operationName: "X", - query: ` - query X { - a { - a0( - a0_0: { - a0_00: 1.0 - } - a0_1: "no" - ) { - a00 - } - } - b( - b_0: { - b_00: "go" - } - b_1: [0.0, 1.0] - ) { - b0 - } - c( - c_0: [ - { - c_000: ["hohoho"] - } - ] - c_1: [ - [ - { - c_1000: -1.0 - c_1001: [1.0, 0.0] - } - ] - [ - { - c_1100: "hawk" - } - { - c_1110: "falcon" - } - ] - ] - ) { - c0( - c0_0: 0.0 - ) { - c00 - } - } - } - `, - expect: []pquery.QueryPart{ - {ArgLeafIdx: 0, Hash: Hash("query.a.a0.a0_0.a0_00"), Value: 1.0}, - {ArgLeafIdx: 1, Hash: Hash("query.a.a0.a0_1"), Value: []byte("no")}, - {ArgLeafIdx: -1, Hash: Hash("query.a.a0.a00"), Value: nil}, - {ArgLeafIdx: 0, Hash: Hash("query.b.b_0.b_00"), Value: []byte("go")}, - {ArgLeafIdx: 1, Hash: Hash("query.b.b_1"), Value: &[]any{0.0, 1.0}}, - {ArgLeafIdx: -1, Hash: Hash("query.b.b0"), Value: nil}, - { - ArgLeafIdx: 0, - Hash: Hash("query.c.c_0"), - Value: &[]any{ - MakeMap( - hamap.Pair[string, any]{ - Key: "c_000", - Value: &[]any{ - []byte("hohoho"), - }, - }, - ), - }, - }, - { - ArgLeafIdx: 1, - Hash: Hash("query.c.c_1"), - Value: &[]any{ - &[]any{ - MakeMap( - hamap.Pair[string, any]{ - Key: "c_1000", - Value: -1.0, - }, - hamap.Pair[string, any]{ - Key: "c_1001", - Value: &[]any{1.0, 0.0}, - }, - ), - }, - &[]any{ - MakeMap( - hamap.Pair[string, any]{ - Key: "c_1100", - Value: []byte("hawk"), - }, - ), - MakeMap( - hamap.Pair[string, any]{ - Key: "c_1110", - Value: []byte("falcon"), - }, - ), - }, - }, - }, - {ArgLeafIdx: 0, Hash: Hash("query.c.c0.c0_0"), Value: 0.0}, - {ArgLeafIdx: -1, Hash: Hash("query.c.c0.c00"), Value: nil}, - }, - }, - { - operationName: "X", - query: ` - mutation X { - a { - a0 - } - b( - b_0: 0.0 - ) { - b0 - } - } - `, - expect: []pquery.QueryPart{ - {ArgLeafIdx: -1, Hash: Hash("mutation.a.a0"), Value: nil}, - {ArgLeafIdx: 0, Hash: Hash("mutation.b.b_0"), Value: 0.0}, - {ArgLeafIdx: -1, Hash: Hash("mutation.b.b0"), Value: nil}, - }, - }, - } { - t.Run("", func(t *testing.T) { - var i int +// func Hash(s string) uint64 { +// h := xxhash.New(0) +// xxhash.Write(&h, s) +// return h.Sum64() +// } - gqlparse.NewParser().Parse( - []byte(td.query), - []byte(td.operationName), - []byte(td.variablesJSON), - func( - varValues [][]gqlparse.Token, - operation []gqlparse.Token, - selectionSet []gqlparse.Token, - ) { - pquery.NewMaker(0).ParseQuery( - varValues, - operation[0].ID, - selectionSet, - func(qp pquery.QueryPart) (stop bool) { - require.Equal(t, td.expect[i], qp) - i++ - return false - }, - ) - }, - func(err error) { - t.Fatalf("unexpected parser error: %v", err) - }, - ) - }) - } -} +// func TestNewQueryPart(t *testing.T) { +// for _, td := range []struct { +// query string +// operationName string +// variablesJSON string +// expect []pquery.QueryPart +// }{ +// { +// operationName: "X", +// query: ` +// query X { +// a { +// a0( +// a0_0: { +// a0_00: 1.0 +// } +// a0_1: "no" +// ) { +// a00 +// } +// } +// b( +// b_0: { +// b_00: "go" +// } +// b_1: [0.0, 1.0] +// ) { +// b0 +// } +// c( +// c_0: [ +// { +// c_000: ["hohoho"] +// } +// ] +// c_1: [ +// [ +// { +// c_1000: -1.0 +// c_1001: [1.0, 0.0] +// } +// ] +// [ +// { +// c_1100: "hawk" +// } +// { +// c_1110: "falcon" +// } +// ] +// ] +// ) { +// c0( +// c0_0: 0.0 +// ) { +// c00 +// } +// } +// } +// `, +// expect: []pquery.QueryPart{ +// {ArgLeafIdx: 0, Hash: Hash("query.a.a0.a0_0.a0_00"), Value: 1.0}, +// {ArgLeafIdx: 1, Hash: Hash("query.a.a0.a0_1"), Value: []byte("no")}, +// {ArgLeafIdx: -1, Hash: Hash("query.a.a0.a00"), Value: nil}, +// {ArgLeafIdx: 0, Hash: Hash("query.b.b_0.b_00"), Value: []byte("go")}, +// {ArgLeafIdx: 1, Hash: Hash("query.b.b_1"), Value: &[]any{0.0, 1.0}}, +// {ArgLeafIdx: -1, Hash: Hash("query.b.b0"), Value: nil}, +// { +// ArgLeafIdx: 0, +// Hash: Hash("query.c.c_0"), +// Value: &[]any{ +// MakeMap( +// hamap.Pair[string, any]{ +// Key: "c_000", +// Value: &[]any{ +// []byte("hohoho"), +// }, +// }, +// ), +// }, +// }, +// { +// ArgLeafIdx: 1, +// Hash: Hash("query.c.c_1"), +// Value: &[]any{ +// &[]any{ +// MakeMap( +// hamap.Pair[string, any]{ +// Key: "c_1000", +// Value: -1.0, +// }, +// hamap.Pair[string, any]{ +// Key: "c_1001", +// Value: &[]any{1.0, 0.0}, +// }, +// ), +// }, +// &[]any{ +// MakeMap( +// hamap.Pair[string, any]{ +// Key: "c_1100", +// Value: []byte("hawk"), +// }, +// ), +// MakeMap( +// hamap.Pair[string, any]{ +// Key: "c_1110", +// Value: []byte("falcon"), +// }, +// ), +// }, +// }, +// }, +// {ArgLeafIdx: 0, Hash: Hash("query.c.c0.c0_0"), Value: 0.0}, +// {ArgLeafIdx: -1, Hash: Hash("query.c.c0.c00"), Value: nil}, +// }, +// }, +// { +// operationName: "X", +// query: ` +// mutation X { +// a { +// a0 +// } +// b( +// b_0: 0.0 +// ) { +// b0 +// } +// } +// `, +// expect: []pquery.QueryPart{ +// {ArgLeafIdx: -1, Hash: Hash("mutation.a.a0"), Value: nil}, +// {ArgLeafIdx: 0, Hash: Hash("mutation.b.b_0"), Value: 0.0}, +// {ArgLeafIdx: -1, Hash: Hash("mutation.b.b0"), Value: nil}, +// }, +// }, +// } { +// t.Run("", func(t *testing.T) { +// var i int -func TestPrint(t *testing.T) { - for _, td := range []struct { - query string - operationName string - variablesJSON string - expect string - }{ - { - operationName: "X", - query: ` - query X { - a { - a0( - a0_0: { - a0_00: 1 - } - ) - } - } - `, - expect: fmt.Sprintf(`%d: 1 -`, Hash("query.a.a0.a0_0.a0_00")), - }, - { - query: ` - query { - a( - a_0: [ 1, 2 ] - ) - } - `, - expect: fmt.Sprintf(`%d: - -: - 1 - -: - 2 -`, Hash("query.a.a_0")), - }, - { - query: ` - query { - a( - a_0: [ - { - a_000: 5 - } - { - a_010: [ 0, 1 ] - } - ] - ) - } - `, - expect: fmt.Sprintf(`%d: - -: - a_000: - 5 - -: - a_010: - -: - 0 - -: - 1 -`, Hash("query.a.a_0")), - }, - } { - t.Run("", func(t *testing.T) { - gqlparse.NewParser().Parse( - []byte(td.query), - []byte(td.operationName), - []byte(td.variablesJSON), - func( - varValues [][]gqlparse.Token, - operation []gqlparse.Token, - selectionSet []gqlparse.Token, - ) { - b := new(bytes.Buffer) - pquery.NewMaker(0).ParseQuery( - varValues, - operation[0].ID, - selectionSet, - func(qp pquery.QueryPart) (stop bool) { - qp.Print(b) - return false - }, - ) - require.Equal(t, td.expect, b.String()) - }, - func(err error) { - t.Fatalf("unexpected parser error: %v", err) - }, - ) - }) - } -} +// gqlparse.NewParser().Parse( +// []byte(td.query), +// []byte(td.operationName), +// []byte(td.variablesJSON), +// func( +// varValues [][]gqlparse.Token, +// operation []gqlparse.Token, +// selectionSet []gqlparse.Token, +// ) { +// pquery.NewMaker(0).ParseQuery( +// varValues, +// operation[0].ID, +// selectionSet, +// func(qp pquery.QueryPart) (stop bool) { +// require.Equal(t, td.expect[i], qp) +// i++ +// return false +// }, +// ) +// }, +// func(err error) { +// t.Fatalf("unexpected parser error: %v", err) +// }, +// ) +// }) +// } +// } -func MakeMap(items ...hamap.Pair[string, any]) *hamap.Map[string, any] { - m := hamap.New[string, any](len(items), nil) - for i := range items { - m.Set(items[i].Key, items[i].Value) - } - return m -} +// func TestPrint(t *testing.T) { +// for _, td := range []struct { +// query string +// operationName string +// variablesJSON string +// expect string +// }{ +// { +// operationName: "X", +// query: ` +// query X { +// a { +// a0( +// a0_0: { +// a0_00: 1 +// } +// ) +// } +// } +// `, +// expect: fmt.Sprintf(`%d: 1 +// `, Hash("query.a.a0.a0_0.a0_00")), +// }, +// { +// query: ` +// query { +// a( +// a_0: [ 1, 2 ] +// ) +// } +// `, +// expect: fmt.Sprintf(`%d: +// -: +// 1 +// -: +// 2 +// `, Hash("query.a.a_0")), +// }, +// { +// query: ` +// query { +// a( +// a_0: [ +// { +// a_000: 5 +// } +// { +// a_010: [ 0, 1 ] +// } +// ] +// ) +// } +// `, +// expect: fmt.Sprintf(`%d: +// -: +// a_000: +// 5 +// -: +// a_010: +// -: +// 0 +// -: +// 1 +// `, Hash("query.a.a_0")), +// }, +// } { +// t.Run("", func(t *testing.T) { +// gqlparse.NewParser().Parse( +// []byte(td.query), +// []byte(td.operationName), +// []byte(td.variablesJSON), +// func( +// varValues [][]gqlparse.Token, +// operation []gqlparse.Token, +// selectionSet []gqlparse.Token, +// ) { +// b := new(bytes.Buffer) +// pquery.NewMaker(0).ParseQuery( +// varValues, +// operation[0].ID, +// selectionSet, +// func(qp pquery.QueryPart) (stop bool) { +// qp.Print(b) +// return false +// }, +// ) +// require.Equal(t, td.expect, b.String()) +// }, +// func(err error) { +// t.Fatalf("unexpected parser error: %v", err) +// }, +// ) +// }) +// } +// } + +// func MakeMap(items ...hamap.Pair[string, any]) *hamap.Map[string, any] { +// m := hamap.New[string, any](len(items), nil) +// for i := range items { +// m.Set(items[i].Key, items[i].Value) +// } +// return m +// } diff --git a/go.mod b/go.mod index eee661f..10380e9 100644 --- a/go.mod +++ b/go.mod @@ -11,12 +11,13 @@ require ( github.com/graph-guard/backend v0.0.0-20220826171348-e3dcd100c82b github.com/graph-guard/gqlscan v1.0.0 github.com/graph-guard/gqt v0.0.0-20220830090241-b646278952f9 + github.com/graph-guard/gqt/v4 v4.0.7 github.com/phuslu/log v1.0.81 github.com/pierrec/xxHash v0.1.5 github.com/stretchr/testify v1.7.2 github.com/tidwall/gjson v1.14.2 github.com/valyala/fasthttp v1.38.0 - github.com/vektah/gqlparser/v2 v2.4.6 + github.com/vektah/gqlparser/v2 v2.5.1 github.com/yourbasic/bit v0.0.0-20180313074424-45a4409f4082 github.com/zeebo/xxh3 v1.0.2 golang.org/x/exp v0.0.0-20220823124025-807a23277127 diff --git a/go.sum b/go.sum index 5f3c27b..02344bb 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/graph-guard/gqlscan v1.0.0 h1:hUEDqnJQJ7nV9S8hCPJuYI/H97KP3unu0Gu7wd0 github.com/graph-guard/gqlscan v1.0.0/go.mod h1:A7TeIFxoiP39gw/r4sWjDnTZJnNy+4kaytP9Kh7Luuk= github.com/graph-guard/gqt v0.0.0-20220830090241-b646278952f9 h1:aVvTdG6rHXs4ZzE0BMK83P37zacC9t6I7LOhFb79DVI= github.com/graph-guard/gqt v0.0.0-20220830090241-b646278952f9/go.mod h1:LSgSzLS6fBQ3uD5lREaytQu4qsSkM2EN50Ds1acdu0U= +github.com/graph-guard/gqt/v4 v4.0.7 h1:xp07pamaP7wPqOTRp7+m4wUdpZ78unbisyh44SFnUtE= +github.com/graph-guard/gqt/v4 v4.0.7/go.mod h1:tXBLe4Gr3S/y8pM6ABkcIWFcspyU1c1YfrsXdBf0hKE= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM= @@ -84,8 +86,9 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC github.com/valyala/fasthttp v1.38.0 h1:yTjSSNjuDi2PPvXY2836bIwLmiTS2T4T9p1coQshpco= github.com/valyala/fasthttp v1.38.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -github.com/vektah/gqlparser/v2 v2.4.6 h1:Yjzp66g6oVq93Jihbi0qhGnf/6zIWjcm8H6gA27zstE= github.com/vektah/gqlparser/v2 v2.4.6/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0= +github.com/vektah/gqlparser/v2 v2.5.1 h1:ZGu+bquAY23jsxDRcYpWjttRZrUz07LbiY77gUOHcr4= +github.com/vektah/gqlparser/v2 v2.5.1/go.mod h1:mPgqFBu/woKTVYWyNk8cO3kh4S/f4aRFZrvOnp3hmCs= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yourbasic/bit v0.0.0-20180313074424-45a4409f4082 h1:AWIZQ6fJPAAZdCUElj007LvHa/ER8nOn3CHWajn+1QY= diff --git a/gqlparse/count_values.go b/gqlparse/count_values.go new file mode 100644 index 0000000..def81b3 --- /dev/null +++ b/gqlparse/count_values.go @@ -0,0 +1,53 @@ +package gqlparse + +import "github.com/graph-guard/gqlscan" + +// CountValuesUntil counts the number of tokens and values +// until term is reached. +// Immediately returns true if any call to fn returned true as well. +func CountValuesUntil(a []Token, term gqlscan.Token) (values, tokens int) { + for ; tokens < len(a) && a[tokens].ID != term; tokens++ { + switch a[tokens].ID { + case gqlscan.TokenTrue, + gqlscan.TokenFalse, + gqlscan.TokenStr, + gqlscan.TokenStrBlock, + gqlscan.TokenFloat, + gqlscan.TokenInt, + gqlscan.TokenEnumVal, + gqlscan.TokenNull: + values++ + case gqlscan.TokenObj: + tokens++ + SCAN_OBJ: + for levelObj := 1; ; tokens++ { + switch a[tokens].ID { + case gqlscan.TokenObj: + levelObj++ + case gqlscan.TokenObjEnd: + levelObj-- + if levelObj < 1 { + break SCAN_OBJ + } + } + } + values++ + case gqlscan.TokenArr: + tokens++ + SCAN_ARR: + for levelArr := 1; ; tokens++ { + switch a[tokens].ID { + case gqlscan.TokenArr: + levelArr++ + case gqlscan.TokenArrEnd: + levelArr-- + if levelArr < 1 { + break SCAN_ARR + } + } + } + values++ + } + } + return values, tokens +} diff --git a/gqlparse/count_values_test.go b/gqlparse/count_values_test.go new file mode 100644 index 0000000..6876bc9 --- /dev/null +++ b/gqlparse/count_values_test.go @@ -0,0 +1,391 @@ +package gqlparse_test + +import ( + "testing" + + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/gqlscan" + "github.com/stretchr/testify/require" +) + +func TestCountValuesUntil(t *testing.T) { + for _, tt := range []struct { + Name string + Input []gqlparse.Token + Term gqlscan.Token + ExpectValues int + ExpectTokens int + }{ + { + Name: "sequence", + Term: gqlscan.TokenArrEnd, + Input: []gqlparse.Token{ + {ID: gqlscan.TokenStr, Value: []byte("text")}, + {ID: gqlscan.TokenEnumVal, Value: []byte("red")}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenFloat, Value: []byte("3.1415")}, + {ID: gqlscan.TokenTrue}, + {ID: gqlscan.TokenFalse}, + {ID: gqlscan.TokenNull}, + {ID: gqlscan.TokenStrBlock}, + // {o:{f:[[null]]}} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("o")}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("f")}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenNull}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObjEnd}, + // {f:42} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("f")}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenObjEnd}, + // [] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArrEnd}, + // [[]] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenArrEnd}, + + // Term + {ID: gqlscan.TokenArrEnd}, + }, + ExpectValues: 12, + ExpectTokens: 29, + }, + + // { + // Name: "string", + // Input: []Token{ + // // "text" + // {ID: gqlscan.TokenStr, Value: []byte("text")}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenStr, Value: []byte("text")}}, + // }, + // }, + // { + // Name: "string block", + // Input: []Token{ + // // """text""" + // {ID: gqlscan.TokenStrBlock}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenStrBlock}}, + // }, + // }, + // { + // Name: "float", + // Input: []Token{ + // // 3.14 + // {ID: gqlscan.TokenFloat, Value: []byte("3.14")}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenFloat, Value: []byte("3.14")}}, + // }, + // }, + // { + // Name: "enum", + // Input: []Token{ + // // red + // {ID: gqlscan.TokenEnumVal, Value: []byte("red")}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenEnumVal, Value: []byte("red")}}, + // }, + // }, + // { + // Name: "boolean(true)", + // Input: []Token{ + // // true + // {ID: gqlscan.TokenTrue}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenTrue}}, + // }, + // }, + // { + // Name: "boolean(false)", + // Input: []Token{ + // // false + // {ID: gqlscan.TokenFalse}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenFalse}}, + // }, + // }, + // { + // Name: "null", + // Input: []Token{ + // {ID: gqlscan.TokenNull}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenNull}}, + // }, + // }, + // { + // Name: "object with array inside", + // Input: []Token{ + // // {field:["text"]} + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("field")}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStr, Value: []byte("text")}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenObjEnd}, + // }, + // Expect: [][]Token{ + // { + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("field")}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStr, Value: []byte("text")}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenObjEnd}, + // }, + // }, + // }, + // { + // Name: "object nested", + // Input: []Token{ + // // {x:{y:{z:1}}} + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("x")}, + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("y")}, + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("z")}, + // {ID: gqlscan.TokenInt, Value: []byte("1")}, + // {ID: gqlscan.TokenObjEnd}, + // {ID: gqlscan.TokenObjEnd}, + // {ID: gqlscan.TokenObjEnd}, + // }, + // Expect: [][]Token{ + // { + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("x")}, + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("y")}, + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("z")}, + // {ID: gqlscan.TokenInt, Value: []byte("1")}, + // {ID: gqlscan.TokenObjEnd}, + // {ID: gqlscan.TokenObjEnd}, + // {ID: gqlscan.TokenObjEnd}, + // }, + // }, + // }, + // { + // Name: "empty array", + // Input: []Token{ + // // [] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{}, + // }, + // { + // Name: "array with 1 int", + // Input: []Token{ + // // [42] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenInt, Value: []byte("42")}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenInt, Value: []byte("42")}}, + // }, + // }, + // { + // Name: "array with 3 int", + // Input: []Token{ + // // [42, 0, 100500] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenInt, Value: []byte("42")}, + // {ID: gqlscan.TokenInt, Value: []byte("0")}, + // {ID: gqlscan.TokenInt, Value: []byte("100500")}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenInt, Value: []byte("42")}}, + // {{ID: gqlscan.TokenInt, Value: []byte("0")}}, + // {{ID: gqlscan.TokenInt, Value: []byte("100500")}}, + // }, + // }, + // { + // Name: "array with 1 string", + // Input: []Token{ + // // ["text"] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStr, Value: []byte("text")}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenStr, Value: []byte("text")}}, + // }, + // }, + // { + // Name: "array with 1 string block", + // Input: []Token{ + // // ["""text"""] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStrBlock}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenStrBlock}}, + // }, + // }, + // { + // Name: "array with 1 float", + // Input: []Token{ + // // [3.14] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenFloat, Value: []byte("3.14")}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenFloat, Value: []byte("3.14")}}, + // }, + // }, + // { + // Name: "array with 1 enum", + // Input: []Token{ + // // [red] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenEnumVal, Value: []byte("red")}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenEnumVal, Value: []byte("red")}}, + // }, + // }, + // { + // Name: "array with 1 boolean(true)", + // Input: []Token{ + // // [true] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenTrue}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenTrue}}, + // }, + // }, + // { + // Name: "array with 1 boolean(false)", + // Input: []Token{ + // // [false] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenFalse}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenFalse}}, + // }, + // }, + // { + // Name: "array with 1 null", + // Input: []Token{ + // // [null] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenNull}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenNull}}, + // }, + // }, + // { + // Name: "array with 1 object", + // Input: []Token{ + // // [{x:0}] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("x")}, + // {ID: gqlscan.TokenInt, Value: []byte("0")}, + // {ID: gqlscan.TokenObjEnd}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // { + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("x")}, + // {ID: gqlscan.TokenInt, Value: []byte("0")}, + // {ID: gqlscan.TokenObjEnd}, + // }, + // }, + // }, + // { + // Name: "3d array string", + // Input: []Token{ + // // [[["1"],["2"]],[["3"]]] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStr, Value: []byte("1")}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStr, Value: []byte("2")}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStr, Value: []byte("3")}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenStr, Value: []byte("1")}}, + // {{ID: gqlscan.TokenStr, Value: []byte("2")}}, + // {{ID: gqlscan.TokenStr, Value: []byte("3")}}, + // }, + // }, + // { + // Name: "array with null and nested object with array inside", + // Input: []Token{ + // // [null, {object:{array:["text"]}}] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenNull}, + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("object")}, + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("array")}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStr, Value: []byte("text")}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenObjEnd}, + // {ID: gqlscan.TokenObjEnd}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenNull}}, + // { + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("object")}, + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("array")}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStr, Value: []byte("text")}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenObjEnd}, + // {ID: gqlscan.TokenObjEnd}, + // }, + // }, + // }, + } { + t.Run(tt.Name, func(t *testing.T) { + values, tokens := gqlparse.CountValuesUntil(tt.Input, tt.Term) + require.Equal(t, tt.ExpectValues, values) + require.Equal(t, tt.ExpectTokens, tokens) + }) + } +} diff --git a/gqlparse/getvallen.go b/gqlparse/getvallen.go new file mode 100644 index 0000000..8297a09 --- /dev/null +++ b/gqlparse/getvallen.go @@ -0,0 +1,51 @@ +package gqlparse + +import "github.com/graph-guard/gqlscan" + +// GetValLen reads the length in tokens from the first value in t. +// For example: +// +// `[1,2,3]` has a length of 5 tokens +// `{x:""}` has a length of 4 tokens +// `null` has a length of 1 tokens +func GetValLen(t []Token) (length int) { + switch t[0].ID { + case gqlscan.TokenNull, gqlscan.TokenInt, gqlscan.TokenFloat, + gqlscan.TokenStr, gqlscan.TokenStrBlock, gqlscan.TokenEnumVal, + gqlscan.TokenTrue, gqlscan.TokenFalse: + return 1 + case gqlscan.TokenArr: + t = t[1:] + length += 1 + SCAN_ARR: + for levelArr := 1; ; t = t[1:] { + length++ + switch t[0].ID { + case gqlscan.TokenArr: + levelArr++ + case gqlscan.TokenArrEnd: + levelArr-- + if levelArr < 1 { + break SCAN_ARR + } + } + } + case gqlscan.TokenObj: + t = t[1:] + length += 1 + SCAN_OBJ: + for levelObj := 1; ; t = t[1:] { + length++ + switch t[0].ID { + case gqlscan.TokenObj: + levelObj++ + case gqlscan.TokenObjEnd: + levelObj-- + if levelObj < 1 { + break SCAN_OBJ + } + } + } + } + return length +} diff --git a/gqlparse/getvallen_test.go b/gqlparse/getvallen_test.go new file mode 100644 index 0000000..547b13c --- /dev/null +++ b/gqlparse/getvallen_test.go @@ -0,0 +1,176 @@ +package gqlparse_test + +import ( + "testing" + + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/gqlscan" + "github.com/stretchr/testify/require" +) + +func TestGetValLen(t *testing.T) { + for _, tt := range []struct { + Name string + Input []gqlparse.Token + Expect int + }{ + { + Name: "bool_true", + Input: []gqlparse.Token{{ID: gqlscan.TokenTrue}}, + Expect: 1, + }, + { + Name: "bool_false", + Input: []gqlparse.Token{{ID: gqlscan.TokenFalse}}, + Expect: 1, + }, + { + Name: "int", + Input: []gqlparse.Token{{ID: gqlscan.TokenInt, Value: []byte("-123")}}, + Expect: 1, + }, + { + Name: "float", + Input: []gqlparse.Token{{ID: gqlscan.TokenFloat, Value: []byte("-3.14")}}, + Expect: 1, + }, + { + Name: "string", + Input: []gqlparse.Token{{ID: gqlscan.TokenStr, Value: []byte("text")}}, + Expect: 1, + }, + { + Name: "string_block", + Input: []gqlparse.Token{{ID: gqlscan.TokenStrBlock}}, + Expect: 1, + }, + { + Name: "enum", + Input: []gqlparse.Token{{ID: gqlscan.TokenEnumVal, Value: []byte("red")}}, + Expect: 1, + }, + { + Name: "null", + Input: []gqlparse.Token{{ID: gqlscan.TokenNull}}, + Expect: 1, + }, + { + Name: "array", + Input: []gqlparse.Token{ + // [] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: 2, + }, + { + Name: "array_int_null", + Input: []gqlparse.Token{ + // [1,null,3] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenInt, Value: []byte("1")}, + {ID: gqlscan.TokenNull}, + {ID: gqlscan.TokenInt, Value: []byte("3")}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: 5, + }, + { + Name: "array_2d_int_null", + Input: []gqlparse.Token{ + // [[1],[],[null],null] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenInt, Value: []byte("1")}, + {ID: gqlscan.TokenArrEnd}, + + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArrEnd}, + + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenNull}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenNull}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: 11, + }, + { + Name: "object", + Input: []gqlparse.Token{ + // {foo:42} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("foo")}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenObjEnd}, + }, + Expect: 4, + }, + { + Name: "object_nested", + Input: []gqlparse.Token{ + // {foo:{bar:42}} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("foo")}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("bar")}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObjEnd}, + }, + Expect: 7, + }, + { + Name: "object_nested_with_array", + Input: []gqlparse.Token{ + // {foo:{bar:[42]}} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("foo")}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("bar")}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObjEnd}, + }, + Expect: 9, + }, + { + Name: "array_with_tail", + Input: []gqlparse.Token{ + // [] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArrEnd}, + + // [123] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenInt, Value: []byte("123")}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: 2, + }, + { + Name: "object_with_tail", + Input: []gqlparse.Token{ + // {x:"0"} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("x")}, + {ID: gqlscan.TokenStr, Value: []byte("0")}, + {ID: gqlscan.TokenObjEnd}, + + // {x:"1"} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("x")}, + {ID: gqlscan.TokenStr, Value: []byte("1")}, + {ID: gqlscan.TokenObjEnd}, + }, + Expect: 4, + }, + } { + t.Run(tt.Name, func(t *testing.T) { + a := gqlparse.GetValLen(tt.Input) + require.Equal(t, tt.Expect, a) + }) + } +} diff --git a/gqlparse/gqlparse.go b/gqlparse/gqlparse.go index e0142a5..672f670 100644 --- a/gqlparse/gqlparse.go +++ b/gqlparse/gqlparse.go @@ -24,6 +24,13 @@ type Token struct { Value []byte } +func (t Token) String() string { + if t.Value == nil { + return t.ID.String() + } + return fmt.Sprintf("%s (%q)", t.ID.String(), string(t.Value)) +} + // VariableIndex returns the index of the varible value if // its a variable index token, otherwise returns -1. func (t Token) VariableIndex() (index int) { @@ -138,7 +145,7 @@ type Parser struct { varsConstructed *segmented.Array[[]byte, Token] // typeStack is used during variable default value parsing - typeStack *stack.Stack[typeUnion] + typeStack stack.Stack[typeUnion] // operations holds index ranges of all operation definitions operations []indexRange @@ -289,8 +296,8 @@ func (r *Parser) Parse( r.fragDefs.Set(i.Value(), fragDef{ indexRange: indexRange{ IndexStart: len(r.buffer) - 1, - }}, - ) + }, + }) } return false }); serr.IsErr() { @@ -569,8 +576,7 @@ func (e *ErrorSyntax) Error() string { return fmt.Sprintf("syntax error: %s", e.ScanErr.Error()) } -type ErrorOprAnonNonExcl struct { -} +type ErrorOprAnonNonExcl struct{} func (e *ErrorOprAnonNonExcl) Error() string { return "non-exclusive anonymous operation" @@ -913,16 +919,18 @@ func WriteValue(w io.Writer, definition []Token) { } } -var strNotNull = []byte("!") -var strSqrBrackLeft = []byte("[") -var strSqrBrackRight = []byte("]") -var strCurlBrackLeft = []byte("{") -var strCurlBrackRight = []byte("}") -var strColSp = []byte(":") -var strComSp = []byte(",") -var strTrue = []byte("true") -var strFalse = []byte("false") -var strNull = []byte("null") +var ( + strNotNull = []byte("!") + strSqrBrackLeft = []byte("[") + strSqrBrackRight = []byte("]") + strCurlBrackLeft = []byte("{") + strCurlBrackRight = []byte("}") + strColSp = []byte(":") + strComSp = []byte(",") + strTrue = []byte("true") + strFalse = []byte("false") + strNull = []byte("null") +) func (r *Parser) writeTypeToStack(t []Token, i int) (typeIndex indexRange) { r.typeStack.Reset() diff --git a/gqlparse/internal/graph/graph.go b/gqlparse/internal/graph/graph.go index 21e9527..12dd495 100644 --- a/gqlparse/internal/graph/graph.go +++ b/gqlparse/internal/graph/graph.go @@ -16,7 +16,7 @@ func NewInspector() *Inspector { type Inspector struct { ni *hamap.Map[[]byte, int] - stack *stack.Stack[int] + stack stack.Stack[int] n int cl [MaxFragments]uint8 g [MaxFragments][MaxFragments]bool diff --git a/gqlparse/scan_values.go b/gqlparse/scan_values.go new file mode 100644 index 0000000..e1c6ebb --- /dev/null +++ b/gqlparse/scan_values.go @@ -0,0 +1,63 @@ +package gqlparse + +import "github.com/graph-guard/gqlscan" + +// ScanValues calls fn for every value in a. +// Immediately returns true if any call to fn returned true as well. +func ScanValues( + a []Token, + fn func([]Token) (stop bool), +) (stopped bool) { + for i := 0; i < len(a); i++ { + switch a[i].ID { + case gqlscan.TokenTrue, + gqlscan.TokenFalse, + gqlscan.TokenStr, + gqlscan.TokenStrBlock, + gqlscan.TokenFloat, + gqlscan.TokenInt, + gqlscan.TokenEnumVal, + gqlscan.TokenNull: + if fn(a[i : i+1]) { + return true + } + case gqlscan.TokenObj: + s := i + i++ + SCAN_OBJ: + for levelObj := 1; ; i++ { + switch a[i].ID { + case gqlscan.TokenObj: + levelObj++ + case gqlscan.TokenObjEnd: + levelObj-- + if levelObj < 1 { + break SCAN_OBJ + } + } + } + if fn(a[s : i+1]) { + return true + } + case gqlscan.TokenArr: + s := i + i++ + SCAN_ARR: + for levelArr := 1; ; i++ { + switch a[i].ID { + case gqlscan.TokenArr: + levelArr++ + case gqlscan.TokenArrEnd: + levelArr-- + if levelArr < 1 { + break SCAN_ARR + } + } + } + if fn(a[s : i+1]) { + return true + } + } + } + return false +} diff --git a/gqlparse/scan_values_in_arrays.go b/gqlparse/scan_values_in_arrays.go new file mode 100644 index 0000000..8323453 --- /dev/null +++ b/gqlparse/scan_values_in_arrays.go @@ -0,0 +1,88 @@ +package gqlparse + +import "github.com/graph-guard/gqlscan" + +// ScanValuesInArrays calls fn for every non-array value contained in x recursively. +// Immediately returns true if any call to fn returned true as well. +func ScanValuesInArrays( + x []Token, + fn func([]Token) (stop bool), +) (stopped bool) { + for levelArr, i := 0, 0; i < len(x); i++ { + switch x[i].ID { + case gqlscan.TokenTrue, + gqlscan.TokenFalse, + gqlscan.TokenStr, + gqlscan.TokenStrBlock, + gqlscan.TokenFloat, + gqlscan.TokenInt, + gqlscan.TokenEnumVal, + gqlscan.TokenNull: + if fn(x[i : i+1]) { + return true + } + case gqlscan.TokenObj: + s := i + i++ + OBJ_SCAN_1: + for levelObj := 1; ; i++ { + switch x[i].ID { + case gqlscan.TokenObj: + levelObj++ + case gqlscan.TokenObjEnd: + levelObj-- + if levelObj < 1 { + i++ + break OBJ_SCAN_1 + } + } + } + if fn(x[s:i]) { + return true + } + case gqlscan.TokenArr: + levelArr++ + i++ + VAL_SCAN: + for levelArr > 0 { + switch x[i].ID { + case gqlscan.TokenArrEnd: + i++ + levelArr-- + if levelArr < 1 { + break VAL_SCAN + } + goto VAL_SCAN + case gqlscan.TokenArr: + i++ + levelArr++ + case gqlscan.TokenObj: + s := i + i++ + OBJ_SCAN_2: + for levelObj := 1; ; i++ { + switch x[i].ID { + case gqlscan.TokenObj: + levelObj++ + case gqlscan.TokenObjEnd: + levelObj-- + if levelObj < 1 { + i++ + break OBJ_SCAN_2 + } + } + } + if fn(x[s:i]) { + return true + } + default: + if fn(x[i : i+1]) { + return true + } + i++ + } + } + } + } + return false +} diff --git a/gqlparse/scan_values_in_arrays_test.go b/gqlparse/scan_values_in_arrays_test.go new file mode 100644 index 0000000..55e4b55 --- /dev/null +++ b/gqlparse/scan_values_in_arrays_test.go @@ -0,0 +1,371 @@ +package gqlparse_test + +import ( + "fmt" + "testing" + + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/gqlscan" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestInValues(t *testing.T) { + for _, tt := range []struct { + Name string + Input []gqlparse.Token + Expect [][]gqlparse.Token + }{ + { + Name: "string", + Input: []gqlparse.Token{ + // "text" + {ID: gqlscan.TokenStr, Value: []byte("text")}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenStr, Value: []byte("text")}}, + }, + }, + { + Name: "string block", + Input: []gqlparse.Token{ + // """text""" + {ID: gqlscan.TokenStrBlock}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenStrBlock}}, + }, + }, + { + Name: "float", + Input: []gqlparse.Token{ + // 3.14 + {ID: gqlscan.TokenFloat, Value: []byte("3.14")}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenFloat, Value: []byte("3.14")}}, + }, + }, + { + Name: "enum", + Input: []gqlparse.Token{ + // red + {ID: gqlscan.TokenEnumVal, Value: []byte("red")}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenEnumVal, Value: []byte("red")}}, + }, + }, + { + Name: "boolean(true)", + Input: []gqlparse.Token{ + // true + {ID: gqlscan.TokenTrue}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenTrue}}, + }, + }, + { + Name: "boolean(false)", + Input: []gqlparse.Token{ + // false + {ID: gqlscan.TokenFalse}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenFalse}}, + }, + }, + { + Name: "null", + Input: []gqlparse.Token{ + {ID: gqlscan.TokenNull}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenNull}}, + }, + }, + { + Name: "object with array inside", + Input: []gqlparse.Token{ + // {field:["text"]} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("field")}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenStr, Value: []byte("text")}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenObjEnd}, + }, + Expect: [][]gqlparse.Token{ + { + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("field")}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenStr, Value: []byte("text")}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenObjEnd}, + }, + }, + }, + { + Name: "object nested", + Input: []gqlparse.Token{ + // {x:{y:{z:1}}} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("x")}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("y")}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("z")}, + {ID: gqlscan.TokenInt, Value: []byte("1")}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObjEnd}, + }, + Expect: [][]gqlparse.Token{ + { + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("x")}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("y")}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("z")}, + {ID: gqlscan.TokenInt, Value: []byte("1")}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObjEnd}, + }, + }, + }, + { + Name: "empty array", + Input: []gqlparse.Token{ + // [] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: [][]gqlparse.Token{}, + }, + { + Name: "array with 1 int", + Input: []gqlparse.Token{ + // [42] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenInt, Value: []byte("42")}}, + }, + }, + { + Name: "array with 3 int", + Input: []gqlparse.Token{ + // [42, 0, 100500] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenInt, Value: []byte("0")}, + {ID: gqlscan.TokenInt, Value: []byte("100500")}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenInt, Value: []byte("42")}}, + {{ID: gqlscan.TokenInt, Value: []byte("0")}}, + {{ID: gqlscan.TokenInt, Value: []byte("100500")}}, + }, + }, + { + Name: "array with 1 string", + Input: []gqlparse.Token{ + // ["text"] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenStr, Value: []byte("text")}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenStr, Value: []byte("text")}}, + }, + }, + { + Name: "array with 1 string block", + Input: []gqlparse.Token{ + // ["""text"""] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenStrBlock}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenStrBlock}}, + }, + }, + { + Name: "array with 1 float", + Input: []gqlparse.Token{ + // [3.14] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenFloat, Value: []byte("3.14")}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenFloat, Value: []byte("3.14")}}, + }, + }, + { + Name: "array with 1 enum", + Input: []gqlparse.Token{ + // [red] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenEnumVal, Value: []byte("red")}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenEnumVal, Value: []byte("red")}}, + }, + }, + { + Name: "array with 1 boolean(true)", + Input: []gqlparse.Token{ + // [true] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenTrue}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenTrue}}, + }, + }, + { + Name: "array with 1 boolean(false)", + Input: []gqlparse.Token{ + // [false] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenFalse}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenFalse}}, + }, + }, + { + Name: "array with 1 null", + Input: []gqlparse.Token{ + // [null] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenNull}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenNull}}, + }, + }, + { + Name: "array with 1 object", + Input: []gqlparse.Token{ + // [{x:0}] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("x")}, + {ID: gqlscan.TokenInt, Value: []byte("0")}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: [][]gqlparse.Token{ + { + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("x")}, + {ID: gqlscan.TokenInt, Value: []byte("0")}, + {ID: gqlscan.TokenObjEnd}, + }, + }, + }, + { + Name: "3d array string", + Input: []gqlparse.Token{ + // [[["1"],["2"]],[["3"]]] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenStr, Value: []byte("1")}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenStr, Value: []byte("2")}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenStr, Value: []byte("3")}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenStr, Value: []byte("1")}}, + {{ID: gqlscan.TokenStr, Value: []byte("2")}}, + {{ID: gqlscan.TokenStr, Value: []byte("3")}}, + }, + }, + { + Name: "array with null and nested object with array inside", + Input: []gqlparse.Token{ + // [null, {object:{array:["text"]}}] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenNull}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("object")}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("array")}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenStr, Value: []byte("text")}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: [][]gqlparse.Token{ + {{ID: gqlscan.TokenNull}}, + { + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("object")}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("array")}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenStr, Value: []byte("text")}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObjEnd}, + }, + }, + }, + } { + t.Run(tt.Name, func(t *testing.T) { + actual := [][]gqlparse.Token{} + stopped := gqlparse.ScanValuesInArrays( + tt.Input, + func(t []gqlparse.Token) (stop bool) { + cp := make([]gqlparse.Token, len(t)) + copy(cp, t) + actual = append(actual, cp) + return false + }, + ) + // Manually print diff for better readability + printSet := func(title string, t [][]gqlparse.Token) { + fmt.Printf("%s (%d)\n", title, len(t)) + for i, t := range t { + fmt.Printf(" item %d (%d token(s)):\n", i, len(t)) + for i, t := range t { + fmt.Printf(" %d: %s\n", i, t.String()) + } + } + } + isEqual := assert.ObjectsAreEqual(tt.Expect, actual) + if !isEqual { + printSet("expect", tt.Expect) + printSet("actual", actual) + } + require.True(t, isEqual) + require.False(t, stopped) + require.Equal(t, tt.Expect, actual) + }) + } +} diff --git a/gqlparse/scan_values_test.go b/gqlparse/scan_values_test.go new file mode 100644 index 0000000..ae8ca93 --- /dev/null +++ b/gqlparse/scan_values_test.go @@ -0,0 +1,465 @@ +package gqlparse_test + +import ( + "fmt" + "testing" + + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/gqlscan" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestScanValues(t *testing.T) { + for _, tt := range []struct { + Name string + Input []gqlparse.Token + Expect [][]gqlparse.Token + }{ + { + Name: "sequence", + Input: []gqlparse.Token{ + {ID: gqlscan.TokenStr, Value: []byte("text")}, + {ID: gqlscan.TokenEnumVal, Value: []byte("red")}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenFloat, Value: []byte("3.1415")}, + {ID: gqlscan.TokenTrue}, + {ID: gqlscan.TokenFalse}, + {ID: gqlscan.TokenNull}, + {ID: gqlscan.TokenStrBlock}, + // {o:{f:[[null]]}} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("o")}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("f")}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenNull}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObjEnd}, + // {f:42} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("f")}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenObjEnd}, + // [] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArrEnd}, + // [[]] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenArrEnd}, + }, + Expect: [][]gqlparse.Token{ + { + {ID: gqlscan.TokenStr, Value: []byte("text")}, + }, + { + {ID: gqlscan.TokenEnumVal, Value: []byte("red")}, + }, + { + {ID: gqlscan.TokenInt, Value: []byte("42")}, + }, + { + {ID: gqlscan.TokenFloat, Value: []byte("3.1415")}, + }, + { + {ID: gqlscan.TokenTrue}, + }, + { + {ID: gqlscan.TokenFalse}, + }, + { + {ID: gqlscan.TokenNull}, + }, + { + {ID: gqlscan.TokenStrBlock}, + }, + { // {o:{f:[[null]]}} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("o")}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("f")}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenNull}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObjEnd}, + }, + { // {f:42} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("f")}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenObjEnd}, + }, + { // [] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArrEnd}, + }, + { // [[]] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenArrEnd}, + }, + }, + }, + + // { + // Name: "string", + // Input: []Token{ + // // "text" + // {ID: gqlscan.TokenStr, Value: []byte("text")}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenStr, Value: []byte("text")}}, + // }, + // }, + // { + // Name: "string block", + // Input: []Token{ + // // """text""" + // {ID: gqlscan.TokenStrBlock}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenStrBlock}}, + // }, + // }, + // { + // Name: "float", + // Input: []Token{ + // // 3.14 + // {ID: gqlscan.TokenFloat, Value: []byte("3.14")}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenFloat, Value: []byte("3.14")}}, + // }, + // }, + // { + // Name: "enum", + // Input: []Token{ + // // red + // {ID: gqlscan.TokenEnumVal, Value: []byte("red")}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenEnumVal, Value: []byte("red")}}, + // }, + // }, + // { + // Name: "boolean(true)", + // Input: []Token{ + // // true + // {ID: gqlscan.TokenTrue}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenTrue}}, + // }, + // }, + // { + // Name: "boolean(false)", + // Input: []Token{ + // // false + // {ID: gqlscan.TokenFalse}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenFalse}}, + // }, + // }, + // { + // Name: "null", + // Input: []Token{ + // {ID: gqlscan.TokenNull}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenNull}}, + // }, + // }, + // { + // Name: "object with array inside", + // Input: []Token{ + // // {field:["text"]} + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("field")}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStr, Value: []byte("text")}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenObjEnd}, + // }, + // Expect: [][]Token{ + // { + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("field")}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStr, Value: []byte("text")}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenObjEnd}, + // }, + // }, + // }, + // { + // Name: "object nested", + // Input: []Token{ + // // {x:{y:{z:1}}} + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("x")}, + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("y")}, + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("z")}, + // {ID: gqlscan.TokenInt, Value: []byte("1")}, + // {ID: gqlscan.TokenObjEnd}, + // {ID: gqlscan.TokenObjEnd}, + // {ID: gqlscan.TokenObjEnd}, + // }, + // Expect: [][]Token{ + // { + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("x")}, + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("y")}, + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("z")}, + // {ID: gqlscan.TokenInt, Value: []byte("1")}, + // {ID: gqlscan.TokenObjEnd}, + // {ID: gqlscan.TokenObjEnd}, + // {ID: gqlscan.TokenObjEnd}, + // }, + // }, + // }, + // { + // Name: "empty array", + // Input: []Token{ + // // [] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{}, + // }, + // { + // Name: "array with 1 int", + // Input: []Token{ + // // [42] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenInt, Value: []byte("42")}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenInt, Value: []byte("42")}}, + // }, + // }, + // { + // Name: "array with 3 int", + // Input: []Token{ + // // [42, 0, 100500] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenInt, Value: []byte("42")}, + // {ID: gqlscan.TokenInt, Value: []byte("0")}, + // {ID: gqlscan.TokenInt, Value: []byte("100500")}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenInt, Value: []byte("42")}}, + // {{ID: gqlscan.TokenInt, Value: []byte("0")}}, + // {{ID: gqlscan.TokenInt, Value: []byte("100500")}}, + // }, + // }, + // { + // Name: "array with 1 string", + // Input: []Token{ + // // ["text"] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStr, Value: []byte("text")}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenStr, Value: []byte("text")}}, + // }, + // }, + // { + // Name: "array with 1 string block", + // Input: []Token{ + // // ["""text"""] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStrBlock}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenStrBlock}}, + // }, + // }, + // { + // Name: "array with 1 float", + // Input: []Token{ + // // [3.14] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenFloat, Value: []byte("3.14")}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenFloat, Value: []byte("3.14")}}, + // }, + // }, + // { + // Name: "array with 1 enum", + // Input: []Token{ + // // [red] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenEnumVal, Value: []byte("red")}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenEnumVal, Value: []byte("red")}}, + // }, + // }, + // { + // Name: "array with 1 boolean(true)", + // Input: []Token{ + // // [true] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenTrue}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenTrue}}, + // }, + // }, + // { + // Name: "array with 1 boolean(false)", + // Input: []Token{ + // // [false] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenFalse}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenFalse}}, + // }, + // }, + // { + // Name: "array with 1 null", + // Input: []Token{ + // // [null] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenNull}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenNull}}, + // }, + // }, + // { + // Name: "array with 1 object", + // Input: []Token{ + // // [{x:0}] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("x")}, + // {ID: gqlscan.TokenInt, Value: []byte("0")}, + // {ID: gqlscan.TokenObjEnd}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // { + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("x")}, + // {ID: gqlscan.TokenInt, Value: []byte("0")}, + // {ID: gqlscan.TokenObjEnd}, + // }, + // }, + // }, + // { + // Name: "3d array string", + // Input: []Token{ + // // [[["1"],["2"]],[["3"]]] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStr, Value: []byte("1")}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStr, Value: []byte("2")}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStr, Value: []byte("3")}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenStr, Value: []byte("1")}}, + // {{ID: gqlscan.TokenStr, Value: []byte("2")}}, + // {{ID: gqlscan.TokenStr, Value: []byte("3")}}, + // }, + // }, + // { + // Name: "array with null and nested object with array inside", + // Input: []Token{ + // // [null, {object:{array:["text"]}}] + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenNull}, + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("object")}, + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("array")}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStr, Value: []byte("text")}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenObjEnd}, + // {ID: gqlscan.TokenObjEnd}, + // {ID: gqlscan.TokenArrEnd}, + // }, + // Expect: [][]Token{ + // {{ID: gqlscan.TokenNull}}, + // { + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("object")}, + // {ID: gqlscan.TokenObj}, + // {ID: gqlscan.TokenObjField, Value: []byte("array")}, + // {ID: gqlscan.TokenArr}, + // {ID: gqlscan.TokenStr, Value: []byte("text")}, + // {ID: gqlscan.TokenArrEnd}, + // {ID: gqlscan.TokenObjEnd}, + // {ID: gqlscan.TokenObjEnd}, + // }, + // }, + // }, + } { + t.Run(tt.Name, func(t *testing.T) { + actual := [][]gqlparse.Token{} + stopped := gqlparse.ScanValues( + tt.Input, + func(t []gqlparse.Token) (stop bool) { + cp := make([]gqlparse.Token, len(t)) + copy(cp, t) + actual = append(actual, cp) + return false + }, + ) + // Manually print diff for better readability + printSet := func(title string, t [][]gqlparse.Token) { + fmt.Printf("%s (%d)\n", title, len(t)) + for i, t := range t { + fmt.Printf(" item %d (%d token(s)):\n", i, len(t)) + for i, t := range t { + fmt.Printf(" %d: %s\n", i, t.String()) + } + } + } + isEqual := assert.ObjectsAreEqual(tt.Expect, actual) + if !isEqual { + printSet("expect", tt.Expect) + printSet("actual", actual) + } + require.True(t, isEqual) + require.False(t, stopped) + require.Equal(t, tt.Expect, actual) + }) + } +} diff --git a/utilities/atoi/atoi.go b/utilities/atoi/atoi.go new file mode 100644 index 0000000..2f6d00e --- /dev/null +++ b/utilities/atoi/atoi.go @@ -0,0 +1,53 @@ +// Package atoi provides functions for efficient & allocation-free +// parsing of []byte and string to int32 and float64. +package atoi + +import ( + "fmt" + "strconv" + + "github.com/graph-guard/ggproxy/utilities/unsafe" +) + +// MustI32 parses s assuming that it's a valid signed 32-bit integer. +// Panics if s contains an invalid number. +func MustI32[S []byte | string](s S) int32 { + const intSize = 32 << (^uint(0) >> 63) + + sLen := len(s) + if intSize == 32 && (0 < sLen && sLen < 10) || + intSize == 64 && (0 < sLen && sLen < 19) { + // Fast path for small integers that fit int type. + s0 := s + if s[0] == '-' || s[0] == '+' { + s = s[1:] + if len(s) < 1 { + panic("syntax error") + } + } + + n := int32(0) + for _, ch := range []byte(s) { + ch -= '0' + if ch > 9 { + panic("syntax error") + } + n = n*10 + int32(ch) + } + if s0[0] == '-' { + n = -n + } + return n + } + panic("syntax error") +} + +// MustF64 parses s assuming that it's a valid signed 64-bit float. +// Panics if s contains an invalid number. +func MustF64[S []byte | string](s S) float64 { + f, err := strconv.ParseFloat(unsafe.B2S([]byte(s)), 64) + if err != nil { + panic(fmt.Errorf("unexpected float64 parsing err: %w", err)) + } + return f +} diff --git a/utilities/atoi/atoi_test.go b/utilities/atoi/atoi_test.go new file mode 100644 index 0000000..d4df3fa --- /dev/null +++ b/utilities/atoi/atoi_test.go @@ -0,0 +1,118 @@ +package atoi_test + +import ( + "fmt" + "math" + "strconv" + "testing" + + "github.com/graph-guard/ggproxy/utilities/atoi" + "github.com/stretchr/testify/require" +) + +// Std wraps strconv.Atoi. +func Std[S []byte | string](s S) int32 { + i, _ := strconv.Atoi(string(s)) + return int32(i) +} + +func TestMustI32(t *testing.T) { + require.Equal(t, int32(0), atoi.MustI32("0")) + require.Equal(t, int32(1), atoi.MustI32("1")) + require.Equal(t, int32(8), atoi.MustI32("8")) + require.Equal(t, int32(-1), atoi.MustI32("-1")) + require.Equal(t, int32(1), atoi.MustI32("+1")) + require.Equal(t, int32(123456789), atoi.MustI32("123456789")) + require.Equal(t, int32(1234567890), atoi.MustI32("1234567890")) + require.Equal(t, int32(math.MaxInt32), atoi.MustI32(fmt.Sprintf("%d", math.MaxInt32))) + require.Equal(t, int32(math.MinInt32), atoi.MustI32(fmt.Sprintf("%d", math.MinInt32))) + + // Error + require.Panics(t, func() { atoi.MustI32("a") }) + require.Panics(t, func() { atoi.MustI32("0xa") }) + require.Panics(t, func() { atoi.MustI32(" 1") }) + require.Panics(t, func() { atoi.MustI32("-0xa") }) + require.Panics(t, func() { atoi.MustI32("-") }) + require.Panics(t, func() { atoi.MustI32("") }) +} + +func TestMustF64(t *testing.T) { + require.Equal(t, float64(0), atoi.MustF64("0")) + require.Equal(t, float64(1), atoi.MustF64("1")) + require.Equal(t, float64(3.14), atoi.MustF64("3.14")) + require.Equal(t, float64(-1), atoi.MustF64("-1")) + require.Equal(t, float64(-1.12345), atoi.MustF64("-1.12345")) + require.Equal(t, float64(1), atoi.MustF64("+1")) + require.Equal(t, float64(123456789), atoi.MustF64("123456789")) + require.Equal(t, float64(1234567890), atoi.MustF64("1234567890")) + require.Equal(t, float64(0.1234567890), atoi.MustF64("0.1234567890")) + require.Equal(t, + float64(math.MaxFloat64), + atoi.MustF64(fmt.Sprintf("%f", math.MaxFloat64)), + ) + require.Equal(t, + float64(-math.MaxFloat64), + atoi.MustF64(fmt.Sprintf("%f", -math.MaxFloat64)), + ) + require.Equal(t, float64(1e12), atoi.MustF64("1e12")) + require.Equal(t, float64(1e+12), atoi.MustF64("1e+12")) + require.Equal(t, float64(1e-12), atoi.MustF64("1e-12")) + require.Equal(t, float64(1e-200), atoi.MustF64("1e-200")) + require.Equal(t, float64(1e308), atoi.MustF64("1e308")) + require.Equal(t, float64(1e-308), atoi.MustF64("1e-308")) + + // Error + require.Panics(t, func() { atoi.MustF64("a") }) + require.Panics(t, func() { atoi.MustF64("0xa") }) + require.Panics(t, func() { atoi.MustF64(" 1") }) + require.Panics(t, func() { atoi.MustF64("-0xa") }) + require.Panics(t, func() { atoi.MustF64("-") }) + require.Panics(t, func() { atoi.MustF64("") }) + require.Panics(t, func() { atoi.MustF64(".") }) + require.Panics(t, func() { atoi.MustF64("1.2.3") }) +} + +var GI32 int32 + +func BenchmarkI32(b *testing.B) { + for _, bb := range []struct { + Name string + Input string + }{ + {"min", fmt.Sprintf("%d", math.MinInt32)}, + {"1", fmt.Sprintf("%d", 1)}, + {"123456789", fmt.Sprintf("%d", 123456789)}, + {"max", fmt.Sprintf("%d", math.MaxInt32)}, + {"plus_prefix", "+1"}, + } { + b.Run(bb.Name, func(b *testing.B) { + b.Run("string", func(b *testing.B) { + b.Run("std", func(b *testing.B) { + for i := 0; i < b.N; i++ { + GI32 = Std(bb.Input) + } + }) + b.Run("custom", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + GI32 = atoi.MustI32(bb.Input) + } + }) + }) + b.Run("byte_slice", func(b *testing.B) { + s := []byte(bb.Input) + b.Run("std", func(b *testing.B) { + for i := 0; i < b.N; i++ { + GI32 = Std(s) + } + }) + b.Run("custom", func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + GI32 = atoi.MustI32(s) + } + }) + }) + }) + } +} diff --git a/utilities/stack/stack.go b/utilities/stack/stack.go index 849fd3d..4db49d6 100644 --- a/utilities/stack/stack.go +++ b/utilities/stack/stack.go @@ -3,57 +3,68 @@ package stack // Stack is an implementation of stack container. type Stack[T any] struct{ s []T } -// New creates a new instance of Stack. -func New[T any](capacity int) *Stack[T] { - return &Stack[T]{s: make([]T, 0, capacity)} +// New creates a new instance of Stack with preallocated capacity. +func New[T any](capacity int) Stack[T] { + return Stack[T]{s: make([]T, 0, capacity)} } // Reset resets the stack. -func (s *Stack[T]) Reset() { s.s = s.s[:0] } +func (s *Stack[T]) Reset() { s.s = (s.s)[:0] } // Push adds an element to the stack. func (s *Stack[T]) Push(f T) { s.s = append(s.s, f) } -// Pop returns and deletes the last stack element. -func (s *Stack[T]) Pop() (top T) { +// Pop deletes the last stack element. +func (s *Stack[T]) Pop() { if l := len(s.s) - 1; l >= 0 { - top = s.s[l] - s.s = s.s[:l] + s.s = (s.s)[:l] } - return +} + +// TopPop returns and deletes the last stack element. +func (s *Stack[T]) TopPop() (top T) { + if l := len(s.s) - 1; l >= 0 { + top = (s.s)[l] + s.s = (s.s)[:l] + } + return top } // PopPush executes Pop and Push operations in sequence. -func (s *Stack[T]) PopPush(f T) (popped T) { +func (s *Stack[T]) PopPush(f T) { if l := len(s.s) - 1; l >= 0 { - popped = s.s[l] - s.s = s.s[:l] + s.s = (s.s)[:l] } s.s = append(s.s, f) - return +} + +// TopPopPush executes Pop and Push operations in sequence. +func (s *Stack[T]) TopPopPush(f T) (popped T) { + if l := len(s.s) - 1; l >= 0 { + popped = (s.s)[l] + s.s = (s.s)[:l] + } + s.s = append(s.s, f) + return popped } // Top returns the last stack element. func (s *Stack[T]) Top() (top T) { if l := len(s.s) - 1; l >= 0 { - return s.s[l] + return (s.s)[l] } - return + return top } // TopOffsetFn calls fn with the last stack element at offset. func (s *Stack[T]) TopOffsetFn(offset int, fn func(*T)) { if l := len(s.s) - 1; l >= 0 { - fn(&s.s[l-offset]) + fn(&(s.s)[l-offset]) } } // Get returns the element at index from bottom. -func (s *Stack[T]) Get(index int) T { - return s.s[index] -} +func (s *Stack[T]) Get(index int) T { return (s.s)[index] } // Len returns the stack length. -func (s *Stack[T]) Len() int { - return len(s.s) -} +func (s *Stack[T]) Len() int { return len(s.s) } diff --git a/utilities/stack/stack_test.go b/utilities/stack/stack_test.go index 19ac954..aa34e8e 100644 --- a/utilities/stack/stack_test.go +++ b/utilities/stack/stack_test.go @@ -27,17 +27,26 @@ func TestPop(t *testing.T) { st.Push(0) st.Push(1) st.Push(-1) - require.Equal(t, int64(-1), st.Pop()) + require.Equal(t, int64(-1), st.TopPop()) st.Pop() st.Pop() - require.Equal(t, int64(0), st.Pop()) + require.Equal(t, int64(0), st.TopPop()) +} + +func TestTopPopPush(t *testing.T) { + st := stack.New[float64](2) + st.Push(0.0) + require.Equal(t, 0.0, st.TopPopPush(-1)) + require.Equal(t, -1.0, st.TopPop()) } func TestPopPush(t *testing.T) { st := stack.New[float64](2) st.Push(0.0) - require.Equal(t, 0.0, st.PopPush(-1)) - require.Equal(t, -1.0, st.Pop()) + st.PopPush(-1) + require.Equal(t, -1.0, st.Top()) + st.PopPush(3.14) + require.Equal(t, 3.14, st.Top()) } func TestTop(t *testing.T) { @@ -62,8 +71,8 @@ func TestTopOffsetFn(t *testing.T) { require.Equal(t, 0, *i) *i = 10 }) - require.Equal(t, 20, st.Pop()) - require.Equal(t, 10, st.Pop()) + require.Equal(t, 20, st.TopPop()) + st.Pop() // 10 require.Equal(t, 0, st.Top()) } From a3e66c6625dd9a323a0044e02e30a71e7eb65fd0 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sun, 5 Feb 2023 15:59:36 +0100 Subject: [PATCH 02/35] docs: Improve package pathscan documentation --- engines/playmon/internal/pathscan/pathscan.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/engines/playmon/internal/pathscan/pathscan.go b/engines/playmon/internal/pathscan/pathscan.go index c260fc1..1cfb831 100644 --- a/engines/playmon/internal/pathscan/pathscan.go +++ b/engines/playmon/internal/pathscan/pathscan.go @@ -1,5 +1,7 @@ // Package pathscan provides a scanner that traverses token slices and // finds all paths of leaf nodes in a GraphQL operation. +// Query operations always begin with "Q", mutation operations always begin +// with "M" and subscription operations always begin with "S". // // Consider the following example: // From 3b08b249309b97f6ed80055df31fabb88b6992bb Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sun, 5 Feb 2023 21:46:31 +0100 Subject: [PATCH 03/35] fix: Fix package union --- .../constrcheck/internal/union/union_test.go | 136 +++++++++--------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/engines/playmon/internal/constrcheck/internal/union/union_test.go b/engines/playmon/internal/constrcheck/internal/union/union_test.go index d7a66bc..03f3e49 100644 --- a/engines/playmon/internal/constrcheck/internal/union/union_test.go +++ b/engines/playmon/internal/constrcheck/internal/union/union_test.go @@ -3,8 +3,8 @@ package union_test import ( "testing" - "github.com/graph-guard/constrcheck/internal/token" - "github.com/graph-guard/constrcheck/internal/union" + "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck/internal/union" + "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/gqlscan" "github.com/stretchr/testify/require" ) @@ -96,21 +96,21 @@ func TestAreUnionsEqual(t *testing.T) { { Name: "same_type_tokens_int_equal", - Left: union.Tokens([]token.Token{ - {Type: gqlscan.TokenInt, Value: []byte("42")}, + Left: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenInt, Value: []byte("42")}, }), - Right: union.Tokens([]token.Token{ - {Type: gqlscan.TokenInt, Value: []byte("42")}, + Right: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenInt, Value: []byte("42")}, }), Expect: true, }, { Name: "same_type_tokens_int_diff", - Left: union.Tokens([]token.Token{ - {Type: gqlscan.TokenInt, Value: []byte("42")}, + Left: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenInt, Value: []byte("42")}, }), - Right: union.Tokens([]token.Token{ - {Type: gqlscan.TokenInt, Value: []byte("43")}, + Right: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenInt, Value: []byte("43")}, }), Expect: false, }, @@ -118,15 +118,15 @@ func TestAreUnionsEqual(t *testing.T) { { Name: "inf_int_tokens", Left: union.Int(42), - Right: union.Tokens([]token.Token{ - {Type: gqlscan.TokenInt, Value: []byte("42")}, + Right: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenInt, Value: []byte("42")}, }), Expect: true, }, { Name: "inf_tokens_int", - Left: union.Tokens([]token.Token{ - {Type: gqlscan.TokenInt, Value: []byte("42")}, + Left: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenInt, Value: []byte("42")}, }), Right: union.Int(42), Expect: true, @@ -135,15 +135,15 @@ func TestAreUnionsEqual(t *testing.T) { { Name: "inf_float_tokens", Left: union.Float(3.1415), - Right: union.Tokens([]token.Token{ - {Type: gqlscan.TokenFloat, Value: []byte("3.1415")}, + Right: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenFloat, Value: []byte("3.1415")}, }), Expect: true, }, { Name: "inf_tokens_float", - Left: union.Tokens([]token.Token{ - {Type: gqlscan.TokenFloat, Value: []byte("3.1415")}, + Left: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenFloat, Value: []byte("3.1415")}, }), Right: union.Float(3.1415), Expect: true, @@ -152,15 +152,15 @@ func TestAreUnionsEqual(t *testing.T) { { Name: "inf_string_tokens", Left: union.String("okay"), - Right: union.Tokens([]token.Token{ - {Type: gqlscan.TokenStr, Value: []byte("okay")}, + Right: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenStr, Value: []byte("okay")}, }), Expect: true, }, { Name: "inf_tokens_string", - Left: union.Tokens([]token.Token{ - {Type: gqlscan.TokenStr, Value: []byte("okay")}, + Left: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenStr, Value: []byte("okay")}, }), Right: union.String("okay"), Expect: true, @@ -169,15 +169,15 @@ func TestAreUnionsEqual(t *testing.T) { { Name: "inf_enum_tokens", Left: union.Enum("red"), - Right: union.Tokens([]token.Token{ - {Type: gqlscan.TokenEnumVal, Value: []byte("red")}, + Right: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenEnumVal, Value: []byte("red")}, }), Expect: true, }, { Name: "inf_tokens_enum", - Left: union.Tokens([]token.Token{ - {Type: gqlscan.TokenEnumVal, Value: []byte("red")}, + Left: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenEnumVal, Value: []byte("red")}, }), Right: union.Enum("red"), Expect: true, @@ -186,15 +186,15 @@ func TestAreUnionsEqual(t *testing.T) { { Name: "inf_true_tokens", Left: union.True(), - Right: union.Tokens([]token.Token{ - {Type: gqlscan.TokenTrue}, + Right: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenTrue}, }), Expect: true, }, { Name: "inf_tokens_true", - Left: union.Tokens([]token.Token{ - {Type: gqlscan.TokenTrue}, + Left: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenTrue}, }), Right: union.True(), Expect: true, @@ -203,15 +203,15 @@ func TestAreUnionsEqual(t *testing.T) { { Name: "inf_false_tokens", Left: union.False(), - Right: union.Tokens([]token.Token{ - {Type: gqlscan.TokenFalse}, + Right: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenFalse}, }), Expect: true, }, { Name: "inf_tokens_false", - Left: union.Tokens([]token.Token{ - {Type: gqlscan.TokenFalse}, + Left: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenFalse}, }), Right: union.False(), Expect: true, @@ -220,15 +220,15 @@ func TestAreUnionsEqual(t *testing.T) { { Name: "inf_null_tokens", Left: union.Null(), - Right: union.Tokens([]token.Token{ - {Type: gqlscan.TokenNull}, + Right: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenNull}, }), Expect: true, }, { Name: "inf_tokens_null", - Left: union.Tokens([]token.Token{ - {Type: gqlscan.TokenNull}, + Left: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenNull}, }), Right: union.Null(), Expect: true, @@ -236,55 +236,55 @@ func TestAreUnionsEqual(t *testing.T) { { Name: "equal_tokens_array", - Left: union.Tokens([]token.Token{ - {Type: gqlscan.TokenArr}, - {Type: gqlscan.TokenInt, Value: []byte("1")}, - {Type: gqlscan.TokenInt, Value: []byte("23")}, - {Type: gqlscan.TokenArr}, + Left: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenInt, Value: []byte("1")}, + {ID: gqlscan.TokenInt, Value: []byte("23")}, + {ID: gqlscan.TokenArr}, }), - Right: union.Tokens([]token.Token{ - {Type: gqlscan.TokenArr}, - {Type: gqlscan.TokenInt, Value: []byte("1")}, - {Type: gqlscan.TokenInt, Value: []byte("23")}, - {Type: gqlscan.TokenArr}, + Right: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenInt, Value: []byte("1")}, + {ID: gqlscan.TokenInt, Value: []byte("23")}, + {ID: gqlscan.TokenArr}, }), Expect: true, }, { Name: "not_equal_tokens_array", - Left: union.Tokens([]token.Token{ - {Type: gqlscan.TokenArr}, - {Type: gqlscan.TokenInt, Value: []byte("1")}, - {Type: gqlscan.TokenInt, Value: []byte("23")}, - {Type: gqlscan.TokenArr}, + Left: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenInt, Value: []byte("1")}, + {ID: gqlscan.TokenInt, Value: []byte("23")}, + {ID: gqlscan.TokenArr}, }), - Right: union.Tokens([]token.Token{ - {Type: gqlscan.TokenArr}, - {Type: gqlscan.TokenInt, Value: []byte("23")}, - {Type: gqlscan.TokenInt, Value: []byte("1")}, - {Type: gqlscan.TokenArr}, + Right: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenInt, Value: []byte("23")}, + {ID: gqlscan.TokenInt, Value: []byte("1")}, + {ID: gqlscan.TokenArr}, }), Expect: false, }, { Name: "not_equal_tokens_array_empty", - Left: union.Tokens([]token.Token{ - {Type: gqlscan.TokenArr}, - {Type: gqlscan.TokenInt, Value: []byte("1")}, - {Type: gqlscan.TokenInt, Value: []byte("23")}, - {Type: gqlscan.TokenArr}, + Left: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenInt, Value: []byte("1")}, + {ID: gqlscan.TokenInt, Value: []byte("23")}, + {ID: gqlscan.TokenArr}, }), - Right: union.Tokens([]token.Token{ - {Type: gqlscan.TokenArr}, - {Type: gqlscan.TokenArr}, + Right: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArr}, }), Expect: false, }, { Name: "inf_tokens_null", - Left: union.Tokens([]token.Token{ - {Type: gqlscan.TokenNull}, + Left: union.Tokens([]gqlparse.Token{ + {ID: gqlscan.TokenNull}, }), Right: union.Null(), Expect: true, From cb9c680cc078ba7594f9fb4b81c49bb6e8531023 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sun, 5 Feb 2023 21:47:16 +0100 Subject: [PATCH 04/35] feat: Add schema support to config --- config/config.go | 209 +++++++------- config/config_test.go | 631 ++++++++++++++++++++---------------------- server/api.go | 40 ++- server/proxy.go | 7 +- 4 files changed, 434 insertions(+), 453 deletions(-) diff --git a/config/config.go b/config/config.go index 4486c10..6cf7f83 100644 --- a/config/config.go +++ b/config/config.go @@ -2,27 +2,28 @@ package config import ( "crypto/md5" + "encoding/base32" "errors" "fmt" "io" neturl "net/url" "os" "path/filepath" - "reflect" "regexp" "strings" "github.com/dustin/go-humanize" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "github.com/graph-guard/ggproxy/config/metadata" - "github.com/graph-guard/ggproxy/utilities/container/hamap" - "github.com/graph-guard/gqt" + gqt "github.com/graph-guard/gqt/v4" + gqlparser "github.com/vektah/gqlparser/v2" + gqlast "github.com/vektah/gqlparser/v2/ast" yaml "gopkg.in/yaml.v3" ) -var ConfigFileExtension = regexp.MustCompile(`\.(yml|yaml)$`) -var TemplateFileExtension = regexp.MustCompile(`\.gqt$`) +var ( + ConfigFileExtension = regexp.MustCompile(`\.(yml|yaml)$`) + TemplateFileExtension = regexp.MustCompile(`\.gqt$`) +) // MinReqBodySize defines the minimum accepted value for // `max-request-body-size` in bytes. @@ -40,37 +41,10 @@ var msgMaxReqBodySizeTooSmall = fmt.Sprintf( type Config struct { Proxy ProxyServerConfig API *APIServerConfig - Services *hamap.Map[[]byte, *Service] + Services map[string]*Service ServicesEnabled []*Service } -func (c *Config) Equal(d *Config) bool { - eq := true - c.Services.Visit(func(key []byte, value *Service) (stop bool) { - v, ok := d.Services.Get(key) - if !ok { - eq = false - return true - } - if !v.Equal(value) { - eq = false - return true - } - return - }) - if !eq { - return eq - } - - less := func(a, b *Service) bool { return a.ID < b.ID } - eq = eq && - reflect.DeepEqual(c.Proxy, d.Proxy) && - reflect.DeepEqual(c.API, d.API) && - cmp.Equal(c.ServicesEnabled, d.ServicesEnabled, cmpopts.SortSlices(less)) - - return eq -} - type ProxyServerConfig struct { Host string TLS TLS @@ -91,33 +65,22 @@ type Service struct { ID string Path string ForwardURL string - Templates *hamap.Map[[]byte, *Template] + Schema *gqlast.Schema + Templates map[string]*Template TemplatesEnabled []*Template ForwardReduced bool Enabled bool FilePath string } -func (c *Service) Equal(d *Service) bool { - less := func(a, b *Template) bool { return a.ID < b.ID } - return c.ID == d.ID && - c.Path == d.Path && - c.ForwardURL == d.ForwardURL && - c.ForwardReduced == d.ForwardReduced && - c.Enabled == d.Enabled && - c.FilePath == d.FilePath && - reflect.DeepEqual(c.Templates, d.Templates) && - cmp.Equal(c.TemplatesEnabled, d.TemplatesEnabled, cmpopts.SortSlices(less)) -} - type Template struct { - ID string - Source []byte - Document gqt.Doc - Name string - Tags []string - Enabled bool - FilePath string + ID string + Source []byte + GQTTemplate *gqt.Operation + Name string + Tags []string + Enabled bool + FilePath string } type serverConfig struct { @@ -147,6 +110,7 @@ type serviceConfig struct { ForwardReduced bool `yaml:"forward-reduced"` TemplatesAll string `yaml:"all-templates"` TemplatesEnabled string `yaml:"enabled-templates"` + Schema string `yaml:"schema"` } func New(path string) (c *Config, err error) { @@ -154,14 +118,12 @@ func New(path string) (c *Config, err error) { c = &Config{ Proxy: ProxyServerConfig{}, API: &APIServerConfig{}, - Services: hamap.New[[]byte, *Service](0, nil), + Services: make(map[string]*Service), } - err = c.readServerConfig(path) - if err != nil { + if err = c.readServerConfig(path); err != nil { return nil, err } - - return + return c, nil } func (c *Config) readServerConfig(path string) (err error) { @@ -343,14 +305,14 @@ func (c *Config) readAllServices(path string) (err error) { if err != nil { return err } - original, ok := c.Services.Get(h) + original, ok := c.Services[string(h)] if ok { return &ErrorDuplicate{ Original: original.FilePath, Duplicate: s.FilePath, } } - c.Services.Set(h, s) + c.Services[string(h)] = s } return @@ -381,7 +343,7 @@ func (c *Config) readEnabledServices(path string) (err error) { if err != nil { return err } - s, ok := c.Services.Get(h) + s, ok := c.Services[string(h)] if ok { s.Enabled = true c.ServicesEnabled = append(c.ServicesEnabled, s) @@ -402,17 +364,28 @@ func readServiceConfig(file *os.File) (s *Service, err error) { dirPath := filepath.Dir(file.Name()) sc := &serviceConfig{} - d := yaml.NewDecoder(file) - d.KnownFields(true) - if err := d.Decode(sc); err != nil { - return nil, &ErrorIllegal{ - FilePath: filePath, - Feature: "syntax", - Message: err.Error(), + { + d := yaml.NewDecoder(file) + d.KnownFields(true) + if err := d.Decode(sc); err != nil { + return nil, &ErrorIllegal{ + FilePath: filePath, + Feature: "syntax", + Message: err.Error(), + } } } - err = validateServiceConfig(sc, filePath) + if err := validateServiceConfig(sc, filePath); err != nil { + return nil, err + } + + // TODO: Add support for multiple graphqls files + var schemaPath string + if sc.Schema != "" { + schemaPath = filepath.Join(dirPath, sc.Schema) + } + schema, gqtParser, err := s.readSchema(schemaPath) if err != nil { return nil, err } @@ -439,7 +412,8 @@ func readServiceConfig(file *os.File) (s *Service, err error) { s = &Service{ ID: id, - Templates: hamap.New[[]byte, *Template](0, nil), + Schema: schema, + Templates: map[string]*Template{}, FilePath: filePath, Path: sc.Path, ForwardURL: sc.ForwardURL, @@ -447,7 +421,7 @@ func readServiceConfig(file *os.File) (s *Service, err error) { } // reading all templates - err = s.readAllTemplates(templatesAllPath) + err = s.readAllTemplates(templatesAllPath, gqtParser) if err != nil { return nil, err } @@ -504,7 +478,32 @@ func validateServiceConfig(sc *serviceConfig, path string) (err error) { return } -func (s *Service) readAllTemplates(path string) (err error) { +func (s *Service) readSchema(path string) (*gqlast.Schema, *gqt.Parser, error) { + if path == "" { + p, err := gqt.NewParser(nil) + return nil, p, err + } + + f, err := os.ReadFile(path) + if err != nil { + return nil, nil, fmt.Errorf("reading schema file: %w", err) + } + + schema, err := gqlparser.LoadSchema(&gqlast.Source{ + Name: path, + Input: string(f), + }) + if err != nil { + return nil, nil, fmt.Errorf("parsing schema: %w", err) + } + + gqtParser, err := gqt.NewParser([]gqt.Source{ + {Name: path, Content: string(f)}, + }) + return schema, gqtParser, err +} + +func (s *Service) readAllTemplates(path string, p *gqt.Parser) (err error) { dir, err := os.ReadDir(path) if err != nil { return fmt.Errorf("reading templates directory: %w", err) @@ -528,19 +527,19 @@ func (s *Service) readAllTemplates(path string) (err error) { return err } - t, err := readTemplate(file) + t, err := readTemplate(file, p) if err != nil { return err } - original, ok := s.Templates.Get(h) + original, ok := s.Templates[string(h)] if ok { return &ErrorDuplicate{ Original: original.FilePath, Duplicate: t.FilePath, } } - s.Templates.Set(h, t) + s.Templates[string(h)] = t } return @@ -571,7 +570,7 @@ func (s *Service) readEnabledTemplates(path string) (err error) { if err != nil { return err } - t, ok := s.Templates.Get(h) + t, ok := s.Templates[string(h)] if ok { t.Enabled = true s.TemplatesEnabled = append(s.TemplatesEnabled, t) @@ -587,7 +586,7 @@ func (s *Service) readEnabledTemplates(path string) (err error) { return } -func readTemplate(file *os.File) (t *Template, err error) { +func readTemplate(file *os.File, p *gqt.Parser) (t *Template, err error) { filePath := file.Name() id := strings.ToLower( @@ -617,22 +616,29 @@ func readTemplate(file *os.File) (t *Template, err error) { } } - doc, errParser := gqt.Parse(template) - if errParser.IsErr() { + doc, _, errs := p.Parse(template) + if errs != nil { + var msg strings.Builder + for i := range errs { + msg.WriteString(errs[i].Error()) + if i+1 < len(errs) { + msg.WriteString("; ") + } + } return nil, &ErrorIllegal{ FilePath: filePath, Feature: "template", - Message: errParser.Error(), + Message: msg.String(), } } t = &Template{ - ID: id, - Source: template, - Document: doc, - Name: meta.Name, - Tags: meta.Tags, - FilePath: filePath, + ID: id, + Source: template, + GQTTemplate: doc, + Name: meta.Name, + Tags: meta.Tags, + FilePath: filePath, } return @@ -724,9 +730,11 @@ func (e ErrorIllegal) Error() string { return b.String() } -var ErrPathNotAbsolute = errors.New("path is not starting with /") -var ErrURLProtocolProblem = errors.New("protocol is not supported or undefined") -var ErrURLNoHost = errors.New("host is not defined") +var ( + ErrPathNotAbsolute = errors.New("path is not starting with /") + ErrURLProtocolProblem = errors.New("protocol is not supported or undefined") + ErrURLNoHost = errors.New("host is not defined") +) var ValidProtocolSchemes = []string{"http", "https"} @@ -783,18 +791,15 @@ func openFile(path string) (*os.File, error) { return f, nil } -func calculateHash(file *os.File) (sum []byte, err error) { +func calculateHash(file *os.File) (string, error) { h := md5.New() - - _, err = io.Copy(h, file) - if err != nil { - return nil, err + if _, err := io.Copy(h, file); err != nil { + return "", fmt.Errorf("copying file to md5 hash: %w", err) } - _, err = file.Seek(0, io.SeekStart) - if err != nil { - return nil, err + if _, err := file.Seek(0, io.SeekStart); err != nil { + return "", fmt.Errorf("seeking start in file: %w", err) } - sum = h.Sum(nil) - - return + sum := h.Sum(nil) + s := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(sum) + return s, nil } diff --git a/config/config_test.go b/config/config_test.go index 89ac979..cf72154 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -2,6 +2,7 @@ package config_test import ( "crypto/md5" + "encoding/base32" "fmt" "io" "os" @@ -11,9 +12,11 @@ import ( "testing/fstest" "github.com/graph-guard/ggproxy/config" - "github.com/graph-guard/ggproxy/utilities/container/hamap" - "github.com/graph-guard/gqt" + "github.com/graph-guard/ggproxy/config/metadata" + "github.com/graph-guard/gqt/v4" "github.com/stretchr/testify/require" + gqlparser "github.com/vektah/gqlparser/v2" + "github.com/vektah/gqlparser/v2/ast" ) type TestOK struct { @@ -29,25 +32,16 @@ type TestError struct { var ServerConfigFileName = "config.yml" func TestReadConfig(t *testing.T) { - validFS(func(path string, conf *config.Config) { - for _, td := range []TestOK{ - { - Path: filepath.Join(path, ServerConfigFileName), - Expect: conf, - }, - } { - t.Run("", func(t *testing.T) { - c, err := config.New(td.Path) - require.NoError(t, err) - require.True(t, td.Expect.Equal(c)) - }) - } + validFS(t, func(basePath string, expect *config.Config) { + actual, err := config.New(filepath.Join(basePath, ServerConfigFileName)) + require.NoError(t, err) + require.Equal(t, expect, actual) }) } func TestReadConfigDefaultMaxReqBodySize(t *testing.T) { - validFS(func(path string, conf *config.Config) { - err := createFiles(map[string]any{ + validFS(t, func(basePath string, conf *config.Config) { + createFiles(t, map[string]any{ ServerConfigFileName: lines( `proxy:`, ` host: localhost:443`, @@ -63,44 +57,29 @@ func TestReadConfigDefaultMaxReqBodySize(t *testing.T) { `all-services: all-services`, `enabled-services: enabled-services`, ), - }, nil, path) - require.NoError(t, err) + }, nil, basePath) conf.Proxy.MaxReqBodySizeBytes = config.DefaultMaxReqBodySize - for _, td := range []TestOK{ - { - Path: filepath.Join(path, ServerConfigFileName), - Expect: conf, - }, - } { - t.Run("", func(t *testing.T) { - c, err := config.New(td.Path) - require.NoError(t, err) - require.True(t, td.Expect.Equal(c)) - }) - } }) } func TestReadConfigErrorMissingServerConfig(t *testing.T) { - validFS(func(path string, conf *config.Config) { - p := filepath.Join(path, ServerConfigFileName) + validFS(t, func(basePath string, _ *config.Config) { + p := filepath.Join(basePath, ServerConfigFileName) err := os.Remove(p) require.NoError(t, err) c, err := config.New(p) - require.Nil(t, c) require.Contains(t, err.Error(), "no such file or directory") + require.Nil(t, c) }) } func TestReadConfigErrorMalformedServerConfig(t *testing.T) { - validFS(func(path string, conf *config.Config) { - p := filepath.Join(path, ServerConfigFileName) - err := createFiles(map[string]any{ + validFS(t, func(basePath string, _ *config.Config) { + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ ServerConfigFileName: lines("not a valid config"), - }, nil, path) - require.NoError(t, err) + }, nil, basePath) c, err := config.New(p) - require.Nil(t, c) require.Equal(t, &config.ErrorIllegal{ FilePath: p, Feature: "syntax", @@ -108,53 +87,52 @@ func TestReadConfigErrorMalformedServerConfig(t *testing.T) { "line 1: cannot unmarshal !!str `not a v...` " + "into config.serverConfig", }, err) + require.Nil(t, c) }) } func TestReadConfigErrorMissingProxyHostConfig(t *testing.T) { - validFS(func(path string, conf *config.Config) { - p := filepath.Join(path, ServerConfigFileName) - err := createFiles(map[string]any{ + validFS(t, func(basePath string, _ *config.Config) { + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ ServerConfigFileName: lines( `proxy:`, ` host: `, ), - }, nil, path) - require.NoError(t, err) + }, nil, basePath) c, err := config.New(p) - require.Nil(t, c) require.Equal(t, &config.ErrorMissing{ FilePath: p, Feature: "proxy.host", }, err) + require.Nil(t, c) }) } func TestReadConfigErrorMissingAPIHostConfig(t *testing.T) { - validFS(func(path string, conf *config.Config) { - p := filepath.Join(path, ServerConfigFileName) - err := createFiles(map[string]any{ + validFS(t, func(basePath string, _ *config.Config) { + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ ServerConfigFileName: lines( `proxy:`, ` host: localhost:8080`, `api:`, ` host: `, ), - }, nil, path) - require.NoError(t, err) + }, nil, basePath) c, err := config.New(p) - require.Nil(t, c) require.Equal(t, &config.ErrorMissing{ FilePath: p, Feature: "api.host", }, err) + require.Nil(t, c) }) } func TestReadConfigErrorMissingProxyTLSCert(t *testing.T) { - validFS(func(path string, conf *config.Config) { - p := filepath.Join(path, ServerConfigFileName) - err := createFiles(map[string]any{ + validFS(t, func(basePath string, _ *config.Config) { + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ ServerConfigFileName: lines( `proxy:`, ` host: localhost:8080`, @@ -163,21 +141,20 @@ func TestReadConfigErrorMissingProxyTLSCert(t *testing.T) { `api:`, ` host: localhost:9090`, ), - }, nil, path) - require.NoError(t, err) + }, nil, basePath) c, err := config.New(p) - require.Nil(t, c) require.Equal(t, &config.ErrorMissing{ FilePath: p, Feature: "proxy.tls.cert-file", }, err) + require.Nil(t, c) }) } func TestReadConfigErrorMissingProxyTLSKey(t *testing.T) { - validFS(func(path string, conf *config.Config) { - p := filepath.Join(path, ServerConfigFileName) - err := createFiles(map[string]any{ + validFS(t, func(basePath string, _ *config.Config) { + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ ServerConfigFileName: lines( `proxy:`, ` host: localhost:8080`, @@ -186,21 +163,20 @@ func TestReadConfigErrorMissingProxyTLSKey(t *testing.T) { `api:`, ` host: localhost:9090`, ), - }, nil, path) - require.NoError(t, err) + }, nil, basePath) c, err := config.New(p) - require.Nil(t, c) require.Equal(t, &config.ErrorMissing{ FilePath: p, Feature: "proxy.tls.key-file", }, err) + require.Nil(t, c) }) } func TestReadConfigErrorMissingAPITLSCert(t *testing.T) { - validFS(func(path string, conf *config.Config) { - p := filepath.Join(path, ServerConfigFileName) - err := createFiles(map[string]any{ + validFS(t, func(basePath string, _ *config.Config) { + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ ServerConfigFileName: lines( `proxy:`, ` host: localhost:8080`, @@ -209,21 +185,20 @@ func TestReadConfigErrorMissingAPITLSCert(t *testing.T) { ` tls:`, ` key-file: api.key`, ), - }, nil, path) - require.NoError(t, err) + }, nil, basePath) c, err := config.New(p) - require.Nil(t, c) require.Equal(t, &config.ErrorMissing{ FilePath: p, Feature: "api.tls.cert-file", }, err) + require.Nil(t, c) }) } func TestReadConfigErrorMissingAPITLSKey(t *testing.T) { - validFS(func(path string, conf *config.Config) { - p := filepath.Join(path, ServerConfigFileName) - err := createFiles(map[string]any{ + validFS(t, func(basePath string, _ *config.Config) { + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ ServerConfigFileName: lines( `proxy:`, ` host: localhost:8080`, @@ -232,21 +207,20 @@ func TestReadConfigErrorMissingAPITLSKey(t *testing.T) { ` tls:`, ` cert-file: api.cert`, ), - }, nil, path) - require.NoError(t, err) + }, nil, basePath) c, err := config.New(p) - require.Nil(t, c) require.Equal(t, &config.ErrorMissing{ FilePath: p, Feature: "api.tls.key-file", }, err) + require.Nil(t, c) }) } func TestReadConfigErrorIllegalProxyMaxReqBodySize(t *testing.T) { - validFS(func(path string, conf *config.Config) { - p := filepath.Join(path, ServerConfigFileName) - err := createFiles(map[string]any{ + validFS(t, func(basePath string, _ *config.Config) { + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ ServerConfigFileName: lines( `proxy:`, ` host: localhost:8080`, @@ -254,10 +228,8 @@ func TestReadConfigErrorIllegalProxyMaxReqBodySize(t *testing.T) { `api:`, ` host: localhost:9090`, ), - }, nil, path) - require.NoError(t, err) + }, nil, basePath) c, err := config.New(p) - require.Nil(t, c) require.Equal(t, &config.ErrorIllegal{ FilePath: p, Feature: "proxy.max-request-body-size", @@ -266,72 +238,69 @@ func TestReadConfigErrorIllegalProxyMaxReqBodySize(t *testing.T) { config.MinReqBodySize, ), }, err) + require.Nil(t, c) }) } func TestReadServiceConfigErrorMissingConfig(t *testing.T) { - minValidFS(func(path string) { - p := filepath.Join(path, "all-services", "a.yml") - err := createFiles(map[string]any{ + minValidFS(t, func(basePath string) { + p := filepath.Join(basePath, "all-services", "a.yml") + createFiles(t, map[string]any{ "all-services": map[string]any{ - "irrelevant_file.txt": []byte(`this file only keeps the directory`), + "irrelevant_file.txt": `this file only keeps the directory`, }, - }, nil, path) - require.NoError(t, err) - _, err = config.New(p) + }, nil, basePath) + c, err := config.New(p) require.Contains(t, err.Error(), "no such file or directory") + require.Nil(t, c) }) } func TestReadConfigErrorMalformedMetadata(t *testing.T) { - validFS(func(path string, conf *config.Config) { + validFS(t, func(basePath string, _ *config.Config) { p := filepath.Join("all-templates", "a", "a.gqt") - err := createFiles(map[string]any{ + createFiles(t, map[string]any{ p: lines( "---", "malformed metadata", "---", `query { foo }`, ), - }, nil, path) - require.NoError(t, err) - _, err = config.New(filepath.Join(path, ServerConfigFileName)) + }, nil, basePath) + c, err := config.New(filepath.Join(basePath, ServerConfigFileName)) require.Equal(t, &config.ErrorIllegal{ - FilePath: filepath.Join(path, p), + FilePath: filepath.Join(basePath, p), Feature: "metadata", Message: "decoding yaml: yaml: " + "unmarshal errors:\n " + "line 1: cannot unmarshal !!str `malform...` " + "into metadata.Metadata", }, err) + require.Nil(t, c) }) } func TestReadConfigErrorDuplicateTemplate(t *testing.T) { - validFS(func(path string, conf *config.Config) { + validFS(t, func(basePath string, _ *config.Config) { t1 := filepath.Join("all-templates", "a", "d1.gqt") t2 := filepath.Join("all-templates", "a", "d2.gqt") - err := createFiles(map[string]any{ - t1: []byte( - `query { duplicate }`, - ), - t2: []byte( - `query { duplicate }`, - ), - }, nil, path) - require.NoError(t, err) - _, err = config.New(filepath.Join(path, ServerConfigFileName)) + createFiles(t, map[string]any{ + t1: `query { foo }`, + t2: `query { foo }`, + }, nil, basePath) + c, err := config.New(filepath.Join(basePath, ServerConfigFileName)) require.Equal(t, &config.ErrorDuplicate{ - Original: filepath.Join(path, t1), - Duplicate: filepath.Join(path, t2), + Original: filepath.Join(basePath, t1), + Duplicate: filepath.Join(basePath, t2), }, err) + require.Nil(t, c) }) } func TestReadConfigErrorDuplicateService(t *testing.T) { - minValidFS(func(path string) { - p := filepath.Join(path, ServerConfigFileName) - err := createFiles(map[string]any{ + minValidFS(t, func(basePath string) { + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ "all-services": map[string]any{ "a.yml": lines( `path: /`, @@ -346,141 +315,133 @@ func TestReadConfigErrorDuplicateService(t *testing.T) { `enabled-templates: ../enabled-templates/a`, ), }, - }, nil, path) - require.NoError(t, err) - _, err = config.New(p) + }, nil, basePath) + c, err := config.New(p) require.Equal(t, &config.ErrorDuplicate{ - Original: filepath.Join(path, "all-services", "a.yml"), - Duplicate: filepath.Join(path, "all-services", "b.yml"), + Original: filepath.Join(basePath, "all-services", "a.yml"), + Duplicate: filepath.Join(basePath, "all-services", "b.yml"), }, err) + require.Nil(t, c) }) } func TestReadConfigErrorMissingPath(t *testing.T) { - minValidFS(func(path string) { - p := filepath.Join(path, ServerConfigFileName) - err := createFiles(map[string]any{ + minValidFS(t, func(basePath string) { + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ "all-services": map[string]any{ "a.yml": lines( `forward-url: http://localhost:8080/`, ), }, - }, nil, path) - require.NoError(t, err) - _, err = config.New(p) + }, nil, basePath) + c, err := config.New(p) require.Equal(t, &config.ErrorMissing{ FilePath: filepath.Join( - path, "all-services", "a.yml", + basePath, "all-services", "a.yml", ), Feature: "path", }, err) + require.Nil(t, c) }) - } func TestReadConfigErrorMissingForwardURL(t *testing.T) { - minValidFS(func(path string) { - p := filepath.Join(path, ServerConfigFileName) - err := createFiles(map[string]any{ + minValidFS(t, func(basePath string) { + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ "all-services": map[string]any{ "a.yml": lines( `path: /`, ), }, - }, nil, path) - require.NoError(t, err) - _, err = config.New(p) + }, nil, basePath) + c, err := config.New(p) require.Equal(t, &config.ErrorMissing{ FilePath: filepath.Join( - path, "all-services", "a.yml", + basePath, "all-services", "a.yml", ), Feature: "forward-url", }, err) + require.Nil(t, c) }) } func TestReadConfigErrorInvalidPath(t *testing.T) { - minValidFS(func(path string) { - p := filepath.Join(path, ServerConfigFileName) - err := createFiles(map[string]any{ + minValidFS(t, func(basePath string) { + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ "all-services": map[string]any{ "a.yml": lines( `path: invalid_path`, `forward-url: http://localhost:8080/`, ), }, - }, nil, path) - require.NoError(t, err) - _, err = config.New(p) + }, nil, basePath) + c, err := config.New(p) require.Equal(t, &config.ErrorIllegal{ - FilePath: filepath.Join(path, "all-services", "a.yml"), + FilePath: filepath.Join(basePath, "all-services", "a.yml"), Feature: "path", Message: `path is not starting with /`, }, err) + require.Nil(t, c) }) } func TestReadConfigErrorInvalidForwardURLInvalidScheme(t *testing.T) { - minValidFS(func(path string) { - p := filepath.Join(path, ServerConfigFileName) - err := createFiles(map[string]any{ + minValidFS(t, func(basePath string) { + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ "all-services": map[string]any{ "a.yml": lines( `path: /`, `forward-url: localhost:8080`, ), }, - }, nil, path) - require.NoError(t, err) - _, err = config.New(p) + }, nil, basePath) + c, err := config.New(p) require.Equal(t, &config.ErrorIllegal{ - FilePath: filepath.Join(path, "all-services", "a.yml"), + FilePath: filepath.Join(basePath, "all-services", "a.yml"), Feature: "forward-url", Message: `protocol is not supported or undefined`, }, err) + require.Nil(t, c) }) } func TestReadConfigErrorInvalidTemplate(t *testing.T) { - validFS(func(path string, conf *config.Config) { + validFS(t, func(basePath string, _ *config.Config) { p := filepath.Join("all-templates", "a", "invalid_template.gqt") - err := createFiles(map[string]any{ - p: []byte( - `invalid { template }`, - ), - }, nil, path) - require.NoError(t, err) - _, err = config.New(filepath.Join(path, ServerConfigFileName)) + createFiles(t, map[string]any{p: `invalid { template }`}, nil, basePath) + c, err := config.New(filepath.Join(basePath, ServerConfigFileName)) require.Equal(t, &config.ErrorIllegal{ - FilePath: filepath.Join(path, p), + FilePath: filepath.Join(basePath, p), Feature: "template", - Message: `error at 0: unexpected definition`, + Message: `1:1: unexpected token, expected ` + + `query, mutation, or subscription operation definition`, }, err) + require.Nil(t, c) }) } func TestReadConfigErrorInvalidTemplateID(t *testing.T) { - validFS(func(path string, conf *config.Config) { + validFS(t, func(basePath string, _ *config.Config) { p := filepath.Join("all-templates", "a", "invalid_template#.gqt") - err := createFiles(map[string]any{ - p: []byte( - `invalid { template }`, - ), - }, nil, path) - require.NoError(t, err) - _, err = config.New(filepath.Join(path, ServerConfigFileName)) + createFiles(t, map[string]any{p: `invalid { template }`}, nil, basePath) + c, err := config.New(filepath.Join(basePath, ServerConfigFileName)) require.Equal(t, &config.ErrorIllegal{ - FilePath: filepath.Join(path, p), + FilePath: filepath.Join(basePath, p), Feature: "id", Message: `contains illegal character at index 16`, }, err) + require.Nil(t, c) }) } func TestReadConfigErrorInvalidServiceID(t *testing.T) { - validFS(func(path string, conf *config.Config) { - p := filepath.Join(path, ServerConfigFileName) - err := createFiles(map[string]any{ + validFS(t, func(basePath string, _ *config.Config) { + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ "all-services": map[string]any{ "a#.yml": lines( `path: /`, @@ -489,49 +450,51 @@ func TestReadConfigErrorInvalidServiceID(t *testing.T) { `enabled-templates: ../enabled-templates/a`, ), }, - }, nil, path) - require.NoError(t, err) - _, err = config.New(p) + }, nil, basePath) + c, err := config.New(p) require.Equal(t, &config.ErrorIllegal{ FilePath: filepath.Join( - path, + basePath, "all-services", "a#.yml", ), Feature: "id", Message: `contains illegal character at index 1`, }, err) + require.Nil(t, c) }) } func TestReadConfigErrorMalformedConfig(t *testing.T) { - validFS(func(path string, conf *config.Config) { - p := filepath.Join(path, ServerConfigFileName) - err := createFiles(map[string]any{ + validFS(t, func(basePath string, _ *config.Config) { + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ "all-services": map[string]any{ "a.yml": lines( `malformed yaml`, ), }, - }, nil, path) - require.NoError(t, err) - _, err = config.New(p) + }, nil, basePath) + c, err := config.New(p) require.Equal(t, &config.ErrorIllegal{ - FilePath: filepath.Join(path, "all-services", "a.yml"), + FilePath: filepath.Join(basePath, "all-services", "a.yml"), Feature: "syntax", Message: "yaml: unmarshal errors:\n " + "line 1: cannot unmarshal !!str `malform...` " + "into config.serviceConfig", }, err) + require.Nil(t, c) }) } func TestErrorString(t *testing.T) { for _, td := range []struct { + name string input error expect string }{ { + name: "missing_feature_in", input: config.ErrorMissing{ FilePath: "path/to/file.txt", Feature: "some_feature", @@ -539,12 +502,14 @@ func TestErrorString(t *testing.T) { expect: "missing some_feature in path/to/file.txt", }, { + name: "missing_file", input: config.ErrorMissing{ FilePath: "path/to/file.txt", }, expect: "missing path/to/file.txt", }, { + name: "illegal_feature_in", input: config.ErrorIllegal{ FilePath: "path/to/file.txt", Feature: "some_feature", @@ -553,6 +518,7 @@ func TestErrorString(t *testing.T) { expect: "illegal some_feature in path/to/file.txt: some message", }, { + name: "duplicate", input: config.ErrorDuplicate{ Original: "path/to/file_a.txt", Duplicate: "path/to/file_b.txt", @@ -560,13 +526,13 @@ func TestErrorString(t *testing.T) { expect: "path/to/file_b.txt is a duplicate of path/to/file_a.txt", }, } { - t.Run("", func(t *testing.T) { + t.Run(td.name, func(t *testing.T) { require.Equal(t, td.expect, td.input.Error()) }) } } -func minValidFS(fn func(path string)) { +func minValidFS(t *testing.T, fn func(basePath string)) { base, err := os.MkdirTemp("", "ggproxy-") if err != nil { panic(err) @@ -605,39 +571,38 @@ func minValidFS(fn func(path string)) { }, } - var hashes = make(map[string][]byte) + hashes := make(map[string]string) - if err := createDirs(dirs, base); err != nil { - panic(err) - } - if err := createFiles(files, hashes, base); err != nil { - panic(err) - } + createDirs(t, dirs, base) + createFiles(t, files, hashes, base) fn(base) } -func validFS(fn func(path string, conf *config.Config)) { +// validFS calls fn providing a valid setup filesystem. +func validFS(t *testing.T, fn func(basePath string, conf *config.Config)) { base, err := os.MkdirTemp("", "ggproxy-") if err != nil { panic(err) } defer os.RemoveAll(base) - dirs := map[string]any{ + type M = map[string]any + + dirs := M{ "all-services": nil, "enabled-services": nil, - "all-templates": map[string]any{ + "all-templates": M{ "a": nil, "b": nil, }, - "enabled-templates": map[string]any{ + "enabled-templates": M{ "a": nil, "b": nil, }, "irrelevant-dir": nil, } - files := map[string]any{ + files := M{ ServerConfigFileName: lines( `proxy:`, ` host: localhost:443`, @@ -656,24 +621,27 @@ func validFS(fn func(path string, conf *config.Config)) { `all-services: all-services`, `enabled-services: enabled-services`, ), - "all-services": map[string]any{ + "all-services": M{ "a.yml": lines( `path: "/path"`, `forward-url: "http://localhost:8080/path"`, `forward-reduced: true`, + `schema: "schema_a.graphqls"`, `all-templates: "../all-templates/a"`, `enabled-templates: "../enabled-templates/a"`, ), + "schema_a.graphqls": lines(`type Query { foo:Int bar:String! }`), "b.yml": lines( `path: /`, `forward-url: "http://localhost:9090/"`, + // Schemaless. `all-templates: "../all-templates/b"`, `enabled-templates: "../enabled-templates/b"`, ), - "ignored_file.txt": []byte(`this file should be ignored`), + "ignored_file.txt": `this file should be ignored`, }, - "all-templates": map[string]any{ - "a": map[string]any{ + "all-templates": M{ + "a": M{ "a.gqt": lines( "---", `name: "Template A"`, @@ -691,16 +659,16 @@ func validFS(fn func(path string, conf *config.Config)) { `query { bar }`, ), }, - "b": map[string]any{ - "c.gqt": []byte(`query { maz }`), - "ignored_file.txt": []byte(`this file should be ignored`), + "b": M{ + "c.gqt": `query { maz }`, + "ignored_file.txt": `this file should be ignored`, }, }, "irrelevant-file.txt": lines( `this file is irrelevant and exists only for the purposes`, `of testing function ReadConfig.`, ), - "irrelevant-dir": map[string]any{ + "irrelevant-dir": M{ "irrelevant_file.txt": lines( `this file is irrelevant and exists only for the purposes`, `of testing function ReadConfig.`, @@ -717,99 +685,121 @@ func validFS(fn func(path string, conf *config.Config)) { "all-templates/b/ignored-file.txt": "enabled-templates/b/ignored-file.txt", } - var hashes = make(map[string][]byte) + hashes := make(map[string]string) - if err := createDirs(dirs, base); err != nil { - panic(err) - } - if err := createFiles(files, hashes, base); err != nil { - panic(err) - } - if err := createSymlinks(links, base); err != nil { - panic(err) - } + createDirs(t, dirs, base) + createFiles(t, files, hashes, base) + createSymlinks(t, links, base) - path := "" - services := hamap.New[[]byte, *config.Service](0, nil) - serviceATemplates := hamap.New[[]byte, *config.Template](0, nil) - serviceBTemplates := hamap.New[[]byte, *config.Template](0, nil) - path = filepath.Join(base, "all-templates", "a", "a.gqt") - serviceATemplates.Set(hashes[path], - &config.Template{ + serviceASchema, err := gqlparser.LoadSchema(&ast.Source{ + Name: filepath.Join(base, "all-services", "schema_a.graphqls"), + Input: files["all-services"].(M)["schema_a.graphqls"].(string), + }) + require.NoError(t, err) + + serviceASchemaParser, err := gqt.NewParser([]gqt.Source{{ + Name: filepath.Join(base, "all-services", "schema_a.graphqls"), + Content: files["all-services"].(M)["schema_a.graphqls"].(string), + }}) + require.NoError(t, err) + + services := make(map[string]*config.Service) + templatesA := map[string]*config.Template{} + templatesB := map[string]*config.Template{} + + { + p := filepath.Join(base, "all-templates", "a", "a.gqt") + templatesA[hashes[p]] = &config.Template{ ID: "a", Name: "Template A", Tags: []string{"tag_a"}, - Source: lines(`query { foo }`), - Document: gqt.Doc{ - Query: []gqt.Selection{ - gqt.SelectionField{ - Name: "foo", - }, - }, - }, + Source: []byte(lines(`query { foo }`)), + GQTTemplate: func() *gqt.Operation { + _, body, err := metadata.Parse( + []byte(files["all-templates"].(M)["a"].(M)["a.gqt"].(string)), + ) + require.NoError(t, err) + template, _, errs := serviceASchemaParser.Parse(body) + require.Nil(t, errs) + return template + }(), Enabled: true, - FilePath: path, - }, - ) - path = filepath.Join(base, "all-templates", "a", "b.gqt") - serviceATemplates.Set(hashes[path], - &config.Template{ + FilePath: p, + } + } + + { + p := filepath.Join(base, "all-templates", "a", "b.gqt") + templatesA[hashes[p]] = &config.Template{ ID: "b", Tags: []string{"tag_b1", "tag_b2"}, - Source: lines(`query { bar }`), - Document: gqt.Doc{ - Query: []gqt.Selection{ - gqt.SelectionField{ - Name: "bar", - }, - }, + Source: []byte(lines(`query { bar }`)), + GQTTemplate: func() *gqt.Operation { + _, body, err := metadata.Parse( + []byte(files["all-templates"].(M)["a"].(M)["b.gqt"].(string)), + ) + require.NoError(t, err) + templates, _, errs := serviceASchemaParser.Parse(body) + require.Nil(t, errs) + return templates + }(), + Enabled: true, + FilePath: p, + } + } + + { + p := filepath.Join(base, "all-services", "a.yml") + services[hashes[p]] = &config.Service{ + ID: "a", + Path: "/path", + ForwardURL: "http://localhost:8080/path", + ForwardReduced: true, + Schema: serviceASchema, + Templates: templatesA, + TemplatesEnabled: []*config.Template{ + templatesA[hashes[filepath.Join(base, "all-templates", "a", "a.gqt")]], + templatesA[hashes[filepath.Join(base, "all-templates", "a", "b.gqt")]], }, Enabled: true, - FilePath: path, - }, - ) - path = filepath.Join(base, "all-services", "a.yml") - services.Set(hashes[path], - &config.Service{ - ID: "a", - Path: "/path", - ForwardURL: "http://localhost:8080/path", - ForwardReduced: true, - Templates: serviceATemplates, - TemplatesEnabled: serviceATemplates.Values(), - Enabled: true, - FilePath: path, - }, - ) - path = filepath.Join(base, "all-templates", "b", "c.gqt") - serviceBTemplates.Set(hashes[path], - &config.Template{ + FilePath: p, + } + } + + { + p := filepath.Join(base, "all-templates", "b", "c.gqt") + templatesB[hashes[p]] = &config.Template{ ID: "c", Source: []byte(`query { maz }`), - Document: gqt.Doc{ - Query: []gqt.Selection{ - gqt.SelectionField{ - Name: "maz", - }, - }, + GQTTemplate: func() *gqt.Operation { + _, body, err := metadata.Parse( + []byte(files["all-templates"].(M)["b"].(M)["c.gqt"].(string)), + ) + require.NoError(t, err) + template, _, errs := gqt.Parse(body) + require.Nil(t, errs) + return template + }(), + Enabled: true, + FilePath: p, + } + } + + { + p := filepath.Join(base, "all-services", "b.yml") + services[hashes[p]] = &config.Service{ + ID: "b", + Path: "/", + ForwardURL: "http://localhost:9090/", + ForwardReduced: false, + Templates: templatesB, + TemplatesEnabled: []*config.Template{ + templatesB[hashes[filepath.Join(base, "all-templates", "b", "c.gqt")]], }, Enabled: true, - FilePath: path, - }, - ) - path = filepath.Join(base, "all-services", "b.yml") - services.Set(hashes[path], - &config.Service{ - ID: "b", - Path: "/", - ForwardURL: "http://localhost:9090/", - ForwardReduced: false, - Templates: serviceBTemplates, - TemplatesEnabled: serviceBTemplates.Values(), - Enabled: true, - FilePath: path, - }, - ) + FilePath: p, + } + } conf := &config.Config{ Proxy: config.ProxyServerConfig{ @@ -827,87 +817,80 @@ func validFS(fn func(path string, conf *config.Config)) { KeyFile: "api.key", }, }, - Services: services, - ServicesEnabled: services.Values(), + Services: services, + ServicesEnabled: []*config.Service{ + services[hashes[filepath.Join(base, "all-services", "a.yml")]], + services[hashes[filepath.Join(base, "all-services", "b.yml")]], + }, } fn(base, conf) } -func createDirs(dirs map[string]any, path string) error { +func createDirs(t *testing.T, dirs map[string]any, basePath string) { for k, v := range dirs { - p := filepath.Join(path, k) - if err := os.Mkdir(p, 0775); err != nil { - return err - } + p := filepath.Join(basePath, k) + err := os.Mkdir(p, 0o775) + require.NoError(t, err) if v != nil { switch vt := v.(type) { case map[string]any: - if err := createDirs(vt, p); err != nil { - return err - } + createDirs(t, vt, p) + default: + panic(fmt.Errorf("unsupported dir content type: %v", v)) } } } - - return nil } -func createFiles(files map[string]any, hashes map[string][]byte, path string) error { +func createFiles( + t *testing.T, + files map[string]any, + hashes map[string]string, + basePath string, +) { for k, v := range files { - p := filepath.Join(path, k) + p := filepath.Join(basePath, k) switch vt := v.(type) { - case []byte: + case string: f, err := os.Create(p) - if err != nil { - return err - } else { - if _, err := f.Write(vt); err != nil { - return err - } - } + require.NoError(t, err) + _, err = f.Write([]byte(vt)) + require.NoError(t, err) if hashes != nil { - hashes[p] = calculateHash(f) + hashes[p] = calculateHash(t, f) } case map[string]any: - if err := createFiles(vt, hashes, p); err != nil { - return err - } + createFiles(t, vt, hashes, p) + default: + panic(fmt.Errorf("unsupported file content type: %#v", v)) } } - - return nil } -func createSymlinks(links map[string]string, path string) error { +func createSymlinks(t *testing.T, links map[string]string, path string) { for k, v := range links { - if err := os.Symlink(filepath.Join(path, k), filepath.Join(path, v)); err != nil { - return err - } + err := os.Symlink(filepath.Join(path, k), filepath.Join(path, v)) + require.NoError(t, err) } - - return nil } -func lines(lines ...string) []byte { +func lines(lines ...string) string { var b strings.Builder for i := range lines { b.WriteString(lines[i]) b.WriteByte('\n') } - return []byte(b.String()) + return b.String() } -func calculateHash(file *os.File) []byte { +func calculateHash(t *testing.T, file *os.File) string { _, err := file.Seek(0, io.SeekStart) - if err != nil { - panic(err) - } + require.NoError(t, err) h := md5.New() _, err = io.Copy(h, file) - if err != nil { - panic(err) - } - - return h.Sum(nil) + require.NoError(t, err) + sum := h.Sum(nil) + s := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(sum) + return s } diff --git a/server/api.go b/server/api.go index d29bb51..7690a59 100644 --- a/server/api.go +++ b/server/api.go @@ -22,7 +22,7 @@ import ( "github.com/graph-guard/ggproxy/config" "github.com/graph-guard/ggproxy/engines/rmap" "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/gqt" + gqt "github.com/graph-guard/gqt/v4" plog "github.com/phuslu/log" "github.com/valyala/fasthttp" ) @@ -141,7 +141,6 @@ func (s *API) Serve(listener net.Listener) { // TLS disabled if listener != nil { err = s.server.Serve(listener) - } else { err = s.server.ListenAndServe() } @@ -241,14 +240,10 @@ func makeServices( conf *config.Config, proxyServer *Proxy, ) map[string]*model.Service { - m := make( - map[string]*model.Service, - conf.Services.Len(), - ) - conf.Services.Visit(func(key []byte, s *config.Service) (stop bool) { + m := make(map[string]*model.Service, len(conf.Services)) + for _, s := range conf.Services { m[s.ID] = makeService(conf, s, proxyServer) - return - }) + } return m } @@ -259,22 +254,22 @@ func makeService( ) *model.Service { stats := proxyServer.GetServiceStatistics(s.ID) service := &model.Service{ - Stats: stats, - TemplatesByID: make( - map[string]*model.Template, - s.Templates.Len(), + Stats: stats, + TemplatesByID: make(map[string]*model.Template, len(s.Templates)), + ID: s.ID, + ForwardURL: s.ForwardURL, + Enabled: s.Enabled, + TemplatesEnabled: make([]*model.Template, len(s.TemplatesEnabled)), + TemplatesDisabled: make( + []*model.Template, + len(s.Templates)-len(s.TemplatesEnabled), ), - ID: s.ID, - ForwardURL: s.ForwardURL, - Enabled: s.Enabled, - TemplatesEnabled: make([]*model.Template, len(s.TemplatesEnabled)), - TemplatesDisabled: make([]*model.Template, s.Templates.Len()-len(s.TemplatesEnabled)), } { // Initialize matcher engine - d := make(map[string]gqt.Doc, s.Templates.Len()) - s.Templates.Visit(func(key []byte, t *config.Template) (stop bool) { - d[t.ID] = t.Document + d := make(map[string]*gqt.Operation, len(s.Templates)) + for _, t := range s.Templates { + d[t.ID] = t.GQTTemplate tm := &model.Template{ Service: service, Stats: proxyServer.GetTemplateStatistics(s.ID, t.ID), @@ -286,8 +281,7 @@ func makeService( } service.TemplatesEnabled = append(service.TemplatesEnabled, tm) service.TemplatesByID[t.ID] = tm - return - }) + } var err error service.Matcher, err = rmap.New(d, 0) diff --git a/server/proxy.go b/server/proxy.go index 67be781..bcad012 100644 --- a/server/proxy.go +++ b/server/proxy.go @@ -12,7 +12,7 @@ import ( "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/ggproxy/statistics" "github.com/graph-guard/ggproxy/utilities/tokenwriter" - "github.com/graph-guard/gqt" + gqt "github.com/graph-guard/gqt/v4" plog "github.com/phuslu/log" "github.com/tidwall/gjson" "github.com/valyala/fasthttp" @@ -94,9 +94,9 @@ func NewProxy( log: log, matcherpool: sync.Pool{ New: func() any { - d := make(map[string]gqt.Doc, len(s.TemplatesEnabled)) + d := make(map[string]*gqt.Operation, len(s.TemplatesEnabled)) for _, t := range s.TemplatesEnabled { - d[t.ID] = t.Document + d[t.ID] = t.GQTTemplate } engine, err := rmap.New(d, 0) if err != nil { @@ -330,7 +330,6 @@ func (s *Proxy) Serve(listener net.Listener) { // TLS disabled if listener != nil { err = s.server.Serve(listener) - } else { err = s.server.ListenAndServe(s.config.Proxy.Host) } From 90f538c64b80d410f36d7ebf02bfe6385a590acb Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sun, 5 Feb 2023 21:59:48 +0100 Subject: [PATCH 05/35] feat: Improve config error reporting --- config/config.go | 12 ++++++++++-- config/config_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/config/config.go b/config/config.go index 6cf7f83..d2681a9 100644 --- a/config/config.go +++ b/config/config.go @@ -486,7 +486,10 @@ func (s *Service) readSchema(path string) (*gqlast.Schema, *gqt.Parser, error) { f, err := os.ReadFile(path) if err != nil { - return nil, nil, fmt.Errorf("reading schema file: %w", err) + return nil, nil, &ErrorMissing{ + FilePath: path, + Feature: "schema", + } } schema, err := gqlparser.LoadSchema(&gqlast.Source{ @@ -494,7 +497,11 @@ func (s *Service) readSchema(path string) (*gqlast.Schema, *gqt.Parser, error) { Input: string(f), }) if err != nil { - return nil, nil, fmt.Errorf("parsing schema: %w", err) + return nil, nil, &ErrorIllegal{ + FilePath: path, + Feature: "schema", + Message: fmt.Sprintf("invalid schema: %v", err.Error()), + } } gqtParser, err := gqt.NewParser([]gqt.Source{ @@ -791,6 +798,7 @@ func openFile(path string) (*os.File, error) { return f, nil } +// calculateHash returns a base32 encoded MD5 hash of file. func calculateHash(file *os.File) (string, error) { h := md5.New() if _, err := io.Copy(h, file); err != nil { diff --git a/config/config_test.go b/config/config_test.go index cf72154..058b993 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -409,6 +409,37 @@ func TestReadConfigErrorInvalidForwardURLInvalidScheme(t *testing.T) { }) } +func TestReadConfigErrorInvalidSchema(t *testing.T) { + validFS(t, func(basePath string, _ *config.Config) { + p := filepath.Join("all-services", "schema_a.graphqls") + createFiles(t, map[string]any{p: `type Query{ invalid }`}, nil, basePath) + c, err := config.New(filepath.Join(basePath, ServerConfigFileName)) + require.Equal(t, &config.ErrorIllegal{ + FilePath: filepath.Join(basePath, p), + Feature: "schema", + Message: fmt.Sprintf( + `invalid schema: %s:1: Expected :, found }`, + filepath.Join(basePath, p), + ), + }, err) + require.Nil(t, c) + }) +} + +func TestReadConfigErrorMissingSchema(t *testing.T) { + validFS(t, func(basePath string, _ *config.Config) { + p := filepath.Join(basePath, "all-services", "schema_a.graphqls") + err := os.Remove(p) + require.NoError(t, err) + c, err := config.New(filepath.Join(basePath, ServerConfigFileName)) + require.Equal(t, &config.ErrorMissing{ + FilePath: p, + Feature: "schema", + }, err) + require.Nil(t, c) + }) +} + func TestReadConfigErrorInvalidTemplate(t *testing.T) { validFS(t, func(basePath string, _ *config.Config) { p := filepath.Join("all-templates", "a", "invalid_template.gqt") From e5a0130b94b675e0c3d57ecf4a2a47816fb9f9dc Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Wed, 8 Feb 2023 23:18:01 +0100 Subject: [PATCH 06/35] refactor: Cleanup gqlparse package API --- gqlparse/gqlparse.go | 8 ---- gqlparse/gqlparse_test.go | 90 +++++++++++++++++++++------------------ 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/gqlparse/gqlparse.go b/gqlparse/gqlparse.go index 672f670..7fdabe7 100644 --- a/gqlparse/gqlparse.go +++ b/gqlparse/gqlparse.go @@ -40,14 +40,6 @@ func (t Token) VariableIndex() (index int) { return -1 } -// MakeVariableIndexToken creates a variable index token. -func MakeVariableIndexToken(index int, name string) Token { - return Token{ - ID: gqlscan.Token(TokenTypeValIndexOffset + index), - Value: []byte(name), - } -} - // TokenTypeValIndexOffset defines the offset that needs to be subtracted // from Token.ID in order to get the index of the value when Token.ID is > 99 const TokenTypeValIndexOffset = 100 diff --git a/gqlparse/gqlparse_test.go b/gqlparse/gqlparse_test.go index 4ff3846..a783e72 100644 --- a/gqlparse/gqlparse_test.go +++ b/gqlparse/gqlparse_test.go @@ -32,7 +32,7 @@ var testdata = []decl.Declaration[TestSuccess]{ Token(gqlscan.TokenField, "hero"), Token(gqlscan.TokenArgList), Token(gqlscan.TokenArgName, "episode"), - gqlparse.MakeVariableIndexToken(0, "e"), + MakeVariableIndexToken(0, "e"), Token(gqlscan.TokenArgListEnd), Token(gqlscan.TokenSet), Token(gqlscan.TokenField, "id"), @@ -72,7 +72,7 @@ var testdata = []decl.Declaration[TestSuccess]{ // $v Token(gqlscan.TokenArgName, "a"), - gqlparse.MakeVariableIndexToken(0, "v"), + MakeVariableIndexToken(0, "v"), Token(gqlscan.TokenArgListEnd), Token(gqlscan.TokenSetEnd), @@ -176,7 +176,7 @@ var testdata = []decl.Declaration[TestSuccess]{ Token(gqlscan.TokenField, "foo"), Token(gqlscan.TokenArgList), Token(gqlscan.TokenArgName, "bar"), - gqlparse.MakeVariableIndexToken(0, "v"), + MakeVariableIndexToken(0, "v"), Token(gqlscan.TokenArgListEnd), Token(gqlscan.TokenSetEnd), @@ -208,25 +208,25 @@ var testdata = []decl.Declaration[TestSuccess]{ Token(gqlscan.TokenField, "foo"), Token(gqlscan.TokenArgList), Token(gqlscan.TokenArgName, "bar"), - gqlparse.MakeVariableIndexToken(0, "v"), + MakeVariableIndexToken(0, "v"), Token(gqlscan.TokenArgListEnd), Token(gqlscan.TokenField, "bar"), Token(gqlscan.TokenArgList), Token(gqlscan.TokenArgName, "baz"), - gqlparse.MakeVariableIndexToken(0, "v"), + MakeVariableIndexToken(0, "v"), Token(gqlscan.TokenArgListEnd), Token(gqlscan.TokenField, "bar"), Token(gqlscan.TokenArgList), Token(gqlscan.TokenArgName, "baz"), - gqlparse.MakeVariableIndexToken(0, "v"), + MakeVariableIndexToken(0, "v"), Token(gqlscan.TokenArgListEnd), Token(gqlscan.TokenField, "baz"), Token(gqlscan.TokenArgList), Token(gqlscan.TokenArgName, "fuz"), - gqlparse.MakeVariableIndexToken(0, "v"), + MakeVariableIndexToken(0, "v"), Token(gqlscan.TokenArgListEnd), Token(gqlscan.TokenSetEnd), @@ -529,71 +529,71 @@ var testdata = []decl.Declaration[TestSuccess]{ // $v_s: String! = """default value""", Token(gqlscan.TokenArgName, "a1"), - gqlparse.MakeVariableIndexToken(0, "v_s"), + MakeVariableIndexToken(0, "v_s"), // $v_i: Int! = 42, Token(gqlscan.TokenArgName, "a2"), - gqlparse.MakeVariableIndexToken(1, "v_i"), + MakeVariableIndexToken(1, "v_i"), // $v_f: Float! = 3.14, Token(gqlscan.TokenArgName, "a3"), - gqlparse.MakeVariableIndexToken(2, "v_f"), + MakeVariableIndexToken(2, "v_f"), // $v_b: Boolean! = true, Token(gqlscan.TokenArgName, "a4"), - gqlparse.MakeVariableIndexToken(3, "v_b"), + MakeVariableIndexToken(3, "v_b"), // $v_d: ID! = "default ID", Token(gqlscan.TokenArgName, "a5"), - gqlparse.MakeVariableIndexToken(4, "v_d"), + MakeVariableIndexToken(4, "v_d"), // $v_o: InputObj! = {foo: "bar"}, Token(gqlscan.TokenArgName, "a6"), - gqlparse.MakeVariableIndexToken(5, "v_o"), + MakeVariableIndexToken(5, "v_o"), // $v_so: String = null, Token(gqlscan.TokenArgName, "a7"), - gqlparse.MakeVariableIndexToken(6, "v_so"), + MakeVariableIndexToken(6, "v_so"), // $v_io: Int = null, Token(gqlscan.TokenArgName, "a8"), - gqlparse.MakeVariableIndexToken(7, "v_io"), + MakeVariableIndexToken(7, "v_io"), // $v_fo: Float = null, Token(gqlscan.TokenArgName, "a9"), - gqlparse.MakeVariableIndexToken(8, "v_fo"), + MakeVariableIndexToken(8, "v_fo"), // $v_bo: Boolean = null, Token(gqlscan.TokenArgName, "a10"), - gqlparse.MakeVariableIndexToken(9, "v_bo"), + MakeVariableIndexToken(9, "v_bo"), // $v_do: ID = null, Token(gqlscan.TokenArgName, "a11"), - gqlparse.MakeVariableIndexToken(10, "v_do"), + MakeVariableIndexToken(10, "v_do"), // $v_oo: InputObj = null, Token(gqlscan.TokenArgName, "a12"), - gqlparse.MakeVariableIndexToken(11, "v_oo"), + MakeVariableIndexToken(11, "v_oo"), // $v_aon: [String] = null, Token(gqlscan.TokenArgName, "a13"), - gqlparse.MakeVariableIndexToken(12, "v_aon"), + MakeVariableIndexToken(12, "v_aon"), // $v_aoy: [String] = [], Token(gqlscan.TokenArgName, "a14"), - gqlparse.MakeVariableIndexToken(13, "v_aoy"), + MakeVariableIndexToken(13, "v_aoy"), // $v_a_so: [String]! = ["okay", null], Token(gqlscan.TokenArgName, "a15"), - gqlparse.MakeVariableIndexToken(14, "v_a_so"), + MakeVariableIndexToken(14, "v_a_so"), // $v_a_ao_so: [[String]]! = [["okay", null], [], null], Token(gqlscan.TokenArgName, "a16"), - gqlparse.MakeVariableIndexToken(15, "v_a_ao_so"), + MakeVariableIndexToken(15, "v_a_ao_so"), // $v_a_io: [InputObj]! = [{a: "1", b: null, c: 42, d: false}, null], Token(gqlscan.TokenArgName, "a17"), - gqlparse.MakeVariableIndexToken(16, "v_a_io"), + MakeVariableIndexToken(16, "v_a_io"), Token(gqlscan.TokenArgListEnd), Token(gqlscan.TokenSetEnd), @@ -847,71 +847,71 @@ var testdata = []decl.Declaration[TestSuccess]{ // $v_s: String! = """default value""", Token(gqlscan.TokenArgName, "a1"), - gqlparse.MakeVariableIndexToken(0, "v_s"), + MakeVariableIndexToken(0, "v_s"), // $v_i: Int! = 42, Token(gqlscan.TokenArgName, "a2"), - gqlparse.MakeVariableIndexToken(1, "v_i"), + MakeVariableIndexToken(1, "v_i"), // $v_f: Float! = 3.14, Token(gqlscan.TokenArgName, "a3"), - gqlparse.MakeVariableIndexToken(2, "v_f"), + MakeVariableIndexToken(2, "v_f"), // $v_b: Boolean! = true, Token(gqlscan.TokenArgName, "a4"), - gqlparse.MakeVariableIndexToken(3, "v_b"), + MakeVariableIndexToken(3, "v_b"), // $v_d: ID! = "default ID", Token(gqlscan.TokenArgName, "a5"), - gqlparse.MakeVariableIndexToken(4, "v_d"), + MakeVariableIndexToken(4, "v_d"), // $v_o: InputObj! = {foo: "bar"}, Token(gqlscan.TokenArgName, "a6"), - gqlparse.MakeVariableIndexToken(5, "v_o"), + MakeVariableIndexToken(5, "v_o"), // $v_so: String = null, Token(gqlscan.TokenArgName, "a7"), - gqlparse.MakeVariableIndexToken(6, "v_so"), + MakeVariableIndexToken(6, "v_so"), // $v_io: Int = null, Token(gqlscan.TokenArgName, "a8"), - gqlparse.MakeVariableIndexToken(7, "v_io"), + MakeVariableIndexToken(7, "v_io"), // $v_fo: Float = null, Token(gqlscan.TokenArgName, "a9"), - gqlparse.MakeVariableIndexToken(8, "v_fo"), + MakeVariableIndexToken(8, "v_fo"), // $v_bo: Boolean = null, Token(gqlscan.TokenArgName, "a10"), - gqlparse.MakeVariableIndexToken(9, "v_bo"), + MakeVariableIndexToken(9, "v_bo"), // $v_do: ID = null, Token(gqlscan.TokenArgName, "a11"), - gqlparse.MakeVariableIndexToken(10, "v_do"), + MakeVariableIndexToken(10, "v_do"), // $v_oo: InputObj = null, Token(gqlscan.TokenArgName, "a12"), - gqlparse.MakeVariableIndexToken(11, "v_oo"), + MakeVariableIndexToken(11, "v_oo"), // $v_aon: [String] = null, Token(gqlscan.TokenArgName, "a13"), - gqlparse.MakeVariableIndexToken(12, "v_aon"), + MakeVariableIndexToken(12, "v_aon"), // $v_aoy: [String] = [], Token(gqlscan.TokenArgName, "a14"), - gqlparse.MakeVariableIndexToken(13, "v_aoy"), + MakeVariableIndexToken(13, "v_aoy"), // $v_a_so: [String]! = ["okay", null], Token(gqlscan.TokenArgName, "a15"), - gqlparse.MakeVariableIndexToken(14, "v_a_so"), + MakeVariableIndexToken(14, "v_a_so"), // $v_a_ao_so: [[String]]! = [["okay", null], [], null], Token(gqlscan.TokenArgName, "a16"), - gqlparse.MakeVariableIndexToken(15, "v_a_ao_so"), + MakeVariableIndexToken(15, "v_a_ao_so"), // $v_a_io: [InputObj]! = [{a: "1", b: null, c: 42, d: false}, null], Token(gqlscan.TokenArgName, "a17"), - gqlparse.MakeVariableIndexToken(16, "v_a_io"), + MakeVariableIndexToken(16, "v_a_io"), Token(gqlscan.TokenArgListEnd), Token(gqlscan.TokenSetEnd), @@ -1901,3 +1901,11 @@ func (t *TestWriter) Errorf(format string, v ...any) { t.t.Helper() t.t.Errorf(format, v...) } + +// MakeVariableIndexToken creates a variable index token. +func MakeVariableIndexToken(index int, name string) gqlparse.Token { + return gqlparse.Token{ + ID: gqlscan.Token(gqlparse.TokenTypeValIndexOffset + index), + Value: []byte(name), + } +} From 4cd8f44e89aa994c804d0e6108255653027f3ece Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Thu, 9 Feb 2023 01:42:44 +0100 Subject: [PATCH 07/35] feat: Introduce pathmatch and pathscan --- api/graph/model/model.go | 4 +- api/graph/schema.resolvers.go | 12 +- cmd/tst/main.go | 102 +++++ config/config.go | 9 +- .../internal/constrcheck/bench_test.go | 2 +- .../internal/constrcheck/constrcheck.go | 64 ++- .../internal/constrcheck/constrcheck_test.go | 119 +++++ .../playmon/internal/constrcheck/doc.go | 0 .../constrcheck/internal/test/test.go | 0 .../constrcheck/internal/union/union.go | 2 +- .../constrcheck/internal/union/union_test.go | 2 +- .../constrcheck/internal/unsafe/unsafe.go | 0 .../internal/constrcheck/iswrongtype.go | 0 .../internal/constrcheck/iswrongtype_test.go | 0 .../constrcheck/tests/arithmetic_div.yml | 0 .../constrcheck/tests/arithmetic_mod.yml | 0 .../constrcheck/tests/arithmetic_mul.yml | 0 .../constrcheck/tests/arithmetic_sub.yml | 0 .../tests/equal_strblock_false.todo.yaml | 0 .../tests/false_arithmetic_add.yml | 0 .../constrcheck/tests/false_constr_and.yml | 0 .../constrcheck/tests/false_constr_or.yml | 0 .../constrcheck/tests/false_equal_arr_int.yml | 0 .../constrcheck/tests/false_equal_arr_str.yml | 0 .../constrcheck/tests/false_equal_bool.yml | 0 .../constrcheck/tests/false_equal_enum.yml | 0 .../constrcheck/tests/false_equal_float.yml | 0 .../constrcheck/tests/false_equal_int.yml | 0 .../constrcheck/tests/false_equal_obj.yml | 0 .../constrcheck/tests/false_equal_str.yml | 0 .../tests/false_equal_strblock.yml | 0 .../constrcheck/tests/false_interface.yml | 0 .../constrcheck/tests/false_len_array_int.yml | 0 .../constrcheck/tests/false_len_array_obj.yml | 0 .../constrcheck/tests/false_len_str_eq.yml | 0 .../constrcheck/tests/false_len_str_gr.yml | 0 .../constrcheck/tests/false_len_str_greq.yml | 0 .../constrcheck/tests/false_len_str_leeq.yml | 0 .../constrcheck/tests/false_len_str_neq.yml | 0 .../tests/false_len_strblock_eq.yml | 0 .../tests/false_len_strblock_gr.yml | 0 .../tests/false_len_strblock_greq.yml | 0 .../tests/false_len_strblock_le.yaml | 0 .../tests/false_len_strblock_le.yml | 0 .../tests/false_len_strblock_leeq.yml | 0 .../tests/false_len_strblock_neq.yml | 0 .../constrcheck/tests/false_logical_equal.yml | 0 .../tests/false_logical_expr_relational.yml | 0 .../false_logical_expr_relational_float.yml | 0 .../tests/false_logical_notequal.yml | 0 .../constrcheck/tests/false_logical_or_eq.yml | 0 .../tests/false_logical_or_gr_le.yml | 0 .../constrcheck/tests/false_map_2d.yml | 0 .../tests/false_map_array_object.yml | 0 .../tests/false_map_var_arithmetic.yml | 0 .../tests/false_not_equal_arr_int.yml | 0 .../tests/false_not_equal_arr_str.yml | 0 .../constrcheck/tests/false_not_equal_obj.yml | 0 .../tests/false_relational_float.yml | 0 .../tests/false_relational_int.yml | 0 .../constrcheck/tests/false_union.yml | 0 .../constrcheck/tests/false_var_bool.yml | 0 .../constrcheck/tests/false_var_str.yml | 0 .../constrcheck/tests/false_var_strblock.yml | 0 .../tests/invalid_obj_duplicate_inputs.yml | 0 .../invalid_obj_duplicate_inputs_in_any.yml | 0 .../constrcheck/tests/logical_negation.yml | 0 .../constrcheck/tests/numeric_negation.yml | 0 .../constrcheck/tests/true_arithmetic_add.yml | 0 .../constrcheck/tests/true_constr_and.yml | 0 .../constrcheck/tests/true_constr_or.yml | 0 .../constrcheck/tests/true_equal_arr_int.yml | 0 .../constrcheck/tests/true_equal_arr_str.yml | 0 .../constrcheck/tests/true_equal_bool.yml | 0 .../constrcheck/tests/true_equal_enum.yml | 0 .../constrcheck/tests/true_equal_float.yml | 0 .../constrcheck/tests/true_equal_int.yml | 0 .../constrcheck/tests/true_equal_obj.yml | 0 .../constrcheck/tests/true_equal_str.yml | 0 .../constrcheck/tests/true_equal_strblock.yml | 0 .../constrcheck/tests/true_interface.yml | 0 .../constrcheck/tests/true_len_array_int.yml | 0 .../constrcheck/tests/true_len_array_obj.yml | 0 .../constrcheck/tests/true_len_str.yml | 0 .../constrcheck/tests/true_len_str_eq.yml | 0 .../constrcheck/tests/true_len_str_gr.yml | 0 .../constrcheck/tests/true_len_str_greq.yml | 0 .../constrcheck/tests/true_len_str_le.yml | 0 .../constrcheck/tests/true_len_str_leeq.yml | 0 .../constrcheck/tests/true_len_str_neq.yml | 0 .../constrcheck/tests/true_len_strblock.yml | 0 .../tests/true_len_strblock_eq.yml | 0 .../tests/true_len_strblock_gr.yml | 0 .../tests/true_len_strblock_greq.yml | 0 .../tests/true_len_strblock_le.yml | 0 .../tests/true_len_strblock_leeq.yml | 0 .../tests/true_len_strblock_neq.yml | 0 .../constrcheck/tests/true_logical_equal.yml | 0 .../tests/true_logical_expr_relational.yml | 0 .../true_logical_expr_relational_float.yml | 0 .../tests/true_logical_notequal.yml | 0 .../constrcheck/tests/true_logical_or_eq.yml | 0 .../tests/true_logical_or_gr_le.yml | 0 .../constrcheck/tests/true_map_2d.yml | 0 .../tests/true_map_array_object.yml | 0 .../tests/true_map_var_arithmetic.yml | 0 .../tests/true_not_equal_arr_int.yml | 0 .../tests/true_not_equal_arr_str.yml | 0 .../constrcheck/tests/true_not_equal_obj.yml | 0 .../tests/true_relational_float.yml | 0 .../constrcheck/tests/true_relational_int.yml | 0 .../internal/constrcheck/tests/true_union.yml | 0 .../constrcheck/tests/true_var_bool.yml | 0 .../constrcheck/tests/true_var_str.yml | 0 .../constrcheck/tests/true_var_strblock.yml | 0 .../tests/var_equal_nonexistent.yml | 0 .../constrcheck/tests/wrong_type_equal.yml | 0 .../tests/wrong_type_equal_arrint.yml | 0 .../tests/wrong_type_equal_bool_f.yml | 0 .../tests/wrong_type_equal_bool_t.yml | 0 .../tests/wrong_type_equal_enum.yml | 0 .../tests/wrong_type_equal_float.yml | 0 .../tests/wrong_type_equal_int.yml | 0 .../tests/wrong_type_equal_missing_field.yml | 0 .../tests/wrong_type_equal_null.yml | 0 .../tests/wrong_type_equal_str.yml | 0 .../wrong_type_false_equal_missing_field.yml | 0 ...ong_type_false_not_equal_missing_field.yml | 0 .../tests/wrong_type_len_arr_int.yml | 0 .../tests/wrong_type_len_arr_obj.yml | 0 .../constrcheck/tests/wrong_type_len_str.yml | 0 .../constrcheck/tests/wrong_type_map_2d.yml | 0 .../wrong_type_map_obj_too_many_fields.yml | 0 .../tests/wrong_type_not_equal.yml | 0 .../tests/wrong_type_obj_too_many_inputs.yml | 0 .../tests/wrong_type_relational_float.yml | 0 .../playmon/internal/pathmatch/bench_test.go | 23 + .../playmon/internal/pathmatch/pathmatch.go | 82 ++++ .../internal/pathmatch/pathmatch_test.go | 197 +++++++++ .../pathmatch/test_setups/starwars/config.yml | 13 + .../starwars/services_enabled/starwars.yml | 13 + .../starwars/templates_enabled/a.gqt | 13 + .../starwars/templates_enabled/b.gqt | 20 + .../starwars/templates_enabled/c.gqt | 28 ++ .../starwars/templates_enabled/d.gqt | 20 + engine/playmon/internal/pathscan/pathscan.go | 411 ++++++++++++++++++ .../internal/pathscan/pathscan_test.go | 314 +++++++++++++ engine/playmon/playmon.go | 105 +++++ .../assets/benchassets/_bench_deep/bench.yml | 0 .../benchassets/bench_average/bench.yml | 0 .../assets/benchassets/bench_big/bench.yml | 0 .../tests/assets/benchassets/templates/0.gqt | 0 .../tests/assets/benchassets/templates/1.gqt | 0 .../tests/assets/benchassets/templates/2.gqt | 0 .../tests/assets/benchassets/templates/3.gqt | 0 .../tests/assets/benchassets/templates/4.gqt | 0 .../tests/assets/benchassets/templates/5.gqt | 0 .../tests/assets/benchassets/templates/6.gqt | 0 .../tests/assets/benchassets/templates/7.gqt | 0 .../tests/assets/benchassets/templates/8.tgqt | 0 .../testassets/test_arguments_merge_00/0.gqt | 0 .../testassets/test_arguments_merge_00/1.gqt | 0 .../testassets/test_arguments_merge_00/2.gqt | 0 .../testassets/test_arguments_merge_00/3.gqt | 0 .../test_arguments_merge_00/test.yml | 0 .../testassets/test_arguments_merge_01/0.gqt | 0 .../testassets/test_arguments_merge_01/1.gqt | 0 .../testassets/test_arguments_merge_01/2.gqt | 0 .../testassets/test_arguments_merge_01/3.gqt | 0 .../test_arguments_merge_01/test.yml | 0 .../assets/testassets/test_complex_00/0.gqt | 0 .../assets/testassets/test_complex_00/1.gqt | 0 .../assets/testassets/test_complex_00/2.gqt | 0 .../assets/testassets/test_complex_00/3.gqt | 0 .../assets/testassets/test_complex_00/4.gqt | 0 .../assets/testassets/test_complex_00/5.gqt | 0 .../testassets/test_complex_00/test.yml | 0 .../testassets/test_conditions_00/0.gqt | 0 .../testassets/test_conditions_00/test.yml | 0 .../testassets/test_conditions_01/0.gqt | 0 .../testassets/test_conditions_01/1.gqt | 0 .../testassets/test_conditions_01/test.yml | 0 .../testassets/test_conditions_02/0.gqt | 0 .../testassets/test_conditions_02/1.gqt | 0 .../testassets/test_conditions_02/test.yml | 0 .../testassets/test_conditions_03/0.gqt | 0 .../testassets/test_conditions_03/test.yml | 0 .../testassets/test_conditions_04/0.gqt | 0 .../testassets/test_conditions_04/1.gqt | 0 .../testassets/test_conditions_04/test.yml | 0 .../assets/testassets/test_enum_00/0.gqt | 0 .../assets/testassets/test_enum_00/1.gqt | 0 .../assets/testassets/test_enum_00/test.yml | 0 .../assets/testassets/test_fragments_00/0.gqt | 0 .../testassets/test_fragments_00/test.yml | 0 .../assets/testassets/test_fragments_01/0.gqt | 0 .../testassets/test_fragments_01/test.yml | 0 .../assets/testassets/test_fragments_02/0.gqt | 0 .../testassets/test_fragments_02/test.yml | 0 .../assets/testassets/test_fragments_03/0.gqt | 0 .../testassets/test_fragments_03/test.yml | 0 .../testassets/test_general_logic_00/0.gqt | 0 .../testassets/test_general_logic_00/test.yml | 0 .../testassets/test_general_logic_01/0.gqt | 0 .../testassets/test_general_logic_01/test.yml | 0 .../testassets/test_general_logic_02/0.gqt | 0 .../testassets/test_general_logic_02/test.yml | 0 .../testassets/test_general_logic_03/0.gqt | 0 .../testassets/test_general_logic_03/test.yml | 0 .../testassets/test_general_logic_04/0.gqt | 0 .../testassets/test_general_logic_04/test.yml | 0 .../testassets/test_general_logic_05/0.gqt | 0 .../testassets/test_general_logic_05/1.gqt | 0 .../testassets/test_general_logic_05/test.yml | 0 .../testassets/test_general_logic_06/0.gqt | 0 .../testassets/test_general_logic_06/1.gqt | 0 .../testassets/test_general_logic_06/test.yml | 0 .../testassets/test_general_logic_07/0.gqt | 0 .../testassets/test_general_logic_07/test.yml | 0 .../testassets/test_general_logic_08/0.gqt | 0 .../testassets/test_general_logic_08/test.yml | 0 .../testassets/test_general_logic_09/0.gqt | 0 .../testassets/test_general_logic_09/test.yml | 0 .../testassets/test_general_logic_10/0.gqt | 0 .../testassets/test_general_logic_10/1.gqt | 0 .../testassets/test_general_logic_10/2.gqt | 0 .../testassets/test_general_logic_10/test.yml | 0 .../testassets/test_general_logic_11/0.gqt | 0 .../testassets/test_general_logic_11/1.gqt | 0 .../testassets/test_general_logic_11/2.gqt | 0 .../testassets/test_general_logic_11/test.yml | 0 .../testassets/test_identical_fields_00/0.gqt | 0 .../test_identical_fields_00/test.yml | 0 .../tests/assets/testassets/test_map_00/0.gqt | 0 .../assets/testassets/test_map_00/test.yml | 0 .../tests/assets/testassets/test_max_00/0.gqt | 0 .../assets/testassets/test_max_00/test.yml | 0 .../tests/assets/testassets/test_max_02/0.gqt | 0 .../tests/assets/testassets/test_max_02/1.gqt | 0 .../assets/testassets/test_max_02/test.yml | 0 .../tests/assets/testassets/test_max_03/0.gqt | 0 .../assets/testassets/test_max_03/test.yml | 0 .../testassets/test_max_complex_00/0.gqt | 0 .../testassets/test_max_complex_00/test.yml | 0 .../assets/testassets/test_negation_00/0.gqt | 0 .../testassets/test_negation_00/test.yml | 0 .../assets/testassets/test_negation_01/0.gqt | 0 .../testassets/test_negation_01/test.yml | 0 .../assets/testassets/test_nesting_00/0.gqt | 0 .../testassets/test_nesting_00/test.yml | 0 .../testassets/test_special_fields_00/0.gqt | 0 .../test_special_fields_00/test.yml | 0 .../testassets/test_token_null_00/0.gqt | 0 .../testassets/test_token_null_00/test.yml | 0 .../assets/testassets/test_variables_00/0.gqt | 0 .../testassets/test_variables_00/test.yml | 0 {engines => engine}/tests/benchmark_test.go | 0 {engines => engine}/tests/engine_test.go | 0 .../internal/constrcheck/constrcheck_test.go | 119 ----- engines/playmon/internal/pathscan/pathscan.go | 128 ------ .../internal/pathscan/pathscan_test.go | 88 ---- engines/playmon/playmon.go | 68 --- server/api.go | 15 +- server/proxy.go | 39 +- utilities/bitmask/bitmask.go | 42 +- utilities/xxhash/xxhash.go | 9 +- 266 files changed, 1529 insertions(+), 534 deletions(-) create mode 100644 cmd/tst/main.go rename {engines => engine}/playmon/internal/constrcheck/bench_test.go (98%) rename {engines => engine}/playmon/internal/constrcheck/constrcheck.go (97%) create mode 100644 engine/playmon/internal/constrcheck/constrcheck_test.go rename {engines => engine}/playmon/internal/constrcheck/doc.go (100%) rename {engines => engine}/playmon/internal/constrcheck/internal/test/test.go (100%) rename {engines => engine}/playmon/internal/constrcheck/internal/union/union.go (98%) rename {engines => engine}/playmon/internal/constrcheck/internal/union/union_test.go (98%) rename {engines => engine}/playmon/internal/constrcheck/internal/unsafe/unsafe.go (100%) rename {engines => engine}/playmon/internal/constrcheck/iswrongtype.go (100%) rename {engines => engine}/playmon/internal/constrcheck/iswrongtype_test.go (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/arithmetic_div.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/arithmetic_mod.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/arithmetic_mul.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/arithmetic_sub.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/equal_strblock_false.todo.yaml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_arithmetic_add.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_constr_and.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_constr_or.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_equal_arr_int.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_equal_arr_str.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_equal_bool.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_equal_enum.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_equal_float.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_equal_int.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_equal_obj.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_equal_str.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_equal_strblock.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_interface.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_len_array_int.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_len_array_obj.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_len_str_eq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_len_str_gr.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_len_str_greq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_len_str_leeq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_len_str_neq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_len_strblock_eq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_len_strblock_gr.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_len_strblock_greq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_len_strblock_le.yaml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_len_strblock_le.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_len_strblock_leeq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_len_strblock_neq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_logical_equal.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_logical_expr_relational.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_logical_expr_relational_float.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_logical_notequal.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_logical_or_eq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_logical_or_gr_le.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_map_2d.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_map_array_object.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_map_var_arithmetic.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_not_equal_arr_int.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_not_equal_arr_str.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_not_equal_obj.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_relational_float.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_relational_int.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_union.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_var_bool.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_var_str.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/false_var_strblock.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs_in_any.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/logical_negation.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/numeric_negation.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_arithmetic_add.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_constr_and.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_constr_or.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_equal_arr_int.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_equal_arr_str.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_equal_bool.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_equal_enum.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_equal_float.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_equal_int.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_equal_obj.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_equal_str.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_equal_strblock.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_interface.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_len_array_int.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_len_array_obj.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_len_str.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_len_str_eq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_len_str_gr.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_len_str_greq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_len_str_le.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_len_str_leeq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_len_str_neq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_len_strblock.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_len_strblock_eq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_len_strblock_gr.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_len_strblock_greq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_len_strblock_le.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_len_strblock_leeq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_len_strblock_neq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_logical_equal.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_logical_expr_relational.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_logical_expr_relational_float.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_logical_notequal.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_logical_or_eq.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_logical_or_gr_le.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_map_2d.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_map_array_object.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_map_var_arithmetic.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_not_equal_arr_int.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_not_equal_arr_str.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_not_equal_obj.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_relational_float.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_relational_int.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_union.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_var_bool.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_var_str.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/true_var_strblock.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/var_equal_nonexistent.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_equal.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_equal_arrint.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_equal_bool_f.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_equal_bool_t.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_equal_enum.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_equal_float.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_equal_int.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_equal_missing_field.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_equal_null.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_equal_str.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_false_equal_missing_field.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_false_not_equal_missing_field.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_len_arr_int.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_len_arr_obj.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_len_str.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_map_2d.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_map_obj_too_many_fields.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_not_equal.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_obj_too_many_inputs.yml (100%) rename {engines => engine}/playmon/internal/constrcheck/tests/wrong_type_relational_float.yml (100%) create mode 100644 engine/playmon/internal/pathmatch/bench_test.go create mode 100644 engine/playmon/internal/pathmatch/pathmatch.go create mode 100644 engine/playmon/internal/pathmatch/pathmatch_test.go create mode 100644 engine/playmon/internal/pathmatch/test_setups/starwars/config.yml create mode 100644 engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars.yml create mode 100644 engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/a.gqt create mode 100644 engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/b.gqt create mode 100644 engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/c.gqt create mode 100644 engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/d.gqt create mode 100644 engine/playmon/internal/pathscan/pathscan.go create mode 100644 engine/playmon/internal/pathscan/pathscan_test.go create mode 100644 engine/playmon/playmon.go rename {engines => engine}/tests/assets/benchassets/_bench_deep/bench.yml (100%) rename {engines => engine}/tests/assets/benchassets/bench_average/bench.yml (100%) rename {engines => engine}/tests/assets/benchassets/bench_big/bench.yml (100%) rename {engines => engine}/tests/assets/benchassets/templates/0.gqt (100%) rename {engines => engine}/tests/assets/benchassets/templates/1.gqt (100%) rename {engines => engine}/tests/assets/benchassets/templates/2.gqt (100%) rename {engines => engine}/tests/assets/benchassets/templates/3.gqt (100%) rename {engines => engine}/tests/assets/benchassets/templates/4.gqt (100%) rename {engines => engine}/tests/assets/benchassets/templates/5.gqt (100%) rename {engines => engine}/tests/assets/benchassets/templates/6.gqt (100%) rename {engines => engine}/tests/assets/benchassets/templates/7.gqt (100%) rename {engines => engine}/tests/assets/benchassets/templates/8.tgqt (100%) rename {engines => engine}/tests/assets/testassets/test_arguments_merge_00/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_arguments_merge_00/1.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_arguments_merge_00/2.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_arguments_merge_00/3.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_arguments_merge_00/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_arguments_merge_01/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_arguments_merge_01/1.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_arguments_merge_01/2.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_arguments_merge_01/3.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_arguments_merge_01/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_complex_00/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_complex_00/1.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_complex_00/2.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_complex_00/3.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_complex_00/4.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_complex_00/5.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_complex_00/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_conditions_00/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_conditions_00/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_conditions_01/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_conditions_01/1.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_conditions_01/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_conditions_02/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_conditions_02/1.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_conditions_02/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_conditions_03/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_conditions_03/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_conditions_04/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_conditions_04/1.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_conditions_04/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_enum_00/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_enum_00/1.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_enum_00/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_fragments_00/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_fragments_00/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_fragments_01/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_fragments_01/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_fragments_02/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_fragments_02/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_fragments_03/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_fragments_03/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_00/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_00/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_01/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_01/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_02/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_02/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_03/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_03/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_04/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_04/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_05/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_05/1.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_05/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_06/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_06/1.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_06/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_07/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_07/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_08/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_08/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_09/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_09/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_10/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_10/1.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_10/2.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_10/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_11/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_11/1.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_11/2.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_general_logic_11/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_identical_fields_00/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_identical_fields_00/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_map_00/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_map_00/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_max_00/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_max_00/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_max_02/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_max_02/1.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_max_02/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_max_03/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_max_03/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_max_complex_00/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_max_complex_00/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_negation_00/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_negation_00/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_negation_01/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_negation_01/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_nesting_00/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_nesting_00/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_special_fields_00/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_special_fields_00/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_token_null_00/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_token_null_00/test.yml (100%) rename {engines => engine}/tests/assets/testassets/test_variables_00/0.gqt (100%) rename {engines => engine}/tests/assets/testassets/test_variables_00/test.yml (100%) rename {engines => engine}/tests/benchmark_test.go (100%) rename {engines => engine}/tests/engine_test.go (100%) delete mode 100644 engines/playmon/internal/constrcheck/constrcheck_test.go delete mode 100644 engines/playmon/internal/pathscan/pathscan.go delete mode 100644 engines/playmon/internal/pathscan/pathscan_test.go delete mode 100644 engines/playmon/playmon.go diff --git a/api/graph/model/model.go b/api/graph/model/model.go index 5595c07..0818d60 100644 --- a/api/graph/model/model.go +++ b/api/graph/model/model.go @@ -1,12 +1,12 @@ package model import ( - "github.com/graph-guard/ggproxy/engines/rmap" + "github.com/graph-guard/ggproxy/engine/playmon" "github.com/graph-guard/ggproxy/statistics" ) type Service struct { - Matcher *rmap.RulesMap + Engine *playmon.Engine TemplatesByID map[string]*Template Stats *statistics.ServiceSync diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index 0628ec1..f08e7ed 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -72,7 +72,7 @@ func (r *serviceResolver) MatchAll(ctx context.Context, obj *model.Service, quer ) { m.TimeParsingNs = nsToF64(time.Since(startParsing).Nanoseconds()) startMatching := time.Now() - obj.Matcher.MatchAll( + obj.Engine.MatchAll( varVals, operation[0].ID, selectionSet, @@ -127,7 +127,7 @@ func (r *serviceResolver) Match(ctx context.Context, obj *model.Service, query s ) { m.TimeParsingNs = nsToF64(time.Since(startParsing).Nanoseconds()) startMatching := time.Now() - if id := obj.Matcher.Match(varVals, operation[0].ID, selectionSet); id != "" { + if id := obj.Engine.Match(varVals, operation[0].ID, selectionSet); id != "" { m.Templates = []*model.Template{obj.TemplatesByID[id]} } m.TimeMatchingNs = nsToF64(time.Since(startMatching).Nanoseconds()) @@ -188,6 +188,8 @@ func (r *Resolver) Service() generated.ServiceResolver { return &serviceResolver // Template returns generated.TemplateResolver implementation. func (r *Resolver) Template() generated.TemplateResolver { return &templateResolver{r} } -type queryResolver struct{ *Resolver } -type serviceResolver struct{ *Resolver } -type templateResolver struct{ *Resolver } +type ( + queryResolver struct{ *Resolver } + serviceResolver struct{ *Resolver } + templateResolver struct{ *Resolver } +) diff --git a/cmd/tst/main.go b/cmd/tst/main.go new file mode 100644 index 0000000..ef07dc1 --- /dev/null +++ b/cmd/tst/main.go @@ -0,0 +1,102 @@ +package main + +import ( + "fmt" + "log" + + "github.com/graph-guard/ggproxy/config" + "github.com/graph-guard/ggproxy/engine/playmon" + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/gqt/v4" + "github.com/vektah/gqlparser/v2" + "github.com/vektah/gqlparser/v2/ast" +) + +const ( + schemaSrc = `type Query {f(a:Int):Int}` + requestSrc = `query { f(a:42) }` + operationName = `` + variablesJSON = `` +) + +var templateSrc = map[string]string{ + "testtemplate": `query { f(a:*) }`, +} + +func main() { + gqlParser := gqlparse.NewParser() + var schema *ast.Schema + var gqtParser *gqt.Parser + if schemaSrc != "" { + var err error + if schema, err = gqlparser.LoadSchema(&ast.Source{ + Input: schemaSrc, + Name: "schema.graphqls", + }); err != nil { + log.Fatalf("parsing schema: %v", err) + } + + if gqtParser, err = gqt.NewParser([]gqt.Source{ + { + Name: "schema.graphqls", + Content: schemaSrc, + }, + }); err != nil { + log.Fatalf("initializing GQT parser: %v", err) + } + } else { + var err error + if gqtParser, err = gqt.NewParser(nil); err != nil { + log.Fatalf("initializing GQT parser: %v", err) + } + } + + templates := make(map[string]*config.Template, len(templateSrc)) + for id, src := range templateSrc { + tmpl, _, errs := gqtParser.Parse([]byte(src)) + if errs != nil { + log.Fatalf("parsing template %q: %v", id, errs) + } + templates[id] = &config.Template{ + Enabled: true, + ID: id, + Name: "name of " + id, + Source: []byte(src), + GQTTemplate: tmpl, + FilePath: "/path/to/" + id + ".gqt", + } + } + + s := &config.Service{ + Enabled: true, + ID: "testservice", + Path: "/testservice", + ForwardURL: "https://forward.here", + Schema: schema, + FilePath: "/path/to/testservice", + Templates: templates, + } + e := playmon.New(s) + fmt.Println(e) + + var oprName, varsJSON []byte + if operationName != "" { + oprName = []byte(operationName) + } + if variablesJSON != "" { + varsJSON = []byte(variablesJSON) + } + gqlParser.Parse( + []byte(requestSrc), oprName, varsJSON, + func(varValues [][]gqlparse.Token, operation, selectionSet []gqlparse.Token) { + tid := e.Match(varValues, operation[0].ID, selectionSet) + if tid == "" { + log.Print("no match") + } + fmt.Println("TID:", tid) + }, + func(err error) { + log.Fatalf("parsing request: %v", err) + }, + ) +} diff --git a/config/config.go b/config/config.go index d2681a9..b80bdd9 100644 --- a/config/config.go +++ b/config/config.go @@ -144,8 +144,7 @@ func (c *Config) readServerConfig(path string) (err error) { } } - err = validateServerConfig(sc, path) - if err != nil { + if err := validateServerConfig(sc, path); err != nil { return err } @@ -181,14 +180,12 @@ func (c *Config) readServerConfig(path string) (err error) { } // reading all services - err = c.readAllServices(servicesAllPath) - if err != nil { + if err := c.readAllServices(servicesAllPath); err != nil { return err } // reading enabled services - err = c.readEnabledServices(servicesEnabledPath) - if err != nil { + if err := c.readEnabledServices(servicesEnabledPath); err != nil { return err } diff --git a/engines/playmon/internal/constrcheck/bench_test.go b/engine/playmon/internal/constrcheck/bench_test.go similarity index 98% rename from engines/playmon/internal/constrcheck/bench_test.go rename to engine/playmon/internal/constrcheck/bench_test.go index b5a8b46..864789a 100644 --- a/engines/playmon/internal/constrcheck/bench_test.go +++ b/engine/playmon/internal/constrcheck/bench_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck" + "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/gqlscan" "github.com/graph-guard/gqt/v4" diff --git a/engines/playmon/internal/constrcheck/constrcheck.go b/engine/playmon/internal/constrcheck/constrcheck.go similarity index 97% rename from engines/playmon/internal/constrcheck/constrcheck.go rename to engine/playmon/internal/constrcheck/constrcheck.go index 429aaa8..1aa546d 100644 --- a/engines/playmon/internal/constrcheck/constrcheck.go +++ b/engine/playmon/internal/constrcheck/constrcheck.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck/internal/union" + "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck/internal/union" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/ggproxy/utilities/atoi" "github.com/graph-guard/gqlscan" @@ -24,7 +24,7 @@ type Checker struct { schema *gqlast.Schema checkers map[string]check - inputs map[string][]gqlparse.Token + variableValues map[string][]gqlparse.Token inputValue []gqlparse.Token checkedValue []gqlparse.Token @@ -123,8 +123,8 @@ func (m *Checker) pushStackBool(v bool) { // Init initializes the checker with inputs to validate a request. // Init must be used before making calls to Check in the context of a request. -func (m *Checker) Init(inputs map[string][]gqlparse.Token) { - m.inputs = inputs +func (m *Checker) Init(variableValues map[string][]gqlparse.Token) { + m.variableValues = variableValues } // Check returns true if the value for the given path is accepted, @@ -135,7 +135,7 @@ func (m *Checker) Check(path string) bool { if c == nil { return false } - v := m.inputs[path] + v := m.variableValues[path] m.inputValue, m.checkedValue = v, v return c(m) } @@ -157,7 +157,7 @@ func (m *Checker) resolveExpr(e gqt.Expression) union.Type { return union.TypeArray case *gqt.Variable: p := MakePath(e.Declaration.Parent) - if s := m.inputs[p]; s != nil { + if s := m.variableValues[p]; s != nil { m.stack = append(m.stack, union.Tokens(s)) return union.TypeTokens } @@ -420,6 +420,22 @@ func (m *Checker) resolveExpr(e gqt.Expression) union.Type { panic(fmt.Errorf("unhandled value expression type: %T", e)) } +func (m *Checker) PathsLen() int { return len(m.checkers) } + +func (m *Checker) VisitPaths(fn func(path string) (stop bool)) { + for path := range m.checkers { + if fn(path) { + return + } + } +} + +func (m *Checker) VisitPathsAll(fn func(path string)) { + for path := range m.checkers { + fn(path) + } +} + // New creates a constraint checker instance for each path of o. func New(o *gqt.Operation, s *gqlast.Schema) *Checker { m := &Checker{ @@ -452,9 +468,15 @@ func New(o *gqt.Operation, s *gqlast.Schema) *Checker { }) } + // TODO: move path scanner out of this package traverse(o, func(e gqt.Expression) (stop, skipChildren bool) { switch e := e.(type) { case *gqt.SelectionField: + if len(e.Arguments) < 1 && len(e.Selections) < 1 { + p := MakePath(e) + m.checkers[p] = nil + break + } for _, a := range e.Arguments { var expect *gqlast.Type if a.Def != nil { @@ -1147,36 +1169,6 @@ func Designation(e gqt.Expression) string { panic(fmt.Sprintf("unhandled expression: %T", e)) } -// MakePath generates a path for the given expression. -// For example the path "Query.a1.a2|arg" denotes the argument "arg" -// of the field "a2" inside the field "a1" of the Query type. -func MakePath(e gqt.Expression) string { - var p []gqt.Expression // Reversed path - for ; e != nil; e = e.GetParent() { - p = append(p, e) - } - var s strings.Builder - for i := len(p) - 1; i >= 0; i-- { - switch v := p[i].(type) { - case *gqt.SelectionInlineFrag: - _ = s.WriteByte('&') - _, _ = s.WriteString(v.TypeCondition.TypeName) - case *gqt.SelectionField: - _ = s.WriteByte('.') - _, _ = s.WriteString(v.Name.Name) - case *gqt.ObjectField: - _ = s.WriteByte('/') - _, _ = s.WriteString(v.Name.Name) - case *gqt.Argument: - _ = s.WriteByte('|') - _, _ = s.WriteString(v.Name.Name) - case *gqt.Operation: - _, _ = s.WriteString(v.Type.String()) - } - } - return s.String() -} - func (m *Checker) expectOrNum(expect *gqlast.Type) (wrongType bool) { if expect != nil { wrongType, _ = isWrongType(expect, m.checkedValue, m.schema) diff --git a/engine/playmon/internal/constrcheck/constrcheck_test.go b/engine/playmon/internal/constrcheck/constrcheck_test.go new file mode 100644 index 0000000..db99114 --- /dev/null +++ b/engine/playmon/internal/constrcheck/constrcheck_test.go @@ -0,0 +1,119 @@ +package constrcheck_test + +// import ( +// "embed" +// "io/fs" +// "sort" +// "strings" +// "testing" + +// "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck" +// "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck/internal/test" +// "github.com/graph-guard/ggproxy/gqlparse" +// "github.com/graph-guard/gqt/v4" +// "github.com/stretchr/testify/require" +// "github.com/vektah/gqlparser/v2" +// gqlast "github.com/vektah/gqlparser/v2/ast" +// ) + +// //go:embed tests +// var testsFS embed.FS + +// func TestCheck(t *testing.T) { +// d, err := fs.ReadDir(testsFS, "tests") +// require.NoError(t, err) + +// for _, do := range d { +// fileName := do.Name() +// if do.IsDir() { +// t.Run(fileName, func(t *testing.T) { +// t.Skipf("ignoring directory %q", fileName) +// }) +// continue +// } +// if !strings.HasSuffix(fileName, ".yml") { +// t.Run(fileName, func(t *testing.T) { +// t.Skipf("ignoring file %q", fileName) +// }) +// continue +// } + +// t.Run(strings.TrimSuffix(fileName, ".yml"), func(t *testing.T) { +// ts, err := test.Parse(testsFS, fileName) +// require.NoError(t, err) +// var withschema, schemaless *gqt.Operation +// var schemaAST *gqlast.Schema +// { +// p, err := gqt.NewParser([]gqt.Source{ +// {Name: "schema.graphqls", Content: ts.Schema}, +// }) +// require.NoError(t, err, "unexpected error in schema") +// opr, _, errs := p.Parse([]byte(ts.Template)) +// require.Len(t, errs, 0, "unexpected errors: %#v", errs) +// withschema = opr + +// schemaAST, err = gqlparser.LoadSchema(&gqlast.Source{ +// Name: "schema.graphqls", Input: ts.Schema, +// }) +// require.NoError(t, err) +// } +// { +// opr, _, errs := gqt.Parse([]byte(ts.Template)) +// require.Len(t, errs, 0, "unexpected errors: %#v", errs) +// schemaless = opr +// } + +// run := func(t *testing.T, opr *gqt.Operation) { +// t.Helper() +// c := constrcheck.New(opr, schemaAST) + +// paths := make([]string, 0, len(ts.Inputs)) +// for path := range ts.Inputs { +// paths = append(paths, path) +// } +// sort.Strings(paths) + +// variables := make([][]gqlparse.Token, len(ts.Inputs)) +// for path, v := range ts.Inputs { +// inputs[path] = v.Tokens +// } + +// c.Init(inputs) +// for _, path := range paths { +// if len(ts.Inputs[path].Tokens) < 1 { +// t.Errorf("missing test value for path %q", path) +// continue +// } + +// if ts.Inputs[path].Match == nil { +// i := ts.Inputs[path] +// t := false +// i.Match = &t +// ts.Inputs[path] = i +// } +// if ts.Inputs[path].MatchSchemaless == nil { +// i := ts.Inputs[path] +// i.MatchSchemaless = i.Match +// ts.Inputs[path] = i +// } + +// actual := c.Check(path) +// if opr.Def != nil { +// require.Equal( +// t, *ts.Inputs[path].Match, actual, +// "checking %q", path, +// ) +// } else { +// require.Equal( +// t, *ts.Inputs[path].MatchSchemaless, actual, +// "checking %q", path, +// ) +// } +// } +// } + +// t.Run("schema", func(t *testing.T) { run(t, withschema) }) +// t.Run("schemaless", func(t *testing.T) { run(t, schemaless) }) +// }) +// } +// } diff --git a/engines/playmon/internal/constrcheck/doc.go b/engine/playmon/internal/constrcheck/doc.go similarity index 100% rename from engines/playmon/internal/constrcheck/doc.go rename to engine/playmon/internal/constrcheck/doc.go diff --git a/engines/playmon/internal/constrcheck/internal/test/test.go b/engine/playmon/internal/constrcheck/internal/test/test.go similarity index 100% rename from engines/playmon/internal/constrcheck/internal/test/test.go rename to engine/playmon/internal/constrcheck/internal/test/test.go diff --git a/engines/playmon/internal/constrcheck/internal/union/union.go b/engine/playmon/internal/constrcheck/internal/union/union.go similarity index 98% rename from engines/playmon/internal/constrcheck/internal/union/union.go rename to engine/playmon/internal/constrcheck/internal/union/union.go index 8c88cdf..f147a1e 100644 --- a/engines/playmon/internal/constrcheck/internal/union/union.go +++ b/engine/playmon/internal/constrcheck/internal/union/union.go @@ -1,7 +1,7 @@ package union import ( - "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck/internal/unsafe" + "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck/internal/unsafe" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/ggproxy/utilities/atoi" "github.com/graph-guard/gqlscan" diff --git a/engines/playmon/internal/constrcheck/internal/union/union_test.go b/engine/playmon/internal/constrcheck/internal/union/union_test.go similarity index 98% rename from engines/playmon/internal/constrcheck/internal/union/union_test.go rename to engine/playmon/internal/constrcheck/internal/union/union_test.go index 03f3e49..098c5ca 100644 --- a/engines/playmon/internal/constrcheck/internal/union/union_test.go +++ b/engine/playmon/internal/constrcheck/internal/union/union_test.go @@ -3,7 +3,7 @@ package union_test import ( "testing" - "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck/internal/union" + "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck/internal/union" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/gqlscan" "github.com/stretchr/testify/require" diff --git a/engines/playmon/internal/constrcheck/internal/unsafe/unsafe.go b/engine/playmon/internal/constrcheck/internal/unsafe/unsafe.go similarity index 100% rename from engines/playmon/internal/constrcheck/internal/unsafe/unsafe.go rename to engine/playmon/internal/constrcheck/internal/unsafe/unsafe.go diff --git a/engines/playmon/internal/constrcheck/iswrongtype.go b/engine/playmon/internal/constrcheck/iswrongtype.go similarity index 100% rename from engines/playmon/internal/constrcheck/iswrongtype.go rename to engine/playmon/internal/constrcheck/iswrongtype.go diff --git a/engines/playmon/internal/constrcheck/iswrongtype_test.go b/engine/playmon/internal/constrcheck/iswrongtype_test.go similarity index 100% rename from engines/playmon/internal/constrcheck/iswrongtype_test.go rename to engine/playmon/internal/constrcheck/iswrongtype_test.go diff --git a/engines/playmon/internal/constrcheck/tests/arithmetic_div.yml b/engine/playmon/internal/constrcheck/tests/arithmetic_div.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/arithmetic_div.yml rename to engine/playmon/internal/constrcheck/tests/arithmetic_div.yml diff --git a/engines/playmon/internal/constrcheck/tests/arithmetic_mod.yml b/engine/playmon/internal/constrcheck/tests/arithmetic_mod.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/arithmetic_mod.yml rename to engine/playmon/internal/constrcheck/tests/arithmetic_mod.yml diff --git a/engines/playmon/internal/constrcheck/tests/arithmetic_mul.yml b/engine/playmon/internal/constrcheck/tests/arithmetic_mul.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/arithmetic_mul.yml rename to engine/playmon/internal/constrcheck/tests/arithmetic_mul.yml diff --git a/engines/playmon/internal/constrcheck/tests/arithmetic_sub.yml b/engine/playmon/internal/constrcheck/tests/arithmetic_sub.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/arithmetic_sub.yml rename to engine/playmon/internal/constrcheck/tests/arithmetic_sub.yml diff --git a/engines/playmon/internal/constrcheck/tests/equal_strblock_false.todo.yaml b/engine/playmon/internal/constrcheck/tests/equal_strblock_false.todo.yaml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/equal_strblock_false.todo.yaml rename to engine/playmon/internal/constrcheck/tests/equal_strblock_false.todo.yaml diff --git a/engines/playmon/internal/constrcheck/tests/false_arithmetic_add.yml b/engine/playmon/internal/constrcheck/tests/false_arithmetic_add.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_arithmetic_add.yml rename to engine/playmon/internal/constrcheck/tests/false_arithmetic_add.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_constr_and.yml b/engine/playmon/internal/constrcheck/tests/false_constr_and.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_constr_and.yml rename to engine/playmon/internal/constrcheck/tests/false_constr_and.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_constr_or.yml b/engine/playmon/internal/constrcheck/tests/false_constr_or.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_constr_or.yml rename to engine/playmon/internal/constrcheck/tests/false_constr_or.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_arr_int.yml b/engine/playmon/internal/constrcheck/tests/false_equal_arr_int.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_equal_arr_int.yml rename to engine/playmon/internal/constrcheck/tests/false_equal_arr_int.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_arr_str.yml b/engine/playmon/internal/constrcheck/tests/false_equal_arr_str.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_equal_arr_str.yml rename to engine/playmon/internal/constrcheck/tests/false_equal_arr_str.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_bool.yml b/engine/playmon/internal/constrcheck/tests/false_equal_bool.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_equal_bool.yml rename to engine/playmon/internal/constrcheck/tests/false_equal_bool.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_enum.yml b/engine/playmon/internal/constrcheck/tests/false_equal_enum.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_equal_enum.yml rename to engine/playmon/internal/constrcheck/tests/false_equal_enum.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_float.yml b/engine/playmon/internal/constrcheck/tests/false_equal_float.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_equal_float.yml rename to engine/playmon/internal/constrcheck/tests/false_equal_float.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_int.yml b/engine/playmon/internal/constrcheck/tests/false_equal_int.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_equal_int.yml rename to engine/playmon/internal/constrcheck/tests/false_equal_int.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_obj.yml b/engine/playmon/internal/constrcheck/tests/false_equal_obj.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_equal_obj.yml rename to engine/playmon/internal/constrcheck/tests/false_equal_obj.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_str.yml b/engine/playmon/internal/constrcheck/tests/false_equal_str.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_equal_str.yml rename to engine/playmon/internal/constrcheck/tests/false_equal_str.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_equal_strblock.yml b/engine/playmon/internal/constrcheck/tests/false_equal_strblock.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_equal_strblock.yml rename to engine/playmon/internal/constrcheck/tests/false_equal_strblock.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_interface.yml b/engine/playmon/internal/constrcheck/tests/false_interface.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_interface.yml rename to engine/playmon/internal/constrcheck/tests/false_interface.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_len_array_int.yml b/engine/playmon/internal/constrcheck/tests/false_len_array_int.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_len_array_int.yml rename to engine/playmon/internal/constrcheck/tests/false_len_array_int.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_len_array_obj.yml b/engine/playmon/internal/constrcheck/tests/false_len_array_obj.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_len_array_obj.yml rename to engine/playmon/internal/constrcheck/tests/false_len_array_obj.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_len_str_eq.yml b/engine/playmon/internal/constrcheck/tests/false_len_str_eq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_len_str_eq.yml rename to engine/playmon/internal/constrcheck/tests/false_len_str_eq.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_len_str_gr.yml b/engine/playmon/internal/constrcheck/tests/false_len_str_gr.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_len_str_gr.yml rename to engine/playmon/internal/constrcheck/tests/false_len_str_gr.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_len_str_greq.yml b/engine/playmon/internal/constrcheck/tests/false_len_str_greq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_len_str_greq.yml rename to engine/playmon/internal/constrcheck/tests/false_len_str_greq.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_len_str_leeq.yml b/engine/playmon/internal/constrcheck/tests/false_len_str_leeq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_len_str_leeq.yml rename to engine/playmon/internal/constrcheck/tests/false_len_str_leeq.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_len_str_neq.yml b/engine/playmon/internal/constrcheck/tests/false_len_str_neq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_len_str_neq.yml rename to engine/playmon/internal/constrcheck/tests/false_len_str_neq.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_len_strblock_eq.yml b/engine/playmon/internal/constrcheck/tests/false_len_strblock_eq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_len_strblock_eq.yml rename to engine/playmon/internal/constrcheck/tests/false_len_strblock_eq.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_len_strblock_gr.yml b/engine/playmon/internal/constrcheck/tests/false_len_strblock_gr.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_len_strblock_gr.yml rename to engine/playmon/internal/constrcheck/tests/false_len_strblock_gr.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_len_strblock_greq.yml b/engine/playmon/internal/constrcheck/tests/false_len_strblock_greq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_len_strblock_greq.yml rename to engine/playmon/internal/constrcheck/tests/false_len_strblock_greq.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_len_strblock_le.yaml b/engine/playmon/internal/constrcheck/tests/false_len_strblock_le.yaml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_len_strblock_le.yaml rename to engine/playmon/internal/constrcheck/tests/false_len_strblock_le.yaml diff --git a/engines/playmon/internal/constrcheck/tests/false_len_strblock_le.yml b/engine/playmon/internal/constrcheck/tests/false_len_strblock_le.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_len_strblock_le.yml rename to engine/playmon/internal/constrcheck/tests/false_len_strblock_le.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_len_strblock_leeq.yml b/engine/playmon/internal/constrcheck/tests/false_len_strblock_leeq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_len_strblock_leeq.yml rename to engine/playmon/internal/constrcheck/tests/false_len_strblock_leeq.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_len_strblock_neq.yml b/engine/playmon/internal/constrcheck/tests/false_len_strblock_neq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_len_strblock_neq.yml rename to engine/playmon/internal/constrcheck/tests/false_len_strblock_neq.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_logical_equal.yml b/engine/playmon/internal/constrcheck/tests/false_logical_equal.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_logical_equal.yml rename to engine/playmon/internal/constrcheck/tests/false_logical_equal.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_logical_expr_relational.yml b/engine/playmon/internal/constrcheck/tests/false_logical_expr_relational.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_logical_expr_relational.yml rename to engine/playmon/internal/constrcheck/tests/false_logical_expr_relational.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_logical_expr_relational_float.yml b/engine/playmon/internal/constrcheck/tests/false_logical_expr_relational_float.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_logical_expr_relational_float.yml rename to engine/playmon/internal/constrcheck/tests/false_logical_expr_relational_float.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_logical_notequal.yml b/engine/playmon/internal/constrcheck/tests/false_logical_notequal.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_logical_notequal.yml rename to engine/playmon/internal/constrcheck/tests/false_logical_notequal.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_logical_or_eq.yml b/engine/playmon/internal/constrcheck/tests/false_logical_or_eq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_logical_or_eq.yml rename to engine/playmon/internal/constrcheck/tests/false_logical_or_eq.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_logical_or_gr_le.yml b/engine/playmon/internal/constrcheck/tests/false_logical_or_gr_le.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_logical_or_gr_le.yml rename to engine/playmon/internal/constrcheck/tests/false_logical_or_gr_le.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_map_2d.yml b/engine/playmon/internal/constrcheck/tests/false_map_2d.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_map_2d.yml rename to engine/playmon/internal/constrcheck/tests/false_map_2d.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_map_array_object.yml b/engine/playmon/internal/constrcheck/tests/false_map_array_object.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_map_array_object.yml rename to engine/playmon/internal/constrcheck/tests/false_map_array_object.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_map_var_arithmetic.yml b/engine/playmon/internal/constrcheck/tests/false_map_var_arithmetic.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_map_var_arithmetic.yml rename to engine/playmon/internal/constrcheck/tests/false_map_var_arithmetic.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_not_equal_arr_int.yml b/engine/playmon/internal/constrcheck/tests/false_not_equal_arr_int.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_not_equal_arr_int.yml rename to engine/playmon/internal/constrcheck/tests/false_not_equal_arr_int.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_not_equal_arr_str.yml b/engine/playmon/internal/constrcheck/tests/false_not_equal_arr_str.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_not_equal_arr_str.yml rename to engine/playmon/internal/constrcheck/tests/false_not_equal_arr_str.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_not_equal_obj.yml b/engine/playmon/internal/constrcheck/tests/false_not_equal_obj.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_not_equal_obj.yml rename to engine/playmon/internal/constrcheck/tests/false_not_equal_obj.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_relational_float.yml b/engine/playmon/internal/constrcheck/tests/false_relational_float.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_relational_float.yml rename to engine/playmon/internal/constrcheck/tests/false_relational_float.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_relational_int.yml b/engine/playmon/internal/constrcheck/tests/false_relational_int.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_relational_int.yml rename to engine/playmon/internal/constrcheck/tests/false_relational_int.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_union.yml b/engine/playmon/internal/constrcheck/tests/false_union.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_union.yml rename to engine/playmon/internal/constrcheck/tests/false_union.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_var_bool.yml b/engine/playmon/internal/constrcheck/tests/false_var_bool.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_var_bool.yml rename to engine/playmon/internal/constrcheck/tests/false_var_bool.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_var_str.yml b/engine/playmon/internal/constrcheck/tests/false_var_str.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_var_str.yml rename to engine/playmon/internal/constrcheck/tests/false_var_str.yml diff --git a/engines/playmon/internal/constrcheck/tests/false_var_strblock.yml b/engine/playmon/internal/constrcheck/tests/false_var_strblock.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/false_var_strblock.yml rename to engine/playmon/internal/constrcheck/tests/false_var_strblock.yml diff --git a/engines/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs.yml b/engine/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs.yml rename to engine/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs.yml diff --git a/engines/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs_in_any.yml b/engine/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs_in_any.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs_in_any.yml rename to engine/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs_in_any.yml diff --git a/engines/playmon/internal/constrcheck/tests/logical_negation.yml b/engine/playmon/internal/constrcheck/tests/logical_negation.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/logical_negation.yml rename to engine/playmon/internal/constrcheck/tests/logical_negation.yml diff --git a/engines/playmon/internal/constrcheck/tests/numeric_negation.yml b/engine/playmon/internal/constrcheck/tests/numeric_negation.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/numeric_negation.yml rename to engine/playmon/internal/constrcheck/tests/numeric_negation.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_arithmetic_add.yml b/engine/playmon/internal/constrcheck/tests/true_arithmetic_add.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_arithmetic_add.yml rename to engine/playmon/internal/constrcheck/tests/true_arithmetic_add.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_constr_and.yml b/engine/playmon/internal/constrcheck/tests/true_constr_and.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_constr_and.yml rename to engine/playmon/internal/constrcheck/tests/true_constr_and.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_constr_or.yml b/engine/playmon/internal/constrcheck/tests/true_constr_or.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_constr_or.yml rename to engine/playmon/internal/constrcheck/tests/true_constr_or.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_arr_int.yml b/engine/playmon/internal/constrcheck/tests/true_equal_arr_int.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_equal_arr_int.yml rename to engine/playmon/internal/constrcheck/tests/true_equal_arr_int.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_arr_str.yml b/engine/playmon/internal/constrcheck/tests/true_equal_arr_str.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_equal_arr_str.yml rename to engine/playmon/internal/constrcheck/tests/true_equal_arr_str.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_bool.yml b/engine/playmon/internal/constrcheck/tests/true_equal_bool.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_equal_bool.yml rename to engine/playmon/internal/constrcheck/tests/true_equal_bool.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_enum.yml b/engine/playmon/internal/constrcheck/tests/true_equal_enum.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_equal_enum.yml rename to engine/playmon/internal/constrcheck/tests/true_equal_enum.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_float.yml b/engine/playmon/internal/constrcheck/tests/true_equal_float.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_equal_float.yml rename to engine/playmon/internal/constrcheck/tests/true_equal_float.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_int.yml b/engine/playmon/internal/constrcheck/tests/true_equal_int.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_equal_int.yml rename to engine/playmon/internal/constrcheck/tests/true_equal_int.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_obj.yml b/engine/playmon/internal/constrcheck/tests/true_equal_obj.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_equal_obj.yml rename to engine/playmon/internal/constrcheck/tests/true_equal_obj.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_str.yml b/engine/playmon/internal/constrcheck/tests/true_equal_str.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_equal_str.yml rename to engine/playmon/internal/constrcheck/tests/true_equal_str.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_equal_strblock.yml b/engine/playmon/internal/constrcheck/tests/true_equal_strblock.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_equal_strblock.yml rename to engine/playmon/internal/constrcheck/tests/true_equal_strblock.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_interface.yml b/engine/playmon/internal/constrcheck/tests/true_interface.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_interface.yml rename to engine/playmon/internal/constrcheck/tests/true_interface.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_len_array_int.yml b/engine/playmon/internal/constrcheck/tests/true_len_array_int.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_len_array_int.yml rename to engine/playmon/internal/constrcheck/tests/true_len_array_int.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_len_array_obj.yml b/engine/playmon/internal/constrcheck/tests/true_len_array_obj.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_len_array_obj.yml rename to engine/playmon/internal/constrcheck/tests/true_len_array_obj.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_len_str.yml b/engine/playmon/internal/constrcheck/tests/true_len_str.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_len_str.yml rename to engine/playmon/internal/constrcheck/tests/true_len_str.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_len_str_eq.yml b/engine/playmon/internal/constrcheck/tests/true_len_str_eq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_len_str_eq.yml rename to engine/playmon/internal/constrcheck/tests/true_len_str_eq.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_len_str_gr.yml b/engine/playmon/internal/constrcheck/tests/true_len_str_gr.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_len_str_gr.yml rename to engine/playmon/internal/constrcheck/tests/true_len_str_gr.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_len_str_greq.yml b/engine/playmon/internal/constrcheck/tests/true_len_str_greq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_len_str_greq.yml rename to engine/playmon/internal/constrcheck/tests/true_len_str_greq.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_len_str_le.yml b/engine/playmon/internal/constrcheck/tests/true_len_str_le.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_len_str_le.yml rename to engine/playmon/internal/constrcheck/tests/true_len_str_le.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_len_str_leeq.yml b/engine/playmon/internal/constrcheck/tests/true_len_str_leeq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_len_str_leeq.yml rename to engine/playmon/internal/constrcheck/tests/true_len_str_leeq.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_len_str_neq.yml b/engine/playmon/internal/constrcheck/tests/true_len_str_neq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_len_str_neq.yml rename to engine/playmon/internal/constrcheck/tests/true_len_str_neq.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_len_strblock.yml b/engine/playmon/internal/constrcheck/tests/true_len_strblock.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_len_strblock.yml rename to engine/playmon/internal/constrcheck/tests/true_len_strblock.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_len_strblock_eq.yml b/engine/playmon/internal/constrcheck/tests/true_len_strblock_eq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_len_strblock_eq.yml rename to engine/playmon/internal/constrcheck/tests/true_len_strblock_eq.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_len_strblock_gr.yml b/engine/playmon/internal/constrcheck/tests/true_len_strblock_gr.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_len_strblock_gr.yml rename to engine/playmon/internal/constrcheck/tests/true_len_strblock_gr.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_len_strblock_greq.yml b/engine/playmon/internal/constrcheck/tests/true_len_strblock_greq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_len_strblock_greq.yml rename to engine/playmon/internal/constrcheck/tests/true_len_strblock_greq.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_len_strblock_le.yml b/engine/playmon/internal/constrcheck/tests/true_len_strblock_le.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_len_strblock_le.yml rename to engine/playmon/internal/constrcheck/tests/true_len_strblock_le.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_len_strblock_leeq.yml b/engine/playmon/internal/constrcheck/tests/true_len_strblock_leeq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_len_strblock_leeq.yml rename to engine/playmon/internal/constrcheck/tests/true_len_strblock_leeq.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_len_strblock_neq.yml b/engine/playmon/internal/constrcheck/tests/true_len_strblock_neq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_len_strblock_neq.yml rename to engine/playmon/internal/constrcheck/tests/true_len_strblock_neq.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_logical_equal.yml b/engine/playmon/internal/constrcheck/tests/true_logical_equal.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_logical_equal.yml rename to engine/playmon/internal/constrcheck/tests/true_logical_equal.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_logical_expr_relational.yml b/engine/playmon/internal/constrcheck/tests/true_logical_expr_relational.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_logical_expr_relational.yml rename to engine/playmon/internal/constrcheck/tests/true_logical_expr_relational.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_logical_expr_relational_float.yml b/engine/playmon/internal/constrcheck/tests/true_logical_expr_relational_float.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_logical_expr_relational_float.yml rename to engine/playmon/internal/constrcheck/tests/true_logical_expr_relational_float.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_logical_notequal.yml b/engine/playmon/internal/constrcheck/tests/true_logical_notequal.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_logical_notequal.yml rename to engine/playmon/internal/constrcheck/tests/true_logical_notequal.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_logical_or_eq.yml b/engine/playmon/internal/constrcheck/tests/true_logical_or_eq.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_logical_or_eq.yml rename to engine/playmon/internal/constrcheck/tests/true_logical_or_eq.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_logical_or_gr_le.yml b/engine/playmon/internal/constrcheck/tests/true_logical_or_gr_le.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_logical_or_gr_le.yml rename to engine/playmon/internal/constrcheck/tests/true_logical_or_gr_le.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_map_2d.yml b/engine/playmon/internal/constrcheck/tests/true_map_2d.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_map_2d.yml rename to engine/playmon/internal/constrcheck/tests/true_map_2d.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_map_array_object.yml b/engine/playmon/internal/constrcheck/tests/true_map_array_object.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_map_array_object.yml rename to engine/playmon/internal/constrcheck/tests/true_map_array_object.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_map_var_arithmetic.yml b/engine/playmon/internal/constrcheck/tests/true_map_var_arithmetic.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_map_var_arithmetic.yml rename to engine/playmon/internal/constrcheck/tests/true_map_var_arithmetic.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_not_equal_arr_int.yml b/engine/playmon/internal/constrcheck/tests/true_not_equal_arr_int.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_not_equal_arr_int.yml rename to engine/playmon/internal/constrcheck/tests/true_not_equal_arr_int.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_not_equal_arr_str.yml b/engine/playmon/internal/constrcheck/tests/true_not_equal_arr_str.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_not_equal_arr_str.yml rename to engine/playmon/internal/constrcheck/tests/true_not_equal_arr_str.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_not_equal_obj.yml b/engine/playmon/internal/constrcheck/tests/true_not_equal_obj.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_not_equal_obj.yml rename to engine/playmon/internal/constrcheck/tests/true_not_equal_obj.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_relational_float.yml b/engine/playmon/internal/constrcheck/tests/true_relational_float.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_relational_float.yml rename to engine/playmon/internal/constrcheck/tests/true_relational_float.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_relational_int.yml b/engine/playmon/internal/constrcheck/tests/true_relational_int.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_relational_int.yml rename to engine/playmon/internal/constrcheck/tests/true_relational_int.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_union.yml b/engine/playmon/internal/constrcheck/tests/true_union.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_union.yml rename to engine/playmon/internal/constrcheck/tests/true_union.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_var_bool.yml b/engine/playmon/internal/constrcheck/tests/true_var_bool.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_var_bool.yml rename to engine/playmon/internal/constrcheck/tests/true_var_bool.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_var_str.yml b/engine/playmon/internal/constrcheck/tests/true_var_str.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_var_str.yml rename to engine/playmon/internal/constrcheck/tests/true_var_str.yml diff --git a/engines/playmon/internal/constrcheck/tests/true_var_strblock.yml b/engine/playmon/internal/constrcheck/tests/true_var_strblock.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/true_var_strblock.yml rename to engine/playmon/internal/constrcheck/tests/true_var_strblock.yml diff --git a/engines/playmon/internal/constrcheck/tests/var_equal_nonexistent.yml b/engine/playmon/internal/constrcheck/tests/var_equal_nonexistent.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/var_equal_nonexistent.yml rename to engine/playmon/internal/constrcheck/tests/var_equal_nonexistent.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_equal.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_equal.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_equal.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_arrint.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_equal_arrint.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_equal_arrint.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_equal_arrint.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_bool_f.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_equal_bool_f.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_equal_bool_f.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_equal_bool_f.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_bool_t.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_equal_bool_t.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_equal_bool_t.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_equal_bool_t.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_enum.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_equal_enum.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_equal_enum.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_equal_enum.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_float.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_equal_float.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_equal_float.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_equal_float.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_int.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_equal_int.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_equal_int.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_equal_int.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_missing_field.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_equal_missing_field.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_equal_missing_field.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_equal_missing_field.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_null.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_equal_null.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_equal_null.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_equal_null.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_equal_str.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_equal_str.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_equal_str.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_equal_str.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_false_equal_missing_field.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_false_equal_missing_field.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_false_equal_missing_field.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_false_equal_missing_field.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_false_not_equal_missing_field.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_false_not_equal_missing_field.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_false_not_equal_missing_field.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_false_not_equal_missing_field.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_len_arr_int.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_len_arr_int.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_len_arr_int.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_len_arr_int.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_len_arr_obj.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_len_arr_obj.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_len_arr_obj.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_len_arr_obj.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_len_str.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_len_str.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_len_str.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_len_str.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_map_2d.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_map_2d.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_map_2d.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_map_2d.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_map_obj_too_many_fields.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_map_obj_too_many_fields.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_map_obj_too_many_fields.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_map_obj_too_many_fields.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_not_equal.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_not_equal.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_not_equal.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_not_equal.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_obj_too_many_inputs.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_obj_too_many_inputs.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_obj_too_many_inputs.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_obj_too_many_inputs.yml diff --git a/engines/playmon/internal/constrcheck/tests/wrong_type_relational_float.yml b/engine/playmon/internal/constrcheck/tests/wrong_type_relational_float.yml similarity index 100% rename from engines/playmon/internal/constrcheck/tests/wrong_type_relational_float.yml rename to engine/playmon/internal/constrcheck/tests/wrong_type_relational_float.yml diff --git a/engine/playmon/internal/pathmatch/bench_test.go b/engine/playmon/internal/pathmatch/bench_test.go new file mode 100644 index 0000000..1b7423a --- /dev/null +++ b/engine/playmon/internal/pathmatch/bench_test.go @@ -0,0 +1,23 @@ +package pathmatch_test + +import ( + "testing" + + "github.com/graph-guard/ggproxy/config" +) + +var GI int + +func BenchmarkPathmatch(b *testing.B) { + for _, bb := range tests { + b.Run(bb.name, func(b *testing.B) { + m := prepareTestSetup(b, bb.conf) + b.ResetTimer() + for n := 0; n < b.N; n++ { + m.Match(bb.paths, func(tm *config.Template) { + GI++ + }) + } + }) + } +} diff --git a/engine/playmon/internal/pathmatch/pathmatch.go b/engine/playmon/internal/pathmatch/pathmatch.go new file mode 100644 index 0000000..1616d08 --- /dev/null +++ b/engine/playmon/internal/pathmatch/pathmatch.go @@ -0,0 +1,82 @@ +package pathmatch + +import ( + "github.com/graph-guard/ggproxy/config" + "github.com/graph-guard/ggproxy/engine/playmon/internal/pathscan" + "github.com/graph-guard/ggproxy/utilities/bitmask" + "github.com/graph-guard/gqt/v4" +) + +type Matcher struct { + conf *config.Service + templatePaths [][]string + paths map[string]*bitmask.Set + lengths []int + + // Operational data, reset for every match call + matchMask, rejectedMask *bitmask.Set + matchesPerTemplate []int +} + +// Match calls onMatch for every template matching paths +func (m *Matcher) Match(paths []string, onMatch func(*config.Template)) { + m.matchMask.Reset() + m.rejectedMask.Reset() + for i := range m.matchesPerTemplate { + m.matchesPerTemplate[i] = 0 + } + + for i := range paths { + b, ok := m.paths[paths[i]] + if !ok { + return // Unknown path, can't match any template + } + b.VisitAll(func(n int) { m.matchesPerTemplate[n]++ }) + m.matchMask.SetOr(m.matchMask, b) + } + for i := range m.matchesPerTemplate { + if m.matchesPerTemplate[i] < len(paths) { + m.rejectedMask.Add(i) + } + } + m.matchMask.SetAndNot(m.matchMask, m.rejectedMask) + m.matchMask.VisitAll(func(n int) { + onMatch(m.conf.TemplatesEnabled[n]) + }) +} + +func New(conf *config.Service) *Matcher { + m := &Matcher{ + conf: conf, + // There will at least be 1 path per template + paths: make(map[string]*bitmask.Set, len(conf.TemplatesEnabled)), + templatePaths: make([][]string, len(conf.TemplatesEnabled)), + lengths: make([]int, len(conf.TemplatesEnabled)), + + matchMask: bitmask.New(), + rejectedMask: bitmask.New(), + matchesPerTemplate: make([]int, len(conf.TemplatesEnabled)), + } + for i := range conf.TemplatesEnabled { + { // Associate paths with templates by index + pathscan.InAST( + conf.TemplatesEnabled[i].GQTTemplate, + func(path []byte, e gqt.Expression) (stop bool) { + m.templatePaths[i] = append(m.templatePaths[i], string(path)) + return false + }, + func(path []byte, e gqt.Expression) (stop bool) { return false }, + ) + } + + // Initialize path bitmasks + for _, p := range m.templatePaths[i] { + if _, ok := m.paths[p]; !ok { + m.paths[p] = bitmask.New() + } + m.paths[p].Add(i) + } + m.lengths[i] = len(m.templatePaths[i]) + } + return m +} diff --git a/engine/playmon/internal/pathmatch/pathmatch_test.go b/engine/playmon/internal/pathmatch/pathmatch_test.go new file mode 100644 index 0000000..a94a54e --- /dev/null +++ b/engine/playmon/internal/pathmatch/pathmatch_test.go @@ -0,0 +1,197 @@ +package pathmatch_test + +import ( + "testing" + + "github.com/graph-guard/ggproxy/config" + "github.com/graph-guard/ggproxy/engine/playmon/internal/pathmatch" + "github.com/graph-guard/gqt/v4" + "github.com/stretchr/testify/require" +) + +func TestMatch(t *testing.T) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := prepareTestSetup(t, tt.conf) + var actualMatches []string + m.Match(tt.paths, func(tm *config.Template) { + actualMatches = append(actualMatches, tm.ID) + }) + require.Equal(t, tt.expectIDs, actualMatches) + }) + } +} + +var tests = []struct { + name string + conf *config.Service + paths []string + expectIDs []string +}{ + { + name: "no paths", + conf: &config.Service{ + TemplatesEnabled: []*config.Template{{ + ID: "A", Source: []byte(`query { foo }`), + }}, + }, + paths: nil, + expectIDs: nil, + }, + { + name: "unknown path", + conf: &config.Service{ + TemplatesEnabled: []*config.Template{{ + ID: "A", Source: []byte(`query { foo }`), + }}, + }, + paths: []string{"Q.bar"}, + expectIDs: nil, + }, + { + name: "1_of_1", + conf: &config.Service{ + TemplatesEnabled: []*config.Template{{ + ID: "A", Source: []byte(`query { foo }`), + }}, + }, + paths: []string{"Q.foo"}, + expectIDs: []string{"A"}, + }, + { + name: "first_of_2", + conf: &config.Service{ + TemplatesEnabled: []*config.Template{ + {ID: "A", Source: []byte(`query { foo }`)}, + {ID: "B", Source: []byte(`query { bar }`)}, + }, + }, + paths: []string{"Q.foo"}, + expectIDs: []string{"A"}, + }, + { + name: "second_of_2", + conf: &config.Service{ + TemplatesEnabled: []*config.Template{ + {ID: "A", Source: []byte(`query { foo }`)}, + {ID: "B", Source: []byte(`query { bar }`)}, + }, + }, + paths: []string{"Q.bar"}, + expectIDs: []string{"B"}, + }, + { + name: "not_both", + conf: &config.Service{ + TemplatesEnabled: []*config.Template{ + {ID: "A", Source: []byte(`query { foo }`)}, + {ID: "B", Source: []byte(`query { bar }`)}, + }, + }, + paths: []string{"Q.foo", "Q.bar"}, + expectIDs: nil, + }, + { + name: "1_of_3", + conf: &config.Service{ + TemplatesEnabled: []*config.Template{ + {ID: "A", Source: []byte(`query { foo bazz }`)}, + {ID: "B", Source: []byte(`query { bar bazz }`)}, + }, + }, + paths: []string{"Q.bazz", "Q.bar"}, + expectIDs: []string{"B"}, + }, + { + name: "3_of_3", + conf: &config.Service{ + TemplatesEnabled: []*config.Template{ + {ID: "A", Source: []byte(`query { foo bazz }`)}, + {ID: "B", Source: []byte(`query { bar bazz }`)}, + {ID: "C", Source: []byte(`query { bazz }`)}, + }, + }, + paths: []string{"Q.bazz"}, + expectIDs: []string{"A", "B", "C"}, + }, + // { + // name: "2_of_20", + // conf: &config.Service{ + // TemplatesEnabled: []*config.Template{ + // {ID: "Q1", Source: []byte(`query { foo bazz }`)}, + // {ID: "Q2", Source: []byte(`query { bar bazz }`)}, + // {ID: "Q3", Source: []byte(`query { bazz { fraz } maz }`)}, + // {ID: "Q4", Source: []byte(`query { bazz { fraz } }`)}, + // }, + // }, + // paths: []string{"Q.bazz"}, + // expectIDs: []string{"A", "B", "C"}, + // }, + { + name: "starwars", + conf: func() *config.Service { + s, err := config.New("./test_setups/starwars/config.yml") + if err != nil { + panic(err) + } + return s.ServicesEnabled[0] + }(), + paths: []string{ + "Q.hero|episode.id", + "Q.hero|episode.name", + "Q.hero|episode.appearsIn", + "Q.hero|episode.friends.id", + "Q.hero|episode.friends.name", + "Q.hero|episode.friends.appearsIn", + "Q.hero|episode.friends.friends.id", + "Q.hero|episode.friends.friends.name", + "Q.hero|episode.friends.friends.appearsIn", + "Q.hero|episode.friendsConnection|after,first.totalCount", + "Q.hero|episode.friendsConnection|after,first.friends.id", + "Q.hero|episode.friendsConnection|after,first.friends.name", + "Q.hero|episode.friendsConnection|after,first.friends.appearsIn", + }, + expectIDs: []string{"c"}, + /* query { + hero(episode: EMPIRE || JEDI) { + id + name + appearsIn + friends { + id + name + appearsIn + friends { + id + name + appearsIn + } + } + friendsConnection(first: >= 0, after: len > 0) { + totalCount + friends { + id + name + appearsIn + } + } + } + } */ + }, +} + +func prepareTestSetup(t testing.TB, c *config.Service) *pathmatch.Matcher { + p, err := gqt.NewParser(nil) + if err != nil { + t.Fatalf("initializing gqt parser: %v", err) + } + for _, tmpl := range c.TemplatesEnabled { + opr, _, errs := p.Parse(tmpl.Source) + if errs != nil { + t.Fatalf("parsing template: %v", errs) + } + tmpl.GQTTemplate, tmpl.Enabled = opr, true + } + m := pathmatch.New(c) + return m +} diff --git a/engine/playmon/internal/pathmatch/test_setups/starwars/config.yml b/engine/playmon/internal/pathmatch/test_setups/starwars/config.yml new file mode 100644 index 0000000..f5f9715 --- /dev/null +++ b/engine/playmon/internal/pathmatch/test_setups/starwars/config.yml @@ -0,0 +1,13 @@ +proxy: + # Address and port of the proxy server. + host: localhost:8000 + # Optional, in bytes, default: 4MiB. + max-request-body-size: 4096 + +# Optional, enables API server. +api: + # Address and port of the API server. + host: localhost:3000 + +all-services: services_enabled +enabled-services: services_enabled diff --git a/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars.yml b/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars.yml new file mode 100644 index 0000000..2f8f239 --- /dev/null +++ b/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars.yml @@ -0,0 +1,13 @@ +# The service's display name +name: "Starwars" +# Source URL path +path: "/starwars" +# Destination URL (where to proxy requests to) +forward-url: "http://localhost:8080/starwars" + +# false for forwarding the original request, +# true for the reduced version. +forward-reduced: true + +all-templates: starwars/templates_enabled +enabled-templates: starwars/templates_enabled diff --git a/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/a.gqt b/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/a.gqt new file mode 100644 index 0000000..dcdbdfa --- /dev/null +++ b/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/a.gqt @@ -0,0 +1,13 @@ +--- +name: "A" +--- +query { + hero(episode: *) { + id + name + friends { + id + name + } + } +} diff --git a/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/b.gqt b/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/b.gqt new file mode 100644 index 0000000..47fefd9 --- /dev/null +++ b/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/b.gqt @@ -0,0 +1,20 @@ +--- +name: "B" +--- +query { + hero(episode: EMPIRE || JEDI) { + id + name + appearsIn + friends { + id + name + appearsIn + friends { + id + name + appearsIn + } + } + } +} diff --git a/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/c.gqt b/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/c.gqt new file mode 100644 index 0000000..6cbb20e --- /dev/null +++ b/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/c.gqt @@ -0,0 +1,28 @@ +--- +name: "C" +--- +query { + hero(episode: EMPIRE || JEDI) { + id + name + appearsIn + friends { + id + name + appearsIn + friends { + id + name + appearsIn + } + } + friendsConnection(first: >= 0, after: len > 0) { + totalCount + friends { + id + name + appearsIn + } + } + } +} diff --git a/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/d.gqt b/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/d.gqt new file mode 100644 index 0000000..f5aafe8 --- /dev/null +++ b/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/d.gqt @@ -0,0 +1,20 @@ +--- +name: "D" +--- +query { + hero(episode: EMPIRE || JEDI) { + id + name + appearsIn + friends { + id + name + appearsIn + } + } + reviews(episode: EMPIRE || JEDI) { + stars + commentary + time + } +} diff --git a/engine/playmon/internal/pathscan/pathscan.go b/engine/playmon/internal/pathscan/pathscan.go new file mode 100644 index 0000000..5010267 --- /dev/null +++ b/engine/playmon/internal/pathscan/pathscan.go @@ -0,0 +1,411 @@ +// Package pathscan provides functions for extraction of paths +// from token slices and GQT ASTs. Paths address structural leaf nodes and +// variable values in a GraphQL operation. +// Query operations always begin with "Q", mutation operations always begin +// with "M" and subscription operations always begin with "S". +// +// Consider the following example: +// +// query { +// foo { +// bar { +// burr(x: != $i2) +// } +// baz { +// buzz(b:5, a:null, c=$c:true) +// ... on Kraz { +// fraz +// graz(argument:{i:$c,i2=$i2:"bar"}) { +// lum +// } +// } +// } +// } +// mazz +// } +// +// The above query operation contains 5 structural leafs with +// the following paths: +// +// - Q.foo.bar.burr|x +// - Q.foo.baz.buzz|a,b,c +// - Q.foo.baz&Kraz.fraz +// - Q.foo.baz&Kraz.graz|argument.lum +// - Q.mazz +package pathscan + +import ( + "bytes" + "fmt" + "sort" + + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/utilities/stack" + gqlscan "github.com/graph-guard/gqlscan" + gqt "github.com/graph-guard/gqt/v4" +) + +// PathScanner is reset in every call to InTokens +type PathScanner struct { + pathBuf []byte + stack stack.Stack[int] +} + +func New(preallocateStack, preallocatePathBuffer int) *PathScanner { + return &PathScanner{ + pathBuf: make([]byte, 0, preallocatePathBuffer), + stack: stack.New[int](preallocateStack), + } +} + +// Magic identifier and divider bytes +const ( + initQuery = 'Q' + initMutation = 'M' + initSubscription = 'S' + divSel = '.' + divArgList = '|' + divArg = ',' + divTypeCond = '&' + divObjField = '/' +) + +// InTokens calls onStructural for every encountered structural path, +// and onVariable for every encountered variable. +// operation is expected to be an operation initialization token and +// is used to determine whether the path should start with +// "Q" (query), "M" (mutation) or "S" (subscription). +// InTokens will panic if operation contains any other token. +// gqtVarPaths provides a set of all known GQT variable paths. +// +// WARNING: Aliasing provided paths and using them after +// onStructural or onVariable return may cause data corruption +// because path refers to an internal buffer of PathScanner! +func (s *PathScanner) InTokens( + operation gqlscan.Token, + tokens []gqlparse.Token, + gqtVarPaths map[string]struct{}, + onStructural, onVariable func(path []byte, i int) (stop bool), +) { + s.pathBuf = s.pathBuf[:0] + s.stack.Reset() + + switch operation { + case gqlscan.TokenDefQry: + s.stackPushByte(initQuery) + case gqlscan.TokenDefMut: + s.stackPushByte(initMutation) + case gqlscan.TokenDefSub: + s.stackPushByte(initSubscription) + default: + panic(fmt.Errorf("unexpected operation: %v", operation)) + } + for i, level := 0, 1; i < len(tokens); i++ { + switch tokens[i].ID { + case gqlscan.TokenSet: + level++ + case gqlscan.TokenSetEnd: + level-- + s.stackPop() + case gqlscan.TokenFragInline: + if level > s.stack.Len() { + s.stackPushWithDiv(divTypeCond, tokens[i].Value) + break + } + s.stackPop() + s.stackPushWithDiv(divTypeCond, tokens[i].Value) + case gqlscan.TokenField: + if level > s.stack.Len() { + s.stackPushWithDiv(divSel, tokens[i].Value) + } else { + s.stackPop() + s.stackPushWithDiv(divSel, tokens[i].Value) + } + if t := tokens[i+1].ID; t == gqlscan.TokenArgList || + t == gqlscan.TokenSet { + continue + } + if onStructural(s.pathBuf, i) { + return + } + case gqlscan.TokenArgName: + s.stackPushWithDiv(divArgList, tokens[i].Value) + if onStructural(s.pathBuf, i) { + return + } + s.stackPop() + } + } +} + +func (s *PathScanner) stackPushByte(element byte) { + s.stack.Push(1) + s.pathBuf = append(s.pathBuf, element) +} + +func (s *PathScanner) stackPushWithDiv(div byte, element []byte) { + s.stack.Push(1 + len(element)) + s.pathBuf = append(s.pathBuf, div) + s.pathBuf = append(s.pathBuf, element...) +} + +func (s *PathScanner) stackPop() { + t := s.stack.Top() + s.pathBuf = s.pathBuf[:len(s.pathBuf)-t] + s.stack.Pop() +} + +// InAST calls onStructural for every structural path that can be used for +// (sub)matching. onVariable is called for every path to an argument or an +// object field that has a variable associated. +func InAST( + o *gqt.Operation, + onStructural func(path []byte, e gqt.Expression) (stop bool), + onVariable func(path []byte, e gqt.Expression) (stop bool), +) { + traverse(o, func(e gqt.Expression) (stop, skipChildren bool) { + switch e := e.(type) { + case *gqt.SelectionField: + for _, a := range e.Arguments { + if a.AssociatedVariable != nil { + p := makePathVar(a) + if onVariable(p, e) { + return true, true + } + } + } + if len(e.Selections) > 0 { + break + } + p := makePathStructural(e) + if onStructural(p, e) { + return true, true + } + case *gqt.ObjectField: + if e.AssociatedVariable != nil { + p := makePathVar(e) + if onVariable(p, e) { + return true, true + } + } + } + return false, false // Continue traversal + }) +} + +// traverse returns true after BFS-traversing the entire tree under e +// calling onExpression for every discovered expression. +func traverse( + e gqt.Expression, + onExpression func(gqt.Expression) (stop, skipChildren bool), +) (stopped bool) { + stack := make([]gqt.Expression, 0, 64) + push := func(expression gqt.Expression) { + stack = append(stack, expression) + } + + push(e) + for len(stack) > 0 { + top := stack[len(stack)-1] + stack = stack[:len(stack)-1] + stop, skipChildren := onExpression(top) + if stop { + return true + } else if skipChildren { + continue + } + + switch e := top.(type) { + case *gqt.Operation: + for _, f := range e.Selections { + push(f) + } + case *gqt.SelectionInlineFrag: + for _, f := range e.Selections { + push(f) + } + case *gqt.SelectionField: + for _, f := range e.Selections { + push(f) + } + for _, a := range e.Arguments { + push(a) + } + case *gqt.SelectionMax: + for _, f := range e.Options.Selections { + push(f) + } + case *gqt.Argument: + push(e.Constraint) + case *gqt.ConstrEquals: + push(e.Value) + case *gqt.ConstrNotEquals: + push(e.Value) + case *gqt.ConstrGreater: + push(e.Value) + case *gqt.ConstrGreaterOrEqual: + push(e.Value) + case *gqt.ConstrLess: + push(e.Value) + case *gqt.ConstrLessOrEqual: + push(e.Value) + case *gqt.ConstrLenEquals: + push(e.Value) + case *gqt.ConstrLenNotEquals: + push(e.Value) + case *gqt.ConstrLenGreater: + push(e.Value) + case *gqt.ConstrLenLess: + push(e.Value) + case *gqt.ConstrLenGreaterOrEqual: + push(e.Value) + case *gqt.ConstrLenLessOrEqual: + push(e.Value) + case *gqt.ConstrMap: + push(e.Constraint) + case *gqt.ExprParentheses: + push(e.Expression) + case *gqt.ExprEqual: + push(e.Left) + push(e.Right) + case *gqt.ExprNotEqual: + push(e.Left) + push(e.Right) + case *gqt.ExprLogicalNegation: + push(e.Expression) + case *gqt.ExprNumericNegation: + push(e.Expression) + case *gqt.ExprLogicalOr: + for _, e := range e.Expressions { + push(e) + } + case *gqt.ExprLogicalAnd: + for _, e := range e.Expressions { + push(e) + } + case *gqt.ExprAddition: + push(e.AddendLeft) + push(e.AddendRight) + case *gqt.ExprSubtraction: + push(e.Minuend) + push(e.Subtrahend) + case *gqt.ExprMultiplication: + push(e.Multiplicant) + push(e.Multiplicator) + case *gqt.ExprDivision: + push(e.Dividend) + push(e.Divisor) + case *gqt.ExprModulo: + push(e.Dividend) + push(e.Divisor) + case *gqt.ExprGreater: + push(e.Left) + push(e.Right) + case *gqt.ExprGreaterOrEqual: + push(e.Left) + push(e.Right) + case *gqt.ExprLess: + push(e.Left) + push(e.Right) + case *gqt.ExprLessOrEqual: + push(e.Left) + push(e.Right) + case *gqt.Array: + for _, i := range e.Items { + push(i) + } + case *gqt.Object: + for _, f := range e.Fields { + push(f) + } + case *gqt.ObjectField: + push(e.Constraint) + case *gqt.Number, *gqt.True, *gqt.False, *gqt.Null, + *gqt.Enum, *gqt.String, *gqt.ConstrAny, *gqt.Variable: + default: + panic(fmt.Errorf("unhandled type: %T", top)) + } + } + return false +} + +// makePathStructural generates a structural path for the given expression. +func makePathStructural(e *gqt.SelectionField) []byte { + var p []gqt.Expression // Reversed path + for e := gqt.Expression(e); e != nil; e = e.GetParent() { + p = append(p, e) + } + var s bytes.Buffer + for i := len(p) - 1; i >= 0; i-- { + switch v := p[i].(type) { + case *gqt.SelectionInlineFrag: + _ = s.WriteByte('&') + _, _ = s.WriteString(v.TypeCondition.TypeName) + case *gqt.SelectionField: + _ = s.WriteByte('.') + _, _ = s.WriteString(v.Name.Name) + if len(v.Arguments) > 0 { + _ = s.WriteByte('|') + argNames := make([]string, len(v.Arguments)) + for i := range v.Arguments { + argNames[i] = v.Arguments[i].Name.Name + } + sort.Strings(argNames) + for i := range argNames { + _, _ = s.WriteString(argNames[i]) + if i+1 < len(argNames) { + _ = s.WriteByte(divArg) + } + } + } + case *gqt.Operation: + switch v.Type { + case gqt.OperationTypeQuery: + _, _ = s.WriteString("Q") + case gqt.OperationTypeMutation: + _, _ = s.WriteString("M") + case gqt.OperationTypeSubscription: + _, _ = s.WriteString("S") + default: + panic(fmt.Errorf("unknown operation type: %d", v.Type)) + } + } + } + return s.Bytes() +} + +func makePathVar(e gqt.Expression) []byte { + var p []gqt.Expression // Reversed path + for e := e; e != nil; e = e.GetParent() { + p = append(p, e) + } + var s bytes.Buffer + for i := len(p) - 1; i >= 0; i-- { + switch v := p[i].(type) { + case *gqt.SelectionInlineFrag: + _ = s.WriteByte(divTypeCond) + _, _ = s.WriteString(v.TypeCondition.TypeName) + case *gqt.SelectionField: + _ = s.WriteByte(divSel) + _, _ = s.WriteString(v.Name.Name) + case *gqt.Argument: + _ = s.WriteByte(divArgList) + _, _ = s.WriteString(v.Name.Name) + case *gqt.ObjectField: + _ = s.WriteByte(divObjField) + _, _ = s.WriteString(v.Name.Name) + case *gqt.Operation: + switch v.Type { + case gqt.OperationTypeQuery: + _ = s.WriteByte(initQuery) + case gqt.OperationTypeMutation: + _ = s.WriteByte(initMutation) + case gqt.OperationTypeSubscription: + _ = s.WriteByte(initSubscription) + default: + panic(fmt.Errorf("unknown operation type: %d", v.Type)) + } + } + } + return s.Bytes() +} diff --git a/engine/playmon/internal/pathscan/pathscan_test.go b/engine/playmon/internal/pathscan/pathscan_test.go new file mode 100644 index 0000000..99e34da --- /dev/null +++ b/engine/playmon/internal/pathscan/pathscan_test.go @@ -0,0 +1,314 @@ +package pathscan_test + +import ( + "sort" + "testing" + + "github.com/graph-guard/ggproxy/engine/playmon/internal/pathscan" + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/gqlscan" + "github.com/graph-guard/gqt/v4" + "github.com/stretchr/testify/require" +) + +func TestInTokens(t *testing.T) { + for _, tt := range []struct { + Name string + Input string + VariablePaths map[string]struct{} + Expected map[string]int // path->index + ExpectedVar map[string]int // path->index + }{ + { + Name: "query", + Input: `query { + foo { + bar { + burr(x:4) + } + baz { + ... on Kraz { + fraz + graz(argument:{i:"foo",i2:"bar"}) { + lum + } + } + buzz(b:5, a:null, c:true) + ... on Guz { + guz + } + } + } + mazz + }`, + Expected: map[string]int{ + "Q.foo.bar.burr|x": 7, + "Q.foo.baz&Kraz.fraz": 15, + "Q.foo.baz&Kraz.graz|argument": 18, + "Q.foo.baz&Kraz.graz.lum": 27, + "Q.foo.baz.buzz|b": 32, + "Q.foo.baz.buzz|a": 34, + "Q.foo.baz.buzz|c": 36, + "Q.foo.baz&Guz.guz": 41, + "Q.mazz": 45, + }, + }, + { + Name: "mutation_with_vars", + Input: `mutation($baz:String="baz"){foo(bar:$baz)}`, + Expected: map[string]int{ + "M.foo|bar": 3, + }, + }, + { + Name: "subscription", + Input: `subscription{foo}`, + Expected: map[string]int{ + "S.foo": 1, + }, + }, + } { + t.Run(tt.Name, func(t *testing.T) { + ps := pathscan.New(0, 0) + p := gqlparse.NewParser() + actualPaths := make(map[string]int) + actualVarPaths := make(map[string]int) + p.Parse( + []byte(tt.Input), nil, nil, + func( + varValues [][]gqlparse.Token, + operation, selectionSet []gqlparse.Token, + ) { + ps.InTokens( + operation[0].ID, + selectionSet, + tt.VariablePaths, + func(b []byte, i int) (stop bool) { // On structural + actualPaths[string(b)] = i + return false + }, + func(b []byte, i int) (stop bool) { // On variable + actualVarPaths[string(b)] = i + return false + }, + ) + }, + func(err error) { + t.Fatalf("unexpected GraphQL parsing error: %v", err) + }, + ) + require.Equal(t, tt.Expected, actualPaths) + if tt.ExpectedVar == nil { + tt.ExpectedVar = map[string]int{} + } + require.Equal(t, tt.ExpectedVar, actualVarPaths) + }) + } +} + +func TestInTokensPanic(t *testing.T) { + ps := pathscan.New(0, 0) + require.Panics(t, func() { + ps.InTokens( + gqlscan.TokenOprName, + []gqlparse.Token{ + {ID: gqlscan.TokenSet}, + {ID: gqlscan.TokenField, Value: []byte("foo")}, + {ID: gqlscan.TokenSetEnd}, + }, + map[string]struct{}{ + // No variable paths + }, + func(path []byte, i int) (stop bool) { // On structural + t.Fatal("this function isn't expected to be called") + return false + }, + func(path []byte, i int) (stop bool) { // On variable + t.Fatal("this function isn't expected to be called") + return false + }, + ) + }) +} + +func TestInAst(t *testing.T) { + for _, tt := range []struct { + Name string + GQTTemplateSrc string + ExpectedPaths []string + ExpectedVarPaths []string + }{ + { + Name: "subscription_single", + GQTTemplateSrc: `subscription{foo}`, + ExpectedPaths: []string{ + "S.foo", + }, + }, + { + Name: "subscription_multiple", + GQTTemplateSrc: `subscription{foo bar}`, + ExpectedPaths: []string{ + "S.foo", + "S.bar", + }, + }, + { + Name: "args", + GQTTemplateSrc: `query{foo(b:*,a:"t") bar(c:42)}`, + ExpectedPaths: []string{ + "Q.bar|c", + "Q.foo|a,b", + }, + }, + { + Name: "args_with_subselections", + GQTTemplateSrc: `query{foo(b:*,a:"t") { fraz kraz(c:*) }}`, + ExpectedPaths: []string{ + "Q.foo|a,b.fraz", + "Q.foo|a,b.kraz|c", + }, + }, + { + Name: "mutation_with_vars", + GQTTemplateSrc: `mutation{foo(bar=$bar:{x:*,y:*})}`, + ExpectedPaths: []string{ + "M.foo|bar", + }, + ExpectedVarPaths: []string{ + "M.foo|bar", + }, + }, + { + Name: "mutation_with_vars_on_multiple_levels", + GQTTemplateSrc: `mutation{ + foo(bar=$bar:*){ + fo2(b=$b:*,a=$a:*){ + fo3(c=$c:*) + fa3 + } + fa2 + } + bazz(x:*) + }`, + ExpectedPaths: []string{ + "M.foo|bar.fa2", + "M.foo|bar.fo2|a,b.fo3|c", + "M.foo|bar.fo2|a,b.fa3", + "M.bazz|x", + }, + ExpectedVarPaths: []string{ + "M.foo|bar", + "M.foo.fo2|b", + "M.foo.fo2|a", + "M.foo.fo2.fo3|c", + }, + }, + { + Name: "mutation_var_in_obj", + GQTTemplateSrc: `mutation{ + foo { + bar(bar1:*,bar2:*) { + baz( + o: { + so=$so: { + x=$x: *, + y=$y: *, + } + }, + o2: $so, + x=$x2: $x, + y: $x2 || $y, + ) + } + } + }`, + ExpectedPaths: []string{ + "M.foo.bar|bar1,bar2.baz|o,o2,x,y", + }, + ExpectedVarPaths: []string{ + "M.foo.bar.baz|o/so", + "M.foo.bar.baz|o/so/x", + "M.foo.bar.baz|o/so/y", + "M.foo.bar.baz|x", + }, + }, + { + Name: "type_condition", + GQTTemplateSrc: `query{ + u(u2:*,u1:*) { + ... on Foo { + foo1(x=$foo1x:*, y:*) + foo2(x:*) + } + ... on Bar { + bar1(x:*) + bar2(x=$bar2x:*, y:*) + } + } + }`, + ExpectedPaths: []string{ + "Q.u|u1,u2&Foo.foo1|x,y", + "Q.u|u1,u2&Foo.foo2|x", + "Q.u|u1,u2&Bar.bar1|x", + "Q.u|u1,u2&Bar.bar2|x,y", + }, + ExpectedVarPaths: []string{ + "Q.u&Foo.foo1|x", + "Q.u&Bar.bar2|x", + }, + }, + { + Name: "complex", + GQTTemplateSrc: `query { + foo { + bar { + burr(x:4) + } + baz { + ... on Kraz { + fraz + graz(argument:{i:"foo",i2:"bar"}) { + lum + } + } + buzz(b:5, a:null, c:true) + ... on Guz { + guz + } + } + } + mazz + }`, + ExpectedPaths: []string{ + "Q.foo.bar.burr|x", + "Q.foo.baz&Kraz.fraz", + "Q.foo.baz&Kraz.graz|argument.lum", + "Q.foo.baz&Guz.guz", + "Q.foo.baz.buzz|a,b,c", + "Q.mazz", + }, + }, + } { + t.Run(tt.Name, func(t *testing.T) { + o, _, errs := gqt.Parse([]byte(tt.GQTTemplateSrc)) + require.Nil(t, errs) + var actualPaths, actualVarPaths []string + pathscan.InAST(o, func(path []byte, e gqt.Expression) (stop bool) { + actualPaths = append(actualPaths, string(path)) + require.NotNil(t, e) + return false + }, func(path []byte, e gqt.Expression) (stop bool) { + actualVarPaths = append(actualVarPaths, string(path)) + return false + }) + sort.Strings(tt.ExpectedPaths) + sort.Strings(actualPaths) + require.Equal(t, tt.ExpectedPaths, actualPaths, "structural paths") + + sort.Strings(tt.ExpectedVarPaths) + sort.Strings(actualVarPaths) + require.Equal(t, tt.ExpectedVarPaths, actualVarPaths, "variable paths") + }) + } +} diff --git a/engine/playmon/playmon.go b/engine/playmon/playmon.go new file mode 100644 index 0000000..634a2a3 --- /dev/null +++ b/engine/playmon/playmon.go @@ -0,0 +1,105 @@ +package playmon + +import ( + "fmt" + + "github.com/graph-guard/ggproxy/config" + "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck" + "github.com/graph-guard/ggproxy/engine/playmon/internal/pathmatch" + "github.com/graph-guard/ggproxy/engine/playmon/internal/pathscan" + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/gqlscan" + "github.com/graph-guard/gqt/v4" +) + +type Engine struct { + pathScanner *pathscan.PathScanner + matcher *pathmatch.Matcher + templates []*template + + inputsByPath map[string][]gqlparse.Token +} + +type template struct { + Index int + ID string + GQTOpr *gqt.Operation + ConstraintChecker *constrcheck.Checker +} + +// New expects templates to be initialized and valid. +func New(s *config.Service) *Engine { + e := &Engine{ + pathScanner: pathscan.New(128, 2048), + templates: make([]*template, len(s.Templates)), + + inputsByPath: make(map[string][]gqlparse.Token), + } + idCounter := 0 + for _, t := range s.Templates { + c := constrcheck.New(t.GQTTemplate, s.Schema) + e.templates[idCounter] = &template{ + Index: idCounter, + ID: t.ID, + GQTOpr: t.GQTTemplate, + ConstraintChecker: c, + } + idCounter++ + } + pathmatch.New(s) + return e +} + +// Match returns the ID of the first matching template or "" if none was matched. +func (e *Engine) Match( + variableValues [][]gqlparse.Token, + queryType gqlscan.Token, + selectionSet []gqlparse.Token, +) (id string) { + e.currentMask.Reset() + fmt.Println("MASK: ", e.currentMask.String()) + e.pathScanner.Scan( + queryType, + selectionSet, + func(path []byte, i int) (stop bool) { + m, ok := e.maskByPath[string(path)] + if !ok { + return true + } + fmt.Println("PATH: ", string(path)) + e.currentMask.SetOr(e.currentMask, m) + fmt.Println("MASK: ", e.currentMask.String()) + return false + }, + ) + if e.currentMask.Size() < 1 { + return "" + } + e.currentMask.Visit(func(i int) (skip bool) { + t := e.templates[i] + match := true + t.ConstraintChecker.VisitPaths(func(path string) (stop bool) { + if !t.ConstraintChecker.Check(path) { + match = false + return true + } + return false + }) + if match { + id = t.ID + return true + } + return false + }) + return id +} + +// MatchAll calls fn for every matching template. +func (e *Engine) MatchAll( + variableValues [][]gqlparse.Token, + queryType gqlscan.Token, + selectionSet []gqlparse.Token, + fn func(id string), +) { + panic("todo") +} diff --git a/engines/tests/assets/benchassets/_bench_deep/bench.yml b/engine/tests/assets/benchassets/_bench_deep/bench.yml similarity index 100% rename from engines/tests/assets/benchassets/_bench_deep/bench.yml rename to engine/tests/assets/benchassets/_bench_deep/bench.yml diff --git a/engines/tests/assets/benchassets/bench_average/bench.yml b/engine/tests/assets/benchassets/bench_average/bench.yml similarity index 100% rename from engines/tests/assets/benchassets/bench_average/bench.yml rename to engine/tests/assets/benchassets/bench_average/bench.yml diff --git a/engines/tests/assets/benchassets/bench_big/bench.yml b/engine/tests/assets/benchassets/bench_big/bench.yml similarity index 100% rename from engines/tests/assets/benchassets/bench_big/bench.yml rename to engine/tests/assets/benchassets/bench_big/bench.yml diff --git a/engines/tests/assets/benchassets/templates/0.gqt b/engine/tests/assets/benchassets/templates/0.gqt similarity index 100% rename from engines/tests/assets/benchassets/templates/0.gqt rename to engine/tests/assets/benchassets/templates/0.gqt diff --git a/engines/tests/assets/benchassets/templates/1.gqt b/engine/tests/assets/benchassets/templates/1.gqt similarity index 100% rename from engines/tests/assets/benchassets/templates/1.gqt rename to engine/tests/assets/benchassets/templates/1.gqt diff --git a/engines/tests/assets/benchassets/templates/2.gqt b/engine/tests/assets/benchassets/templates/2.gqt similarity index 100% rename from engines/tests/assets/benchassets/templates/2.gqt rename to engine/tests/assets/benchassets/templates/2.gqt diff --git a/engines/tests/assets/benchassets/templates/3.gqt b/engine/tests/assets/benchassets/templates/3.gqt similarity index 100% rename from engines/tests/assets/benchassets/templates/3.gqt rename to engine/tests/assets/benchassets/templates/3.gqt diff --git a/engines/tests/assets/benchassets/templates/4.gqt b/engine/tests/assets/benchassets/templates/4.gqt similarity index 100% rename from engines/tests/assets/benchassets/templates/4.gqt rename to engine/tests/assets/benchassets/templates/4.gqt diff --git a/engines/tests/assets/benchassets/templates/5.gqt b/engine/tests/assets/benchassets/templates/5.gqt similarity index 100% rename from engines/tests/assets/benchassets/templates/5.gqt rename to engine/tests/assets/benchassets/templates/5.gqt diff --git a/engines/tests/assets/benchassets/templates/6.gqt b/engine/tests/assets/benchassets/templates/6.gqt similarity index 100% rename from engines/tests/assets/benchassets/templates/6.gqt rename to engine/tests/assets/benchassets/templates/6.gqt diff --git a/engines/tests/assets/benchassets/templates/7.gqt b/engine/tests/assets/benchassets/templates/7.gqt similarity index 100% rename from engines/tests/assets/benchassets/templates/7.gqt rename to engine/tests/assets/benchassets/templates/7.gqt diff --git a/engines/tests/assets/benchassets/templates/8.tgqt b/engine/tests/assets/benchassets/templates/8.tgqt similarity index 100% rename from engines/tests/assets/benchassets/templates/8.tgqt rename to engine/tests/assets/benchassets/templates/8.tgqt diff --git a/engines/tests/assets/testassets/test_arguments_merge_00/0.gqt b/engine/tests/assets/testassets/test_arguments_merge_00/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_arguments_merge_00/0.gqt rename to engine/tests/assets/testassets/test_arguments_merge_00/0.gqt diff --git a/engines/tests/assets/testassets/test_arguments_merge_00/1.gqt b/engine/tests/assets/testassets/test_arguments_merge_00/1.gqt similarity index 100% rename from engines/tests/assets/testassets/test_arguments_merge_00/1.gqt rename to engine/tests/assets/testassets/test_arguments_merge_00/1.gqt diff --git a/engines/tests/assets/testassets/test_arguments_merge_00/2.gqt b/engine/tests/assets/testassets/test_arguments_merge_00/2.gqt similarity index 100% rename from engines/tests/assets/testassets/test_arguments_merge_00/2.gqt rename to engine/tests/assets/testassets/test_arguments_merge_00/2.gqt diff --git a/engines/tests/assets/testassets/test_arguments_merge_00/3.gqt b/engine/tests/assets/testassets/test_arguments_merge_00/3.gqt similarity index 100% rename from engines/tests/assets/testassets/test_arguments_merge_00/3.gqt rename to engine/tests/assets/testassets/test_arguments_merge_00/3.gqt diff --git a/engines/tests/assets/testassets/test_arguments_merge_00/test.yml b/engine/tests/assets/testassets/test_arguments_merge_00/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_arguments_merge_00/test.yml rename to engine/tests/assets/testassets/test_arguments_merge_00/test.yml diff --git a/engines/tests/assets/testassets/test_arguments_merge_01/0.gqt b/engine/tests/assets/testassets/test_arguments_merge_01/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_arguments_merge_01/0.gqt rename to engine/tests/assets/testassets/test_arguments_merge_01/0.gqt diff --git a/engines/tests/assets/testassets/test_arguments_merge_01/1.gqt b/engine/tests/assets/testassets/test_arguments_merge_01/1.gqt similarity index 100% rename from engines/tests/assets/testassets/test_arguments_merge_01/1.gqt rename to engine/tests/assets/testassets/test_arguments_merge_01/1.gqt diff --git a/engines/tests/assets/testassets/test_arguments_merge_01/2.gqt b/engine/tests/assets/testassets/test_arguments_merge_01/2.gqt similarity index 100% rename from engines/tests/assets/testassets/test_arguments_merge_01/2.gqt rename to engine/tests/assets/testassets/test_arguments_merge_01/2.gqt diff --git a/engines/tests/assets/testassets/test_arguments_merge_01/3.gqt b/engine/tests/assets/testassets/test_arguments_merge_01/3.gqt similarity index 100% rename from engines/tests/assets/testassets/test_arguments_merge_01/3.gqt rename to engine/tests/assets/testassets/test_arguments_merge_01/3.gqt diff --git a/engines/tests/assets/testassets/test_arguments_merge_01/test.yml b/engine/tests/assets/testassets/test_arguments_merge_01/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_arguments_merge_01/test.yml rename to engine/tests/assets/testassets/test_arguments_merge_01/test.yml diff --git a/engines/tests/assets/testassets/test_complex_00/0.gqt b/engine/tests/assets/testassets/test_complex_00/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_complex_00/0.gqt rename to engine/tests/assets/testassets/test_complex_00/0.gqt diff --git a/engines/tests/assets/testassets/test_complex_00/1.gqt b/engine/tests/assets/testassets/test_complex_00/1.gqt similarity index 100% rename from engines/tests/assets/testassets/test_complex_00/1.gqt rename to engine/tests/assets/testassets/test_complex_00/1.gqt diff --git a/engines/tests/assets/testassets/test_complex_00/2.gqt b/engine/tests/assets/testassets/test_complex_00/2.gqt similarity index 100% rename from engines/tests/assets/testassets/test_complex_00/2.gqt rename to engine/tests/assets/testassets/test_complex_00/2.gqt diff --git a/engines/tests/assets/testassets/test_complex_00/3.gqt b/engine/tests/assets/testassets/test_complex_00/3.gqt similarity index 100% rename from engines/tests/assets/testassets/test_complex_00/3.gqt rename to engine/tests/assets/testassets/test_complex_00/3.gqt diff --git a/engines/tests/assets/testassets/test_complex_00/4.gqt b/engine/tests/assets/testassets/test_complex_00/4.gqt similarity index 100% rename from engines/tests/assets/testassets/test_complex_00/4.gqt rename to engine/tests/assets/testassets/test_complex_00/4.gqt diff --git a/engines/tests/assets/testassets/test_complex_00/5.gqt b/engine/tests/assets/testassets/test_complex_00/5.gqt similarity index 100% rename from engines/tests/assets/testassets/test_complex_00/5.gqt rename to engine/tests/assets/testassets/test_complex_00/5.gqt diff --git a/engines/tests/assets/testassets/test_complex_00/test.yml b/engine/tests/assets/testassets/test_complex_00/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_complex_00/test.yml rename to engine/tests/assets/testassets/test_complex_00/test.yml diff --git a/engines/tests/assets/testassets/test_conditions_00/0.gqt b/engine/tests/assets/testassets/test_conditions_00/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_conditions_00/0.gqt rename to engine/tests/assets/testassets/test_conditions_00/0.gqt diff --git a/engines/tests/assets/testassets/test_conditions_00/test.yml b/engine/tests/assets/testassets/test_conditions_00/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_conditions_00/test.yml rename to engine/tests/assets/testassets/test_conditions_00/test.yml diff --git a/engines/tests/assets/testassets/test_conditions_01/0.gqt b/engine/tests/assets/testassets/test_conditions_01/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_conditions_01/0.gqt rename to engine/tests/assets/testassets/test_conditions_01/0.gqt diff --git a/engines/tests/assets/testassets/test_conditions_01/1.gqt b/engine/tests/assets/testassets/test_conditions_01/1.gqt similarity index 100% rename from engines/tests/assets/testassets/test_conditions_01/1.gqt rename to engine/tests/assets/testassets/test_conditions_01/1.gqt diff --git a/engines/tests/assets/testassets/test_conditions_01/test.yml b/engine/tests/assets/testassets/test_conditions_01/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_conditions_01/test.yml rename to engine/tests/assets/testassets/test_conditions_01/test.yml diff --git a/engines/tests/assets/testassets/test_conditions_02/0.gqt b/engine/tests/assets/testassets/test_conditions_02/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_conditions_02/0.gqt rename to engine/tests/assets/testassets/test_conditions_02/0.gqt diff --git a/engines/tests/assets/testassets/test_conditions_02/1.gqt b/engine/tests/assets/testassets/test_conditions_02/1.gqt similarity index 100% rename from engines/tests/assets/testassets/test_conditions_02/1.gqt rename to engine/tests/assets/testassets/test_conditions_02/1.gqt diff --git a/engines/tests/assets/testassets/test_conditions_02/test.yml b/engine/tests/assets/testassets/test_conditions_02/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_conditions_02/test.yml rename to engine/tests/assets/testassets/test_conditions_02/test.yml diff --git a/engines/tests/assets/testassets/test_conditions_03/0.gqt b/engine/tests/assets/testassets/test_conditions_03/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_conditions_03/0.gqt rename to engine/tests/assets/testassets/test_conditions_03/0.gqt diff --git a/engines/tests/assets/testassets/test_conditions_03/test.yml b/engine/tests/assets/testassets/test_conditions_03/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_conditions_03/test.yml rename to engine/tests/assets/testassets/test_conditions_03/test.yml diff --git a/engines/tests/assets/testassets/test_conditions_04/0.gqt b/engine/tests/assets/testassets/test_conditions_04/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_conditions_04/0.gqt rename to engine/tests/assets/testassets/test_conditions_04/0.gqt diff --git a/engines/tests/assets/testassets/test_conditions_04/1.gqt b/engine/tests/assets/testassets/test_conditions_04/1.gqt similarity index 100% rename from engines/tests/assets/testassets/test_conditions_04/1.gqt rename to engine/tests/assets/testassets/test_conditions_04/1.gqt diff --git a/engines/tests/assets/testassets/test_conditions_04/test.yml b/engine/tests/assets/testassets/test_conditions_04/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_conditions_04/test.yml rename to engine/tests/assets/testassets/test_conditions_04/test.yml diff --git a/engines/tests/assets/testassets/test_enum_00/0.gqt b/engine/tests/assets/testassets/test_enum_00/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_enum_00/0.gqt rename to engine/tests/assets/testassets/test_enum_00/0.gqt diff --git a/engines/tests/assets/testassets/test_enum_00/1.gqt b/engine/tests/assets/testassets/test_enum_00/1.gqt similarity index 100% rename from engines/tests/assets/testassets/test_enum_00/1.gqt rename to engine/tests/assets/testassets/test_enum_00/1.gqt diff --git a/engines/tests/assets/testassets/test_enum_00/test.yml b/engine/tests/assets/testassets/test_enum_00/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_enum_00/test.yml rename to engine/tests/assets/testassets/test_enum_00/test.yml diff --git a/engines/tests/assets/testassets/test_fragments_00/0.gqt b/engine/tests/assets/testassets/test_fragments_00/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_fragments_00/0.gqt rename to engine/tests/assets/testassets/test_fragments_00/0.gqt diff --git a/engines/tests/assets/testassets/test_fragments_00/test.yml b/engine/tests/assets/testassets/test_fragments_00/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_fragments_00/test.yml rename to engine/tests/assets/testassets/test_fragments_00/test.yml diff --git a/engines/tests/assets/testassets/test_fragments_01/0.gqt b/engine/tests/assets/testassets/test_fragments_01/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_fragments_01/0.gqt rename to engine/tests/assets/testassets/test_fragments_01/0.gqt diff --git a/engines/tests/assets/testassets/test_fragments_01/test.yml b/engine/tests/assets/testassets/test_fragments_01/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_fragments_01/test.yml rename to engine/tests/assets/testassets/test_fragments_01/test.yml diff --git a/engines/tests/assets/testassets/test_fragments_02/0.gqt b/engine/tests/assets/testassets/test_fragments_02/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_fragments_02/0.gqt rename to engine/tests/assets/testassets/test_fragments_02/0.gqt diff --git a/engines/tests/assets/testassets/test_fragments_02/test.yml b/engine/tests/assets/testassets/test_fragments_02/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_fragments_02/test.yml rename to engine/tests/assets/testassets/test_fragments_02/test.yml diff --git a/engines/tests/assets/testassets/test_fragments_03/0.gqt b/engine/tests/assets/testassets/test_fragments_03/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_fragments_03/0.gqt rename to engine/tests/assets/testassets/test_fragments_03/0.gqt diff --git a/engines/tests/assets/testassets/test_fragments_03/test.yml b/engine/tests/assets/testassets/test_fragments_03/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_fragments_03/test.yml rename to engine/tests/assets/testassets/test_fragments_03/test.yml diff --git a/engines/tests/assets/testassets/test_general_logic_00/0.gqt b/engine/tests/assets/testassets/test_general_logic_00/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_00/0.gqt rename to engine/tests/assets/testassets/test_general_logic_00/0.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_00/test.yml b/engine/tests/assets/testassets/test_general_logic_00/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_00/test.yml rename to engine/tests/assets/testassets/test_general_logic_00/test.yml diff --git a/engines/tests/assets/testassets/test_general_logic_01/0.gqt b/engine/tests/assets/testassets/test_general_logic_01/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_01/0.gqt rename to engine/tests/assets/testassets/test_general_logic_01/0.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_01/test.yml b/engine/tests/assets/testassets/test_general_logic_01/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_01/test.yml rename to engine/tests/assets/testassets/test_general_logic_01/test.yml diff --git a/engines/tests/assets/testassets/test_general_logic_02/0.gqt b/engine/tests/assets/testassets/test_general_logic_02/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_02/0.gqt rename to engine/tests/assets/testassets/test_general_logic_02/0.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_02/test.yml b/engine/tests/assets/testassets/test_general_logic_02/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_02/test.yml rename to engine/tests/assets/testassets/test_general_logic_02/test.yml diff --git a/engines/tests/assets/testassets/test_general_logic_03/0.gqt b/engine/tests/assets/testassets/test_general_logic_03/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_03/0.gqt rename to engine/tests/assets/testassets/test_general_logic_03/0.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_03/test.yml b/engine/tests/assets/testassets/test_general_logic_03/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_03/test.yml rename to engine/tests/assets/testassets/test_general_logic_03/test.yml diff --git a/engines/tests/assets/testassets/test_general_logic_04/0.gqt b/engine/tests/assets/testassets/test_general_logic_04/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_04/0.gqt rename to engine/tests/assets/testassets/test_general_logic_04/0.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_04/test.yml b/engine/tests/assets/testassets/test_general_logic_04/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_04/test.yml rename to engine/tests/assets/testassets/test_general_logic_04/test.yml diff --git a/engines/tests/assets/testassets/test_general_logic_05/0.gqt b/engine/tests/assets/testassets/test_general_logic_05/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_05/0.gqt rename to engine/tests/assets/testassets/test_general_logic_05/0.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_05/1.gqt b/engine/tests/assets/testassets/test_general_logic_05/1.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_05/1.gqt rename to engine/tests/assets/testassets/test_general_logic_05/1.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_05/test.yml b/engine/tests/assets/testassets/test_general_logic_05/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_05/test.yml rename to engine/tests/assets/testassets/test_general_logic_05/test.yml diff --git a/engines/tests/assets/testassets/test_general_logic_06/0.gqt b/engine/tests/assets/testassets/test_general_logic_06/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_06/0.gqt rename to engine/tests/assets/testassets/test_general_logic_06/0.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_06/1.gqt b/engine/tests/assets/testassets/test_general_logic_06/1.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_06/1.gqt rename to engine/tests/assets/testassets/test_general_logic_06/1.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_06/test.yml b/engine/tests/assets/testassets/test_general_logic_06/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_06/test.yml rename to engine/tests/assets/testassets/test_general_logic_06/test.yml diff --git a/engines/tests/assets/testassets/test_general_logic_07/0.gqt b/engine/tests/assets/testassets/test_general_logic_07/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_07/0.gqt rename to engine/tests/assets/testassets/test_general_logic_07/0.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_07/test.yml b/engine/tests/assets/testassets/test_general_logic_07/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_07/test.yml rename to engine/tests/assets/testassets/test_general_logic_07/test.yml diff --git a/engines/tests/assets/testassets/test_general_logic_08/0.gqt b/engine/tests/assets/testassets/test_general_logic_08/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_08/0.gqt rename to engine/tests/assets/testassets/test_general_logic_08/0.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_08/test.yml b/engine/tests/assets/testassets/test_general_logic_08/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_08/test.yml rename to engine/tests/assets/testassets/test_general_logic_08/test.yml diff --git a/engines/tests/assets/testassets/test_general_logic_09/0.gqt b/engine/tests/assets/testassets/test_general_logic_09/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_09/0.gqt rename to engine/tests/assets/testassets/test_general_logic_09/0.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_09/test.yml b/engine/tests/assets/testassets/test_general_logic_09/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_09/test.yml rename to engine/tests/assets/testassets/test_general_logic_09/test.yml diff --git a/engines/tests/assets/testassets/test_general_logic_10/0.gqt b/engine/tests/assets/testassets/test_general_logic_10/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_10/0.gqt rename to engine/tests/assets/testassets/test_general_logic_10/0.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_10/1.gqt b/engine/tests/assets/testassets/test_general_logic_10/1.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_10/1.gqt rename to engine/tests/assets/testassets/test_general_logic_10/1.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_10/2.gqt b/engine/tests/assets/testassets/test_general_logic_10/2.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_10/2.gqt rename to engine/tests/assets/testassets/test_general_logic_10/2.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_10/test.yml b/engine/tests/assets/testassets/test_general_logic_10/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_10/test.yml rename to engine/tests/assets/testassets/test_general_logic_10/test.yml diff --git a/engines/tests/assets/testassets/test_general_logic_11/0.gqt b/engine/tests/assets/testassets/test_general_logic_11/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_11/0.gqt rename to engine/tests/assets/testassets/test_general_logic_11/0.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_11/1.gqt b/engine/tests/assets/testassets/test_general_logic_11/1.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_11/1.gqt rename to engine/tests/assets/testassets/test_general_logic_11/1.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_11/2.gqt b/engine/tests/assets/testassets/test_general_logic_11/2.gqt similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_11/2.gqt rename to engine/tests/assets/testassets/test_general_logic_11/2.gqt diff --git a/engines/tests/assets/testassets/test_general_logic_11/test.yml b/engine/tests/assets/testassets/test_general_logic_11/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_general_logic_11/test.yml rename to engine/tests/assets/testassets/test_general_logic_11/test.yml diff --git a/engines/tests/assets/testassets/test_identical_fields_00/0.gqt b/engine/tests/assets/testassets/test_identical_fields_00/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_identical_fields_00/0.gqt rename to engine/tests/assets/testassets/test_identical_fields_00/0.gqt diff --git a/engines/tests/assets/testassets/test_identical_fields_00/test.yml b/engine/tests/assets/testassets/test_identical_fields_00/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_identical_fields_00/test.yml rename to engine/tests/assets/testassets/test_identical_fields_00/test.yml diff --git a/engines/tests/assets/testassets/test_map_00/0.gqt b/engine/tests/assets/testassets/test_map_00/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_map_00/0.gqt rename to engine/tests/assets/testassets/test_map_00/0.gqt diff --git a/engines/tests/assets/testassets/test_map_00/test.yml b/engine/tests/assets/testassets/test_map_00/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_map_00/test.yml rename to engine/tests/assets/testassets/test_map_00/test.yml diff --git a/engines/tests/assets/testassets/test_max_00/0.gqt b/engine/tests/assets/testassets/test_max_00/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_max_00/0.gqt rename to engine/tests/assets/testassets/test_max_00/0.gqt diff --git a/engines/tests/assets/testassets/test_max_00/test.yml b/engine/tests/assets/testassets/test_max_00/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_max_00/test.yml rename to engine/tests/assets/testassets/test_max_00/test.yml diff --git a/engines/tests/assets/testassets/test_max_02/0.gqt b/engine/tests/assets/testassets/test_max_02/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_max_02/0.gqt rename to engine/tests/assets/testassets/test_max_02/0.gqt diff --git a/engines/tests/assets/testassets/test_max_02/1.gqt b/engine/tests/assets/testassets/test_max_02/1.gqt similarity index 100% rename from engines/tests/assets/testassets/test_max_02/1.gqt rename to engine/tests/assets/testassets/test_max_02/1.gqt diff --git a/engines/tests/assets/testassets/test_max_02/test.yml b/engine/tests/assets/testassets/test_max_02/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_max_02/test.yml rename to engine/tests/assets/testassets/test_max_02/test.yml diff --git a/engines/tests/assets/testassets/test_max_03/0.gqt b/engine/tests/assets/testassets/test_max_03/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_max_03/0.gqt rename to engine/tests/assets/testassets/test_max_03/0.gqt diff --git a/engines/tests/assets/testassets/test_max_03/test.yml b/engine/tests/assets/testassets/test_max_03/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_max_03/test.yml rename to engine/tests/assets/testassets/test_max_03/test.yml diff --git a/engines/tests/assets/testassets/test_max_complex_00/0.gqt b/engine/tests/assets/testassets/test_max_complex_00/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_max_complex_00/0.gqt rename to engine/tests/assets/testassets/test_max_complex_00/0.gqt diff --git a/engines/tests/assets/testassets/test_max_complex_00/test.yml b/engine/tests/assets/testassets/test_max_complex_00/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_max_complex_00/test.yml rename to engine/tests/assets/testassets/test_max_complex_00/test.yml diff --git a/engines/tests/assets/testassets/test_negation_00/0.gqt b/engine/tests/assets/testassets/test_negation_00/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_negation_00/0.gqt rename to engine/tests/assets/testassets/test_negation_00/0.gqt diff --git a/engines/tests/assets/testassets/test_negation_00/test.yml b/engine/tests/assets/testassets/test_negation_00/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_negation_00/test.yml rename to engine/tests/assets/testassets/test_negation_00/test.yml diff --git a/engines/tests/assets/testassets/test_negation_01/0.gqt b/engine/tests/assets/testassets/test_negation_01/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_negation_01/0.gqt rename to engine/tests/assets/testassets/test_negation_01/0.gqt diff --git a/engines/tests/assets/testassets/test_negation_01/test.yml b/engine/tests/assets/testassets/test_negation_01/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_negation_01/test.yml rename to engine/tests/assets/testassets/test_negation_01/test.yml diff --git a/engines/tests/assets/testassets/test_nesting_00/0.gqt b/engine/tests/assets/testassets/test_nesting_00/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_nesting_00/0.gqt rename to engine/tests/assets/testassets/test_nesting_00/0.gqt diff --git a/engines/tests/assets/testassets/test_nesting_00/test.yml b/engine/tests/assets/testassets/test_nesting_00/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_nesting_00/test.yml rename to engine/tests/assets/testassets/test_nesting_00/test.yml diff --git a/engines/tests/assets/testassets/test_special_fields_00/0.gqt b/engine/tests/assets/testassets/test_special_fields_00/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_special_fields_00/0.gqt rename to engine/tests/assets/testassets/test_special_fields_00/0.gqt diff --git a/engines/tests/assets/testassets/test_special_fields_00/test.yml b/engine/tests/assets/testassets/test_special_fields_00/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_special_fields_00/test.yml rename to engine/tests/assets/testassets/test_special_fields_00/test.yml diff --git a/engines/tests/assets/testassets/test_token_null_00/0.gqt b/engine/tests/assets/testassets/test_token_null_00/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_token_null_00/0.gqt rename to engine/tests/assets/testassets/test_token_null_00/0.gqt diff --git a/engines/tests/assets/testassets/test_token_null_00/test.yml b/engine/tests/assets/testassets/test_token_null_00/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_token_null_00/test.yml rename to engine/tests/assets/testassets/test_token_null_00/test.yml diff --git a/engines/tests/assets/testassets/test_variables_00/0.gqt b/engine/tests/assets/testassets/test_variables_00/0.gqt similarity index 100% rename from engines/tests/assets/testassets/test_variables_00/0.gqt rename to engine/tests/assets/testassets/test_variables_00/0.gqt diff --git a/engines/tests/assets/testassets/test_variables_00/test.yml b/engine/tests/assets/testassets/test_variables_00/test.yml similarity index 100% rename from engines/tests/assets/testassets/test_variables_00/test.yml rename to engine/tests/assets/testassets/test_variables_00/test.yml diff --git a/engines/tests/benchmark_test.go b/engine/tests/benchmark_test.go similarity index 100% rename from engines/tests/benchmark_test.go rename to engine/tests/benchmark_test.go diff --git a/engines/tests/engine_test.go b/engine/tests/engine_test.go similarity index 100% rename from engines/tests/engine_test.go rename to engine/tests/engine_test.go diff --git a/engines/playmon/internal/constrcheck/constrcheck_test.go b/engines/playmon/internal/constrcheck/constrcheck_test.go deleted file mode 100644 index 7c64fea..0000000 --- a/engines/playmon/internal/constrcheck/constrcheck_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package constrcheck_test - -import ( - "embed" - "io/fs" - "sort" - "strings" - "testing" - - "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck" - "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck/internal/test" - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/gqt/v4" - "github.com/stretchr/testify/require" - "github.com/vektah/gqlparser/v2" - gqlast "github.com/vektah/gqlparser/v2/ast" -) - -//go:embed tests -var testsFS embed.FS - -func TestCheck(t *testing.T) { - d, err := fs.ReadDir(testsFS, "tests") - require.NoError(t, err) - - for _, do := range d { - fileName := do.Name() - if do.IsDir() { - t.Run(fileName, func(t *testing.T) { - t.Skipf("ignoring directory %q", fileName) - }) - continue - } - if !strings.HasSuffix(fileName, ".yml") { - t.Run(fileName, func(t *testing.T) { - t.Skipf("ignoring file %q", fileName) - }) - continue - } - - t.Run(strings.TrimSuffix(fileName, ".yml"), func(t *testing.T) { - ts, err := test.Parse(testsFS, fileName) - require.NoError(t, err) - var withschema, schemaless *gqt.Operation - var schemaAST *gqlast.Schema - { - p, err := gqt.NewParser([]gqt.Source{ - {Name: "schema.graphqls", Content: ts.Schema}, - }) - require.NoError(t, err, "unexpected error in schema") - opr, _, errs := p.Parse([]byte(ts.Template)) - require.Len(t, errs, 0, "unexpected errors: %#v", errs) - withschema = opr - - schemaAST, err = gqlparser.LoadSchema(&gqlast.Source{ - Name: "schema.graphqls", Input: ts.Schema, - }) - require.NoError(t, err) - } - { - opr, _, errs := gqt.Parse([]byte(ts.Template)) - require.Len(t, errs, 0, "unexpected errors: %#v", errs) - schemaless = opr - } - - run := func(t *testing.T, opr *gqt.Operation) { - t.Helper() - c := constrcheck.New(opr, schemaAST) - - paths := make([]string, 0, len(ts.Inputs)) - for path := range ts.Inputs { - paths = append(paths, path) - } - sort.Strings(paths) - - inputs := make(map[string][]gqlparse.Token, len(ts.Inputs)) - for path, v := range ts.Inputs { - inputs[path] = v.Tokens - } - - c.Init(inputs) - for _, path := range paths { - if len(ts.Inputs[path].Tokens) < 1 { - t.Errorf("missing test value for path %q", path) - continue - } - - if ts.Inputs[path].Match == nil { - i := ts.Inputs[path] - t := false - i.Match = &t - ts.Inputs[path] = i - } - if ts.Inputs[path].MatchSchemaless == nil { - i := ts.Inputs[path] - i.MatchSchemaless = i.Match - ts.Inputs[path] = i - } - - actual := c.Check(path) - if opr.Def != nil { - require.Equal( - t, *ts.Inputs[path].Match, actual, - "checking %q", path, - ) - } else { - require.Equal( - t, *ts.Inputs[path].MatchSchemaless, actual, - "checking %q", path, - ) - } - } - } - - t.Run("schema", func(t *testing.T) { run(t, withschema) }) - t.Run("schemaless", func(t *testing.T) { run(t, schemaless) }) - }) - } -} diff --git a/engines/playmon/internal/pathscan/pathscan.go b/engines/playmon/internal/pathscan/pathscan.go deleted file mode 100644 index 1cfb831..0000000 --- a/engines/playmon/internal/pathscan/pathscan.go +++ /dev/null @@ -1,128 +0,0 @@ -// Package pathscan provides a scanner that traverses token slices and -// finds all paths of leaf nodes in a GraphQL operation. -// Query operations always begin with "Q", mutation operations always begin -// with "M" and subscription operations always begin with "S". -// -// Consider the following example: -// -// query { -// foo { -// bar { -// burr(x:4) -// } -// baz { -// buzz(b:5, a:null, c:true) -// ... on Kraz { -// fraz -// graz(argument:{i:"foo",i2:"bar"}) { -// lum -// } -// } -// } -// } -// mazz -// } -// -// The above query contains 7 leafs with the following paths: -// -// - Q.foo.bar.burr|x -// - Q.foo.baz.buzz|b -// - Q.foo.baz.buzz|a -// - Q.foo.baz.buzz|c -// - Q.foo.baz&Kraz.fraz -// - Q.foo.baz&Kraz.graz|argument -// - Q.foo.baz&Kraz.graz.lum -// - Q.mazz -package pathscan - -import ( - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/ggproxy/utilities/stack" - gqlscan "github.com/graph-guard/gqlscan" -) - -type PathScanner struct { - pathBuf []byte - stack stack.Stack[int] -} - -func New(preallocateStack, preallocatePathBuffer int) *PathScanner { - return &PathScanner{ - pathBuf: make([]byte, 0, preallocatePathBuffer), - stack: stack.New[int](preallocateStack), - } -} - -// Magic identifier and divider bytes -const ( - elemQuery = 'Q' - elemMutation = 'M' - elemSubscription = 'S' - divField = '.' - divArg = '|' - divTypeCond = '&' -) - -func (s *PathScanner) Scan(tokens []gqlparse.Token, onPath func([]byte) (stop bool)) { - s.pathBuf = s.pathBuf[:0] - s.stack.Reset() - for i, level := 0, 1; i < len(tokens); i++ { - switch tokens[i].ID { - case gqlscan.TokenDefQry: - s.stackPushByte(elemQuery) - case gqlscan.TokenDefMut: - s.stackPushByte(elemMutation) - case gqlscan.TokenDefSub: - s.stackPushByte(elemSubscription) - case gqlscan.TokenSet: - level++ - case gqlscan.TokenSetEnd: - level-- - s.stackPop() - case gqlscan.TokenFragInline: - if level > s.stack.Len() { - s.stackPushWithDiv(divTypeCond, tokens[i].Value) - break - } - s.stackPop() - s.stackPushWithDiv(divTypeCond, tokens[i].Value) - case gqlscan.TokenField: - if level > s.stack.Len() { - s.stackPushWithDiv(divField, tokens[i].Value) - } else { - s.stackPop() - s.stackPushWithDiv(divField, tokens[i].Value) - } - if t := tokens[i+1].ID; t == gqlscan.TokenArgList || - t == gqlscan.TokenSet { - continue - } - if onPath(s.pathBuf) { - return - } - case gqlscan.TokenArgName: - s.stackPushWithDiv(divArg, tokens[i].Value) - if onPath(s.pathBuf) { - return - } - s.stackPop() - } - } -} - -func (s *PathScanner) stackPushByte(element byte) { - s.stack.Push(1) - s.pathBuf = append(s.pathBuf, element) -} - -func (s *PathScanner) stackPushWithDiv(div byte, element []byte) { - s.stack.Push(1 + len(element)) - s.pathBuf = append(s.pathBuf, div) - s.pathBuf = append(s.pathBuf, element...) -} - -func (s *PathScanner) stackPop() { - t := s.stack.Top() - s.pathBuf = s.pathBuf[:len(s.pathBuf)-t] - s.stack.Pop() -} diff --git a/engines/playmon/internal/pathscan/pathscan_test.go b/engines/playmon/internal/pathscan/pathscan_test.go deleted file mode 100644 index e7739fb..0000000 --- a/engines/playmon/internal/pathscan/pathscan_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package pathscan_test - -import ( - "testing" - - "github.com/graph-guard/ggproxy/engines/playmon/internal/pathscan" - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/stretchr/testify/require" -) - -func TestPathScan(t *testing.T) { - for _, tt := range []struct { - Name string - Input string - ExpectedPaths []string - }{ - { - Name: "query", - Input: `query { - foo { - bar { - burr(x:4) - } - baz { - ... on Kraz { - fraz - graz(argument:{i:"foo",i2:"bar"}) { - lum - } - } - buzz(b:5, a:null, c:true) - ... on Guz { - guz - } - } - } - mazz - }`, - ExpectedPaths: []string{ - "Q.foo.bar.burr|x", - "Q.foo.baz&Kraz.fraz", - "Q.foo.baz&Kraz.graz|argument", - "Q.foo.baz&Kraz.graz.lum", - "Q.foo.baz.buzz|b", - "Q.foo.baz.buzz|a", - "Q.foo.baz.buzz|c", - "Q.foo.baz&Guz.guz", - "Q.mazz", - }, - }, - { - Name: "mutation_with_vars", - Input: `mutation($baz:String="baz"){foo(bar:$baz)}`, - ExpectedPaths: []string{ - "M.foo|bar", - }, - }, - { - Name: "subscription", - Input: `subscription{foo}`, - ExpectedPaths: []string{ - "S.foo", - }, - }, - } { - t.Run(tt.Name, func(t *testing.T) { - ps := pathscan.New(0, 0) - p := gqlparse.NewParser() - var actualPaths []string - p.Parse( - []byte(tt.Input), nil, nil, - func( - varValues [][]gqlparse.Token, - operation, selectionSet []gqlparse.Token, - ) { - ps.Scan(operation, func(b []byte) (stop bool) { - actualPaths = append(actualPaths, string(b)) - return false - }) - }, - func(err error) { - t.Fatalf("unexpected GraphQL parsing error: %v", err) - }, - ) - require.Equal(t, tt.ExpectedPaths, actualPaths) - }) - } -} diff --git a/engines/playmon/playmon.go b/engines/playmon/playmon.go deleted file mode 100644 index a37af62..0000000 --- a/engines/playmon/playmon.go +++ /dev/null @@ -1,68 +0,0 @@ -package playmon - -import ( - "fmt" - - "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck" - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/ggproxy/utilities/bitmask" - "github.com/graph-guard/gqlscan" - "github.com/graph-guard/gqt/v4" -) - -type Engine struct { - constrchecker *constrcheck.Checker - currentMask *bitmask.Set - maskByPath map[string]*bitmask.Set - templatesByName map[string]*Template -} - -type Template struct { - Name string - RawGQTTemplateSource []byte - GQTOpr *gqt.Operation - ConstraintChecker *constrcheck.Checker -} - -// New expects templates to be initialized and valid. -func New(templates ...*Template) *Engine { - e := &Engine{ - maskByPath: make(map[string]*bitmask.Set), - templatesByName: make(map[string]*Template, len(templates)), - } - for i := range templates { - n := templates[i].Name - if _, ok := e.templatesByName[n]; ok { - panic(fmt.Errorf("redeclared template: %q", n)) - } - if templates[i].GQTOpr == nil { - panic(fmt.Errorf("uninitialized template, "+ - "missing GQT operation for path %q", n)) - } - if templates[i].ConstraintChecker == nil { - panic(fmt.Errorf("uninitialized template, "+ - "missing constraint checker for path %q", n)) - } - e.templatesByName[n] = templates[i] - } - return e -} - -// Match returns the ID of the first matching template or "" if none was matched. -func (e *Engine) Match( - variableValues [][]gqlparse.Token, - queryType gqlscan.Token, - selectionSet []gqlparse.Token, -) (id string) { - panic("todo") -} - -// MatchAll calls fn for every matching template. -func (e *Engine) MatchAll( - variableValues [][]gqlparse.Token, - queryType gqlscan.Token, - selectionSet []gqlparse.Token, - fn func(id string), -) { - panic("todo") -} diff --git a/server/api.go b/server/api.go index 7690a59..38d1866 100644 --- a/server/api.go +++ b/server/api.go @@ -4,7 +4,6 @@ import ( "context" "crypto/tls" "errors" - "fmt" stdlog "log" "net" "net/http" @@ -20,7 +19,7 @@ import ( "github.com/graph-guard/ggproxy/api/graph/generated" "github.com/graph-guard/ggproxy/api/graph/model" "github.com/graph-guard/ggproxy/config" - "github.com/graph-guard/ggproxy/engines/rmap" + "github.com/graph-guard/ggproxy/engine/playmon" "github.com/graph-guard/ggproxy/gqlparse" gqt "github.com/graph-guard/gqt/v4" plog "github.com/phuslu/log" @@ -266,14 +265,13 @@ func makeService( ), } - { // Initialize matcher engine + { // Initialize engine d := make(map[string]*gqt.Operation, len(s.Templates)) for _, t := range s.Templates { d[t.ID] = t.GQTTemplate tm := &model.Template{ Service: service, Stats: proxyServer.GetTemplateStatistics(s.ID, t.ID), - ID: t.ID, Tags: t.Tags, Source: string(t.Source), @@ -283,14 +281,7 @@ func makeService( service.TemplatesByID[t.ID] = tm } - var err error - service.Matcher, err = rmap.New(d, 0) - if err != nil { - panic(fmt.Errorf( - "initializing matcher for service %q: %w", - s.ID, err, - )) - } + service.Engine = playmon.New(s) } { // Set proxy URL diff --git a/server/proxy.go b/server/proxy.go index bcad012..52ffe1c 100644 --- a/server/proxy.go +++ b/server/proxy.go @@ -2,13 +2,12 @@ package server import ( "crypto/tls" - "fmt" "net" "sync" "time" "github.com/graph-guard/ggproxy/config" - "github.com/graph-guard/ggproxy/engines/rmap" + "github.com/graph-guard/ggproxy/engine/playmon" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/ggproxy/statistics" "github.com/graph-guard/ggproxy/utilities/tokenwriter" @@ -32,14 +31,14 @@ type service struct { forwardURL string forwardReduced bool log plog.Logger - matcherpool sync.Pool + enginePool sync.Pool statistics *statistics.ServiceSync templateStatistics map[string]*statistics.TemplateSync } -type matcher struct { +type engine struct { Parser *gqlparse.Parser - Engine *rmap.RulesMap + Engine *playmon.Engine } func NewProxy( @@ -92,23 +91,17 @@ func NewProxy( forwardURL: s.ForwardURL, forwardReduced: s.ForwardReduced, log: log, - matcherpool: sync.Pool{ + enginePool: sync.Pool{ New: func() any { d := make(map[string]*gqt.Operation, len(s.TemplatesEnabled)) for _, t := range s.TemplatesEnabled { d[t.ID] = t.GQTTemplate } - engine, err := rmap.New(d, 0) - if err != nil { - panic(fmt.Errorf( - "initializing engine for service %q: %w", - s.ID, err, - )) - } - parser := gqlparse.NewParser() - return &matcher{ - Parser: parser, - Engine: engine, + p := gqlparse.NewParser() + e := playmon.New(s) + return &engine{ + Parser: p, + Engine: e, } }, }, @@ -116,16 +109,16 @@ func NewProxy( templateStatistics: templateStatistics, } - // Warm up matcher pool + // Warm up engine pool func() { // n := runtime.NumCPU() n := 1 - m := make([]*matcher, n) + m := make([]*engine, n) for i := 0; i < n; i++ { - m[i] = services[s.Path].matcherpool.Get().(*matcher) + m[i] = services[s.Path].enginePool.Get().(*engine) } for i := 0; i < n; i++ { - services[s.Path].matcherpool.Put(m[i]) + services[s.Path].enginePool.Put(m[i]) } }() } @@ -190,8 +183,8 @@ func (s *Proxy) handle(ctx *fasthttp.RequestCtx) { return } - m := service.matcherpool.Get().(*matcher) - defer service.matcherpool.Put(m) + m := service.enginePool.Get().(*engine) + defer service.enginePool.Put(m) m.Parser.Parse( query, operationName, variablesJSON, diff --git a/utilities/bitmask/bitmask.go b/utilities/bitmask/bitmask.go index eb126b2..c7b793c 100644 --- a/utilities/bitmask/bitmask.go +++ b/utilities/bitmask/bitmask.go @@ -1,25 +1,11 @@ -//go:build go1.10 -// +build go1.10 - -// Package bitmask provides a bit array implementation. +// Package bitmask provides a bit array/mask implementation. // -// # Bit set +// Forked from github.com/yourbasic/bit. // // A bit set, or bit array, is an efficient set data structure // that consists of an array of 64-bit words. Because it uses // bit-level parallelism, limits memory access, and efficiently uses // the data cache, a bit set often outperforms other data structures. -// -// # Tutorial -// -// The Basics example shows how to create, combine, compare and -// print bit sets. -// -// Primes contains a short and simple, but still efficient, -// implementation of a prime number sieve. -// -// Union is a more advanced example demonstrating how to build -// an efficient variadic Union function using the SetOr method. package bitmask import ( @@ -410,30 +396,6 @@ func (s *Set) DeleteRange(m, n int) *Set { return s } -// And creates a new set that consists of all elements that belong -// to both s1 and s2. -func (s *Set) And(s1 *Set) *Set { - return new(Set).SetAnd(s1, s1) -} - -// Or creates a new set that contains all elements that belong -// to either s1 or s2. -func (s *Set) Or(s1 *Set) *Set { - return new(Set).SetOr(s, s1) -} - -// Xor creates a new set that contains all elements that belong -// to either s1 or s2, but not to both. -func (s *Set) Xor(s1 *Set) *Set { - return new(Set).SetXor(s, s1) -} - -// AndNot creates a new set that consists of all elements that belong -// to s1, but not to s2. -func (s *Set) AndNot(s1 *Set) *Set { - return new(Set).SetAndNot(s, s1) -} - // Set sets s to s1 and then returns a pointer to the updated set s. func (s *Set) Set(s1 *Set) *Set { s.realloc(len(s1.data)) diff --git a/utilities/xxhash/xxhash.go b/utilities/xxhash/xxhash.go index 5fb884a..a2733c4 100644 --- a/utilities/xxhash/xxhash.go +++ b/utilities/xxhash/xxhash.go @@ -1,5 +1,6 @@ -// Package xxhash borrows code from github.com/pierrec/xxHash/xxHash64 -// and adapts it to the needs of the matcher. +// Package xxhash provides XXH64 hashing capabilities. +// +// Forked from github.com/pierrec/xxHash. package xxhash const ( @@ -60,7 +61,7 @@ func Write[B []byte | string](h *Hash, input B) { // Causes compiler to work directly from registers instead of stack: v1, v2, v3, v4 := h.v1, h.v2, h.v3, h.v4 for n := n - 32; p <= n; p += 32 { - sub := input[p:][:32] //BCE hint for compiler + sub := input[p:][:32] // BCE hint for compiler v1 = rol31(v1+u64(sub[:])*prime64_2) * prime64_1 v2 = rol31(v2+u64(sub[8:])*prime64_2) * prime64_1 v3 = rol31(v3+u64(sub[16:])*prime64_2) * prime64_1 @@ -104,7 +105,7 @@ func Write8(h *Hash, input [8]byte) { // Causes compiler to work directly from registers instead of stack: v1, v2, v3, v4 := h.v1, h.v2, h.v3, h.v4 for n := n - 32; p <= n; p += 32 { - sub := input[p:][:32] //BCE hint for compiler + sub := input[p:][:32] // BCE hint for compiler v1 = rol31(v1+u64(sub[:])*prime64_2) * prime64_1 v2 = rol31(v2+u64(sub[8:])*prime64_2) * prime64_1 v3 = rol31(v3+u64(sub[16:])*prime64_2) * prime64_1 From ac052f9241495af8c14f4fbed5c0389843db54c1 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Mon, 13 Feb 2023 23:52:39 +0100 Subject: [PATCH 08/35] fix: Fix path scanner --- .../internal/constrcheck/bench_test.go | 371 +++--- .../internal/constrcheck/constrcheck.go | 1144 +++++++---------- .../internal/constrcheck/constrcheck_test.go | 210 +-- .../playmon/internal/pathmatch/bench_test.go | 7 +- .../playmon/internal/pathmatch/pathmatch.go | 21 +- .../internal/pathmatch/pathmatch_test.go | 7 +- engine/playmon/internal/pathscan/pathscan.go | 341 +++-- .../internal/pathscan/pathscan_test.go | 656 +++++++--- engine/playmon/internal/travgqt/travgqt.go | 145 +++ engine/playmon/playmon.go | 94 +- 10 files changed, 1643 insertions(+), 1353 deletions(-) create mode 100644 engine/playmon/internal/travgqt/travgqt.go diff --git a/engine/playmon/internal/constrcheck/bench_test.go b/engine/playmon/internal/constrcheck/bench_test.go index 864789a..0a803b1 100644 --- a/engine/playmon/internal/constrcheck/bench_test.go +++ b/engine/playmon/internal/constrcheck/bench_test.go @@ -1,187 +1,188 @@ package constrcheck_test -import ( - "strconv" - "strings" - "testing" - - "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck" - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/gqlscan" - "github.com/graph-guard/gqt/v4" - "github.com/vektah/gqlparser/v2" - "github.com/vektah/gqlparser/v2/ast" -) - -var GB bool - -func Token(t gqlscan.Token, value string) gqlparse.Token { - v := []byte(nil) - if value != "" { - v = []byte(value) - } - return gqlparse.Token{ID: t, Value: v} -} - -func BenchmarkSimple(b *testing.B) { - fn := makeBenchmarkFn( - b, - `type Query { f(a:Int!):Int! }`, - `query { f(a:42) }`, - "Query.f|a", - Token(gqlscan.TokenInt, "42"), - ) - - b.ResetTimer() - for n := 0; n < b.N; n++ { - if GB = fn(); !GB { - b.Fatal("unexpected result: ", GB) - } - } -} - -func BenchmarkSimpleArray(b *testing.B) { - fn := makeBenchmarkFn( - b, - `type Query { f(a:[Int!]!):Int! }`, - `query { f(a:[1,2,3,4,5]) }`, - "Query.f|a", - Token(gqlscan.TokenArr, ""), - Token(gqlscan.TokenInt, "1"), - Token(gqlscan.TokenInt, "2"), - Token(gqlscan.TokenInt, "3"), - Token(gqlscan.TokenInt, "4"), - Token(gqlscan.TokenInt, "5"), - Token(gqlscan.TokenArrEnd, ""), - ) - - b.ResetTimer() - for n := 0; n < b.N; n++ { - if GB = fn(); !GB { - b.Fatal("unexpected result: ", GB) - } - } -} - -func BenchmarkBigArray(b *testing.B) { - items := 1024 - - tokens := make([]gqlparse.Token, items+2) - tokens[0] = Token(gqlscan.TokenArr, "") - tokens[len(tokens)-1] = Token(gqlscan.TokenArrEnd, "") - for i := 1; i <= items; i++ { - tokens[i] = Token(gqlscan.TokenInt, strconv.Itoa(i)) - } - - var tmpl strings.Builder - tmpl.WriteString(`query { f(a:[`) - for i := 1; i <= items; i++ { - tmpl.WriteString(strconv.Itoa(i)) - if i != items { - tmpl.WriteByte(',') - } - } - tmpl.WriteString(`]) }`) - - fn := makeBenchmarkFn( - b, - `type Query { f(a:[Int!]!):Int! }`, - tmpl.String(), - "Query.f|a", - tokens..., - ) - - b.ResetTimer() - for n := 0; n < b.N; n++ { - if GB = fn(); !GB { - b.Fatal("unexpected result: ", GB) - } - } -} - -func BenchmarkObject(b *testing.B) { - fn := makeBenchmarkFn( - b, - ` - type Query { f(object:Object!):Int } - input Object { subobject: SubObject! } - input SubObject { array: [ArrayObject!]! } - input ArrayObject { name: String!, index: Int! } - `, - `query { f(object:{ - subobject: { - array: [{ - name: "first", index: 0 - }, { - name: "second", index: 1 - }] - } - })}`, - "Query.f|object", - Token(gqlscan.TokenObj, ""), - Token(gqlscan.TokenObjField, "subobject"), - Token(gqlscan.TokenObj, ""), - Token(gqlscan.TokenObjField, "array"), - Token(gqlscan.TokenArr, ""), - - Token(gqlscan.TokenObj, ""), - Token(gqlscan.TokenObjField, "name"), - Token(gqlscan.TokenStr, "first"), - Token(gqlscan.TokenObjField, "index"), - Token(gqlscan.TokenInt, "0"), - Token(gqlscan.TokenObjEnd, ""), - - Token(gqlscan.TokenObj, ""), - Token(gqlscan.TokenObjField, "name"), - Token(gqlscan.TokenStr, "second"), - Token(gqlscan.TokenObjField, "index"), - Token(gqlscan.TokenInt, "1"), - Token(gqlscan.TokenObjEnd, ""), - - Token(gqlscan.TokenArrEnd, ""), - Token(gqlscan.TokenObjEnd, ""), - Token(gqlscan.TokenObjEnd, ""), - ) - - b.ResetTimer() - for n := 0; n < b.N; n++ { - if GB = fn(); !GB { - b.Fatal("unexpected result: ", GB) - } - } -} - -func makeBenchmarkFn( - b interface{ Fatal(...any) }, - schema, template, path string, tokens ...gqlparse.Token, -) func() bool { - s, err := gqlparser.LoadSchema(&ast.Source{ - Name: "schema.graphqls", - Input: schema, - }) - if err != nil { - b.Fatal(err) - } - - p, err := gqt.NewParser([]gqt.Source{{ - Name: "schema.graphqls", - Content: schema, - }}) - if err != nil { - b.Fatal(err) - } - - opr, _, errs := p.Parse([]byte(template)) - if errs != nil { - b.Fatal(errs) - } - - m := constrcheck.New(opr, s) - - inputs := map[string][]gqlparse.Token{path: tokens} - - return func() bool { - m.Init(inputs) - return m.Check(path) - } -} +// import ( +// "strconv" +// "strings" +// "testing" + +// "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck" +// "github.com/graph-guard/ggproxy/gqlparse" +// "github.com/graph-guard/gqlscan" +// "github.com/graph-guard/gqt/v4" +// "github.com/vektah/gqlparser/v2" +// "github.com/vektah/gqlparser/v2/ast" +// ) + +// var GB bool + +// func Token(t gqlscan.Token, value string) gqlparse.Token { +// v := []byte(nil) +// if value != "" { +// v = []byte(value) +// } +// return gqlparse.Token{ID: t, Value: v} +// } + +// func BenchmarkSimple(b *testing.B) { +// fn := makeBenchmarkFn( +// b, +// `type Query { f(a:Int!):Int! }`, +// `query { f(a:42) }`, +// "Query.f|a", +// Token(gqlscan.TokenInt, "42"), +// ) + +// b.ResetTimer() +// for n := 0; n < b.N; n++ { +// if GB = fn(); !GB { +// b.Fatal("unexpected result: ", GB) +// } +// } +// } + +// func BenchmarkSimpleArray(b *testing.B) { +// fn := makeBenchmarkFn( +// b, +// `type Query { f(a:[Int!]!):Int! }`, +// `query { f(a:[1,2,3,4,5]) }`, +// "Query.f|a", +// Token(gqlscan.TokenArr, ""), +// Token(gqlscan.TokenInt, "1"), +// Token(gqlscan.TokenInt, "2"), +// Token(gqlscan.TokenInt, "3"), +// Token(gqlscan.TokenInt, "4"), +// Token(gqlscan.TokenInt, "5"), +// Token(gqlscan.TokenArrEnd, ""), +// ) + +// b.ResetTimer() +// for n := 0; n < b.N; n++ { +// if GB = fn(); !GB { +// b.Fatal("unexpected result: ", GB) +// } +// } +// } + +// func BenchmarkBigArray(b *testing.B) { +// items := 1024 + +// tokens := make([]gqlparse.Token, items+2) +// tokens[0] = Token(gqlscan.TokenArr, "") +// tokens[len(tokens)-1] = Token(gqlscan.TokenArrEnd, "") +// for i := 1; i <= items; i++ { +// tokens[i] = Token(gqlscan.TokenInt, strconv.Itoa(i)) +// } + +// var tmpl strings.Builder +// tmpl.WriteString(`query { f(a:[`) +// for i := 1; i <= items; i++ { +// tmpl.WriteString(strconv.Itoa(i)) +// if i != items { +// tmpl.WriteByte(',') +// } +// } +// tmpl.WriteString(`]) }`) + +// fn := makeBenchmarkFn( +// b, +// `type Query { f(a:[Int!]!):Int! }`, +// tmpl.String(), +// "Query.f|a", +// tokens..., +// ) + +// b.ResetTimer() +// for n := 0; n < b.N; n++ { +// if GB = fn(); !GB { +// b.Fatal("unexpected result: ", GB) +// } +// } +// } + +// func BenchmarkObject(b *testing.B) { +// fn := makeBenchmarkFn( +// b, +// ` +// type Query { f(object:Object!):Int } +// input Object { subobject: SubObject! } +// input SubObject { array: [ArrayObject!]! } +// input ArrayObject { name: String!, index: Int! } +// `, +// `query { f(object:{ +// subobject: { +// array: [{ +// name: "first", index: 0 +// }, { +// name: "second", index: 1 +// }] +// } +// })}`, +// "Query.f|object", +// Token(gqlscan.TokenObj, ""), +// Token(gqlscan.TokenObjField, "subobject"), +// Token(gqlscan.TokenObj, ""), +// Token(gqlscan.TokenObjField, "array"), +// Token(gqlscan.TokenArr, ""), + +// Token(gqlscan.TokenObj, ""), +// Token(gqlscan.TokenObjField, "name"), +// Token(gqlscan.TokenStr, "first"), +// Token(gqlscan.TokenObjField, "index"), +// Token(gqlscan.TokenInt, "0"), +// Token(gqlscan.TokenObjEnd, ""), + +// Token(gqlscan.TokenObj, ""), +// Token(gqlscan.TokenObjField, "name"), +// Token(gqlscan.TokenStr, "second"), +// Token(gqlscan.TokenObjField, "index"), +// Token(gqlscan.TokenInt, "1"), +// Token(gqlscan.TokenObjEnd, ""), + +// Token(gqlscan.TokenArrEnd, ""), +// Token(gqlscan.TokenObjEnd, ""), +// Token(gqlscan.TokenObjEnd, ""), +// ) + +// b.ResetTimer() +// for n := 0; n < b.N; n++ { +// if GB = fn(); !GB { +// b.Fatal("unexpected result: ", GB) +// } +// } +// } + +// func makeBenchmarkFn( +// b interface{ Fatal(...any) }, +// schema, template, path string, tokens ...gqlparse.Token, +// ) func() bool { +// s, err := gqlparser.LoadSchema(&ast.Source{ +// Name: "schema.graphqls", +// Input: schema, +// }) +// if err != nil { +// b.Fatal(err) +// } + +// p, err := gqt.NewParser([]gqt.Source{{ +// Name: "schema.graphqls", +// Content: schema, +// }}) +// if err != nil { +// b.Fatal(err) +// } + +// opr, _, errs := p.Parse([]byte(template)) +// if errs != nil { +// b.Fatal(errs) +// } + +// m := constrcheck.New(opr, s) + +// inputs := map[string][]gqlparse.Token{path: tokens} + +// m.Init(t) + +// return func() bool { +// return m.Check(path) +// } +// } diff --git a/engine/playmon/internal/constrcheck/constrcheck.go b/engine/playmon/internal/constrcheck/constrcheck.go index 1aa546d..e5f0711 100644 --- a/engine/playmon/internal/constrcheck/constrcheck.go +++ b/engine/playmon/internal/constrcheck/constrcheck.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck/internal/union" + "github.com/graph-guard/ggproxy/engine/playmon/internal/pathscan" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/ggproxy/utilities/atoi" "github.com/graph-guard/gqlscan" @@ -23,44 +24,48 @@ type Checker struct { operation *gqt.Operation schema *gqlast.Schema checkers map[string]check + pathByVar map[*gqt.Variable]string + // variableValues keys will be set at initialization + // but values must be set before every call to Check. variableValues map[string][]gqlparse.Token - inputValue []gqlparse.Token + // inputValue is set before every call to Check. + inputValue []gqlparse.Token + // checkedValue is set before every call to Check. checkedValue []gqlparse.Token - stack []union.Union + + // stack is reset in every call to Check. + stack []union.Union +} + +func (c *Checker) SetVariableValue(path []byte, value []gqlparse.Token) { + c.variableValues[string(path)] = value } -// // Paths executes fn for every path the checker is aware of. -// // Returns true immediatelly if any call to fn returns true. -// func (m *Checker) Paths(fn func(string) (stop bool)) (stopped bool) { -// for p := range m.checkers { -// if fn(p) { -// return true -// } -// } -// return false -// } - -func (m *Checker) stackTopType() union.Type { - return m.stack[len(m.stack)-1].Type() +func (c *Checker) Init(inputValue []gqlparse.Token) { + c.inputValue = inputValue } -func (m *Checker) popStack() union.Union { - t := m.stack[len(m.stack)-1] - m.stack = m.stack[:len(m.stack)-1] +func (c *Checker) stackTopType() union.Type { + return c.stack[len(c.stack)-1].Type() +} + +func (c *Checker) popStack() union.Union { + t := c.stack[len(c.stack)-1] + c.stack = c.stack[:len(c.stack)-1] return t } -func (m *Checker) popStackArrays() (left, right []union.Union) { - if m.stack[len(m.stack)-1].Type() != union.TypeArray { +func (c *Checker) popStackArrays() (left, right []union.Union) { + if c.stack[len(c.stack)-1].Type() != union.TypeArray { return } - i := len(m.stack) - 2 - var c int + i := len(c.stack) - 2 + var count int LOOP_R: - for l := 1; i > 0; i, c = i-1, c+1 { - switch m.stack[i].Type() { + for l := 1; i > 0; i, count = i-1, count+1 { + switch c.stack[i].Type() { case union.TypeArray: l++ case union.TypeArrayEnd: @@ -70,18 +75,18 @@ LOOP_R: } } } - right = m.stack[i+1 : len(m.stack)-1] + right = c.stack[i+1 : len(c.stack)-1] i-- iv := i - if m.stack[i].Type() != union.TypeArray { + if c.stack[i].Type() != union.TypeArray { return } - c = 0 + count = 0 i-- LOOP_L: - for l := 1; i > 0; i, c = i-1, c+1 { - switch m.stack[i].Type() { + for l := 1; i > 0; i, count = i-1, count+1 { + switch c.stack[i].Type() { case union.TypeArray: l++ case union.TypeArrayEnd: @@ -92,52 +97,46 @@ LOOP_L: } } - left = m.stack[i+1 : iv] - m.stack = m.stack[:i] + left = c.stack[i+1 : iv] + c.stack = c.stack[:i] return left, right } -func (m *Checker) pushStackArray() { - m.stack = append(m.stack, union.Array()) +func (c *Checker) pushStackArray() { + c.stack = append(c.stack, union.Array()) } -func (m *Checker) pushStackArrayEnd() { - m.stack = append(m.stack, union.ArrayEnd()) +func (c *Checker) pushStackArrayEnd() { + c.stack = append(c.stack, union.ArrayEnd()) } -func (m *Checker) pushStackInt(v int32) { - m.stack = append(m.stack, union.Int(v)) +func (c *Checker) pushStackInt(v int32) { + c.stack = append(c.stack, union.Int(v)) } -func (m *Checker) pushStackFloat(v float64) { - m.stack = append(m.stack, union.Float(v)) +func (c *Checker) pushStackFloat(v float64) { + c.stack = append(c.stack, union.Float(v)) } -func (m *Checker) pushStackBool(v bool) { +func (c *Checker) pushStackBool(v bool) { if v { - m.stack = append(m.stack, union.True()) + c.stack = append(c.stack, union.True()) return } - m.stack = append(m.stack, union.False()) -} - -// Init initializes the checker with inputs to validate a request. -// Init must be used before making calls to Check in the context of a request. -func (m *Checker) Init(variableValues map[string][]gqlparse.Token) { - m.variableValues = variableValues + c.stack = append(c.stack, union.False()) } // Check returns true if the value for the given path is accepted, // otherwise returns false. -func (m *Checker) Check(path string) bool { - m.stack = m.stack[:0] - c := m.checkers[path] - if c == nil { +func (c *Checker) Check(path string) bool { + c.stack = c.stack[:0] + cf := c.checkers[path] + if cf == nil { return false } - v := m.variableValues[path] - m.inputValue, m.checkedValue = v, v - return c(m) + v := c.variableValues[path] + c.inputValue, c.checkedValue = v, v + return cf(c) } // check is a constraint check function which returns true @@ -146,352 +145,318 @@ func (m *Checker) Check(path string) bool { type check func(*Checker) (match bool) // resolveExpr resolves expression e into a union and pushes it onto the stack. -func (m *Checker) resolveExpr(e gqt.Expression) union.Type { +func (c *Checker) resolveExpr(e gqt.Expression) union.Type { switch e := e.(type) { case *gqt.Array: - m.pushStackArrayEnd() + c.pushStackArrayEnd() for i := len(e.Items) - 1; i >= 0; i-- { - m.resolveExpr(e.Items[i].(*gqt.ConstrEquals).Value) + c.resolveExpr(e.Items[i].(*gqt.ConstrEquals).Value) } - m.pushStackArray() + c.pushStackArray() return union.TypeArray case *gqt.Variable: - p := MakePath(e.Declaration.Parent) - if s := m.variableValues[p]; s != nil { - m.stack = append(m.stack, union.Tokens(s)) - return union.TypeTokens + p, ok := c.pathByVar[e] + if !ok { + if s := c.variableValues[p]; s != nil { + c.stack = append(c.stack, union.Tokens(s)) + return union.TypeTokens + } } - m.stack = append(m.stack, union.Null()) + c.stack = append(c.stack, union.Null()) return union.TypeNull case *gqt.Number: if i, ok := e.Int(); ok { - m.stack = append(m.stack, union.Int(int32(i))) + c.stack = append(c.stack, union.Int(int32(i))) return union.TypeInt } f, _ := e.Float() - m.stack = append(m.stack, union.Float(f)) + c.stack = append(c.stack, union.Float(f)) return union.TypeFloat case *gqt.True: - m.stack = append(m.stack, union.True()) + c.stack = append(c.stack, union.True()) return union.TypeBoolean case *gqt.False: - m.stack = append(m.stack, union.False()) + c.stack = append(c.stack, union.False()) return union.TypeBoolean case *gqt.Enum: - m.stack = append(m.stack, union.Enum(e.Value)) + c.stack = append(c.stack, union.Enum(e.Value)) return union.TypeEnum case *gqt.String: - m.stack = append(m.stack, union.String(e.Value)) + c.stack = append(c.stack, union.String(e.Value)) return union.TypeString case *gqt.Null: return union.TypeNull case *gqt.ExprAddition: - m.resolveExpr(e.AddendLeft) - m.resolveExpr(e.AddendRight) - r := m.popStack() - l := m.popStack() + c.resolveExpr(e.AddendLeft) + c.resolveExpr(e.AddendRight) + r := c.popStack() + l := c.popStack() if l.Type() == union.TypeInt && r.Type() == union.TypeInt { l, _ := l.Int() r, _ := r.Int() - m.pushStackInt(l + r) + c.pushStackInt(l + r) return union.TypeInt } { l, _ := l.Float() r, _ := r.Float() - m.pushStackFloat(l + r) + c.pushStackFloat(l + r) } return union.TypeFloat case *gqt.ExprSubtraction: - m.resolveExpr(e.Minuend) - m.resolveExpr(e.Subtrahend) - r := m.popStack() - l := m.popStack() + c.resolveExpr(e.Minuend) + c.resolveExpr(e.Subtrahend) + r := c.popStack() + l := c.popStack() if l.Type() == union.TypeInt && r.Type() == union.TypeInt { l, _ := l.Int() r, _ := r.Int() - m.pushStackInt(l - r) + c.pushStackInt(l - r) return union.TypeInt } { l, _ := l.Float() r, _ := r.Float() - m.pushStackFloat(l - r) + c.pushStackFloat(l - r) } return union.TypeFloat case *gqt.ExprMultiplication: - m.resolveExpr(e.Multiplicant) - m.resolveExpr(e.Multiplicator) - r := m.popStack() - l := m.popStack() + c.resolveExpr(e.Multiplicant) + c.resolveExpr(e.Multiplicator) + r := c.popStack() + l := c.popStack() if l.Type() == union.TypeInt && r.Type() == union.TypeInt { l, _ := l.Int() r, _ := r.Int() - m.pushStackInt(l * r) + c.pushStackInt(l * r) return union.TypeInt } { l, _ := l.Float() r, _ := r.Float() - m.pushStackFloat(l * r) + c.pushStackFloat(l * r) } return union.TypeFloat case *gqt.ExprDivision: - m.resolveExpr(e.Dividend) - m.resolveExpr(e.Divisor) - r := m.popStack() - l := m.popStack() + c.resolveExpr(e.Dividend) + c.resolveExpr(e.Divisor) + r := c.popStack() + l := c.popStack() if l.Type() == union.TypeInt && r.Type() == union.TypeInt { l, _ := l.Int() r, _ := r.Int() - m.pushStackInt(l / r) + c.pushStackInt(l / r) return union.TypeInt } { l, _ := l.Float() r, _ := r.Float() - m.pushStackFloat(l / r) + c.pushStackFloat(l / r) } return union.TypeFloat case *gqt.ExprModulo: - m.resolveExpr(e.Dividend) - m.resolveExpr(e.Divisor) - r := m.popStack() - l := m.popStack() + c.resolveExpr(e.Dividend) + c.resolveExpr(e.Divisor) + r := c.popStack() + l := c.popStack() if l.Type() == union.TypeInt && r.Type() == union.TypeInt { l, _ := l.Int() r, _ := r.Int() - m.pushStackInt(l % r) + c.pushStackInt(l % r) return union.TypeInt } { l, _ := l.Float() r, _ := r.Float() - m.pushStackFloat(math.Mod(l, r)) + c.pushStackFloat(math.Mod(l, r)) } return union.TypeFloat case *gqt.ExprNumericNegation: - m.resolveExpr(e.Expression) - i := m.popStack() + c.resolveExpr(e.Expression) + i := c.popStack() switch i.Type() { case union.TypeFloat: i, _ := i.Float() - m.pushStackFloat(-i) + c.pushStackFloat(-i) return union.TypeFloat case union.TypeInt: i, _ := i.Int() - m.pushStackInt(-i) + c.pushStackInt(-i) return union.TypeInt case union.TypeTokens: switch i.Tokens()[0].ID { case gqlscan.TokenInt: i := atoi.MustI32(i.Tokens()[0].Value) - m.pushStackInt(-i) + c.pushStackInt(-i) return union.TypeInt case gqlscan.TokenFloat: f := atoi.MustF64(i.Tokens()[0].Value) - m.pushStackFloat(-f) + c.pushStackFloat(-f) return union.TypeFloat } panic(fmt.Errorf("unexpected token type: %q", i.Tokens()[0].ID.String())) } panic(fmt.Errorf("unexpected union type: %q", i.Type().String())) case *gqt.ExprEqual: - m.resolveExpr(e.Left) - m.resolveExpr(e.Right) + c.resolveExpr(e.Left) + c.resolveExpr(e.Right) equal := false - if m.stackTopType() == union.TypeArray { - left, right := m.popStackArrays() + if c.stackTopType() == union.TypeArray { + left, right := c.popStackArrays() equal = unionsEqual(left, right) } else { - r := m.popStack() - l := m.popStack() + r := c.popStack() + l := c.popStack() equal = union.Equal(l, r) } - m.pushStackBool(equal) + c.pushStackBool(equal) return union.TypeBoolean case *gqt.ExprNotEqual: - m.resolveExpr(e.Left) - m.resolveExpr(e.Right) + c.resolveExpr(e.Left) + c.resolveExpr(e.Right) notEqual := false - if m.stackTopType() == union.TypeArray { - left, right := m.popStackArrays() + if c.stackTopType() == union.TypeArray { + left, right := c.popStackArrays() notEqual = !unionsEqual(left, right) } else { - r := m.popStack() - l := m.popStack() + r := c.popStack() + l := c.popStack() notEqual = !union.Equal(l, r) } - m.pushStackBool(notEqual) + c.pushStackBool(notEqual) return union.TypeBoolean case *gqt.ExprLogicalNegation: - m.resolveExpr(e.Expression) - u := m.popStack() + c.resolveExpr(e.Expression) + u := c.popStack() b, _ := u.Bool() - m.pushStackBool(!b) + c.pushStackBool(!b) return union.TypeBoolean case *gqt.ExprGreater: - m.resolveExpr(e.Left) - m.resolveExpr(e.Right) - r := m.popStack() - l := m.popStack() + c.resolveExpr(e.Left) + c.resolveExpr(e.Right) + r := c.popStack() + l := c.popStack() if l.Type() == union.TypeInt && r.Type() == union.TypeInt { l, _ := l.Int() r, _ := r.Int() - m.pushStackBool(l > r) + c.pushStackBool(l > r) } else { l, _ := l.Float() r, _ := r.Float() - m.pushStackBool(l > r) + c.pushStackBool(l > r) } return union.TypeBoolean case *gqt.ExprLess: - m.resolveExpr(e.Left) - m.resolveExpr(e.Right) - r := m.popStack() - l := m.popStack() + c.resolveExpr(e.Left) + c.resolveExpr(e.Right) + r := c.popStack() + l := c.popStack() if l.Type() == union.TypeInt && r.Type() == union.TypeInt { l, _ := l.Int() r, _ := r.Int() - m.pushStackBool(l < r) + c.pushStackBool(l < r) } else { l, _ := l.Float() r, _ := r.Float() - m.pushStackBool(l < r) + c.pushStackBool(l < r) } return union.TypeBoolean case *gqt.ExprGreaterOrEqual: - m.resolveExpr(e.Left) - m.resolveExpr(e.Right) - r := m.popStack() - l := m.popStack() + c.resolveExpr(e.Left) + c.resolveExpr(e.Right) + r := c.popStack() + l := c.popStack() if l.Type() == union.TypeInt && r.Type() == union.TypeInt { l, _ := l.Int() r, _ := r.Int() - m.pushStackBool(l >= r) + c.pushStackBool(l >= r) } else { l, _ := l.Float() r, _ := r.Float() - m.pushStackBool(l >= r) + c.pushStackBool(l >= r) } return union.TypeBoolean case *gqt.ExprLessOrEqual: - m.resolveExpr(e.Left) - m.resolveExpr(e.Right) - r := m.popStack() - l := m.popStack() + c.resolveExpr(e.Left) + c.resolveExpr(e.Right) + r := c.popStack() + l := c.popStack() if l.Type() == union.TypeInt && r.Type() == union.TypeInt { l, _ := l.Int() r, _ := r.Int() - m.pushStackBool(l <= r) + c.pushStackBool(l <= r) } else { l, _ := l.Float() r, _ := r.Float() - m.pushStackBool(l <= r) + c.pushStackBool(l <= r) } return union.TypeBoolean case *gqt.ExprLogicalOr: for _, x := range e.Expressions { - m.resolveExpr(x) - u := m.popStack() + c.resolveExpr(x) + u := c.popStack() b, _ := u.Bool() if b { - m.pushStackBool(true) + c.pushStackBool(true) return union.TypeBoolean } } - m.pushStackBool(false) + c.pushStackBool(false) return union.TypeBoolean case *gqt.ExprLogicalAnd: for _, x := range e.Expressions { - m.resolveExpr(x) - u := m.popStack() + c.resolveExpr(x) + u := c.popStack() b, _ := u.Bool() if !b { - m.pushStackBool(false) + c.pushStackBool(false) return union.TypeBoolean } } - m.pushStackBool(true) + c.pushStackBool(true) return union.TypeBoolean case *gqt.ExprParentheses: - return m.resolveExpr(e.Expression) + return c.resolveExpr(e.Expression) } panic(fmt.Errorf("unhandled value expression type: %T", e)) } -func (m *Checker) PathsLen() int { return len(m.checkers) } - -func (m *Checker) VisitPaths(fn func(path string) (stop bool)) { - for path := range m.checkers { - if fn(path) { - return - } - } -} - -func (m *Checker) VisitPathsAll(fn func(path string)) { - for path := range m.checkers { - fn(path) - } -} - // New creates a constraint checker instance for each path of o. func New(o *gqt.Operation, s *gqlast.Schema) *Checker { - m := &Checker{ - operation: o, - schema: s, - checkers: make(map[string]check), - stack: make([]union.Union, 1024), + c := &Checker{ + operation: o, + schema: s, + checkers: make(map[string]check), + stack: make([]union.Union, 1024), + variableValues: make(map[string][]gqlparse.Token), + pathByVar: make(map[*gqt.Variable]string), } - - inputTypes := map[string]*gqlast.Type{} - if s != nil { - traverse(o, func(e gqt.Expression) (stop, skipChildren bool) { - switch e := e.(type) { - case *gqt.SelectionField: - for _, a := range e.Arguments { - if a.Def == nil { - continue - } - p := MakePath(a) - inputTypes[p] = a.Def.Type - } - case *gqt.ObjectField: - if e.Def == nil { - break - } - p := MakePath(e) - inputTypes[p] = e.Def.Type + pathscan.InAST( + o, + func(path []byte, e gqt.Expression) (stop bool) { + // On structural + return false + }, func(path []byte, e gqt.Expression) (stop bool) { + // On argument + a := e.(*gqt.Argument) + var expect *gqlast.Type + if a.Def != nil { + expect = a.Def.Type } - return false, false - }) - } - - // TODO: move path scanner out of this package - traverse(o, func(e gqt.Expression) (stop, skipChildren bool) { - switch e := e.(type) { - case *gqt.SelectionField: - if len(e.Arguments) < 1 && len(e.Selections) < 1 { - p := MakePath(e) - m.checkers[p] = nil - break - } - for _, a := range e.Arguments { - var expect *gqlast.Type - if a.Def != nil { - expect = a.Def.Type - } - if fn := makeCheck(a.Constraint, expect, s); fn != nil { - p := MakePath(a) - m.checkers[p] = fn - } + if fn := makeCheck(a.Constraint, expect, s); fn != nil { + c.checkers[string(path)] = fn } - } - return false, false // Continue traversal - }) - - return m + return false + }, + func(path []byte, e gqt.Expression) (stop bool) { + // On variable + a := e.(*gqt.Variable) + c.pathByVar[a] = string(path) + c.variableValues[string(path)] = nil // Set key + return false + }, + ) + return c } func makeCheck( @@ -501,13 +466,13 @@ func makeCheck( ) check { switch e := e.(type) { case *gqt.ConstrAny: - return func(m *Checker) (match bool) { - if wrong, _ := isWrongType(expect, m.checkedValue, schema); wrong { + return func(c *Checker) (match bool) { + if wrong, _ := isWrongType(expect, c.checkedValue, schema); wrong { return false } // Make sure the value is semantically valid. - return !gqlparse.ScanValuesInArrays(m.checkedValue, func(t []gqlparse.Token) (stop bool) { + return !gqlparse.ScanValuesInArrays(c.checkedValue, func(t []gqlparse.Token) (stop bool) { if t[0].ID != gqlscan.TokenObj { return false } @@ -532,14 +497,14 @@ func makeCheck( for i, e := range e.Expressions { exprCheckers[i] = makeCheck(e, expect, schema) } - return func(m *Checker) (match bool) { - r := m.checkedValue + return func(c *Checker) (match bool) { + r := c.checkedValue for _, e := range exprCheckers { - if e(m) { + if e(c) { return true } // Reset value to start checking from the same index - m.checkedValue = r + c.checkedValue = r } return false } @@ -548,27 +513,27 @@ func makeCheck( for i, e := range e.Expressions { exprCheckers[i] = makeCheck(e, expect, schema) } - return func(m *Checker) (match bool) { - r := m.checkedValue + return func(c *Checker) (match bool) { + r := c.checkedValue for _, e := range exprCheckers { - if !e(m) { + if !e(c) { return false } // Reset value to start checking from the same index - m.checkedValue = r + c.checkedValue = r } return true } case *gqt.ConstrGreater: - return func(m *Checker) (match bool) { - if m.expectOrNum(expect) { + return func(c *Checker) (match bool) { + if c.expectOrNum(expect) { return false } - m.resolveExpr(e.Value) - u := m.popStack() - switch m.checkedValue[0].ID { + c.resolveExpr(e.Value) + u := c.popStack() + switch c.checkedValue[0].ID { case gqlscan.TokenInt: - i := atoi.MustI32(m.checkedValue[0].Value) + i := atoi.MustI32(c.checkedValue[0].Value) if u, ok := u.Int(); ok != union.ValueNone { match = i > u break @@ -576,23 +541,23 @@ func makeCheck( u, _ := u.Float() match = float64(i) > u case gqlscan.TokenFloat: - i := atoi.MustF64(m.checkedValue[0].Value) + i := atoi.MustF64(c.checkedValue[0].Value) u, _ := u.Float() match = i > u } - m.checkedValue = m.checkedValue[1:] + c.checkedValue = c.checkedValue[1:] return match } case *gqt.ConstrGreaterOrEqual: - return func(m *Checker) (match bool) { - if m.expectOrNum(expect) { + return func(c *Checker) (match bool) { + if c.expectOrNum(expect) { return false } - m.resolveExpr(e.Value) - u := m.popStack() - switch m.checkedValue[0].ID { + c.resolveExpr(e.Value) + u := c.popStack() + switch c.checkedValue[0].ID { case gqlscan.TokenInt: - i := atoi.MustI32(m.checkedValue[0].Value) + i := atoi.MustI32(c.checkedValue[0].Value) if u, ok := u.Int(); ok != union.ValueNone { match = i >= u break @@ -600,23 +565,23 @@ func makeCheck( u, _ := u.Float() match = float64(i) >= u case gqlscan.TokenFloat: - i := atoi.MustF64(m.checkedValue[0].Value) + i := atoi.MustF64(c.checkedValue[0].Value) u, _ := u.Float() match = i >= u } - m.checkedValue = m.checkedValue[1:] + c.checkedValue = c.checkedValue[1:] return match } case *gqt.ConstrLess: - return func(m *Checker) (match bool) { - if m.expectOrNum(expect) { + return func(c *Checker) (match bool) { + if c.expectOrNum(expect) { return false } - m.resolveExpr(e.Value) - u := m.popStack() - switch m.checkedValue[0].ID { + c.resolveExpr(e.Value) + u := c.popStack() + switch c.checkedValue[0].ID { case gqlscan.TokenInt: - i := atoi.MustI32(m.checkedValue[0].Value) + i := atoi.MustI32(c.checkedValue[0].Value) if u, ok := u.Int(); ok != union.ValueNone { match = i < u break @@ -624,23 +589,23 @@ func makeCheck( u, _ := u.Float() match = float64(i) < u case gqlscan.TokenFloat: - i := atoi.MustF64(m.checkedValue[0].Value) + i := atoi.MustF64(c.checkedValue[0].Value) u, _ := u.Float() match = i < u } - m.checkedValue = m.checkedValue[1:] + c.checkedValue = c.checkedValue[1:] return match } case *gqt.ConstrLessOrEqual: - return func(m *Checker) (match bool) { - if m.expectOrNum(expect) { + return func(c *Checker) (match bool) { + if c.expectOrNum(expect) { return false } - m.resolveExpr(e.Value) - u := m.popStack() - switch m.checkedValue[0].ID { + c.resolveExpr(e.Value) + u := c.popStack() + switch c.checkedValue[0].ID { case gqlscan.TokenInt: - i := atoi.MustI32(m.checkedValue[0].Value) + i := atoi.MustI32(c.checkedValue[0].Value) if u, ok := u.Int(); ok != union.ValueNone { match = i <= u break @@ -648,11 +613,11 @@ func makeCheck( u, _ := u.Float() match = float64(i) <= u case gqlscan.TokenFloat: - i := atoi.MustF64(m.checkedValue[0].Value) + i := atoi.MustF64(c.checkedValue[0].Value) u, _ := u.Float() match = i <= u } - m.checkedValue = m.checkedValue[1:] + c.checkedValue = c.checkedValue[1:] return match } case *gqt.ConstrEquals: @@ -696,32 +661,32 @@ func makeCheck( return makeNotEqBool(true, expect, schema) } fn := makeEqExpression(e.Value, expect, schema) - return func(m *Checker) (match bool) { - return !fn(m) + return func(c *Checker) (match bool) { + return !fn(c) } case *gqt.ConstrLenEquals: - return func(m *Checker) (match bool) { - if m.expectOrHasLen(expect) { + return func(c *Checker) (match bool) { + if c.expectOrHasLen(expect) { return false } var length int - switch m.checkedValue[0].ID { + switch c.checkedValue[0].ID { case gqlscan.TokenArr: var tokensRead int length, tokensRead = gqlparse.CountValuesUntil( - m.checkedValue[1:], + c.checkedValue[1:], gqlscan.TokenArrEnd, ) - m.checkedValue = m.checkedValue[tokensRead:] + c.checkedValue = c.checkedValue[tokensRead:] case gqlscan.TokenStr: - length = len(m.checkedValue[0].Value) + length = len(c.checkedValue[0].Value) case gqlscan.TokenStrBlock: - length = len(m.checkedValue[0].Value) + length = len(c.checkedValue[0].Value) } - m.resolveExpr(e.Value) - u := m.popStack() + c.resolveExpr(e.Value) + u := c.popStack() if u, ok := u.Int(); ok != union.ValueNone { return int32(length) == u } @@ -731,28 +696,28 @@ func makeCheck( panic(fmt.Errorf("unexpected value type: %s", u.Type().String())) } case *gqt.ConstrLenNotEquals: - return func(m *Checker) (match bool) { - if m.expectOrHasLen(expect) { + return func(c *Checker) (match bool) { + if c.expectOrHasLen(expect) { return false } var length int - switch m.checkedValue[0].ID { + switch c.checkedValue[0].ID { case gqlscan.TokenArr: var tokensRead int length, tokensRead = gqlparse.CountValuesUntil( - m.checkedValue[1:], + c.checkedValue[1:], gqlscan.TokenArrEnd, ) - m.checkedValue = m.checkedValue[tokensRead:] + c.checkedValue = c.checkedValue[tokensRead:] case gqlscan.TokenStr: - length = len(m.checkedValue[0].Value) + length = len(c.checkedValue[0].Value) case gqlscan.TokenStrBlock: - length = len(m.checkedValue[0].Value) + length = len(c.checkedValue[0].Value) } - m.resolveExpr(e.Value) - u := m.popStack() + c.resolveExpr(e.Value) + u := c.popStack() if u, ok := u.Int(); ok != union.ValueNone { return int32(length) != u } @@ -762,28 +727,28 @@ func makeCheck( panic(fmt.Errorf("unexpected value type: %s", u.Type().String())) } case *gqt.ConstrLenGreater: - return func(m *Checker) (match bool) { - if m.expectOrHasLen(expect) { + return func(c *Checker) (match bool) { + if c.expectOrHasLen(expect) { return false } var length int - switch m.checkedValue[0].ID { + switch c.checkedValue[0].ID { case gqlscan.TokenArr: var tokensRead int length, tokensRead = gqlparse.CountValuesUntil( - m.checkedValue[1:], + c.checkedValue[1:], gqlscan.TokenArrEnd, ) - m.checkedValue = m.checkedValue[tokensRead:] + c.checkedValue = c.checkedValue[tokensRead:] case gqlscan.TokenStr: - length = len(m.checkedValue[0].Value) + length = len(c.checkedValue[0].Value) case gqlscan.TokenStrBlock: - length = len(m.checkedValue[0].Value) + length = len(c.checkedValue[0].Value) } - m.resolveExpr(e.Value) - u := m.popStack() + c.resolveExpr(e.Value) + u := c.popStack() if u, ok := u.Int(); ok != union.ValueNone { return int32(length) > u } @@ -793,28 +758,28 @@ func makeCheck( panic(fmt.Errorf("unexpected value type: %s", u.Type().String())) } case *gqt.ConstrLenGreaterOrEqual: - return func(m *Checker) (match bool) { - if m.expectOrHasLen(expect) { + return func(c *Checker) (match bool) { + if c.expectOrHasLen(expect) { return false } var length int - switch m.checkedValue[0].ID { + switch c.checkedValue[0].ID { case gqlscan.TokenArr: var tokensRead int length, tokensRead = gqlparse.CountValuesUntil( - m.checkedValue[1:], + c.checkedValue[1:], gqlscan.TokenArrEnd, ) - m.checkedValue = m.checkedValue[tokensRead:] + c.checkedValue = c.checkedValue[tokensRead:] case gqlscan.TokenStr: - length = len(m.checkedValue[0].Value) + length = len(c.checkedValue[0].Value) case gqlscan.TokenStrBlock: - length = len(m.checkedValue[0].Value) + length = len(c.checkedValue[0].Value) } - m.resolveExpr(e.Value) - u := m.popStack() + c.resolveExpr(e.Value) + u := c.popStack() if u, ok := u.Int(); ok != union.ValueNone { return int32(length) >= u } @@ -824,28 +789,28 @@ func makeCheck( panic(fmt.Errorf("unexpected value type: %s", u.Type().String())) } case *gqt.ConstrLenLess: - return func(m *Checker) (match bool) { - if m.expectOrHasLen(expect) { + return func(c *Checker) (match bool) { + if c.expectOrHasLen(expect) { return false } var length int - switch m.checkedValue[0].ID { + switch c.checkedValue[0].ID { case gqlscan.TokenArr: var tokensRead int length, tokensRead = gqlparse.CountValuesUntil( - m.checkedValue[1:], + c.checkedValue[1:], gqlscan.TokenArrEnd, ) - m.checkedValue = m.checkedValue[tokensRead:] + c.checkedValue = c.checkedValue[tokensRead:] case gqlscan.TokenStr: - length = len(m.checkedValue[0].Value) + length = len(c.checkedValue[0].Value) case gqlscan.TokenStrBlock: - length = len(m.checkedValue[0].Value) + length = len(c.checkedValue[0].Value) } - m.resolveExpr(e.Value) - u := m.popStack() + c.resolveExpr(e.Value) + u := c.popStack() if u, ok := u.Int(); ok != union.ValueNone { return int32(length) < u } @@ -855,28 +820,28 @@ func makeCheck( panic(fmt.Errorf("unexpected value type: %s", u.Type().String())) } case *gqt.ConstrLenLessOrEqual: - return func(m *Checker) (match bool) { - if m.expectOrHasLen(expect) { + return func(c *Checker) (match bool) { + if c.expectOrHasLen(expect) { return false } var length int - switch m.checkedValue[0].ID { + switch c.checkedValue[0].ID { case gqlscan.TokenArr: var tokensRead int length, tokensRead = gqlparse.CountValuesUntil( - m.checkedValue[1:], + c.checkedValue[1:], gqlscan.TokenArrEnd, ) - m.checkedValue = m.checkedValue[tokensRead:] + c.checkedValue = c.checkedValue[tokensRead:] case gqlscan.TokenStr: - length = len(m.checkedValue[0].Value) + length = len(c.checkedValue[0].Value) case gqlscan.TokenStrBlock: - length = len(m.checkedValue[0].Value) + length = len(c.checkedValue[0].Value) } - m.resolveExpr(e.Value) - u := m.popStack() + c.resolveExpr(e.Value) + u := c.popStack() if u, ok := u.Int(); ok != union.ValueNone { return int32(length) <= u } @@ -891,221 +856,107 @@ func makeCheck( expectItem = expect.Elem } itemCheck := makeCheck(e.Constraint, expectItem, schema) - return func(m *Checker) (match bool) { - if wrong, _ := isWrongType(expect, m.checkedValue, schema); wrong { + return func(c *Checker) (match bool) { + if wrong, _ := isWrongType(expect, c.checkedValue, schema); wrong { return false } - for m.checkedValue[0].ID != gqlscan.TokenArr { + for c.checkedValue[0].ID != gqlscan.TokenArr { return false } - m.checkedValue = m.checkedValue[1:] - for m.checkedValue[0].ID != gqlscan.TokenArrEnd { - if !itemCheck(m) { + c.checkedValue = c.checkedValue[1:] + for c.checkedValue[0].ID != gqlscan.TokenArrEnd { + if !itemCheck(c) { return false } } - m.checkedValue = m.checkedValue[1:] + c.checkedValue = c.checkedValue[1:] return true } } return nil } -// traverse returns true after BFS-traversing the entire tree under e -// calling onExpression for every discovered expression. -func traverse( - e gqt.Expression, - onExpression func(gqt.Expression) (stop, skipChildren bool), -) (stopped bool) { - stack := make([]gqt.Expression, 0, 64) - push := func(expression gqt.Expression) { - stack = append(stack, expression) - } - - push(e) - for len(stack) > 0 { - top := stack[len(stack)-1] - stack = stack[:len(stack)-1] - stop, skipChildren := onExpression(top) - if stop { - return true - } else if skipChildren { - continue - } - - switch e := top.(type) { - case *gqt.Operation: - for _, f := range e.Selections { - push(f) - } - case *gqt.SelectionInlineFrag: - for _, f := range e.Selections { - push(f) - } - case *gqt.SelectionField: - for _, f := range e.Selections { - push(f) - } - for _, a := range e.Arguments { - push(a) - } - case *gqt.SelectionMax: - for _, f := range e.Options.Selections { - push(f) - } - case *gqt.Argument: - push(e.Constraint) - case *gqt.ConstrEquals: - push(e.Value) - case *gqt.ConstrNotEquals: - push(e.Value) - case *gqt.ConstrGreater: - push(e.Value) - case *gqt.ConstrGreaterOrEqual: - push(e.Value) - case *gqt.ConstrLess: - push(e.Value) - case *gqt.ConstrLessOrEqual: - push(e.Value) - case *gqt.ConstrLenEquals: - push(e.Value) - case *gqt.ConstrLenNotEquals: - push(e.Value) - case *gqt.ConstrLenGreater: - push(e.Value) - case *gqt.ConstrLenLess: - push(e.Value) - case *gqt.ConstrLenGreaterOrEqual: - push(e.Value) - case *gqt.ConstrLenLessOrEqual: - push(e.Value) - case *gqt.ConstrMap: - push(e.Constraint) - case *gqt.ExprParentheses: - push(e.Expression) - case *gqt.ExprEqual: - push(e.Left) - push(e.Right) - case *gqt.ExprNotEqual: - push(e.Left) - push(e.Right) - case *gqt.ExprLogicalNegation: - push(e.Expression) - case *gqt.ExprNumericNegation: - push(e.Expression) - case *gqt.ExprLogicalOr: - for _, e := range e.Expressions { - push(e) - } - case *gqt.ExprLogicalAnd: - for _, e := range e.Expressions { - push(e) - } - case *gqt.ExprAddition: - push(e.AddendLeft) - push(e.AddendRight) - case *gqt.ExprSubtraction: - push(e.Minuend) - push(e.Subtrahend) - case *gqt.ExprMultiplication: - push(e.Multiplicant) - push(e.Multiplicator) - case *gqt.ExprDivision: - push(e.Dividend) - push(e.Divisor) - case *gqt.ExprModulo: - push(e.Dividend) - push(e.Divisor) - case *gqt.ExprGreater: - push(e.Left) - push(e.Right) - case *gqt.ExprGreaterOrEqual: - push(e.Left) - push(e.Right) - case *gqt.ExprLess: - push(e.Left) - push(e.Right) - case *gqt.ExprLessOrEqual: - push(e.Left) - push(e.Right) - case *gqt.Array: - for _, i := range e.Items { - push(i) - } - case *gqt.Object: - for _, f := range e.Fields { - push(f) - } - case *gqt.ObjectField: - push(e.Constraint) - case *gqt.Number, *gqt.True, *gqt.False, *gqt.Null, - *gqt.Enum, *gqt.String, *gqt.ConstrAny, *gqt.Variable: - default: - panic(fmt.Errorf("unhandled type: %T", top)) - } - } - return false -} - // Designation creates a textual explanation for the given expression. -func Designation(e gqt.Expression) string { +func Designation(c *Checker, e gqt.Expression) string { switch e := e.(type) { case *gqt.ConstrAny: return "can be any value" case *gqt.ConstrEquals: - return "must be equal " + Designation(e.Value) + return "must be equal " + Designation(c, e.Value) case *gqt.ConstrNotEquals: - return "must not be equal " + Designation(e.Value) + return "must not be equal " + Designation(c, e.Value) case *gqt.ConstrLenEquals: - return "length must be equal " + Designation(e.Value) + return "length must be equal " + Designation(c, e.Value) case *gqt.ConstrLenNotEquals: - return "length must not be equal " + Designation(e.Value) + return "length must not be equal " + Designation(c, e.Value) case *gqt.ConstrLenLess: - return "length must be less than " + Designation(e.Value) + return "length must be less than " + Designation(c, e.Value) case *gqt.ConstrLenGreater: - return "length must be greater than " + Designation(e.Value) + return "length must be greater than " + Designation(c, e.Value) case *gqt.ConstrLenLessOrEqual: - return "length must be less than or equal " + Designation(e.Value) + return "length must be less than or equal " + Designation(c, e.Value) case *gqt.ConstrLenGreaterOrEqual: - return "length must be greater than or equal " + Designation(e.Value) + return "length must be greater than or equal " + Designation(c, e.Value) case *gqt.ConstrMap: - return "each item: " + Designation(e.Constraint) + return "each item: " + Designation(c, e.Constraint) case *gqt.ConstrLess: - return "must be less than " + Designation(e.Value) + return "must be less than " + Designation(c, e.Value) case *gqt.ConstrGreater: - return "must be greater than " + Designation(e.Value) + return "must be greater than " + Designation(c, e.Value) case *gqt.ConstrLessOrEqual: - return "must be less than or equal " + Designation(e.Value) + return "must be less than or equal " + Designation(c, e.Value) case *gqt.ConstrGreaterOrEqual: - return "must be greater than or equal " + Designation(e.Value) + return "must be greater than or equal " + Designation(c, e.Value) case *gqt.ExprParentheses: - return "(" + Designation(e.Expression) + ")" + return "(" + Designation(c, e.Expression) + ")" case *gqt.ExprAddition: - return Designation(e.AddendLeft) + " + " + Designation(e.AddendRight) + return Designation(c, e.AddendLeft) + + " + " + + Designation(c, e.AddendRight) case *gqt.ExprSubtraction: - return Designation(e.Minuend) + " - " + Designation(e.Subtrahend) + return Designation(c, e.Minuend) + + " - " + + Designation(c, e.Subtrahend) case *gqt.ExprMultiplication: - return Designation(e.Multiplicant) + " * " + Designation(e.Multiplicator) + return Designation(c, e.Multiplicant) + + " * " + + Designation(c, e.Multiplicator) case *gqt.ExprDivision: - return Designation(e.Dividend) + " / " + Designation(e.Divisor) + return Designation(c, e.Dividend) + + " / " + + Designation(c, e.Divisor) case *gqt.ExprModulo: - return Designation(e.Dividend) + " % " + Designation(e.Divisor) + return Designation(c, e.Dividend) + + " % " + + Designation(c, e.Divisor) case *gqt.ExprEqual: - return Designation(e.Left) + " equal " + Designation(e.Right) + return Designation(c, e.Left) + + " equal " + + Designation(c, e.Right) case *gqt.ExprNotEqual: - return Designation(e.Left) + " not equal " + Designation(e.Right) + return Designation(c, e.Left) + + " not equal " + + Designation(c, e.Right) case *gqt.ExprGreater: - return Designation(e.Left) + " greater than " + Designation(e.Right) + return Designation(c, e.Left) + + " greater than " + + Designation(c, e.Right) case *gqt.ExprLess: - return Designation(e.Left) + " less than " + Designation(e.Right) + return Designation(c, e.Left) + + " less than " + + Designation(c, e.Right) case *gqt.ExprLessOrEqual: - return Designation(e.Left) + " less than or equal " + Designation(e.Right) + return Designation(c, e.Left) + + " less than or equal " + + Designation(c, e.Right) case *gqt.ExprGreaterOrEqual: - return Designation(e.Left) + " greater than or equal " + Designation(e.Right) + return Designation(c, e.Left) + + " greater than or equal " + + Designation(c, e.Right) case *gqt.ExprLogicalNegation: - return "not(" + Designation(e.Expression) + ")" + return "not(" + Designation(c, e.Expression) + ")" case *gqt.ExprNumericNegation: - return "negative(" + Designation(e.Expression) + ")" + return "negative(" + Designation(c, e.Expression) + ")" case *gqt.Number: i, ok := e.Int() if ok { @@ -1128,21 +979,24 @@ func Designation(e gqt.Expression) string { if i > 0 { b.WriteString(" ,") } - fmt.Fprintf(&b, "%d: %v", i, Designation(v)) + fmt.Fprintf(&b, "%d: %v", i, Designation(c, v)) } b.WriteByte(']') return b.String() case *gqt.Enum: return e.Value case *gqt.Variable: - return MakePath(e.Declaration.Parent) + p, ok := c.pathByVar[e] + if !ok { + return p + } case *gqt.ExprLogicalOr: var b strings.Builder for i, v := range e.Expressions { if i > 0 { b.WriteString(", or ") } - b.WriteString(Designation(v)) + b.WriteString(Designation(c, v)) } return b.String() case *gqt.ExprLogicalAnd: @@ -1151,7 +1005,7 @@ func Designation(e gqt.Expression) string { if i > 0 { b.WriteString(", and ") } - b.WriteString(Designation(v)) + b.WriteString(Designation(c, v)) } return b.String() case *gqt.Object: @@ -1161,7 +1015,7 @@ func Designation(e gqt.Expression) string { if i > 0 { b.WriteString(" ,") } - fmt.Fprintf(&b, "%q: %v", v.Name.Name, Designation(v.Constraint)) + fmt.Fprintf(&b, "%q: %v", v.Name.Name, Designation(c, v.Constraint)) } b.WriteByte('}') return b.String() @@ -1169,67 +1023,67 @@ func Designation(e gqt.Expression) string { panic(fmt.Sprintf("unhandled expression: %T", e)) } -func (m *Checker) expectOrNum(expect *gqlast.Type) (wrongType bool) { +func (c *Checker) expectOrNum(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(expect, m.checkedValue, m.schema) + wrongType, _ = isWrongType(expect, c.checkedValue, c.schema) return wrongType } - return m.checkedValue[0].ID != gqlscan.TokenInt && - m.checkedValue[0].ID != gqlscan.TokenFloat + return c.checkedValue[0].ID != gqlscan.TokenInt && + c.checkedValue[0].ID != gqlscan.TokenFloat } -func (m *Checker) expectOrInt(expect *gqlast.Type) (wrongType bool) { +func (c *Checker) expectOrInt(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(expect, m.checkedValue, m.schema) + wrongType, _ = isWrongType(expect, c.checkedValue, c.schema) return wrongType } - return m.checkedValue[0].ID != gqlscan.TokenInt + return c.checkedValue[0].ID != gqlscan.TokenInt } -func (m *Checker) expectOrFloat(expect *gqlast.Type) (wrongType bool) { +func (c *Checker) expectOrFloat(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(expect, m.checkedValue, m.schema) + wrongType, _ = isWrongType(expect, c.checkedValue, c.schema) return wrongType } - return m.checkedValue[0].ID != gqlscan.TokenFloat + return c.checkedValue[0].ID != gqlscan.TokenFloat } -func (m *Checker) expectOrString(expect *gqlast.Type) (wrongType bool) { +func (c *Checker) expectOrString(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(expect, m.checkedValue, m.schema) + wrongType, _ = isWrongType(expect, c.checkedValue, c.schema) return wrongType } - return m.checkedValue[0].ID != gqlscan.TokenStr && - m.checkedValue[0].ID != gqlscan.TokenStrBlock + return c.checkedValue[0].ID != gqlscan.TokenStr && + c.checkedValue[0].ID != gqlscan.TokenStrBlock } -func (m *Checker) expectOrEnum(expect *gqlast.Type) (wrongType bool) { +func (c *Checker) expectOrEnum(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(expect, m.checkedValue, m.schema) + wrongType, _ = isWrongType(expect, c.checkedValue, c.schema) return wrongType } - return m.checkedValue[0].ID != gqlscan.TokenEnumVal + return c.checkedValue[0].ID != gqlscan.TokenEnumVal } -func (m *Checker) expectOrBool(expect *gqlast.Type) (wrongType bool) { +func (c *Checker) expectOrBool(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(expect, m.checkedValue, m.schema) + wrongType, _ = isWrongType(expect, c.checkedValue, c.schema) return wrongType } - return m.checkedValue[0].ID != gqlscan.TokenTrue && - m.checkedValue[0].ID != gqlscan.TokenFalse + return c.checkedValue[0].ID != gqlscan.TokenTrue && + c.checkedValue[0].ID != gqlscan.TokenFalse } -func (m *Checker) expectOrHasLen( +func (c *Checker) expectOrHasLen( expect *gqlast.Type, ) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(expect, m.checkedValue, m.schema) + wrongType, _ = isWrongType(expect, c.checkedValue, c.schema) return wrongType } - return m.checkedValue[0].ID != gqlscan.TokenArr && - m.checkedValue[0].ID != gqlscan.TokenStr && - m.checkedValue[0].ID != gqlscan.TokenStrBlock + return c.checkedValue[0].ID != gqlscan.TokenArr && + c.checkedValue[0].ID != gqlscan.TokenStr && + c.checkedValue[0].ID != gqlscan.TokenStrBlock } func makeEqArray( @@ -1245,24 +1099,24 @@ func makeEqArray( } checks[i] = makeCheck(v.Items[i], expect, schema) } - return func(m *Checker) (match bool) { - if m.checkedValue[0].ID != gqlscan.TokenArr { + return func(c *Checker) (match bool) { + if c.checkedValue[0].ID != gqlscan.TokenArr { return false } - m.checkedValue = m.checkedValue[1:] - c := 0 - for ; m.checkedValue[0].ID != gqlscan.TokenArrEnd; c++ { - if c >= len(checks) { + c.checkedValue = c.checkedValue[1:] + count := 0 + for ; c.checkedValue[0].ID != gqlscan.TokenArrEnd; count++ { + if count >= len(checks) { return false } - if !checks[c](m) { + if !checks[count](c) { return false } } - if c != len(checks) { + if count != len(checks) { return false } - m.checkedValue = m.checkedValue[1:] + c.checkedValue = c.checkedValue[1:] return true } } @@ -1280,33 +1134,33 @@ func makeNotEqArray( } checks[i] = makeCheck(v.Items[i], expect, schema) } - return func(m *Checker) (match bool) { - if m.checkedValue[0].ID != gqlscan.TokenArr { + return func(c *Checker) (match bool) { + if c.checkedValue[0].ID != gqlscan.TokenArr { return false } - m.checkedValue = m.checkedValue[1:] - c := 0 - for ; m.checkedValue[0].ID != gqlscan.TokenArrEnd; c++ { - if c >= len(checks) { + c.checkedValue = c.checkedValue[1:] + count := 0 + for ; c.checkedValue[0].ID != gqlscan.TokenArrEnd; count++ { + if count >= len(checks) { // Skip all following values to return correct tokenRead - for m.checkedValue[0].ID != gqlscan.TokenArrEnd { - l := gqlparse.GetValLen(m.checkedValue) - m.checkedValue = m.checkedValue[l:] + for c.checkedValue[0].ID != gqlscan.TokenArrEnd { + l := gqlparse.GetValLen(c.checkedValue) + c.checkedValue = c.checkedValue[l:] } return true } - if !checks[c](m) { + if !checks[count](c) { // Skip all following values to return correct tokenRead - for m.checkedValue[0].ID != gqlscan.TokenArrEnd { - l := gqlparse.GetValLen(m.checkedValue) - m.checkedValue = m.checkedValue[l:] + for c.checkedValue[0].ID != gqlscan.TokenArrEnd { + l := gqlparse.GetValLen(c.checkedValue) + c.checkedValue = c.checkedValue[l:] } return true } } - m.checkedValue = m.checkedValue[1:] + c.checkedValue = c.checkedValue[1:] // All item checks were matched, finally check length - return c != len(checks) + return count != len(checks) } } @@ -1316,22 +1170,22 @@ func makeEqNumber( schema *gqlast.Schema, ) check { if i, ok := v.Int(); ok { - return func(m *Checker) (match bool) { - if m.expectOrInt(expect) { + return func(c *Checker) (match bool) { + if c.expectOrInt(expect) { return false } - a := atoi.MustI32(m.checkedValue[0].Value) - m.checkedValue = m.checkedValue[1:] + a := atoi.MustI32(c.checkedValue[0].Value) + c.checkedValue = c.checkedValue[1:] return int32(i) == a } } f, _ := v.Float() - return func(m *Checker) (match bool) { - if m.expectOrFloat(expect) { + return func(c *Checker) (match bool) { + if c.expectOrFloat(expect) { return false } - a := atoi.MustF64(m.checkedValue[0].Value) - m.checkedValue = m.checkedValue[1:] + a := atoi.MustF64(c.checkedValue[0].Value) + c.checkedValue = c.checkedValue[1:] return f == a } } @@ -1342,22 +1196,22 @@ func makeNotEqNumber( schema *gqlast.Schema, ) check { if i, ok := v.Int(); ok { - return func(m *Checker) (match bool) { - if m.expectOrInt(expect) { + return func(c *Checker) (match bool) { + if c.expectOrInt(expect) { return false } - a := atoi.MustI32(m.checkedValue[0].Value) - m.checkedValue = m.checkedValue[1:] + a := atoi.MustI32(c.checkedValue[0].Value) + c.checkedValue = c.checkedValue[1:] return int32(i) != a } } f, _ := v.Float() - return func(m *Checker) (match bool) { - if m.expectOrFloat(expect) { + return func(c *Checker) (match bool) { + if c.expectOrFloat(expect) { return false } - a := atoi.MustF64(m.checkedValue[0].Value) - m.checkedValue = m.checkedValue[1:] + a := atoi.MustF64(c.checkedValue[0].Value) + c.checkedValue = c.checkedValue[1:] return f != a } } @@ -1367,12 +1221,12 @@ func makeEqString( expect *gqlast.Type, schema *gqlast.Schema, ) check { - return func(m *Checker) (match bool) { - if m.expectOrString(expect) { + return func(c *Checker) (match bool) { + if c.expectOrString(expect) { return false } - t := m.checkedValue[0] - m.checkedValue = m.checkedValue[1:] + t := c.checkedValue[0] + c.checkedValue = c.checkedValue[1:] switch t.ID { case gqlscan.TokenStr: return v.Value == string(t.Value) @@ -1388,12 +1242,12 @@ func makeNotEqString( expect *gqlast.Type, schema *gqlast.Schema, ) check { - return func(m *Checker) (match bool) { - if m.expectOrString(expect) { + return func(c *Checker) (match bool) { + if c.expectOrString(expect) { return false } - t := m.checkedValue[0] - m.checkedValue = m.checkedValue[1:] + t := c.checkedValue[0] + c.checkedValue = c.checkedValue[1:] switch t.ID { case gqlscan.TokenStr: return v.Value != string(t.Value) @@ -1409,12 +1263,12 @@ func makeEqEnum( expect *gqlast.Type, schema *gqlast.Schema, ) check { - return func(m *Checker) (match bool) { - if m.expectOrEnum(expect) { + return func(c *Checker) (match bool) { + if c.expectOrEnum(expect) { return false } - b := m.checkedValue[0].Value - m.checkedValue = m.checkedValue[1:] + b := c.checkedValue[0].Value + c.checkedValue = c.checkedValue[1:] return v.Value == string(b) } } @@ -1424,12 +1278,12 @@ func makeNotEqEnum( expect *gqlast.Type, schema *gqlast.Schema, ) check { - return func(m *Checker) (match bool) { - if m.expectOrEnum(expect) { + return func(c *Checker) (match bool) { + if c.expectOrEnum(expect) { return false } - b := m.checkedValue[0].Value - m.checkedValue = m.checkedValue[1:] + b := c.checkedValue[0].Value + c.checkedValue = c.checkedValue[1:] return v.Value != string(b) } } @@ -1438,12 +1292,12 @@ func makeEqNull( expect *gqlast.Type, schema *gqlast.Schema, ) check { - return func(m *Checker) (match bool) { - if wrong, _ := isWrongType(expect, m.checkedValue, schema); wrong { + return func(c *Checker) (match bool) { + if wrong, _ := isWrongType(expect, c.checkedValue, schema); wrong { return false } - t := m.checkedValue[0].ID - m.checkedValue = m.checkedValue[1:] + t := c.checkedValue[0].ID + c.checkedValue = c.checkedValue[1:] return t == gqlscan.TokenNull } } @@ -1452,12 +1306,12 @@ func makeNotEqNull( expect *gqlast.Type, schema *gqlast.Schema, ) check { - return func(m *Checker) (match bool) { - if wrong, _ := isWrongType(expect, m.checkedValue, schema); wrong { + return func(c *Checker) (match bool) { + if wrong, _ := isWrongType(expect, c.checkedValue, schema); wrong { return false } - t := m.checkedValue[0].ID - m.checkedValue = m.checkedValue[1:] + t := c.checkedValue[0].ID + c.checkedValue = c.checkedValue[1:] return t != gqlscan.TokenNull } } @@ -1471,12 +1325,12 @@ func makeEqBool( if value { v = gqlscan.TokenTrue } - return func(m *Checker) (match bool) { - if m.expectOrBool(expect) { + return func(c *Checker) (match bool) { + if c.expectOrBool(expect) { return false } - t := m.checkedValue[0].ID - m.checkedValue = m.checkedValue[1:] + t := c.checkedValue[0].ID + c.checkedValue = c.checkedValue[1:] return t == v } } @@ -1490,12 +1344,12 @@ func makeNotEqBool( if value { v = gqlscan.TokenTrue } - return func(m *Checker) (match bool) { - if m.expectOrBool(expect) { + return func(c *Checker) (match bool) { + if c.expectOrBool(expect) { return false } - t := m.checkedValue[0].ID - m.checkedValue = m.checkedValue[1:] + t := c.checkedValue[0].ID + c.checkedValue = c.checkedValue[1:] return t != v } } @@ -1519,49 +1373,49 @@ func makeEqObject( checks[v.Name.Name] = makeCheck(v.Constraint, expect, schema) fieldChecked[v.Name.Name] = false } - return func(m *Checker) (match bool) { - if wrong, _ := isWrongType(expect, m.checkedValue, schema); wrong { + return func(c *Checker) (match bool) { + if wrong, _ := isWrongType(expect, c.checkedValue, schema); wrong { return false } - if m.checkedValue[0].ID != gqlscan.TokenObj { + if c.checkedValue[0].ID != gqlscan.TokenObj { return false } - m.checkedValue = m.checkedValue[1:] + c.checkedValue = c.checkedValue[1:] // Reset check status for k := range fieldChecked { fieldChecked[k] = false } - c := 0 - for ; m.checkedValue[0].ID != gqlscan.TokenObjEnd; c++ { - if c >= len(checks) { + count := 0 + for ; c.checkedValue[0].ID != gqlscan.TokenObjEnd; count++ { + if count >= len(checks) { return false } - check := checks[string(m.checkedValue[0].Value)] + check := checks[string(c.checkedValue[0].Value)] if check == nil { // Unknown field, wrong object type return false } - if fieldChecked[string(m.checkedValue[0].Value)] { + if fieldChecked[string(c.checkedValue[0].Value)] { // Field provided twice, invalid object return false } - fieldChecked[string(m.checkedValue[0].Value)] = true + fieldChecked[string(c.checkedValue[0].Value)] = true - m.checkedValue = m.checkedValue[1:] + c.checkedValue = c.checkedValue[1:] - if !check(m) { + if !check(c) { return false } } - if requiredChecks < 1 && c != len(checks) || c < requiredChecks { + if requiredChecks < 1 && count != len(checks) || count < requiredChecks { // Not all required fields were provided, wrong object type return false } - m.checkedValue = m.checkedValue[1:] + c.checkedValue = c.checkedValue[1:] return true } } @@ -1585,51 +1439,51 @@ func makeNotEqObject( checks[v.Name.Name] = makeCheck(v.Constraint, expect, schema) fieldChecked[v.Name.Name] = false } - return func(m *Checker) (match bool) { - if wrong, _ := isWrongType(expect, m.checkedValue, schema); wrong { + return func(c *Checker) (match bool) { + if wrong, _ := isWrongType(expect, c.checkedValue, schema); wrong { return false } - if m.checkedValue[0].ID != gqlscan.TokenObj { + if c.checkedValue[0].ID != gqlscan.TokenObj { return false } - m.checkedValue = m.checkedValue[1:] + c.checkedValue = c.checkedValue[1:] // Reset check status for k := range fieldChecked { fieldChecked[k] = false } - c := 0 - for ; m.checkedValue[0].ID != gqlscan.TokenObjEnd; c++ { - if c >= len(checks) { + count := 0 + for ; c.checkedValue[0].ID != gqlscan.TokenObjEnd; count++ { + if count >= len(checks) { return false } - check := checks[string(m.checkedValue[0].Value)] + check := checks[string(c.checkedValue[0].Value)] if check == nil { // Unknown field, wrong object type return false } - if fieldChecked[string(m.checkedValue[0].Value)] { + if fieldChecked[string(c.checkedValue[0].Value)] { // Field provided twice, invalid object return false } - fieldChecked[string(m.checkedValue[0].Value)] = true + fieldChecked[string(c.checkedValue[0].Value)] = true - m.checkedValue = m.checkedValue[1:] + c.checkedValue = c.checkedValue[1:] - if check(m) { + if check(c) { // Don't return just yet as we're not sure whether the // object type was correct. match = true } } - if requiredChecks < 1 && c != len(checks) || c < requiredChecks { + if requiredChecks < 1 && count != len(checks) || count < requiredChecks { // Not all required fields were provided, wrong object type return false } - m.checkedValue = m.checkedValue[1:] + c.checkedValue = c.checkedValue[1:] return !match } } @@ -1639,61 +1493,61 @@ func makeEqExpression( expect *gqlast.Type, schema *gqlast.Schema, ) check { - return func(m *Checker) (match bool) { - if wrong, _ := isWrongType(expect, m.checkedValue, schema); wrong { + return func(c *Checker) (match bool) { + if wrong, _ := isWrongType(expect, c.checkedValue, schema); wrong { return false } - m.resolveExpr(v) - u := m.popStack() + c.resolveExpr(v) + u := c.popStack() switch u.Type() { case union.TypeNull: - return m.checkedValue[0].ID == gqlscan.TokenNull + return c.checkedValue[0].ID == gqlscan.TokenNull case union.TypeBoolean: b, _ := u.Bool() if b { - return m.checkedValue[0].ID == gqlscan.TokenTrue + return c.checkedValue[0].ID == gqlscan.TokenTrue } - return m.checkedValue[0].ID == gqlscan.TokenFalse + return c.checkedValue[0].ID == gqlscan.TokenFalse case union.TypeInt: - if m.checkedValue[0].ID == gqlscan.TokenInt { + if c.checkedValue[0].ID == gqlscan.TokenInt { u, _ := u.Int() - return u == atoi.MustI32(m.checkedValue[0].Value) + return u == atoi.MustI32(c.checkedValue[0].Value) } return false case union.TypeFloat: - if m.checkedValue[0].ID == gqlscan.TokenFloat { + if c.checkedValue[0].ID == gqlscan.TokenFloat { u, _ := u.Float() - return u == atoi.MustF64(m.checkedValue[0].Value) + return u == atoi.MustF64(c.checkedValue[0].Value) } return false case union.TypeString: - if m.checkedValue[0].ID == gqlscan.TokenStr { + if c.checkedValue[0].ID == gqlscan.TokenStr { u, _ := u.String() - return u == string(m.checkedValue[0].Value) + return u == string(c.checkedValue[0].Value) } return false case union.TypeEnum: - if m.checkedValue[0].ID == gqlscan.TokenEnumVal { + if c.checkedValue[0].ID == gqlscan.TokenEnumVal { u, _ := u.Enum() - return u == string(m.checkedValue[0].Value) + return u == string(c.checkedValue[0].Value) } return false case union.TypeTokens: - if len(u.Tokens()) != len(m.checkedValue) { + if len(u.Tokens()) != len(c.checkedValue) { return false } - for i := range m.checkedValue { - if m.checkedValue[i].ID != u.Tokens()[i].ID { + for i := range c.checkedValue { + if c.checkedValue[i].ID != u.Tokens()[i].ID { return false } - switch m.checkedValue[i].ID { + switch c.checkedValue[i].ID { case gqlscan.TokenStrBlock: - if string(m.checkedValue[i].Value) != string(u.Tokens()[i].Value) { + if string(c.checkedValue[i].Value) != string(u.Tokens()[i].Value) { return false } default: - if string(m.checkedValue[i].Value) != string(u.Tokens()[i].Value) { + if string(c.checkedValue[i].Value) != string(u.Tokens()[i].Value) { return false } } diff --git a/engine/playmon/internal/constrcheck/constrcheck_test.go b/engine/playmon/internal/constrcheck/constrcheck_test.go index db99114..3c3b721 100644 --- a/engine/playmon/internal/constrcheck/constrcheck_test.go +++ b/engine/playmon/internal/constrcheck/constrcheck_test.go @@ -1,119 +1,121 @@ package constrcheck_test -// import ( -// "embed" -// "io/fs" -// "sort" -// "strings" -// "testing" +import ( + "embed" + "io/fs" + "sort" + "strings" + "testing" -// "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck" -// "github.com/graph-guard/ggproxy/engines/playmon/internal/constrcheck/internal/test" -// "github.com/graph-guard/ggproxy/gqlparse" -// "github.com/graph-guard/gqt/v4" -// "github.com/stretchr/testify/require" -// "github.com/vektah/gqlparser/v2" -// gqlast "github.com/vektah/gqlparser/v2/ast" -// ) + "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck" + "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck/internal/test" + "github.com/graph-guard/gqt/v4" + "github.com/stretchr/testify/require" + "github.com/vektah/gqlparser/v2" + gqlast "github.com/vektah/gqlparser/v2/ast" +) -// //go:embed tests -// var testsFS embed.FS +//go:embed tests +var testsFS embed.FS -// func TestCheck(t *testing.T) { -// d, err := fs.ReadDir(testsFS, "tests") -// require.NoError(t, err) +func TestCheck(t *testing.T) { + d, err := fs.ReadDir(testsFS, "tests") + require.NoError(t, err) -// for _, do := range d { -// fileName := do.Name() -// if do.IsDir() { -// t.Run(fileName, func(t *testing.T) { -// t.Skipf("ignoring directory %q", fileName) -// }) -// continue -// } -// if !strings.HasSuffix(fileName, ".yml") { -// t.Run(fileName, func(t *testing.T) { -// t.Skipf("ignoring file %q", fileName) -// }) -// continue -// } + for _, do := range d { + fileName := do.Name() + if do.IsDir() { + t.Run(fileName, func(t *testing.T) { + t.Skipf("ignoring directory %q", fileName) + }) + continue + } + if !strings.HasSuffix(fileName, ".yml") { + t.Run(fileName, func(t *testing.T) { + t.Skipf("ignoring file %q", fileName) + }) + continue + } -// t.Run(strings.TrimSuffix(fileName, ".yml"), func(t *testing.T) { -// ts, err := test.Parse(testsFS, fileName) -// require.NoError(t, err) -// var withschema, schemaless *gqt.Operation -// var schemaAST *gqlast.Schema -// { -// p, err := gqt.NewParser([]gqt.Source{ -// {Name: "schema.graphqls", Content: ts.Schema}, -// }) -// require.NoError(t, err, "unexpected error in schema") -// opr, _, errs := p.Parse([]byte(ts.Template)) -// require.Len(t, errs, 0, "unexpected errors: %#v", errs) -// withschema = opr + t.Run(strings.TrimSuffix(fileName, ".yml"), func(t *testing.T) { + ts, err := test.Parse(testsFS, fileName) + require.NoError(t, err) + var withschema, schemaless *gqt.Operation + var schemaAST *gqlast.Schema + { + p, err := gqt.NewParser([]gqt.Source{ + {Name: "schema.graphqls", Content: ts.Schema}, + }) + require.NoError(t, err, "unexpected error in schema") + opr, _, errs := p.Parse([]byte(ts.Template)) + require.Len(t, errs, 0, "unexpected errors: %#v", errs) + withschema = opr -// schemaAST, err = gqlparser.LoadSchema(&gqlast.Source{ -// Name: "schema.graphqls", Input: ts.Schema, -// }) -// require.NoError(t, err) -// } -// { -// opr, _, errs := gqt.Parse([]byte(ts.Template)) -// require.Len(t, errs, 0, "unexpected errors: %#v", errs) -// schemaless = opr -// } + schemaAST, err = gqlparser.LoadSchema(&gqlast.Source{ + Name: "schema.graphqls", Input: ts.Schema, + }) + require.NoError(t, err) + } + { + opr, _, errs := gqt.Parse([]byte(ts.Template)) + require.Len(t, errs, 0, "unexpected errors: %#v", errs) + schemaless = opr + } -// run := func(t *testing.T, opr *gqt.Operation) { -// t.Helper() -// c := constrcheck.New(opr, schemaAST) + run := func(t *testing.T, opr *gqt.Operation) { + t.Helper() + c := constrcheck.New(opr, schemaAST) + require.NotNil(t, c) -// paths := make([]string, 0, len(ts.Inputs)) -// for path := range ts.Inputs { -// paths = append(paths, path) -// } -// sort.Strings(paths) + paths := make([]string, 0, len(ts.Inputs)) + for path := range ts.Inputs { + paths = append(paths, path) + } + sort.Strings(paths) -// variables := make([][]gqlparse.Token, len(ts.Inputs)) -// for path, v := range ts.Inputs { -// inputs[path] = v.Tokens -// } + // TODO: implement test -// c.Init(inputs) -// for _, path := range paths { -// if len(ts.Inputs[path].Tokens) < 1 { -// t.Errorf("missing test value for path %q", path) -// continue -// } + // variables := make(map[string][]gqlparse.Token, len(ts.Inputs)) + // for path, v := range ts.Inputs { + // variables[path] = v.Tokens + // } -// if ts.Inputs[path].Match == nil { -// i := ts.Inputs[path] -// t := false -// i.Match = &t -// ts.Inputs[path] = i -// } -// if ts.Inputs[path].MatchSchemaless == nil { -// i := ts.Inputs[path] -// i.MatchSchemaless = i.Match -// ts.Inputs[path] = i -// } + // c.Init(inputs) + // for _, path := range paths { + // if len(ts.Inputs[path].Tokens) < 1 { + // t.Errorf("missing test value for path %q", path) + // continue + // } -// actual := c.Check(path) -// if opr.Def != nil { -// require.Equal( -// t, *ts.Inputs[path].Match, actual, -// "checking %q", path, -// ) -// } else { -// require.Equal( -// t, *ts.Inputs[path].MatchSchemaless, actual, -// "checking %q", path, -// ) -// } -// } -// } + // if ts.Inputs[path].Match == nil { + // i := ts.Inputs[path] + // t := false + // i.Match = &t + // ts.Inputs[path] = i + // } + // if ts.Inputs[path].MatchSchemaless == nil { + // i := ts.Inputs[path] + // i.MatchSchemaless = i.Match + // ts.Inputs[path] = i + // } -// t.Run("schema", func(t *testing.T) { run(t, withschema) }) -// t.Run("schemaless", func(t *testing.T) { run(t, schemaless) }) -// }) -// } -// } + // actual := c.Check(path) + // if opr.Def != nil { + // require.Equal( + // t, *ts.Inputs[path].Match, actual, + // "checking %q", path, + // ) + // } else { + // require.Equal( + // t, *ts.Inputs[path].MatchSchemaless, actual, + // "checking %q", path, + // ) + // } + // } + } + + t.Run("schema", func(t *testing.T) { run(t, withschema) }) + t.Run("schemaless", func(t *testing.T) { run(t, schemaless) }) + }) + } +} diff --git a/engine/playmon/internal/pathmatch/bench_test.go b/engine/playmon/internal/pathmatch/bench_test.go index 1b7423a..7783621 100644 --- a/engine/playmon/internal/pathmatch/bench_test.go +++ b/engine/playmon/internal/pathmatch/bench_test.go @@ -12,10 +12,15 @@ func BenchmarkPathmatch(b *testing.B) { for _, bb := range tests { b.Run(bb.name, func(b *testing.B) { m := prepareTestSetup(b, bb.conf) + paths := make([][]byte, len(bb.paths)) + for i := range bb.paths { + paths[i] = []byte(bb.paths[i]) + } b.ResetTimer() for n := 0; n < b.N; n++ { - m.Match(bb.paths, func(tm *config.Template) { + m.Match(paths, func(tm *config.Template) (stop bool) { GI++ + return false }) } }) diff --git a/engine/playmon/internal/pathmatch/pathmatch.go b/engine/playmon/internal/pathmatch/pathmatch.go index 1616d08..ca75358 100644 --- a/engine/playmon/internal/pathmatch/pathmatch.go +++ b/engine/playmon/internal/pathmatch/pathmatch.go @@ -19,7 +19,10 @@ type Matcher struct { } // Match calls onMatch for every template matching paths -func (m *Matcher) Match(paths []string, onMatch func(*config.Template)) { +func (m *Matcher) Match( + paths [][]byte, + onMatch func(*config.Template) (stop bool), +) { m.matchMask.Reset() m.rejectedMask.Reset() for i := range m.matchesPerTemplate { @@ -27,7 +30,7 @@ func (m *Matcher) Match(paths []string, onMatch func(*config.Template)) { } for i := range paths { - b, ok := m.paths[paths[i]] + b, ok := m.paths[string(paths[i])] if !ok { return // Unknown path, can't match any template } @@ -40,8 +43,8 @@ func (m *Matcher) Match(paths []string, onMatch func(*config.Template)) { } } m.matchMask.SetAndNot(m.matchMask, m.rejectedMask) - m.matchMask.VisitAll(func(n int) { - onMatch(m.conf.TemplatesEnabled[n]) + m.matchMask.Visit(func(n int) (stop bool) { + return onMatch(m.conf.TemplatesEnabled[n]) }) } @@ -62,10 +65,18 @@ func New(conf *config.Service) *Matcher { pathscan.InAST( conf.TemplatesEnabled[i].GQTTemplate, func(path []byte, e gqt.Expression) (stop bool) { + // On structural m.templatePaths[i] = append(m.templatePaths[i], string(path)) return false }, - func(path []byte, e gqt.Expression) (stop bool) { return false }, + func(path []byte, e gqt.Expression) (stop bool) { + // On argument + return false + }, + func(path []byte, e gqt.Expression) (stop bool) { + // On variable + return false + }, ) } diff --git a/engine/playmon/internal/pathmatch/pathmatch_test.go b/engine/playmon/internal/pathmatch/pathmatch_test.go index a94a54e..a9608cc 100644 --- a/engine/playmon/internal/pathmatch/pathmatch_test.go +++ b/engine/playmon/internal/pathmatch/pathmatch_test.go @@ -14,8 +14,13 @@ func TestMatch(t *testing.T) { t.Run(tt.name, func(t *testing.T) { m := prepareTestSetup(t, tt.conf) var actualMatches []string - m.Match(tt.paths, func(tm *config.Template) { + paths := make([][]byte, len(tt.paths)) + for i := range tt.paths { + paths[i] = []byte(tt.paths[i]) + } + m.Match(paths, func(tm *config.Template) (stop bool) { actualMatches = append(actualMatches, tm.ID) + return false }) require.Equal(t, tt.expectIDs, actualMatches) }) diff --git a/engine/playmon/internal/pathscan/pathscan.go b/engine/playmon/internal/pathscan/pathscan.go index 5010267..ee7c3cd 100644 --- a/engine/playmon/internal/pathscan/pathscan.go +++ b/engine/playmon/internal/pathscan/pathscan.go @@ -39,22 +39,28 @@ import ( "fmt" "sort" + "github.com/graph-guard/ggproxy/engine/playmon/internal/travgqt" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/ggproxy/utilities/stack" gqlscan "github.com/graph-guard/gqlscan" gqt "github.com/graph-guard/gqt/v4" + + "golang.org/x/exp/slices" ) // PathScanner is reset in every call to InTokens type PathScanner struct { - pathBuf []byte - stack stack.Stack[int] + valPathBuf []byte + valStack stack.Stack[int] + argBuf [][]byte + structuralPathBuf []byte + structuralStack stack.Stack[int] } func New(preallocateStack, preallocatePathBuffer int) *PathScanner { return &PathScanner{ - pathBuf: make([]byte, 0, preallocatePathBuffer), - stack: stack.New[int](preallocateStack), + structuralPathBuf: make([]byte, 0, preallocatePathBuffer), + structuralStack: stack.New[int](preallocateStack), } } @@ -71,7 +77,8 @@ const ( ) // InTokens calls onStructural for every encountered structural path, -// and onVariable for every encountered variable. +// onArg for every argument and onGQTVarVal for every encountered +// GQT variable value. // operation is expected to be an operation initialization token and // is used to determine whether the path should start with // "Q" (query), "M" (mutation) or "S" (subscription). @@ -84,89 +91,185 @@ const ( func (s *PathScanner) InTokens( operation gqlscan.Token, tokens []gqlparse.Token, - gqtVarPaths map[string]struct{}, - onStructural, onVariable func(path []byte, i int) (stop bool), + gqtVarPaths map[string]int, + onStructural func(path []byte) (stop bool), + onArg, onGQTVarVal func(path []byte, i int) (stop bool), ) { - s.pathBuf = s.pathBuf[:0] - s.stack.Reset() + s.valPathBuf = s.valPathBuf[:0] + s.valStack.Reset() + s.structuralPathBuf = s.structuralPathBuf[:0] + s.structuralStack.Reset() switch operation { case gqlscan.TokenDefQry: - s.stackPushByte(initQuery) + s.valPathBuf = append(s.valPathBuf, initQuery) + s.structuralPathBuf = append(s.structuralPathBuf, initQuery) case gqlscan.TokenDefMut: - s.stackPushByte(initMutation) + s.valPathBuf = append(s.valPathBuf, initMutation) + s.structuralPathBuf = append(s.structuralPathBuf, initMutation) case gqlscan.TokenDefSub: - s.stackPushByte(initSubscription) + s.valPathBuf = append(s.valPathBuf, initSubscription) + s.structuralPathBuf = append(s.structuralPathBuf, initSubscription) default: panic(fmt.Errorf("unexpected operation: %v", operation)) } - for i, level := 0, 1; i < len(tokens); i++ { + for i, level := 0, 0; i < len(tokens); i++ { switch tokens[i].ID { case gqlscan.TokenSet: level++ case gqlscan.TokenSetEnd: level-- - s.stackPop() + s.structuralPop() + s.valPop() case gqlscan.TokenFragInline: - if level > s.stack.Len() { - s.stackPushWithDiv(divTypeCond, tokens[i].Value) - break + if level <= s.structuralStack.Len() { + s.structuralPop() + s.valPop() } - s.stackPop() - s.stackPushWithDiv(divTypeCond, tokens[i].Value) + s.structuralWithDiv(divTypeCond, tokens[i].Value) + s.valWithDiv(divTypeCond, tokens[i].Value) case gqlscan.TokenField: - if level > s.stack.Len() { - s.stackPushWithDiv(divSel, tokens[i].Value) - } else { - s.stackPop() - s.stackPushWithDiv(divSel, tokens[i].Value) - } - if t := tokens[i+1].ID; t == gqlscan.TokenArgList || - t == gqlscan.TokenSet { - continue + if level <= s.structuralStack.Len() { + s.structuralPop() + s.valPop() } - if onStructural(s.pathBuf, i) { - return - } - case gqlscan.TokenArgName: - s.stackPushWithDiv(divArgList, tokens[i].Value) - if onStructural(s.pathBuf, i) { - return + s.valWithDiv(divSel, tokens[i].Value) + s.structuralPathBuf = append(s.structuralPathBuf, divSel) + s.structuralPathBuf = append(s.structuralPathBuf, tokens[i].Value...) + l := len(tokens[i].Value) + 1 + + switch tokens[i+1].ID { + case gqlscan.TokenArgList: + s.argBuf = s.argBuf[:0] + // Collect and sort arguments + level++ + for i += 2; tokens[i].ID != gqlscan.TokenArgListEnd; i++ { + switch tokens[i].ID { + case gqlscan.TokenArgName: + if level <= s.valStack.Len() { + s.valPop() + } + s.argBuf = append(s.argBuf, tokens[i].Value) + s.valWithDiv(divArgList, tokens[i].Value) + onArg(s.valPathBuf, i) + if _, ok := gqtVarPaths[string(s.valPathBuf)]; ok { + if onGQTVarVal(s.valPathBuf, i+1) { + return + } + } + case gqlscan.TokenObj: + if tokens[i+1].ID == gqlscan.TokenObjEnd { + // Empty object + i++ + break + } + level++ + case gqlscan.TokenObjEnd: + level-- + s.valPop() + case gqlscan.TokenObjField: + if level <= s.valStack.Len() { + s.valPop() + } + s.valWithDiv(divObjField, tokens[i].Value) + if _, ok := gqtVarPaths[string(s.valPathBuf)]; ok { + if onGQTVarVal(s.valPathBuf, i+1) { + return + } + } + } + } + level-- + s.valPop() // Pop last argument + slices.SortFunc(s.argBuf, func(i, j []byte) bool { + return bytes.Compare(i, j) < 0 + }) + + // Write args to path + s.structuralPathBuf = append(s.structuralPathBuf, divArgList) + l++ + for i := range s.argBuf { + s.structuralPathBuf = append(s.structuralPathBuf, s.argBuf[i]...) + s.structuralPathBuf = append(s.structuralPathBuf, divArg) + l += len(s.argBuf[i]) + 1 + } + s.structuralStack.Push(l) + + // Check for leaf + if tokens[i+1].ID != gqlscan.TokenSet { + if onStructural(s.structuralPathBuf) { + return + } + s.structuralPop() + s.valPop() + continue + } + case gqlscan.TokenSet: + s.structuralStack.Push(l) + default: + s.structuralStack.Push(l) + if onStructural(s.structuralPathBuf) { + return + } + s.structuralPop() + s.valPop() } - s.stackPop() } } } -func (s *PathScanner) stackPushByte(element byte) { - s.stack.Push(1) - s.pathBuf = append(s.pathBuf, element) +func (s *PathScanner) valByte(element byte) { + s.valStack.Push(1) + s.valPathBuf = append(s.valPathBuf, element) +} + +func (s *PathScanner) valWithDiv(div byte, element []byte) { + s.valStack.Push(1 + len(element)) + s.valPathBuf = append(s.valPathBuf, div) + s.valPathBuf = append(s.valPathBuf, element...) +} + +func (s *PathScanner) valPop() { + t := s.valStack.Top() + s.valPathBuf = s.valPathBuf[:len(s.valPathBuf)-t] + s.valStack.Pop() } -func (s *PathScanner) stackPushWithDiv(div byte, element []byte) { - s.stack.Push(1 + len(element)) - s.pathBuf = append(s.pathBuf, div) - s.pathBuf = append(s.pathBuf, element...) +func (s *PathScanner) structuralByte(element byte) { + s.structuralStack.Push(1) + s.structuralPathBuf = append(s.structuralPathBuf, element) } -func (s *PathScanner) stackPop() { - t := s.stack.Top() - s.pathBuf = s.pathBuf[:len(s.pathBuf)-t] - s.stack.Pop() +func (s *PathScanner) structuralWithDiv(div byte, element []byte) { + s.structuralStack.Push(1 + len(element)) + s.structuralPathBuf = append(s.structuralPathBuf, div) + s.structuralPathBuf = append(s.structuralPathBuf, element...) +} + +func (s *PathScanner) structuralPop() { + t := s.structuralStack.Top() + s.structuralPathBuf = s.structuralPathBuf[:len(s.structuralPathBuf)-t] + s.structuralStack.Pop() } // InAST calls onStructural for every structural path that can be used for // (sub)matching. onVariable is called for every path to an argument or an -// object field that has a variable associated. +// object field that has a variable associated. onArg is called for every +// argument. func InAST( o *gqt.Operation, onStructural func(path []byte, e gqt.Expression) (stop bool), + onArg func(path []byte, e gqt.Expression) (stop bool), onVariable func(path []byte, e gqt.Expression) (stop bool), ) { - traverse(o, func(e gqt.Expression) (stop, skipChildren bool) { + travgqt.Traverse(o, func(e gqt.Expression) (stop, skipChildren bool) { switch e := e.(type) { case *gqt.SelectionField: for _, a := range e.Arguments { + p := makePathVar(a) + if onArg(p, a) { + return true, true + } if a.AssociatedVariable != nil { p := makePathVar(a) if onVariable(p, e) { @@ -193,142 +296,6 @@ func InAST( }) } -// traverse returns true after BFS-traversing the entire tree under e -// calling onExpression for every discovered expression. -func traverse( - e gqt.Expression, - onExpression func(gqt.Expression) (stop, skipChildren bool), -) (stopped bool) { - stack := make([]gqt.Expression, 0, 64) - push := func(expression gqt.Expression) { - stack = append(stack, expression) - } - - push(e) - for len(stack) > 0 { - top := stack[len(stack)-1] - stack = stack[:len(stack)-1] - stop, skipChildren := onExpression(top) - if stop { - return true - } else if skipChildren { - continue - } - - switch e := top.(type) { - case *gqt.Operation: - for _, f := range e.Selections { - push(f) - } - case *gqt.SelectionInlineFrag: - for _, f := range e.Selections { - push(f) - } - case *gqt.SelectionField: - for _, f := range e.Selections { - push(f) - } - for _, a := range e.Arguments { - push(a) - } - case *gqt.SelectionMax: - for _, f := range e.Options.Selections { - push(f) - } - case *gqt.Argument: - push(e.Constraint) - case *gqt.ConstrEquals: - push(e.Value) - case *gqt.ConstrNotEquals: - push(e.Value) - case *gqt.ConstrGreater: - push(e.Value) - case *gqt.ConstrGreaterOrEqual: - push(e.Value) - case *gqt.ConstrLess: - push(e.Value) - case *gqt.ConstrLessOrEqual: - push(e.Value) - case *gqt.ConstrLenEquals: - push(e.Value) - case *gqt.ConstrLenNotEquals: - push(e.Value) - case *gqt.ConstrLenGreater: - push(e.Value) - case *gqt.ConstrLenLess: - push(e.Value) - case *gqt.ConstrLenGreaterOrEqual: - push(e.Value) - case *gqt.ConstrLenLessOrEqual: - push(e.Value) - case *gqt.ConstrMap: - push(e.Constraint) - case *gqt.ExprParentheses: - push(e.Expression) - case *gqt.ExprEqual: - push(e.Left) - push(e.Right) - case *gqt.ExprNotEqual: - push(e.Left) - push(e.Right) - case *gqt.ExprLogicalNegation: - push(e.Expression) - case *gqt.ExprNumericNegation: - push(e.Expression) - case *gqt.ExprLogicalOr: - for _, e := range e.Expressions { - push(e) - } - case *gqt.ExprLogicalAnd: - for _, e := range e.Expressions { - push(e) - } - case *gqt.ExprAddition: - push(e.AddendLeft) - push(e.AddendRight) - case *gqt.ExprSubtraction: - push(e.Minuend) - push(e.Subtrahend) - case *gqt.ExprMultiplication: - push(e.Multiplicant) - push(e.Multiplicator) - case *gqt.ExprDivision: - push(e.Dividend) - push(e.Divisor) - case *gqt.ExprModulo: - push(e.Dividend) - push(e.Divisor) - case *gqt.ExprGreater: - push(e.Left) - push(e.Right) - case *gqt.ExprGreaterOrEqual: - push(e.Left) - push(e.Right) - case *gqt.ExprLess: - push(e.Left) - push(e.Right) - case *gqt.ExprLessOrEqual: - push(e.Left) - push(e.Right) - case *gqt.Array: - for _, i := range e.Items { - push(i) - } - case *gqt.Object: - for _, f := range e.Fields { - push(f) - } - case *gqt.ObjectField: - push(e.Constraint) - case *gqt.Number, *gqt.True, *gqt.False, *gqt.Null, - *gqt.Enum, *gqt.String, *gqt.ConstrAny, *gqt.Variable: - default: - panic(fmt.Errorf("unhandled type: %T", top)) - } - } - return false -} - // makePathStructural generates a structural path for the given expression. func makePathStructural(e *gqt.SelectionField) []byte { var p []gqt.Expression // Reversed path @@ -353,9 +320,7 @@ func makePathStructural(e *gqt.SelectionField) []byte { sort.Strings(argNames) for i := range argNames { _, _ = s.WriteString(argNames[i]) - if i+1 < len(argNames) { - _ = s.WriteByte(divArg) - } + _ = s.WriteByte(divArg) } } case *gqt.Operation: diff --git a/engine/playmon/internal/pathscan/pathscan_test.go b/engine/playmon/internal/pathscan/pathscan_test.go index 99e34da..111b6ce 100644 --- a/engine/playmon/internal/pathscan/pathscan_test.go +++ b/engine/playmon/internal/pathscan/pathscan_test.go @@ -11,17 +11,148 @@ import ( "github.com/stretchr/testify/require" ) -func TestInTokens(t *testing.T) { - for _, tt := range []struct { - Name string - Input string - VariablePaths map[string]struct{} - Expected map[string]int // path->index - ExpectedVar map[string]int // path->index - }{ - { - Name: "query", - Input: `query { +var testsInTokens = []struct { + Name string + GraphQLOperation string + VariablePaths []string + ExpectedStructural []string + ExpectedVarVal map[string]int // path->index + ExpectedArg map[string]int // path->index +}{ + { + Name: "query_selections_noargs", + GraphQLOperation: `query { + foo { foo2 } + bar { bar2 bar3 bar4 } + bazz + fuzz + maz { kraz { glaz { traz } } } + jazz + }`, + ExpectedStructural: []string{ + "Q.foo.foo2", + "Q.bar.bar2", + "Q.bar.bar3", + "Q.bar.bar4", + "Q.bazz", + "Q.fuzz", + "Q.maz.kraz.glaz.traz", + "Q.jazz", + }, + }, + { + Name: "query_type_conditions", + GraphQLOperation: `query { + u { + ... on Foo { + foo foo2 + ... on Far { + far + far2 + } + } + ... on Bar { bar bar2 } + } + }`, + ExpectedStructural: []string{ + "Q.u&Foo.foo", + "Q.u&Foo.foo2", + "Q.u&Foo&Far.far", + "Q.u&Foo&Far.far2", + "Q.u&Bar.bar", + "Q.u&Bar.bar2", + }, + }, + { + Name: "subscription", + GraphQLOperation: `subscription { s }`, + ExpectedStructural: []string{"S.s"}, + }, + { + Name: "mutation", + GraphQLOperation: `mutation { m }`, + ExpectedStructural: []string{"M.m"}, + }, + { + Name: "args", + GraphQLOperation: `query{ + titles(options:{lang: DE}) + entities(filter:["filter","this","out"]) + }`, + VariablePaths: []string{ + "Q.titles|options", + "Q.entities|filter", + }, + ExpectedStructural: []string{ + "Q.titles|options,", + "Q.entities|filter,", + }, + ExpectedArg: map[string]int{ + "Q.titles|options": 3, + "Q.entities|filter": 11, + }, + ExpectedVarVal: map[string]int{ + "Q.titles|options": 4, + "Q.entities|filter": 12, + }, + }, + { + Name: "args_complex", + GraphQLOperation: `mutation($variable:Int! = 42){ + foo(i:42, t:true, f:false, n:null) + maz(var:$variable) { + kraz(x:"""text""") { + fraz(x:"more text") { + graz + } + } + } + bazz(object:{array:[1,2,3,null], enum: ENUM_VALUE}) + bar(strings:["array","of","strings"]) + }`, + VariablePaths: []string{ + "M.foo|i", + "M.foo|t", + "M.foo|f", + "M.foo|n", + "M.maz|var", + "M.maz.kraz|x", + "M.maz.kraz.fraz|x", + "M.bazz|object", + "M.bar|strings", + }, + ExpectedStructural: []string{ + "M.foo|f,i,n,t,", + "M.maz|var,.kraz|x,.fraz|x,.graz", + "M.bazz|object,", + "M.bar|strings,", + }, + ExpectedArg: map[string]int{ + "M.foo|i": 3, + "M.foo|t": 5, + "M.foo|f": 7, + "M.foo|n": 9, + "M.maz|var": 14, + "M.maz.kraz|x": 20, + "M.maz.kraz.fraz|x": 26, + "M.bazz|object": 36, + "M.bar|strings": 51, + }, + ExpectedVarVal: map[string]int{ + "M.foo|i": 4, + "M.foo|t": 6, + "M.foo|f": 8, + "M.foo|n": 10, + "M.maz|var": 15, + "M.maz.kraz|x": 21, + "M.maz.kraz.fraz|x": 27, + "M.bazz|object": 37, + "M.bar|strings": 52, + }, + }, + { + Name: "query_complex", + GraphQLOperation: `query { foo { bar { burr(x:4) @@ -31,50 +162,108 @@ func TestInTokens(t *testing.T) { fraz graz(argument:{i:"foo",i2:"bar"}) { lum + klum } } buzz(b:5, a:null, c:true) + brazz(b:5, a:null, c:true) ... on Guz { guz + guzz { + blaz + } } } } mazz + laz(x:ENUM_VALUE) }`, - Expected: map[string]int{ - "Q.foo.bar.burr|x": 7, - "Q.foo.baz&Kraz.fraz": 15, - "Q.foo.baz&Kraz.graz|argument": 18, - "Q.foo.baz&Kraz.graz.lum": 27, - "Q.foo.baz.buzz|b": 32, - "Q.foo.baz.buzz|a": 34, - "Q.foo.baz.buzz|c": 36, - "Q.foo.baz&Guz.guz": 41, - "Q.mazz": 45, - }, + ExpectedStructural: []string{ + "Q.foo.bar.burr|x,", + "Q.foo.baz&Kraz.fraz", + "Q.foo.baz&Kraz.graz|argument,.lum", + "Q.foo.baz&Kraz.graz|argument,.klum", + "Q.foo.baz.buzz|a,b,c,", + "Q.foo.baz.brazz|a,b,c,", + "Q.foo.baz&Guz.guz", + "Q.foo.baz&Guz.guzz.blaz", + "Q.mazz", + "Q.laz|x,", }, - { - Name: "mutation_with_vars", - Input: `mutation($baz:String="baz"){foo(bar:$baz)}`, - Expected: map[string]int{ - "M.foo|bar": 3, - }, + ExpectedArg: map[string]int{ + "Q.foo.bar.burr|x": 7, + "Q.foo.baz&Kraz.graz|argument": 18, + "Q.foo.baz.buzz|b": 33, + "Q.foo.baz.buzz|a": 35, + "Q.foo.baz.buzz|c": 37, + "Q.foo.baz.brazz|b": 42, + "Q.foo.baz.brazz|a": 44, + "Q.foo.baz.brazz|c": 46, + "Q.laz|x": 62, }, - { - Name: "subscription", - Input: `subscription{foo}`, - Expected: map[string]int{ - "S.foo": 1, - }, + }, + { + Name: "query_gqtvar_object", + GraphQLOperation: `query { + f(obj:{foo:{bar:1,baz:2}, fraz:3}) + }`, + VariablePaths: []string{ + "Q.f|obj", + "Q.f|obj/foo", + "Q.f|obj/foo/bar", + "Q.f|obj/foo/baz", + "Q.f|obj/fraz", + }, + ExpectedStructural: []string{ + "Q.f|obj,", + }, + ExpectedArg: map[string]int{ + "Q.f|obj": 3, + }, + ExpectedVarVal: map[string]int{ + "Q.f|obj": 4, + "Q.f|obj/foo": 6, + "Q.f|obj/foo/bar": 8, + "Q.f|obj/foo/baz": 10, + "Q.f|obj/fraz": 13, }, - } { + }, + { + Name: "query_gqtvar_object_partialvars", + GraphQLOperation: `query { + f(obj:{foo:{bar:1,baz:2}, fraz:3}) + }`, + VariablePaths: []string{ + "Q.f|obj/foo/baz", + "Q.f|obj/fraz", + }, + ExpectedStructural: []string{ + "Q.f|obj,", + }, + ExpectedArg: map[string]int{ + "Q.f|obj": 3, + }, + ExpectedVarVal: map[string]int{ + "Q.f|obj/foo/baz": 10, + "Q.f|obj/fraz": 13, + }, + }, +} + +func TestInTokens(t *testing.T) { + for _, tt := range testsInTokens { t.Run(tt.Name, func(t *testing.T) { ps := pathscan.New(0, 0) p := gqlparse.NewParser() - actualPaths := make(map[string]int) + actualStructuralPaths := []string{} + actualArgPaths := make(map[string]int) actualVarPaths := make(map[string]int) + variablePaths := make(map[string]int, len(tt.VariablePaths)) + for _, p := range tt.VariablePaths { + variablePaths[p] = 0 + } p.Parse( - []byte(tt.Input), nil, nil, + []byte(tt.GraphQLOperation), nil, nil, func( varValues [][]gqlparse.Token, operation, selectionSet []gqlparse.Token, @@ -82,13 +271,19 @@ func TestInTokens(t *testing.T) { ps.InTokens( operation[0].ID, selectionSet, - tt.VariablePaths, - func(b []byte, i int) (stop bool) { // On structural - actualPaths[string(b)] = i + variablePaths, + func(path []byte) (stop bool) { // On structural + actualStructuralPaths = append( + actualStructuralPaths, string(path), + ) + return false + }, + func(path []byte, i int) (stop bool) { // On argument + actualArgPaths[string(path)] = i return false }, - func(b []byte, i int) (stop bool) { // On variable - actualVarPaths[string(b)] = i + func(path []byte, i int) (stop bool) { // On GQT variable + actualVarPaths[string(path)] = i return false }, ) @@ -97,11 +292,18 @@ func TestInTokens(t *testing.T) { t.Fatalf("unexpected GraphQL parsing error: %v", err) }, ) - require.Equal(t, tt.Expected, actualPaths) - if tt.ExpectedVar == nil { - tt.ExpectedVar = map[string]int{} + require.Equal( + t, tt.ExpectedStructural, actualStructuralPaths, + "structural paths", + ) + if tt.ExpectedVarVal == nil { + tt.ExpectedVarVal = map[string]int{} } - require.Equal(t, tt.ExpectedVar, actualVarPaths) + if tt.ExpectedArg == nil { + tt.ExpectedArg = map[string]int{} + } + require.Equal(t, tt.ExpectedArg, actualArgPaths, "argument paths") + require.Equal(t, tt.ExpectedVarVal, actualVarPaths, "variable paths") }) } } @@ -116,10 +318,14 @@ func TestInTokensPanic(t *testing.T) { {ID: gqlscan.TokenField, Value: []byte("foo")}, {ID: gqlscan.TokenSetEnd}, }, - map[string]struct{}{ + map[string]int{ // No variable paths }, - func(path []byte, i int) (stop bool) { // On structural + func(path []byte) (stop bool) { // On structural + t.Fatal("this function isn't expected to be called") + return false + }, + func(path []byte, i int) (stop bool) { // On argument t.Fatal("this function isn't expected to be called") return false }, @@ -131,180 +337,240 @@ func TestInTokensPanic(t *testing.T) { }) } -func TestInAst(t *testing.T) { - for _, tt := range []struct { - Name string - GQTTemplateSrc string - ExpectedPaths []string - ExpectedVarPaths []string - }{ - { - Name: "subscription_single", - GQTTemplateSrc: `subscription{foo}`, - ExpectedPaths: []string{ - "S.foo", - }, +var testsInAST = []struct { + Name string + GQTTemplateSrc string + ExpectStructural []string + ExpectedArgPaths []string + ExpectedVarPaths []string +}{ + { + Name: "subscription_single", + GQTTemplateSrc: `subscription{foo}`, + ExpectStructural: []string{ + "S.foo", }, - { - Name: "subscription_multiple", - GQTTemplateSrc: `subscription{foo bar}`, - ExpectedPaths: []string{ - "S.foo", - "S.bar", - }, + }, + { + Name: "subscription_multiple", + GQTTemplateSrc: `subscription{foo bar}`, + ExpectStructural: []string{ + "S.foo", + "S.bar", }, - { - Name: "args", - GQTTemplateSrc: `query{foo(b:*,a:"t") bar(c:42)}`, - ExpectedPaths: []string{ - "Q.bar|c", - "Q.foo|a,b", - }, + }, + { + Name: "args", + GQTTemplateSrc: `query{foo(b:*,a:"t") bar(c:42)}`, + ExpectStructural: []string{ + "Q.foo|a,b,", + "Q.bar|c,", }, - { - Name: "args_with_subselections", - GQTTemplateSrc: `query{foo(b:*,a:"t") { fraz kraz(c:*) }}`, - ExpectedPaths: []string{ - "Q.foo|a,b.fraz", - "Q.foo|a,b.kraz|c", - }, + ExpectedArgPaths: []string{ + "Q.foo|b", + "Q.foo|a", + "Q.bar|c", }, - { - Name: "mutation_with_vars", - GQTTemplateSrc: `mutation{foo(bar=$bar:{x:*,y:*})}`, - ExpectedPaths: []string{ - "M.foo|bar", - }, - ExpectedVarPaths: []string{ - "M.foo|bar", - }, + }, + { + Name: "args_with_subselections", + GQTTemplateSrc: `query{foo(b:*,a:"t") { fraz kraz(c:*) }}`, + ExpectStructural: []string{ + "Q.foo|a,b,.fraz", + "Q.foo|a,b,.kraz|c,", }, - { - Name: "mutation_with_vars_on_multiple_levels", - GQTTemplateSrc: `mutation{ - foo(bar=$bar:*){ - fo2(b=$b:*,a=$a:*){ - fo3(c=$c:*) - fa3 - } - fa2 + ExpectedArgPaths: []string{ + "Q.foo|b", + "Q.foo|a", + "Q.foo.kraz|c", + }, + }, + { + Name: "mutation_with_vars", + GQTTemplateSrc: `mutation{foo(bar=$bar:{x:*,y:*})}`, + ExpectStructural: []string{ + "M.foo|bar,", + }, + ExpectedArgPaths: []string{ + "M.foo|bar", + }, + ExpectedVarPaths: []string{ + "M.foo|bar", + }, + }, + { + Name: "mutation_with_vars_on_multiple_levels", + GQTTemplateSrc: `mutation{ + foo(bar=$bar:*){ + fo2(b=$b:*,a=$a:*){ + fo3(c=$c:*) + fa3 } - bazz(x:*) - }`, - ExpectedPaths: []string{ - "M.foo|bar.fa2", - "M.foo|bar.fo2|a,b.fo3|c", - "M.foo|bar.fo2|a,b.fa3", - "M.bazz|x", - }, - ExpectedVarPaths: []string{ - "M.foo|bar", - "M.foo.fo2|b", - "M.foo.fo2|a", - "M.foo.fo2.fo3|c", - }, + fa2 + } + bazz(x:*) + }`, + ExpectStructural: []string{ + "M.foo|bar,.fa2", + "M.foo|bar,.fo2|a,b,.fo3|c,", + "M.foo|bar,.fo2|a,b,.fa3", + "M.bazz|x,", }, - { - Name: "mutation_var_in_obj", - GQTTemplateSrc: `mutation{ - foo { - bar(bar1:*,bar2:*) { - baz( - o: { - so=$so: { - x=$x: *, - y=$y: *, - } - }, - o2: $so, - x=$x2: $x, - y: $x2 || $y, - ) - } + ExpectedArgPaths: []string{ + "M.foo|bar", + "M.foo.fo2|b", + "M.foo.fo2|a", + "M.foo.fo2.fo3|c", + "M.bazz|x", + }, + ExpectedVarPaths: []string{ + "M.foo|bar", + "M.foo.fo2|b", + "M.foo.fo2|a", + "M.foo.fo2.fo3|c", + }, + }, + { + Name: "mutation_var_in_obj", + GQTTemplateSrc: `mutation{ + foo { + bar(bar1:*,bar2:*) { + baz( + o: { + so=$so: { + x=$x: *, + y=$y: *, + } + }, + o2: $so, + x=$x2: $x, + y: $x2 || $y, + ) } - }`, - ExpectedPaths: []string{ - "M.foo.bar|bar1,bar2.baz|o,o2,x,y", - }, - ExpectedVarPaths: []string{ - "M.foo.bar.baz|o/so", - "M.foo.bar.baz|o/so/x", - "M.foo.bar.baz|o/so/y", - "M.foo.bar.baz|x", - }, + } + }`, + ExpectStructural: []string{ + "M.foo.bar|bar1,bar2,.baz|o,o2,x,y,", }, - { - Name: "type_condition", - GQTTemplateSrc: `query{ - u(u2:*,u1:*) { - ... on Foo { - foo1(x=$foo1x:*, y:*) - foo2(x:*) - } - ... on Bar { - bar1(x:*) - bar2(x=$bar2x:*, y:*) - } + ExpectedArgPaths: []string{ + "M.foo.bar|bar1", + "M.foo.bar|bar2", + "M.foo.bar.baz|o", + "M.foo.bar.baz|o2", + "M.foo.bar.baz|x", + "M.foo.bar.baz|y", + }, + ExpectedVarPaths: []string{ + "M.foo.bar.baz|o/so", + "M.foo.bar.baz|o/so/x", + "M.foo.bar.baz|o/so/y", + "M.foo.bar.baz|x", + }, + }, + { + Name: "type_condition", + GQTTemplateSrc: `query{ + u(u2:*,u1:*) { + ... on Foo { + foo1(x=$foo1x:*, y:*) + foo2(x:*) } - }`, - ExpectedPaths: []string{ - "Q.u|u1,u2&Foo.foo1|x,y", - "Q.u|u1,u2&Foo.foo2|x", - "Q.u|u1,u2&Bar.bar1|x", - "Q.u|u1,u2&Bar.bar2|x,y", - }, - ExpectedVarPaths: []string{ - "Q.u&Foo.foo1|x", - "Q.u&Bar.bar2|x", - }, + ... on Bar { + bar1(x:*) + bar2(x=$bar2x:*, y:*) + } + } + }`, + ExpectStructural: []string{ + "Q.u|u1,u2,&Foo.foo1|x,y,", + "Q.u|u1,u2,&Foo.foo2|x,", + "Q.u|u1,u2,&Bar.bar1|x,", + "Q.u|u1,u2,&Bar.bar2|x,y,", }, - { - Name: "complex", - GQTTemplateSrc: `query { - foo { - bar { - burr(x:4) - } - baz { - ... on Kraz { - fraz - graz(argument:{i:"foo",i2:"bar"}) { - lum - } - } - buzz(b:5, a:null, c:true) - ... on Guz { - guz + ExpectedArgPaths: []string{ + "Q.u|u2", + "Q.u|u1", + "Q.u&Foo.foo1|x", + "Q.u&Foo.foo1|y", + "Q.u&Foo.foo2|x", + "Q.u&Bar.bar1|x", + "Q.u&Bar.bar2|x", + "Q.u&Bar.bar2|y", + }, + ExpectedVarPaths: []string{ + "Q.u&Foo.foo1|x", + "Q.u&Bar.bar2|x", + }, + }, + { + Name: "complex", + GQTTemplateSrc: `query { + foo { + bar { + burr(x:4) + } + baz { + ... on Kraz { + fraz + graz(argument:{i:"foo",i2:"bar"}) { + lum } } + buzz(b:5, a:null, c:true) + ... on Guz { + guz + } } - mazz - }`, - ExpectedPaths: []string{ - "Q.foo.bar.burr|x", - "Q.foo.baz&Kraz.fraz", - "Q.foo.baz&Kraz.graz|argument.lum", - "Q.foo.baz&Guz.guz", - "Q.foo.baz.buzz|a,b,c", - "Q.mazz", - }, + } + mazz + }`, + ExpectStructural: []string{ + "Q.foo.bar.burr|x,", + "Q.foo.baz&Kraz.fraz", + "Q.foo.baz&Kraz.graz|argument,.lum", + "Q.foo.baz&Guz.guz", + "Q.foo.baz.buzz|a,b,c,", + "Q.mazz", }, - } { + ExpectedArgPaths: []string{ + "Q.foo.bar.burr|x", + "Q.foo.baz&Kraz.graz|argument", + "Q.foo.baz.buzz|b", + "Q.foo.baz.buzz|a", + "Q.foo.baz.buzz|c", + }, + }, +} + +func TestInAst(t *testing.T) { + for _, tt := range testsInAST { t.Run(tt.Name, func(t *testing.T) { o, _, errs := gqt.Parse([]byte(tt.GQTTemplateSrc)) require.Nil(t, errs) - var actualPaths, actualVarPaths []string + + var actualPaths, actualArgPaths, actualVarPaths []string pathscan.InAST(o, func(path []byte, e gqt.Expression) (stop bool) { + // On structural actualPaths = append(actualPaths, string(path)) require.NotNil(t, e) return false }, func(path []byte, e gqt.Expression) (stop bool) { + // On argument + actualArgPaths = append(actualArgPaths, string(path)) + return false + }, func(path []byte, e gqt.Expression) (stop bool) { + // On variable actualVarPaths = append(actualVarPaths, string(path)) return false }) - sort.Strings(tt.ExpectedPaths) + + sort.Strings(tt.ExpectStructural) sort.Strings(actualPaths) - require.Equal(t, tt.ExpectedPaths, actualPaths, "structural paths") + require.Equal(t, tt.ExpectStructural, actualPaths, "structural paths") + + sort.Strings(tt.ExpectedArgPaths) + sort.Strings(actualArgPaths) + require.Equal(t, tt.ExpectedArgPaths, actualArgPaths, "argument paths") sort.Strings(tt.ExpectedVarPaths) sort.Strings(actualVarPaths) diff --git a/engine/playmon/internal/travgqt/travgqt.go b/engine/playmon/internal/travgqt/travgqt.go new file mode 100644 index 0000000..6d9750c --- /dev/null +++ b/engine/playmon/internal/travgqt/travgqt.go @@ -0,0 +1,145 @@ +// Package travgqt provides a GQT expression traversal function. +package travgqt + +import ( + "fmt" + + "github.com/graph-guard/gqt/v4" +) + +// Traverse returns true after BFS-traversing the entire tree under e +// calling onExpression for every discovered expression. +// Returns true immediatelly if onExpression returns stop=true. +func Traverse( + e gqt.Expression, + onExpression func(gqt.Expression) (stop, skipChildren bool), +) (stopped bool) { + stack := make([]gqt.Expression, 0, 64) + push := func(expression gqt.Expression) { + stack = append(stack, expression) + } + + push(e) + for len(stack) > 0 { + top := stack[len(stack)-1] + stack = stack[:len(stack)-1] + stop, skipChildren := onExpression(top) + if stop { + return true + } else if skipChildren { + continue + } + + switch e := top.(type) { + case *gqt.Operation: + for _, f := range e.Selections { + push(f) + } + case *gqt.SelectionInlineFrag: + for _, f := range e.Selections { + push(f) + } + case *gqt.SelectionField: + for _, f := range e.Selections { + push(f) + } + for _, a := range e.Arguments { + push(a) + } + case *gqt.SelectionMax: + for _, f := range e.Options.Selections { + push(f) + } + case *gqt.Argument: + push(e.Constraint) + case *gqt.ConstrEquals: + push(e.Value) + case *gqt.ConstrNotEquals: + push(e.Value) + case *gqt.ConstrGreater: + push(e.Value) + case *gqt.ConstrGreaterOrEqual: + push(e.Value) + case *gqt.ConstrLess: + push(e.Value) + case *gqt.ConstrLessOrEqual: + push(e.Value) + case *gqt.ConstrLenEquals: + push(e.Value) + case *gqt.ConstrLenNotEquals: + push(e.Value) + case *gqt.ConstrLenGreater: + push(e.Value) + case *gqt.ConstrLenLess: + push(e.Value) + case *gqt.ConstrLenGreaterOrEqual: + push(e.Value) + case *gqt.ConstrLenLessOrEqual: + push(e.Value) + case *gqt.ConstrMap: + push(e.Constraint) + case *gqt.ExprParentheses: + push(e.Expression) + case *gqt.ExprEqual: + push(e.Left) + push(e.Right) + case *gqt.ExprNotEqual: + push(e.Left) + push(e.Right) + case *gqt.ExprLogicalNegation: + push(e.Expression) + case *gqt.ExprNumericNegation: + push(e.Expression) + case *gqt.ExprLogicalOr: + for _, e := range e.Expressions { + push(e) + } + case *gqt.ExprLogicalAnd: + for _, e := range e.Expressions { + push(e) + } + case *gqt.ExprAddition: + push(e.AddendLeft) + push(e.AddendRight) + case *gqt.ExprSubtraction: + push(e.Minuend) + push(e.Subtrahend) + case *gqt.ExprMultiplication: + push(e.Multiplicant) + push(e.Multiplicator) + case *gqt.ExprDivision: + push(e.Dividend) + push(e.Divisor) + case *gqt.ExprModulo: + push(e.Dividend) + push(e.Divisor) + case *gqt.ExprGreater: + push(e.Left) + push(e.Right) + case *gqt.ExprGreaterOrEqual: + push(e.Left) + push(e.Right) + case *gqt.ExprLess: + push(e.Left) + push(e.Right) + case *gqt.ExprLessOrEqual: + push(e.Left) + push(e.Right) + case *gqt.Array: + for _, i := range e.Items { + push(i) + } + case *gqt.Object: + for _, f := range e.Fields { + push(f) + } + case *gqt.ObjectField: + push(e.Constraint) + case *gqt.Number, *gqt.True, *gqt.False, *gqt.Null, + *gqt.Enum, *gqt.String, *gqt.ConstrAny, *gqt.Variable: + default: + panic(fmt.Errorf("unhandled type: %T", top)) + } + } + return false +} diff --git a/engine/playmon/playmon.go b/engine/playmon/playmon.go index 634a2a3..c3c3fc0 100644 --- a/engine/playmon/playmon.go +++ b/engine/playmon/playmon.go @@ -17,7 +17,9 @@ type Engine struct { matcher *pathmatch.Matcher templates []*template - inputsByPath map[string][]gqlparse.Token + structuralPaths [][]byte + gqtVarPaths map[string]int + inputsByPath map[string][]gqlparse.Token } type template struct { @@ -32,65 +34,99 @@ func New(s *config.Service) *Engine { e := &Engine{ pathScanner: pathscan.New(128, 2048), templates: make([]*template, len(s.Templates)), + gqtVarPaths: make(map[string]int), inputsByPath: make(map[string][]gqlparse.Token), } idCounter := 0 for _, t := range s.Templates { c := constrcheck.New(t.GQTTemplate, s.Schema) - e.templates[idCounter] = &template{ + tmpl := &template{ Index: idCounter, ID: t.ID, GQTOpr: t.GQTTemplate, ConstraintChecker: c, } + e.templates[idCounter] = tmpl + pathscan.InAST( + tmpl.GQTOpr, + func(path []byte, e gqt.Expression) (stop bool) { + return false + }, func(path []byte, _ gqt.Expression) (stop bool) { + return false + }, func(path []byte, _ gqt.Expression) (stop bool) { + e.gqtVarPaths[string(path)] = 0 + return false + }, + ) idCounter++ } - pathmatch.New(s) + e.matcher = pathmatch.New(s) return e } +func (e *Engine) reset() { + e.structuralPaths = e.structuralPaths[:0] + for i := range e.gqtVarPaths { + e.gqtVarPaths[i] = 0 + } + for path := range e.inputsByPath { + e.gqtVarPaths[path] = 0 + } +} + // Match returns the ID of the first matching template or "" if none was matched. func (e *Engine) Match( variableValues [][]gqlparse.Token, queryType gqlscan.Token, selectionSet []gqlparse.Token, ) (id string) { - e.currentMask.Reset() - fmt.Println("MASK: ", e.currentMask.String()) - e.pathScanner.Scan( + e.reset() + fmt.Println(selectionSet) + e.pathScanner.InTokens( queryType, selectionSet, + e.gqtVarPaths, + func(path []byte) (stop bool) { + e.structuralPaths = append(e.structuralPaths, path) + return false + }, func(path []byte, i int) (stop bool) { - m, ok := e.maskByPath[string(path)] - if !ok { - return true - } - fmt.Println("PATH: ", string(path)) - e.currentMask.SetOr(e.currentMask, m) - fmt.Println("MASK: ", e.currentMask.String()) + return false + }, + func(path []byte, i int) (stop bool) { + fmt.Println("FUCK") + e.inputsByPath[string(path)] = selectionSet[i:] return false }, ) - if e.currentMask.Size() < 1 { - return "" + fmt.Println("STR", e.structuralPaths) + for i := range e.structuralPaths { + fmt.Println(" ", string(e.structuralPaths[i])) } - e.currentMask.Visit(func(i int) (skip bool) { - t := e.templates[i] - match := true - t.ConstraintChecker.VisitPaths(func(path string) (stop bool) { - if !t.ConstraintChecker.Check(path) { - match = false - return true - } - return false - }) - if match { - id = t.ID - return true - } + + e.matcher.Match(e.structuralPaths, func(t *config.Template) (stop bool) { + fmt.Println("STRUCTURAL MATCH ", t.ID) + fmt.Println(e.inputsByPath) + id = t.ID return false }) + // e.currentMask.Visit(func(i int) (skip bool) { + // t := e.templates[i] + // match := true + // t.ConstraintChecker.VisitPaths(func(path string) (stop bool) { + // if !t.ConstraintChecker.Check(path) { + // match = false + // return true + // } + // return false + // }) + // if match { + // id = t.ID + // return true + // } + // return false + // }) return id } From a171cb2e3a8703467c54a3c8b732dff9b6c8936f Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Tue, 14 Feb 2023 00:13:30 +0100 Subject: [PATCH 09/35] fix: Fix pathmatch test --- .../internal/pathmatch/pathmatch_test.go | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/engine/playmon/internal/pathmatch/pathmatch_test.go b/engine/playmon/internal/pathmatch/pathmatch_test.go index a9608cc..59198fe 100644 --- a/engine/playmon/internal/pathmatch/pathmatch_test.go +++ b/engine/playmon/internal/pathmatch/pathmatch_test.go @@ -142,19 +142,19 @@ var tests = []struct { return s.ServicesEnabled[0] }(), paths: []string{ - "Q.hero|episode.id", - "Q.hero|episode.name", - "Q.hero|episode.appearsIn", - "Q.hero|episode.friends.id", - "Q.hero|episode.friends.name", - "Q.hero|episode.friends.appearsIn", - "Q.hero|episode.friends.friends.id", - "Q.hero|episode.friends.friends.name", - "Q.hero|episode.friends.friends.appearsIn", - "Q.hero|episode.friendsConnection|after,first.totalCount", - "Q.hero|episode.friendsConnection|after,first.friends.id", - "Q.hero|episode.friendsConnection|after,first.friends.name", - "Q.hero|episode.friendsConnection|after,first.friends.appearsIn", + "Q.hero|episode,.id", + "Q.hero|episode,.name", + "Q.hero|episode,.appearsIn", + "Q.hero|episode,.friends.id", + "Q.hero|episode,.friends.name", + "Q.hero|episode,.friends.appearsIn", + "Q.hero|episode,.friends.friends.id", + "Q.hero|episode,.friends.friends.name", + "Q.hero|episode,.friends.friends.appearsIn", + "Q.hero|episode,.friendsConnection|after,first,.totalCount", + "Q.hero|episode,.friendsConnection|after,first,.friends.id", + "Q.hero|episode,.friendsConnection|after,first,.friends.name", + "Q.hero|episode,.friendsConnection|after,first,.friends.appearsIn", }, expectIDs: []string{"c"}, /* query { From 2ef24e594a47bdbf56fc6b344e0b41caf283a525 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Fri, 17 Feb 2023 01:49:18 +0100 Subject: [PATCH 10/35] fix: Fix variable value fetching --- cmd/ggproxy/read_config.go | 9 +- config/config.go | 303 ++--- config/config_test.go | 1029 +++++++++-------- engine/playmon/bench_test.go | 59 + .../internal/constrcheck/constrcheck.go | 71 +- .../playmon/internal/pathmatch/bench_test.go | 5 +- .../playmon/internal/pathmatch/pathmatch.go | 26 +- .../internal/pathmatch/pathmatch_test.go | 64 +- engine/playmon/internal/pathscan/pathscan.go | 63 +- .../internal/pathscan/pathscan_test.go | 156 ++- engine/playmon/playmon.go | 110 +- go.mod | 1 - go.sum | 2 - server/server_test.go | 156 +-- server/tests/README.md | 99 -- .../setup_0/enabled-services/testservice.yml | 1 - .../testservice/template_mut.gqt | 1 - .../testservice/template_qry.gqt | 1 - server/tests/setup_1/config.yaml | 4 - .../setup_1/enabled-services/service_a.yaml | 1 - .../service_a/template_a.gqt | 1 - testsetup/starwars/config.yml | 13 + .../starwars/services_enabled/starwars.yml | 13 + .../starwars/templates_enabled/a.gqt | 13 + .../starwars/templates_enabled/b.gqt | 20 + .../starwars/templates_enabled/c.gqt | 28 + .../starwars/templates_enabled/d.gqt | 20 + testsetup/starwars/test_qry_ok.yaml | 96 ++ .../test1/all_services}/testservice.yaml | 4 +- .../test1/all_templates}/template_mut.gqt | 4 +- .../test1/all_templates}/template_qry.gqt | 0 testsetup/test1/config.yml | 5 + .../test1/enabled_services/testservice.yaml | 6 + .../test1/enabled_templates/template_mut.gqt | 16 + .../test1/enabled_templates/template_qry.gqt | 12 + .../test1}/test_qry_ok.yaml | 0 .../test1}/x_test_mut_ok.yaml | 0 .../test2/all-services/service_a.yaml | 0 .../all-templates/service_a/template_a.gqt | 2 +- .../config.yaml => testsetup/test2/config.yml | 0 .../test2/enabled-services/service_a.yaml | 6 + .../service_a/template_a.gqt | 13 + .../setup_1 => testsetup/test2}/test_1.yaml | 0 testsetup/testsetup.go | 159 +++ testsetup/testsetup_test.go | 18 + 45 files changed, 1503 insertions(+), 1107 deletions(-) create mode 100644 engine/playmon/bench_test.go delete mode 100644 server/tests/README.md delete mode 120000 server/tests/setup_0/enabled-services/testservice.yml delete mode 120000 server/tests/setup_0/enabled-templates/testservice/template_mut.gqt delete mode 120000 server/tests/setup_0/enabled-templates/testservice/template_qry.gqt delete mode 100644 server/tests/setup_1/config.yaml delete mode 120000 server/tests/setup_1/enabled-services/service_a.yaml delete mode 120000 server/tests/setup_1/enabled-templates/service_a/template_a.gqt create mode 100644 testsetup/starwars/config.yml create mode 100644 testsetup/starwars/services_enabled/starwars.yml create mode 100644 testsetup/starwars/services_enabled/starwars/templates_enabled/a.gqt create mode 100644 testsetup/starwars/services_enabled/starwars/templates_enabled/b.gqt create mode 100644 testsetup/starwars/services_enabled/starwars/templates_enabled/c.gqt create mode 100644 testsetup/starwars/services_enabled/starwars/templates_enabled/d.gqt create mode 100644 testsetup/starwars/test_qry_ok.yaml rename {server/tests/setup_0/all-services => testsetup/test1/all_services}/testservice.yaml (52%) rename {server/tests/setup_0/all-templates/testservice => testsetup/test1/all_templates}/template_mut.gqt (70%) rename {server/tests/setup_0/all-templates/testservice => testsetup/test1/all_templates}/template_qry.gqt (100%) create mode 100644 testsetup/test1/config.yml create mode 100644 testsetup/test1/enabled_services/testservice.yaml create mode 100644 testsetup/test1/enabled_templates/template_mut.gqt create mode 100644 testsetup/test1/enabled_templates/template_qry.gqt rename {server/tests/setup_0 => testsetup/test1}/test_qry_ok.yaml (100%) rename {server/tests/setup_0 => testsetup/test1}/x_test_mut_ok.yaml (100%) rename server/tests/setup_1/all-services/service_a.yml => testsetup/test2/all-services/service_a.yaml (100%) rename {server/tests/setup_1 => testsetup/test2}/all-templates/service_a/template_a.gqt (63%) rename server/tests/setup_0/config.yaml => testsetup/test2/config.yml (100%) create mode 100644 testsetup/test2/enabled-services/service_a.yaml create mode 100644 testsetup/test2/enabled-templates/service_a/template_a.gqt rename {server/tests/setup_1 => testsetup/test2}/test_1.yaml (100%) create mode 100644 testsetup/testsetup.go create mode 100644 testsetup/testsetup_test.go diff --git a/cmd/ggproxy/read_config.go b/cmd/ggproxy/read_config.go index 1aeffc5..0f0f22d 100644 --- a/cmd/ggproxy/read_config.go +++ b/cmd/ggproxy/read_config.go @@ -3,6 +3,8 @@ package main import ( "fmt" "io" + "os" + "path/filepath" "github.com/graph-guard/ggproxy/config" ) @@ -11,7 +13,8 @@ func ReadConfig( w io.Writer, configPath string, ) *config.Config { - conf, err := config.New(configPath) + basePath, fileName := basePathAndFileName(configPath) + conf, err := config.Read(os.DirFS(basePath), basePath, fileName) if err != nil { fmt.Fprintf(w, "reading config: %s\n", err) return nil @@ -34,3 +37,7 @@ func ReadConfig( return conf } + +func basePathAndFileName(path string) (basePath, fileName string) { + return filepath.Base(path), path[:len(path)-len(filepath.Base(path))] +} diff --git a/config/config.go b/config/config.go index b80bdd9..bd16be6 100644 --- a/config/config.go +++ b/config/config.go @@ -1,13 +1,13 @@ package config import ( + "bytes" "crypto/md5" "encoding/base32" "errors" "fmt" - "io" + "io/fs" neturl "net/url" - "os" "path/filepath" "regexp" "strings" @@ -17,6 +17,7 @@ import ( gqt "github.com/graph-guard/gqt/v4" gqlparser "github.com/vektah/gqlparser/v2" gqlast "github.com/vektah/gqlparser/v2/ast" + "golang.org/x/exp/slices" yaml "gopkg.in/yaml.v3" ) @@ -113,24 +114,25 @@ type serviceConfig struct { Schema string `yaml:"schema"` } -func New(path string) (c *Config, err error) { +// Read parses configuration files and composes the server config. +func Read(fsys fs.FS, basePath, path string) (c *Config, err error) { // Set default config values c = &Config{ Proxy: ProxyServerConfig{}, API: &APIServerConfig{}, Services: make(map[string]*Service), } - if err = c.readServerConfig(path); err != nil { + if err = c.readServerConfig(fsys, basePath, path); err != nil { return nil, err } return c, nil } -func (c *Config) readServerConfig(path string) (err error) { +func (c *Config) readServerConfig(fsys fs.FS, basePath, path string) (err error) { dirPath := filepath.Dir(path) - file, err := os.Open(path) + file, err := openFile(fsys, basePath, path, "server config") if err != nil { - return fmt.Errorf("opening server config file: %w", err) + return err } sc := &serverConfig{} @@ -138,13 +140,13 @@ func (c *Config) readServerConfig(path string) (err error) { d.KnownFields(true) if err := d.Decode(sc); err != nil { return &ErrorIllegal{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "syntax", Message: err.Error(), } } - if err := validateServerConfig(sc, path); err != nil { + if err := validateServerConfig(sc, basePath, path); err != nil { return err } @@ -180,22 +182,47 @@ func (c *Config) readServerConfig(path string) (err error) { } // reading all services - if err := c.readAllServices(servicesAllPath); err != nil { + if err := c.readAllServices(fsys, basePath, servicesAllPath); err != nil { return err } + if len(c.Services) < 1 { + return ErrNoServices + } + // reading enabled services - if err := c.readEnabledServices(servicesEnabledPath); err != nil { + if err := c.readEnabledServices(fsys, servicesEnabledPath); err != nil { return err } + if len(c.ServicesEnabled) < 1 { + return ErrNoServicesEnabled + } + + { // Make sure there are no collisions on service paths + sk := sortedKeys(c.Services) + paths := make(map[string]*Service, len(c.Services)) + for _, sk := range sk { + s := c.Services[sk] + if s2, ok := paths[s.Path]; ok { + return &ErrorConflict{ + Feature: "path", + Value: s.Path, + Subject1: s.FilePath, + Subject2: s2.FilePath, + } + } + paths[s.Path] = s + } + } + return } -func validateServerConfig(sc *serverConfig, path string) (err error) { +func validateServerConfig(sc *serverConfig, basePath, path string) (err error) { if sc.Proxy.Host == "" { return &ErrorMissing{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "proxy.host", } } @@ -207,13 +234,13 @@ func validateServerConfig(sc *serverConfig, path string) (err error) { switch { case c.CertFile != "" && c.KeyFile == "": return &ErrorMissing{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "proxy.tls.key-file", } case (c.KeyFile != "" && c.CertFile == "") || (c.KeyFile == "" && c.CertFile == ""): return &ErrorMissing{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "proxy.tls.cert-file", } } @@ -223,7 +250,7 @@ func validateServerConfig(sc *serverConfig, path string) (err error) { c := sc.API if c.Host == "" { return &ErrorMissing{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "api.host", } } @@ -235,13 +262,13 @@ func validateServerConfig(sc *serverConfig, path string) (err error) { switch { case c.CertFile != "" && c.KeyFile == "": return &ErrorMissing{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "api.tls.key-file", } case (c.KeyFile != "" && c.CertFile == "") || (c.KeyFile == "" && c.CertFile == ""): return &ErrorMissing{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "api.tls.cert-file", } } @@ -251,7 +278,7 @@ func validateServerConfig(sc *serverConfig, path string) (err error) { if sc.Proxy.MaxRequestBodySizeBytes != nil { if *sc.Proxy.MaxRequestBodySizeBytes < MinReqBodySize { return &ErrorIllegal{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "proxy.max-request-body-size", Message: msgMaxReqBodySizeTooSmall, } @@ -260,13 +287,13 @@ func validateServerConfig(sc *serverConfig, path string) (err error) { if sc.ServicesAll == "" { return &ErrorMissing{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "all-services", } } if sc.ServicesEnabled == "" { return &ErrorMissing{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "enabled-services", } } @@ -274,8 +301,8 @@ func validateServerConfig(sc *serverConfig, path string) (err error) { return } -func (c *Config) readAllServices(path string) (err error) { - d, err := os.ReadDir(path) +func (c *Config) readAllServices(fsys fs.FS, basePath, path string) (err error) { + d, err := fs.ReadDir(fsys, path) if err != nil { return fmt.Errorf("reading services directory: %w", err) } @@ -289,16 +316,13 @@ func (c *Config) readAllServices(path string) (err error) { continue } - file, err := openFile(filepath.Join(path, sf.Name())) - if err != nil { - return err - } - h, err := calculateHash(file) + filePath := filepath.Join(path, sf.Name()) + h, err := calculateHash(fsys, filePath) if err != nil { return err } - s, err := readServiceConfig(file) + s, err := readServiceConfig(fsys, basePath, filePath) if err != nil { return err } @@ -315,8 +339,8 @@ func (c *Config) readAllServices(path string) (err error) { return } -func (c *Config) readEnabledServices(path string) (err error) { - d, err := os.ReadDir(path) +func (c *Config) readEnabledServices(fsys fs.FS, path string) (err error) { + d, err := fs.ReadDir(fsys, path) if err != nil { return fmt.Errorf("reading enabled services directory: %w", err) } @@ -332,11 +356,7 @@ func (c *Config) readEnabledServices(path string) (err error) { } filePath := filepath.Join(path, sf.Name()) - file, err := openFile(filePath) - if err != nil { - return err - } - h, err := calculateHash(file) + h, err := calculateHash(fsys, filePath) if err != nil { return err } @@ -356,24 +376,27 @@ func (c *Config) readEnabledServices(path string) (err error) { return } -func readServiceConfig(file *os.File) (s *Service, err error) { - filePath := file.Name() - dirPath := filepath.Dir(file.Name()) +func readServiceConfig(fsys fs.FS, basePath, filePath string) (s *Service, err error) { + dirPath := filepath.Dir(filePath) sc := &serviceConfig{} { - d := yaml.NewDecoder(file) + c, err := fs.ReadFile(fsys, filePath) + if err != nil { + return nil, err + } + d := yaml.NewDecoder(bytes.NewReader(c)) d.KnownFields(true) if err := d.Decode(sc); err != nil { return nil, &ErrorIllegal{ - FilePath: filePath, + FilePath: filepath.Join(basePath, filePath), Feature: "syntax", Message: err.Error(), } } } - if err := validateServiceConfig(sc, filePath); err != nil { + if err := validateServiceConfig(sc, basePath, filePath); err != nil { return nil, err } @@ -382,7 +405,7 @@ func readServiceConfig(file *os.File) (s *Service, err error) { if sc.Schema != "" { schemaPath = filepath.Join(dirPath, sc.Schema) } - schema, gqtParser, err := s.readSchema(schemaPath) + schema, gqtParser, err := s.readSchema(fsys, basePath, schemaPath) if err != nil { return nil, err } @@ -400,7 +423,7 @@ func readServiceConfig(file *os.File) (s *Service, err error) { id := strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath)) if err := ValidateID(id); err != "" { return nil, &ErrorIllegal{ - FilePath: filePath, + FilePath: filepath.Join(basePath, filePath), Feature: "id", Message: err, } @@ -411,63 +434,71 @@ func readServiceConfig(file *os.File) (s *Service, err error) { ID: id, Schema: schema, Templates: map[string]*Template{}, - FilePath: filePath, + FilePath: filepath.Join(basePath, filePath), Path: sc.Path, ForwardURL: sc.ForwardURL, ForwardReduced: sc.ForwardReduced, } // reading all templates - err = s.readAllTemplates(templatesAllPath, gqtParser) - if err != nil { + if err := s.readAllTemplates( + fsys, basePath, templatesAllPath, gqtParser, + ); err != nil { return nil, err } + if len(s.Templates) < 1 { + return nil, ErrNoTemplates + } + // reading enabled templates - err = s.readEnabledTemplates(templatesEnabledPath) - if err != nil { + if err := s.readEnabledTemplates(fsys, templatesEnabledPath); err != nil { return nil, err } + if len(s.TemplatesEnabled) < 1 { + return nil, ErrNoTemplatesEnabled + } + return } -func validateServiceConfig(sc *serviceConfig, path string) (err error) { +func validateServiceConfig(sc *serviceConfig, basePath, path string) (err error) { if sc.Path == "" { return &ErrorMissing{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "path", } } if err := validatePath(sc.Path); err != nil { return &ErrorIllegal{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "path", Message: err.Error(), } } if sc.ForwardURL == "" { return &ErrorMissing{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "forward-url", } } if err := validateURL(sc.ForwardURL); err != nil { return &ErrorIllegal{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "forward-url", Message: err.Error(), } } if sc.TemplatesAll == "" { return &ErrorMissing{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "all-templates", } } if sc.TemplatesEnabled == "" { return &ErrorMissing{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "enabled-templates", } } @@ -475,40 +506,44 @@ func validateServiceConfig(sc *serviceConfig, path string) (err error) { return } -func (s *Service) readSchema(path string) (*gqlast.Schema, *gqt.Parser, error) { +func (s *Service) readSchema(fsys fs.FS, basePath, path string) (*gqlast.Schema, *gqt.Parser, error) { if path == "" { p, err := gqt.NewParser(nil) return nil, p, err } - f, err := os.ReadFile(path) + f, err := fs.ReadFile(fsys, path) if err != nil { return nil, nil, &ErrorMissing{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "schema", } } schema, err := gqlparser.LoadSchema(&gqlast.Source{ - Name: path, + Name: filepath.Join(basePath, path), Input: string(f), }) if err != nil { return nil, nil, &ErrorIllegal{ - FilePath: path, + FilePath: filepath.Join(basePath, path), Feature: "schema", Message: fmt.Sprintf("invalid schema: %v", err.Error()), } } gqtParser, err := gqt.NewParser([]gqt.Source{ - {Name: path, Content: string(f)}, + {Name: filepath.Join(basePath, path), Content: string(f)}, }) return schema, gqtParser, err } -func (s *Service) readAllTemplates(path string, p *gqt.Parser) (err error) { - dir, err := os.ReadDir(path) +func (s *Service) readAllTemplates( + fsys fs.FS, + basePath, path string, + p *gqt.Parser, +) (err error) { + dir, err := fs.ReadDir(fsys, path) if err != nil { return fmt.Errorf("reading templates directory: %w", err) } @@ -522,16 +557,13 @@ func (s *Service) readAllTemplates(path string, p *gqt.Parser) (err error) { continue } - file, err := openFile(filepath.Join(path, tf.Name())) - if err != nil { - return err - } - h, err := calculateHash(file) + path := filepath.Join(path, tf.Name()) + h, err := calculateHash(fsys, path) if err != nil { return err } - t, err := readTemplate(file, p) + t, err := readTemplate(fsys, basePath, path, p) if err != nil { return err } @@ -549,8 +581,8 @@ func (s *Service) readAllTemplates(path string, p *gqt.Parser) (err error) { return } -func (s *Service) readEnabledTemplates(path string) (err error) { - dir, err := os.ReadDir(path) +func (s *Service) readEnabledTemplates(fsys fs.FS, path string) (err error) { + dir, err := fs.ReadDir(fsys, path) if err != nil { return fmt.Errorf("reading enabled templates directory: %w", err) } @@ -565,12 +597,8 @@ func (s *Service) readEnabledTemplates(path string) (err error) { continue } - file, err := openFile(filepath.Join(path, tf.Name())) - if err != nil { - return err - } - - h, err := calculateHash(file) + p := filepath.Join(path, tf.Name()) + h, err := calculateHash(fsys, p) if err != nil { return err } @@ -590,9 +618,11 @@ func (s *Service) readEnabledTemplates(path string) (err error) { return } -func readTemplate(file *os.File, p *gqt.Parser) (t *Template, err error) { - filePath := file.Name() - +func readTemplate( + fsys fs.FS, + basePath, filePath string, + p *gqt.Parser, +) (t *Template, err error) { id := strings.ToLower( strings.TrimSuffix( filepath.Base(filePath), filepath.Ext(filePath), @@ -600,13 +630,13 @@ func readTemplate(file *os.File, p *gqt.Parser) (t *Template, err error) { ) if err := ValidateID(id); err != "" { return nil, &ErrorIllegal{ - FilePath: filePath, + FilePath: filepath.Join(basePath, filePath), Feature: "id", Message: err, } } - b, err := io.ReadAll(file) + b, err := fs.ReadFile(fsys, filePath) if err != nil { return nil, fmt.Errorf("reading template %q: %w", filePath, err) } @@ -614,7 +644,7 @@ func readTemplate(file *os.File, p *gqt.Parser) (t *Template, err error) { meta, template, err := metadata.Parse(b) if err != nil { return nil, &ErrorIllegal{ - FilePath: filePath, + FilePath: filepath.Join(basePath, filePath), Feature: "metadata", Message: err.Error(), } @@ -630,7 +660,7 @@ func readTemplate(file *os.File, p *gqt.Parser) (t *Template, err error) { } } return nil, &ErrorIllegal{ - FilePath: filePath, + FilePath: filepath.Join(basePath, filePath), Feature: "template", Message: msg.String(), } @@ -638,11 +668,11 @@ func readTemplate(file *os.File, p *gqt.Parser) (t *Template, err error) { t = &Template{ ID: id, + FilePath: filepath.Join(basePath, filePath), Source: template, GQTTemplate: doc, Name: meta.Name, Tags: meta.Tags, - FilePath: filePath, } return @@ -665,18 +695,20 @@ const IDValidCharDict = "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "_-" -type ErrorDuplicate struct { - Original string - Duplicate string -} +type ErrorDuplicate struct{ Original, Duplicate string } func (e ErrorDuplicate) Error() string { return fmt.Sprintf("%s is a duplicate of %s", e.Duplicate, e.Original) } -type ErrorAlien struct { - Items []string -} +var ( + ErrNoServices = errors.New("no services defined") + ErrNoServicesEnabled = errors.New("no services enabled") + ErrNoTemplates = errors.New("no templates defined") + ErrNoTemplatesEnabled = errors.New("no templates enabled") +) + +type ErrorAlien struct{ Items []string } func (e ErrorAlien) Error() string { var b strings.Builder @@ -690,48 +722,26 @@ func (e ErrorAlien) Error() string { return b.String() } -type ErrorMissing struct { - FilePath string - Feature string +type ErrorConflict struct{ Feature, Value, Subject1, Subject2 string } + +func (e ErrorConflict) Error() string { + return "conflict on " + e.Feature + " (" + e.Value + + ") between " + e.Subject1 + " and " + e.Subject2 } +type ErrorMissing struct{ FilePath, Feature string } + func (e ErrorMissing) Error() string { - var b strings.Builder if e.Feature == "" { - b.Grow(len("missing ") + len(e.FilePath)) - b.WriteString("missing ") - b.WriteString(e.FilePath) - return b.String() - } - b.Grow(len("missing ") + len(e.Feature) + len(" in ") + len(e.FilePath)) - b.WriteString("missing ") - b.WriteString(e.Feature) - b.WriteString(" in ") - b.WriteString(e.FilePath) - return b.String() + return "missing " + e.FilePath + } + return "missing " + e.Feature + " in " + e.FilePath } -type ErrorIllegal struct { - FilePath string - Feature string - Message string -} +type ErrorIllegal struct{ FilePath, Feature, Message string } func (e ErrorIllegal) Error() string { - var b strings.Builder - b.Grow(len("illegal ") + - len(e.Feature) + - len(" in ") + - len(e.FilePath) + - len(": ") + - len(e.Message)) - b.WriteString("illegal ") - b.WriteString(e.Feature) - b.WriteString(" in ") - b.WriteString(e.FilePath) - b.WriteString(": ") - b.WriteString(e.Message) - return b.String() + return "illegal " + e.Feature + " in " + e.FilePath + ": " + e.Message } var ( @@ -776,35 +786,40 @@ func contains[T any](arr []T, x T, equal func(a, b T) bool) int { return -1 } -func openFile(path string) (*os.File, error) { - info, err := os.Lstat(path) +func openFile(fsys fs.FS, basePath, path, feature string) (fs.File, error) { + f, err := fsys.Open(path) if err != nil { - return nil, fmt.Errorf("getting information about file: %w", err) - } - if info.Mode()&os.ModeSymlink != 0 { - path, err = filepath.EvalSymlinks(path) - if err != nil { - return nil, fmt.Errorf("reading link: %w", err) + if errors.Is(err, fs.ErrNotExist) { + return nil, &ErrorMissing{ + FilePath: filepath.Join(basePath, path), + Feature: feature, + } } - } - f, err := os.Open(path) - if err != nil { return nil, fmt.Errorf("opening file: %w", err) } - return f, nil } // calculateHash returns a base32 encoded MD5 hash of file. -func calculateHash(file *os.File) (string, error) { +func calculateHash(fsys fs.FS, path string) (string, error) { h := md5.New() - if _, err := io.Copy(h, file); err != nil { - return "", fmt.Errorf("copying file to md5 hash: %w", err) + c, err := fs.ReadFile(fsys, path) + if err != nil { + return "", fmt.Errorf("reading file: %w", err) } - if _, err := file.Seek(0, io.SeekStart); err != nil { - return "", fmt.Errorf("seeking start in file: %w", err) + if _, err := h.Write(c); err != nil { + return "", fmt.Errorf("writing: %w", err) } sum := h.Sum(nil) s := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(sum) return s, nil } + +func sortedKeys[T any](m map[string]T) []string { + l := make([]string, 0, len(m)) + for k := range m { + l = append(l, k) + } + slices.Sort(l) + return l +} diff --git a/config/config_test.go b/config/config_test.go index 058b993..c742e13 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -31,491 +31,568 @@ type TestError struct { var ServerConfigFileName = "config.yml" -func TestReadConfig(t *testing.T) { - validFS(t, func(basePath string, expect *config.Config) { - actual, err := config.New(filepath.Join(basePath, ServerConfigFileName)) - require.NoError(t, err) - require.Equal(t, expect, actual) - }) -} - -func TestReadConfigDefaultMaxReqBodySize(t *testing.T) { - validFS(t, func(basePath string, conf *config.Config) { - createFiles(t, map[string]any{ - ServerConfigFileName: lines( - `proxy:`, - ` host: localhost:443`, - ` tls:`, - ` cert-file: proxy.cert`, - ` key-file: proxy.key`, - ` # max-request-body-size: 1234`, - `api:`, - ` host: localhost:3000`, - ` tls:`, - ` cert-file: api.cert`, - ` key-file: api.key`, - `all-services: all-services`, - `enabled-services: enabled-services`, - ), - }, nil, basePath) - conf.Proxy.MaxReqBodySizeBytes = config.DefaultMaxReqBodySize - }) -} - -func TestReadConfigErrorMissingServerConfig(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - p := filepath.Join(basePath, ServerConfigFileName) - err := os.Remove(p) - require.NoError(t, err) - c, err := config.New(p) - require.Contains(t, err.Error(), "no such file or directory") - require.Nil(t, c) - }) -} - -func TestReadConfigErrorMalformedServerConfig(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - p := filepath.Join(basePath, ServerConfigFileName) - createFiles(t, map[string]any{ - ServerConfigFileName: lines("not a valid config"), - }, nil, basePath) - c, err := config.New(p) - require.Equal(t, &config.ErrorIllegal{ - FilePath: p, - Feature: "syntax", - Message: "yaml: unmarshal errors:\n " + - "line 1: cannot unmarshal !!str `not a v...` " + - "into config.serverConfig", - }, err) - require.Nil(t, c) - }) -} - -func TestReadConfigErrorMissingProxyHostConfig(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - p := filepath.Join(basePath, ServerConfigFileName) - createFiles(t, map[string]any{ - ServerConfigFileName: lines( - `proxy:`, - ` host: `, - ), - }, nil, basePath) - c, err := config.New(p) - require.Equal(t, &config.ErrorMissing{ - FilePath: p, - Feature: "proxy.host", - }, err) - require.Nil(t, c) - }) +func TestRead(t *testing.T) { + basePath, expect := validFS(t) + actual, err := config.Read( + os.DirFS(basePath), + basePath, + ServerConfigFileName, + ) + require.NoError(t, err) + require.Equal(t, expect, actual) } -func TestReadConfigErrorMissingAPIHostConfig(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - p := filepath.Join(basePath, ServerConfigFileName) - createFiles(t, map[string]any{ - ServerConfigFileName: lines( - `proxy:`, - ` host: localhost:8080`, - `api:`, - ` host: `, - ), - }, nil, basePath) - c, err := config.New(p) - require.Equal(t, &config.ErrorMissing{ - FilePath: p, - Feature: "api.host", - }, err) - require.Nil(t, c) - }) +func TestReadDefaultMaxReqBodySize(t *testing.T) { + basePath, conf := validFS(t) + createFiles(t, map[string]any{ + ServerConfigFileName: lines( + `proxy:`, + ` host: localhost:443`, + ` tls:`, + ` cert-file: proxy.cert`, + ` key-file: proxy.key`, + ` # max-request-body-size: 1234`, + `api:`, + ` host: localhost:3000`, + ` tls:`, + ` cert-file: api.cert`, + ` key-file: api.key`, + `all-services: all-services`, + `enabled-services: enabled-services`, + ), + }, nil, basePath) + conf.Proxy.MaxReqBodySizeBytes = config.DefaultMaxReqBodySize } -func TestReadConfigErrorMissingProxyTLSCert(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - p := filepath.Join(basePath, ServerConfigFileName) - createFiles(t, map[string]any{ - ServerConfigFileName: lines( - `proxy:`, - ` host: localhost:8080`, - ` tls:`, - ` key-file: proxy.key`, - `api:`, - ` host: localhost:9090`, - ), - }, nil, basePath) - c, err := config.New(p) - require.Equal(t, &config.ErrorMissing{ - FilePath: p, - Feature: "proxy.tls.cert-file", - }, err) - require.Nil(t, c) - }) +func TestErrMissingServerConfig(t *testing.T) { + basePath, _ := validFS(t) + p := filepath.Join(basePath, ServerConfigFileName) + err := os.Remove(p) + require.NoError(t, err) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorMissing{ + FilePath: p, + Feature: "server config", + }, err) + require.Nil(t, c) +} + +func TestErrMalformedServerConfig(t *testing.T) { + basePath, _ := validFS(t) + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ + ServerConfigFileName: lines("not a valid config"), + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorIllegal{ + FilePath: p, + Feature: "syntax", + Message: "yaml: unmarshal errors:\n " + + "line 1: cannot unmarshal !!str `not a v...` " + + "into config.serverConfig", + }, err) + require.Nil(t, c) +} + +func TestErrMissingProxyHostConfig(t *testing.T) { + basePath, _ := validFS(t) + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ + ServerConfigFileName: lines( + `proxy:`, + ` host: `, + ), + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorMissing{ + FilePath: p, + Feature: "proxy.host", + }, err) + require.Nil(t, c) +} + +func TestErrMissingAPIHostConfig(t *testing.T) { + basePath, _ := validFS(t) + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ + ServerConfigFileName: lines( + `proxy:`, + ` host: localhost:8080`, + `api:`, + ` host: `, + ), + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorMissing{ + FilePath: p, + Feature: "api.host", + }, err) + require.Nil(t, c) +} + +func TestErrMissingProxyTLSCert(t *testing.T) { + basePath, _ := validFS(t) + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ + ServerConfigFileName: lines( + `proxy:`, + ` host: localhost:8080`, + ` tls:`, + ` key-file: proxy.key`, + `api:`, + ` host: localhost:9090`, + ), + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorMissing{ + FilePath: p, + Feature: "proxy.tls.cert-file", + }, err) + require.Nil(t, c) +} + +func TestErrMissingProxyTLSKey(t *testing.T) { + basePath, _ := validFS(t) + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ + ServerConfigFileName: lines( + `proxy:`, + ` host: localhost:8080`, + ` tls:`, + ` cert-file: proxy.cert`, + `api:`, + ` host: localhost:9090`, + ), + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorMissing{ + FilePath: p, + Feature: "proxy.tls.key-file", + }, err) + require.Nil(t, c) +} + +func TestErrMissingAPITLSCert(t *testing.T) { + basePath, _ := validFS(t) + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ + ServerConfigFileName: lines( + `proxy:`, + ` host: localhost:8080`, + `api:`, + ` host: localhost:9090`, + ` tls:`, + ` key-file: api.key`, + ), + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorMissing{ + FilePath: p, + Feature: "api.tls.cert-file", + }, err) + require.Nil(t, c) +} + +func TestErrMissingAPITLSKey(t *testing.T) { + basePath, _ := validFS(t) + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ + ServerConfigFileName: lines( + `proxy:`, + ` host: localhost:8080`, + `api:`, + ` host: localhost:9090`, + ` tls:`, + ` cert-file: api.cert`, + ), + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorMissing{ + FilePath: p, + Feature: "api.tls.key-file", + }, err) + require.Nil(t, c) +} + +func TestErrIllegalProxyMaxReqBodySize(t *testing.T) { + basePath, _ := validFS(t) + p := filepath.Join(basePath, ServerConfigFileName) + createFiles(t, map[string]any{ + ServerConfigFileName: lines( + `proxy:`, + ` host: localhost:8080`, + ` max-request-body-size: 255`, + `api:`, + ` host: localhost:9090`, + ), + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorIllegal{ + FilePath: p, + Feature: "proxy.max-request-body-size", + Message: fmt.Sprintf( + "maximum request body size should not be smaller than %d B", + config.MinReqBodySize, + ), + }, err) + require.Nil(t, c) } -func TestReadConfigErrorMissingProxyTLSKey(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - p := filepath.Join(basePath, ServerConfigFileName) - createFiles(t, map[string]any{ - ServerConfigFileName: lines( - `proxy:`, - ` host: localhost:8080`, - ` tls:`, - ` cert-file: proxy.cert`, - `api:`, - ` host: localhost:9090`, - ), - }, nil, basePath) - c, err := config.New(p) - require.Equal(t, &config.ErrorMissing{ - FilePath: p, - Feature: "proxy.tls.key-file", - }, err) - require.Nil(t, c) - }) +func TestErrNoServices(t *testing.T) { + basePath := minValidFS(t) + createFiles(t, map[string]any{ + "all-services": map[string]any{ + "irrelevant_file.txt": `this file only keeps the directory`, + }, + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, config.ErrNoServices, err) + require.Nil(t, c) } -func TestReadConfigErrorMissingAPITLSCert(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - p := filepath.Join(basePath, ServerConfigFileName) - createFiles(t, map[string]any{ - ServerConfigFileName: lines( - `proxy:`, - ` host: localhost:8080`, - `api:`, - ` host: localhost:9090`, - ` tls:`, - ` key-file: api.key`, +func TestErrNoServicesEnabled(t *testing.T) { + basePath := minValidFS(t) + createFiles(t, map[string]any{ + "all-services": map[string]any{ + "a.yml": lines( + `path: /`, + `forward-url: http://localhost:8080/`, + `all-templates: ../all-templates/a`, + `enabled-templates: ../enabled-templates/a`, ), - }, nil, basePath) - c, err := config.New(p) - require.Equal(t, &config.ErrorMissing{ - FilePath: p, - Feature: "api.tls.cert-file", - }, err) - require.Nil(t, c) - }) -} - -func TestReadConfigErrorMissingAPITLSKey(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - p := filepath.Join(basePath, ServerConfigFileName) - createFiles(t, map[string]any{ - ServerConfigFileName: lines( - `proxy:`, - ` host: localhost:8080`, - `api:`, - ` host: localhost:9090`, - ` tls:`, - ` cert-file: api.cert`, + }, + "all-templates": map[string]any{ + "a": map[string]any{ + "a.gqt": `query { foo }`, + }, + }, + "enabled-templates": map[string]any{ + "a": map[string]any{ + "a.gqt": `query { foo }`, + }, + }, + "enabled-services": map[string]any{ + "placeholder.txt": "no services here", + }, + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, config.ErrNoServicesEnabled, err) + require.Nil(t, c) +} + +func TestErrNoTemplates(t *testing.T) { + basePath := minValidFS(t) + serviceAConf := lines( + `path: /`, + `forward-url: http://localhost:8080/`, + `all-templates: ../all-templates/a`, + `enabled-templates: ../enabled-templates/a`, + ) + createFiles(t, map[string]any{ + "all-services": map[string]any{ + "a.yml": serviceAConf, + }, + "all-templates": map[string]any{ + "placeholder.txt": "no templates here", + }, + "enabled-services": map[string]any{ + "a.yml": serviceAConf, + }, + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, config.ErrNoTemplates, err) + require.Nil(t, c) +} + +func TestErrMalformedMetadata(t *testing.T) { + basePath, _ := validFS(t) + p := filepath.Join("all-templates", "a", "a.gqt") + createFiles(t, map[string]any{ + p: lines( + "---", + "malformed metadata", + "---", + `query { foo }`, + ), + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorIllegal{ + FilePath: filepath.Join(basePath, p), + Feature: "metadata", + Message: "decoding yaml: yaml: " + + "unmarshal errors:\n " + + "line 1: cannot unmarshal !!str `malform...` " + + "into metadata.Metadata", + }, err) + require.Nil(t, c) +} + +func TestErrDuplicateTemplate(t *testing.T) { + basePath, _ := validFS(t) + t1 := filepath.Join("all-templates", "a", "d1.gqt") + t2 := filepath.Join("all-templates", "a", "d2.gqt") + createFiles(t, map[string]any{ + t1: `query { foo }`, + t2: `query { foo }`, + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorDuplicate{ + Original: filepath.Join(basePath, t1), + Duplicate: filepath.Join(basePath, t2), + }, err) + require.Nil(t, c) +} + +func TestErrDuplicateService(t *testing.T) { + basePath := minValidFS(t) + createFiles(t, map[string]any{ + "all-services": map[string]any{ + "a.yml": lines( + `path: /`, + `forward-url: http://localhost:8080/`, + `all-templates: ../all-templates/a`, + `enabled-templates: ../enabled-templates/a`, ), - }, nil, basePath) - c, err := config.New(p) - require.Equal(t, &config.ErrorMissing{ - FilePath: p, - Feature: "api.tls.key-file", - }, err) - require.Nil(t, c) - }) -} - -func TestReadConfigErrorIllegalProxyMaxReqBodySize(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - p := filepath.Join(basePath, ServerConfigFileName) - createFiles(t, map[string]any{ - ServerConfigFileName: lines( - `proxy:`, - ` host: localhost:8080`, - ` max-request-body-size: 255`, - `api:`, - ` host: localhost:9090`, + "b.yml": lines( + `path: /`, + `forward-url: http://localhost:8080/`, + `all-templates: ../all-templates/a`, + `enabled-templates: ../enabled-templates/a`, ), - }, nil, basePath) - c, err := config.New(p) - require.Equal(t, &config.ErrorIllegal{ - FilePath: p, - Feature: "proxy.max-request-body-size", - Message: fmt.Sprintf( - "maximum request body size should not be smaller than %d B", - config.MinReqBodySize, + }, + "all-templates": map[string]any{ + "a": map[string]any{"a.gqt": `query {foo}`}, + }, + "enabled-templates": map[string]any{ + "a": map[string]any{"a.gqt": `query {foo}`}, + }, + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorDuplicate{ + Original: filepath.Join(basePath, "all-services", "a.yml"), + Duplicate: filepath.Join(basePath, "all-services", "b.yml"), + }, err) + require.Nil(t, c) +} + +func TestErrConflictServicePath(t *testing.T) { + basePath := minValidFS(t) + serviceAConf := lines( + `path: /`, + `forward-url: http://localhost:8080/`, + `all-templates: ../all-templates/a`, + `enabled-templates: ../enabled-templates/a`, + ) + serviceBConf := lines( + `path: /`, + `forward-url: http://localhost:8080/`, + `all-templates: ../all-templates/b`, + `enabled-templates: ../enabled-templates/b`, + ) + createFiles(t, map[string]any{ + "all-services": map[string]any{ + "a.yml": serviceAConf, + "b.yml": serviceBConf, + }, + "enabled-services": map[string]any{ + "a.yml": serviceAConf, + "b.yml": serviceBConf, + }, + "all-templates": map[string]any{ + "a": map[string]any{"a.gqt": `query {foo}`}, + "b": map[string]any{"b.gqt": `query {foo}`}, + }, + "enabled-templates": map[string]any{ + "a": map[string]any{"a.gqt": `query {foo}`}, + "b": map[string]any{"b.gqt": `query {foo}`}, + }, + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Error(t, err) + require.Equal(t, &config.ErrorConflict{ + Feature: "path", + Value: "/", + Subject1: filepath.Join(basePath, "all-services", "b.yml"), + Subject2: filepath.Join(basePath, "all-services", "a.yml"), + }, err) + require.Nil(t, c) +} + +func TestErrMissingPath(t *testing.T) { + basePath := minValidFS(t) + createFiles(t, map[string]any{ + "all-services": map[string]any{ + "a.yml": lines( + `forward-url: http://localhost:8080/`, ), - }, err) - require.Nil(t, c) - }) -} - -func TestReadServiceConfigErrorMissingConfig(t *testing.T) { - minValidFS(t, func(basePath string) { - p := filepath.Join(basePath, "all-services", "a.yml") - createFiles(t, map[string]any{ - "all-services": map[string]any{ - "irrelevant_file.txt": `this file only keeps the directory`, - }, - }, nil, basePath) - c, err := config.New(p) - require.Contains(t, err.Error(), "no such file or directory") - require.Nil(t, c) - }) + }, + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorMissing{ + FilePath: filepath.Join( + basePath, "all-services", "a.yml", + ), + Feature: "path", + }, err) + require.Nil(t, c) } -func TestReadConfigErrorMalformedMetadata(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - p := filepath.Join("all-templates", "a", "a.gqt") - createFiles(t, map[string]any{ - p: lines( - "---", - "malformed metadata", - "---", - `query { foo }`, +func TestErrMissingForwardURL(t *testing.T) { + basePath := minValidFS(t) + createFiles(t, map[string]any{ + "all-services": map[string]any{ + "a.yml": lines( + `path: /`, ), - }, nil, basePath) - c, err := config.New(filepath.Join(basePath, ServerConfigFileName)) - require.Equal(t, &config.ErrorIllegal{ - FilePath: filepath.Join(basePath, p), - Feature: "metadata", - Message: "decoding yaml: yaml: " + - "unmarshal errors:\n " + - "line 1: cannot unmarshal !!str `malform...` " + - "into metadata.Metadata", - }, err) - require.Nil(t, c) - }) -} - -func TestReadConfigErrorDuplicateTemplate(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - t1 := filepath.Join("all-templates", "a", "d1.gqt") - t2 := filepath.Join("all-templates", "a", "d2.gqt") - createFiles(t, map[string]any{ - t1: `query { foo }`, - t2: `query { foo }`, - }, nil, basePath) - c, err := config.New(filepath.Join(basePath, ServerConfigFileName)) - require.Equal(t, &config.ErrorDuplicate{ - Original: filepath.Join(basePath, t1), - Duplicate: filepath.Join(basePath, t2), - }, err) - require.Nil(t, c) - }) -} - -func TestReadConfigErrorDuplicateService(t *testing.T) { - minValidFS(t, func(basePath string) { - p := filepath.Join(basePath, ServerConfigFileName) - createFiles(t, map[string]any{ - "all-services": map[string]any{ - "a.yml": lines( - `path: /`, - `forward-url: http://localhost:8080/`, - `all-templates: ../all-templates/a`, - `enabled-templates: ../enabled-templates/a`, - ), - "b.yml": lines( - `path: /`, - `forward-url: http://localhost:8080/`, - `all-templates: ../all-templates/a`, - `enabled-templates: ../enabled-templates/a`, - ), - }, - }, nil, basePath) - c, err := config.New(p) - require.Equal(t, &config.ErrorDuplicate{ - Original: filepath.Join(basePath, "all-services", "a.yml"), - Duplicate: filepath.Join(basePath, "all-services", "b.yml"), - }, err) - require.Nil(t, c) - }) + }, + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorMissing{ + FilePath: filepath.Join( + basePath, "all-services", "a.yml", + ), + Feature: "forward-url", + }, err) + require.Nil(t, c) } -func TestReadConfigErrorMissingPath(t *testing.T) { - minValidFS(t, func(basePath string) { - p := filepath.Join(basePath, ServerConfigFileName) - createFiles(t, map[string]any{ - "all-services": map[string]any{ - "a.yml": lines( - `forward-url: http://localhost:8080/`, - ), - }, - }, nil, basePath) - c, err := config.New(p) - require.Equal(t, &config.ErrorMissing{ - FilePath: filepath.Join( - basePath, "all-services", "a.yml", +func TestErrInvalidPath(t *testing.T) { + basePath := minValidFS(t) + createFiles(t, map[string]any{ + "all-services": map[string]any{ + "a.yml": lines( + `path: invalid_path`, + `forward-url: http://localhost:8080/`, ), - Feature: "path", - }, err) - require.Nil(t, c) - }) -} - -func TestReadConfigErrorMissingForwardURL(t *testing.T) { - minValidFS(t, func(basePath string) { - p := filepath.Join(basePath, ServerConfigFileName) - createFiles(t, map[string]any{ - "all-services": map[string]any{ - "a.yml": lines( - `path: /`, - ), - }, - }, nil, basePath) - c, err := config.New(p) - require.Equal(t, &config.ErrorMissing{ - FilePath: filepath.Join( - basePath, "all-services", "a.yml", + }, + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorIllegal{ + FilePath: filepath.Join(basePath, "all-services", "a.yml"), + Feature: "path", + Message: `path is not starting with /`, + }, err) + require.Nil(t, c) +} + +func TestErrInvalidForwardURLInvalidScheme(t *testing.T) { + basePath := minValidFS(t) + createFiles(t, map[string]any{ + "all-services": map[string]any{ + "a.yml": lines( + `path: /`, + `forward-url: localhost:8080`, ), - Feature: "forward-url", - }, err) - require.Nil(t, c) - }) -} - -func TestReadConfigErrorInvalidPath(t *testing.T) { - minValidFS(t, func(basePath string) { - p := filepath.Join(basePath, ServerConfigFileName) - createFiles(t, map[string]any{ - "all-services": map[string]any{ - "a.yml": lines( - `path: invalid_path`, - `forward-url: http://localhost:8080/`, - ), - }, - }, nil, basePath) - c, err := config.New(p) - require.Equal(t, &config.ErrorIllegal{ - FilePath: filepath.Join(basePath, "all-services", "a.yml"), - Feature: "path", - Message: `path is not starting with /`, - }, err) - require.Nil(t, c) - }) -} - -func TestReadConfigErrorInvalidForwardURLInvalidScheme(t *testing.T) { - minValidFS(t, func(basePath string) { - p := filepath.Join(basePath, ServerConfigFileName) - createFiles(t, map[string]any{ - "all-services": map[string]any{ - "a.yml": lines( - `path: /`, - `forward-url: localhost:8080`, - ), - }, - }, nil, basePath) - c, err := config.New(p) - require.Equal(t, &config.ErrorIllegal{ - FilePath: filepath.Join(basePath, "all-services", "a.yml"), - Feature: "forward-url", - Message: `protocol is not supported or undefined`, - }, err) - require.Nil(t, c) - }) + }, + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorIllegal{ + FilePath: filepath.Join(basePath, "all-services", "a.yml"), + Feature: "forward-url", + Message: `protocol is not supported or undefined`, + }, err) + require.Nil(t, c) +} + +func TestErrInvalidSchema(t *testing.T) { + basePath, _ := validFS(t) + p := filepath.Join("all-services", "schema_a.graphqls") + createFiles(t, map[string]any{p: `type Query{ invalid }`}, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorIllegal{ + FilePath: filepath.Join(basePath, p), + Feature: "schema", + Message: fmt.Sprintf( + `invalid schema: %s:1: Expected :, found }`, + filepath.Join(basePath, p), + ), + }, err) + require.Nil(t, c) } -func TestReadConfigErrorInvalidSchema(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - p := filepath.Join("all-services", "schema_a.graphqls") - createFiles(t, map[string]any{p: `type Query{ invalid }`}, nil, basePath) - c, err := config.New(filepath.Join(basePath, ServerConfigFileName)) - require.Equal(t, &config.ErrorIllegal{ - FilePath: filepath.Join(basePath, p), - Feature: "schema", - Message: fmt.Sprintf( - `invalid schema: %s:1: Expected :, found }`, - filepath.Join(basePath, p), +func TestErrMissingSchema(t *testing.T) { + basePath, _ := validFS(t) + p := filepath.Join(basePath, "all-services", "schema_a.graphqls") + err := os.Remove(p) + require.NoError(t, err) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorMissing{ + FilePath: p, + Feature: "schema", + }, err) + require.Nil(t, c) +} + +func TestErrInvalidTemplate(t *testing.T) { + basePath, _ := validFS(t) + p := filepath.Join("all-templates", "a", "invalid_template.gqt") + createFiles(t, map[string]any{p: `invalid { template }`}, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorIllegal{ + FilePath: filepath.Join(basePath, p), + Feature: "template", + Message: `1:1: unexpected token, expected ` + + `query, mutation, or subscription operation definition`, + }, err) + require.Nil(t, c) +} + +func TestErrInvalidTemplateID(t *testing.T) { + basePath, _ := validFS(t) + p := filepath.Join("all-templates", "a", "invalid_template#.gqt") + createFiles(t, map[string]any{p: `invalid { template }`}, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorIllegal{ + FilePath: filepath.Join(basePath, p), + Feature: "id", + Message: `contains illegal character at index 16`, + }, err) + require.Nil(t, c) +} + +func TestErrInvalidServiceID(t *testing.T) { + basePath, _ := validFS(t) + createFiles(t, map[string]any{ + "all-services": map[string]any{ + "a#.yml": lines( + `path: /`, + `forward-url: http://localhost:8080/`, + `all-templates: ../all-templates/a`, + `enabled-templates: ../enabled-templates/a`, ), - }, err) - require.Nil(t, c) - }) -} - -func TestReadConfigErrorMissingSchema(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - p := filepath.Join(basePath, "all-services", "schema_a.graphqls") - err := os.Remove(p) - require.NoError(t, err) - c, err := config.New(filepath.Join(basePath, ServerConfigFileName)) - require.Equal(t, &config.ErrorMissing{ - FilePath: p, - Feature: "schema", - }, err) - require.Nil(t, c) - }) -} - -func TestReadConfigErrorInvalidTemplate(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - p := filepath.Join("all-templates", "a", "invalid_template.gqt") - createFiles(t, map[string]any{p: `invalid { template }`}, nil, basePath) - c, err := config.New(filepath.Join(basePath, ServerConfigFileName)) - require.Equal(t, &config.ErrorIllegal{ - FilePath: filepath.Join(basePath, p), - Feature: "template", - Message: `1:1: unexpected token, expected ` + - `query, mutation, or subscription operation definition`, - }, err) - require.Nil(t, c) - }) -} - -func TestReadConfigErrorInvalidTemplateID(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - p := filepath.Join("all-templates", "a", "invalid_template#.gqt") - createFiles(t, map[string]any{p: `invalid { template }`}, nil, basePath) - c, err := config.New(filepath.Join(basePath, ServerConfigFileName)) - require.Equal(t, &config.ErrorIllegal{ - FilePath: filepath.Join(basePath, p), - Feature: "id", - Message: `contains illegal character at index 16`, - }, err) - require.Nil(t, c) - }) + }, + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorIllegal{ + FilePath: filepath.Join( + basePath, + "all-services", + "a#.yml", + ), + Feature: "id", + Message: `contains illegal character at index 1`, + }, err) + require.Nil(t, c) } -func TestReadConfigErrorInvalidServiceID(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - p := filepath.Join(basePath, ServerConfigFileName) - createFiles(t, map[string]any{ - "all-services": map[string]any{ - "a#.yml": lines( - `path: /`, - `forward-url: http://localhost:8080/`, - `all-templates: ../all-templates/a`, - `enabled-templates: ../enabled-templates/a`, - ), - }, - }, nil, basePath) - c, err := config.New(p) - require.Equal(t, &config.ErrorIllegal{ - FilePath: filepath.Join( - basePath, - "all-services", - "a#.yml", +func TestErrMalformedConfig(t *testing.T) { + basePath, _ := validFS(t) + createFiles(t, map[string]any{ + "all-services": map[string]any{ + "a.yml": lines( + `malformed yaml`, ), - Feature: "id", - Message: `contains illegal character at index 1`, - }, err) - require.Nil(t, c) - }) -} - -func TestReadConfigErrorMalformedConfig(t *testing.T) { - validFS(t, func(basePath string, _ *config.Config) { - p := filepath.Join(basePath, ServerConfigFileName) - createFiles(t, map[string]any{ - "all-services": map[string]any{ - "a.yml": lines( - `malformed yaml`, - ), - }, - }, nil, basePath) - c, err := config.New(p) - require.Equal(t, &config.ErrorIllegal{ - FilePath: filepath.Join(basePath, "all-services", "a.yml"), - Feature: "syntax", - Message: "yaml: unmarshal errors:\n " + - "line 1: cannot unmarshal !!str `malform...` " + - "into config.serviceConfig", - }, err) - require.Nil(t, c) - }) + }, + }, nil, basePath) + c, err := config.Read(os.DirFS(basePath), basePath, ServerConfigFileName) + require.Equal(t, &config.ErrorIllegal{ + FilePath: filepath.Join(basePath, "all-services", "a.yml"), + Feature: "syntax", + Message: "yaml: unmarshal errors:\n " + + "line 1: cannot unmarshal !!str `malform...` " + + "into config.serviceConfig", + }, err) + require.Nil(t, c) } func TestErrorString(t *testing.T) { @@ -563,13 +640,8 @@ func TestErrorString(t *testing.T) { } } -func minValidFS(t *testing.T, fn func(basePath string)) { - base, err := os.MkdirTemp("", "ggproxy-") - if err != nil { - panic(err) - } - defer os.RemoveAll(base) - +func minValidFS(t *testing.T) (base string) { + base = t.TempDir() dirs := map[string]any{ "all-services": nil, "enabled-services": nil, @@ -592,12 +664,12 @@ func minValidFS(t *testing.T, fn func(basePath string)) { ), "irrelevant-file.txt": lines( `this file is irrelevant and exists only for the purposes`, - `of testing function ReadConfig.`, + `of testing function Read.`, ), "irrelevant-dir": map[string]any{ "irrelevant_file.txt": lines( `this file is irrelevant and exists only for the purposes`, - `of testing function ReadConfig.`, + `of testing function Read.`, ), }, } @@ -607,17 +679,12 @@ func minValidFS(t *testing.T, fn func(basePath string)) { createDirs(t, dirs, base) createFiles(t, files, hashes, base) - fn(base) + return base } // validFS calls fn providing a valid setup filesystem. -func validFS(t *testing.T, fn func(basePath string, conf *config.Config)) { - base, err := os.MkdirTemp("", "ggproxy-") - if err != nil { - panic(err) - } - defer os.RemoveAll(base) - +func validFS(t *testing.T) (base string, conf *config.Config) { + base = t.TempDir() type M = map[string]any dirs := M{ @@ -697,12 +764,12 @@ func validFS(t *testing.T, fn func(basePath string, conf *config.Config)) { }, "irrelevant-file.txt": lines( `this file is irrelevant and exists only for the purposes`, - `of testing function ReadConfig.`, + `of testing function Read.`, ), "irrelevant-dir": M{ "irrelevant_file.txt": lines( `this file is irrelevant and exists only for the purposes`, - `of testing function ReadConfig.`, + `of testing function Read.`, ), }, } @@ -832,7 +899,7 @@ func validFS(t *testing.T, fn func(basePath string, conf *config.Config)) { } } - conf := &config.Config{ + return base, &config.Config{ Proxy: config.ProxyServerConfig{ Host: "localhost:443", TLS: config.TLS{ @@ -854,8 +921,6 @@ func validFS(t *testing.T, fn func(basePath string, conf *config.Config)) { services[hashes[filepath.Join(base, "all-services", "b.yml")]], }, } - - fn(base, conf) } func createDirs(t *testing.T, dirs map[string]any, basePath string) { diff --git a/engine/playmon/bench_test.go b/engine/playmon/bench_test.go new file mode 100644 index 0000000..8ec1ea8 --- /dev/null +++ b/engine/playmon/bench_test.go @@ -0,0 +1,59 @@ +package playmon_test + +import ( + "testing" + + "github.com/graph-guard/ggproxy/engine/playmon" + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/testsetup" + "github.com/graph-guard/gqlscan" +) + +var GS string + +func BenchmarkMatchStarwars(b *testing.B) { + s := testsetup.Starwars() + e := playmon.New(s.Config.ServicesEnabled[0]) + + p := gqlparse.NewParser() + var varvals [][]gqlparse.Token + var opr gqlscan.Token + var selset []gqlparse.Token + src := s.Tests[0].Client.Input.BodyJSON["query"].(string) + b.Logf(src) + p.Parse( + []byte(src), nil, nil, + func( + varValues [][]gqlparse.Token, + operation, selectionSet []gqlparse.Token, + ) { + varvals = make([][]gqlparse.Token, len(varValues)) + for i, vv := range varValues { + varvals[i] = copyTokens(vv) + } + opr = operation[0].ID + selset = copyTokens(selectionSet) + }, + func(err error) { + b.Fatalf("parsing request: %v", err) + }, + ) + + b.ResetTimer() + for n := 0; n < b.N; n++ { + GS = e.Match(varvals, opr, selset) + } +} + +func copyTokens(original []gqlparse.Token) []gqlparse.Token { + cp := make([]gqlparse.Token, len(original)) + for i, t := range original { + v := make([]byte, len(t.Value)) + copy(v, t.Value) + cp[i] = gqlparse.Token{ + ID: t.ID, + Value: v, + } + } + return cp +} diff --git a/engine/playmon/internal/constrcheck/constrcheck.go b/engine/playmon/internal/constrcheck/constrcheck.go index e5f0711..58d9b48 100644 --- a/engine/playmon/internal/constrcheck/constrcheck.go +++ b/engine/playmon/internal/constrcheck/constrcheck.go @@ -21,32 +21,23 @@ type Enum string // Checker is a constraint checker instance. // Before calling Check, make sure you initialize the Checker using Init. type Checker struct { - operation *gqt.Operation - schema *gqlast.Schema - checkers map[string]check - pathByVar map[*gqt.Variable]string + operation *gqt.Operation + schema *gqlast.Schema + checkers map[uint64]check + pathByVarDecl map[*gqt.VariableDeclaration]uint64 - // variableValues keys will be set at initialization - // but values must be set before every call to Check. - variableValues map[string][]gqlparse.Token + // varValues is set in every call to Check. + varValues map[uint64][]gqlparse.Token - // inputValue is set before every call to Check. + // inputValue is set in every call to Check. inputValue []gqlparse.Token - // checkedValue is set before every call to Check. + // checkedValue is set in every call to Check. checkedValue []gqlparse.Token // stack is reset in every call to Check. stack []union.Union } -func (c *Checker) SetVariableValue(path []byte, value []gqlparse.Token) { - c.variableValues[string(path)] = value -} - -func (c *Checker) Init(inputValue []gqlparse.Token) { - c.inputValue = inputValue -} - func (c *Checker) stackTopType() union.Type { return c.stack[len(c.stack)-1].Type() } @@ -128,14 +119,17 @@ func (c *Checker) pushStackBool(v bool) { // Check returns true if the value for the given path is accepted, // otherwise returns false. -func (c *Checker) Check(path string) bool { +func (c *Checker) Check( + varVals map[uint64][]gqlparse.Token, + path uint64, + value []gqlparse.Token, +) bool { c.stack = c.stack[:0] cf := c.checkers[path] if cf == nil { return false } - v := c.variableValues[path] - c.inputValue, c.checkedValue = v, v + c.varValues, c.inputValue, c.checkedValue = varVals, value, value return cf(c) } @@ -155,9 +149,9 @@ func (c *Checker) resolveExpr(e gqt.Expression) union.Type { c.pushStackArray() return union.TypeArray case *gqt.Variable: - p, ok := c.pathByVar[e] + p, ok := c.pathByVarDecl[e.Declaration] if !ok { - if s := c.variableValues[p]; s != nil { + if s := c.varValues[p]; s != nil { c.stack = append(c.stack, union.Tokens(s)) return union.TypeTokens } @@ -424,19 +418,18 @@ func (c *Checker) resolveExpr(e gqt.Expression) union.Type { // New creates a constraint checker instance for each path of o. func New(o *gqt.Operation, s *gqlast.Schema) *Checker { c := &Checker{ - operation: o, - schema: s, - checkers: make(map[string]check), - stack: make([]union.Union, 1024), - variableValues: make(map[string][]gqlparse.Token), - pathByVar: make(map[*gqt.Variable]string), + operation: o, + schema: s, + checkers: make(map[uint64]check), + stack: make([]union.Union, 1024), + pathByVarDecl: make(map[*gqt.VariableDeclaration]uint64), } - pathscan.InAST( + if errs := pathscan.InAST( o, - func(path []byte, e gqt.Expression) (stop bool) { + func(path uint64, e gqt.Expression) (stop bool) { // On structural return false - }, func(path []byte, e gqt.Expression) (stop bool) { + }, func(path uint64, e gqt.Expression) (stop bool) { // On argument a := e.(*gqt.Argument) var expect *gqlast.Type @@ -444,18 +437,18 @@ func New(o *gqt.Operation, s *gqlast.Schema) *Checker { expect = a.Def.Type } if fn := makeCheck(a.Constraint, expect, s); fn != nil { - c.checkers[string(path)] = fn + c.checkers[path] = fn } return false }, - func(path []byte, e gqt.Expression) (stop bool) { + func(path uint64, e *gqt.VariableDeclaration) (stop bool) { // On variable - a := e.(*gqt.Variable) - c.pathByVar[a] = string(path) - c.variableValues[string(path)] = nil // Set key + c.pathByVarDecl[e] = path return false }, - ) + ); errs != nil { + panic(errs) + } return c } @@ -986,9 +979,9 @@ func Designation(c *Checker, e gqt.Expression) string { case *gqt.Enum: return e.Value case *gqt.Variable: - p, ok := c.pathByVar[e] + p, ok := c.pathByVarDecl[e.Declaration] if !ok { - return p + return strconv.FormatUint(p, 10) } case *gqt.ExprLogicalOr: var b strings.Builder diff --git a/engine/playmon/internal/pathmatch/bench_test.go b/engine/playmon/internal/pathmatch/bench_test.go index 7783621..2dbf66b 100644 --- a/engine/playmon/internal/pathmatch/bench_test.go +++ b/engine/playmon/internal/pathmatch/bench_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/graph-guard/ggproxy/config" + "github.com/graph-guard/ggproxy/engine/playmon/internal/pathscan" ) var GI int @@ -12,9 +13,9 @@ func BenchmarkPathmatch(b *testing.B) { for _, bb := range tests { b.Run(bb.name, func(b *testing.B) { m := prepareTestSetup(b, bb.conf) - paths := make([][]byte, len(bb.paths)) + paths := make([]uint64, len(bb.paths)) for i := range bb.paths { - paths[i] = []byte(bb.paths[i]) + paths[i] = pathscan.Hash(bb.paths[i]) } b.ResetTimer() for n := 0; n < b.N; n++ { diff --git a/engine/playmon/internal/pathmatch/pathmatch.go b/engine/playmon/internal/pathmatch/pathmatch.go index ca75358..7968e15 100644 --- a/engine/playmon/internal/pathmatch/pathmatch.go +++ b/engine/playmon/internal/pathmatch/pathmatch.go @@ -9,8 +9,8 @@ import ( type Matcher struct { conf *config.Service - templatePaths [][]string - paths map[string]*bitmask.Set + templatePaths [][]uint64 + paths map[uint64]*bitmask.Set lengths []int // Operational data, reset for every match call @@ -20,7 +20,7 @@ type Matcher struct { // Match calls onMatch for every template matching paths func (m *Matcher) Match( - paths [][]byte, + paths []uint64, onMatch func(*config.Template) (stop bool), ) { m.matchMask.Reset() @@ -30,7 +30,7 @@ func (m *Matcher) Match( } for i := range paths { - b, ok := m.paths[string(paths[i])] + b, ok := m.paths[paths[i]] if !ok { return // Unknown path, can't match any template } @@ -52,8 +52,8 @@ func New(conf *config.Service) *Matcher { m := &Matcher{ conf: conf, // There will at least be 1 path per template - paths: make(map[string]*bitmask.Set, len(conf.TemplatesEnabled)), - templatePaths: make([][]string, len(conf.TemplatesEnabled)), + paths: make(map[uint64]*bitmask.Set, len(conf.TemplatesEnabled)), + templatePaths: make([][]uint64, len(conf.TemplatesEnabled)), lengths: make([]int, len(conf.TemplatesEnabled)), matchMask: bitmask.New(), @@ -62,22 +62,24 @@ func New(conf *config.Service) *Matcher { } for i := range conf.TemplatesEnabled { { // Associate paths with templates by index - pathscan.InAST( + if errs := pathscan.InAST( conf.TemplatesEnabled[i].GQTTemplate, - func(path []byte, e gqt.Expression) (stop bool) { + func(pathHash uint64, e gqt.Expression) (stop bool) { // On structural - m.templatePaths[i] = append(m.templatePaths[i], string(path)) + m.templatePaths[i] = append(m.templatePaths[i], pathHash) return false }, - func(path []byte, e gqt.Expression) (stop bool) { + func(pathHash uint64, e gqt.Expression) (stop bool) { // On argument return false }, - func(path []byte, e gqt.Expression) (stop bool) { + func(pathHash uint64, e *gqt.VariableDeclaration) (stop bool) { // On variable return false }, - ) + ); errs != nil { + panic(errs) + } } // Initialize path bitmasks diff --git a/engine/playmon/internal/pathmatch/pathmatch_test.go b/engine/playmon/internal/pathmatch/pathmatch_test.go index 59198fe..d393c0e 100644 --- a/engine/playmon/internal/pathmatch/pathmatch_test.go +++ b/engine/playmon/internal/pathmatch/pathmatch_test.go @@ -1,10 +1,12 @@ package pathmatch_test import ( + "embed" "testing" "github.com/graph-guard/ggproxy/config" "github.com/graph-guard/ggproxy/engine/playmon/internal/pathmatch" + "github.com/graph-guard/ggproxy/engine/playmon/internal/pathscan" "github.com/graph-guard/gqt/v4" "github.com/stretchr/testify/require" ) @@ -14,9 +16,9 @@ func TestMatch(t *testing.T) { t.Run(tt.name, func(t *testing.T) { m := prepareTestSetup(t, tt.conf) var actualMatches []string - paths := make([][]byte, len(tt.paths)) + paths := make([]uint64, len(tt.paths)) for i := range tt.paths { - paths[i] = []byte(tt.paths[i]) + paths[i] = pathscan.Hash(tt.paths[i]) } m.Match(paths, func(tm *config.Template) (stop bool) { actualMatches = append(actualMatches, tm.ID) @@ -27,6 +29,9 @@ func TestMatch(t *testing.T) { } } +//go:embed test_setups +var embeddedTestSetups embed.FS + var tests = []struct { name string conf *config.Service @@ -119,28 +124,30 @@ var tests = []struct { paths: []string{"Q.bazz"}, expectIDs: []string{"A", "B", "C"}, }, - // { - // name: "2_of_20", - // conf: &config.Service{ - // TemplatesEnabled: []*config.Template{ - // {ID: "Q1", Source: []byte(`query { foo bazz }`)}, - // {ID: "Q2", Source: []byte(`query { bar bazz }`)}, - // {ID: "Q3", Source: []byte(`query { bazz { fraz } maz }`)}, - // {ID: "Q4", Source: []byte(`query { bazz { fraz } }`)}, - // }, - // }, - // paths: []string{"Q.bazz"}, - // expectIDs: []string{"A", "B", "C"}, - // }, { name: "starwars", conf: func() *config.Service { - s, err := config.New("./test_setups/starwars/config.yml") + s, err := config.Read( + embeddedTestSetups, + "test_setups/starwars/", + "test_setups/starwars/config.yml", + ) if err != nil { panic(err) } return s.ServicesEnabled[0] }(), + /* query { hero(episode: EMPIRE || JEDI) { + id, name, appearsIn + friends { + id, name, appearsIn + friends { id, name, appearsIn } + } + friendsConnection(first: >= 0, after: len > 0) { + totalCount + friends { id, name, appearsIn } + } + } } */ paths: []string{ "Q.hero|episode,.id", "Q.hero|episode,.name", @@ -157,31 +164,6 @@ var tests = []struct { "Q.hero|episode,.friendsConnection|after,first,.friends.appearsIn", }, expectIDs: []string{"c"}, - /* query { - hero(episode: EMPIRE || JEDI) { - id - name - appearsIn - friends { - id - name - appearsIn - friends { - id - name - appearsIn - } - } - friendsConnection(first: >= 0, after: len > 0) { - totalCount - friends { - id - name - appearsIn - } - } - } - } */ }, } diff --git a/engine/playmon/internal/pathscan/pathscan.go b/engine/playmon/internal/pathscan/pathscan.go index ee7c3cd..dd48a19 100644 --- a/engine/playmon/internal/pathscan/pathscan.go +++ b/engine/playmon/internal/pathscan/pathscan.go @@ -42,6 +42,7 @@ import ( "github.com/graph-guard/ggproxy/engine/playmon/internal/travgqt" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/ggproxy/utilities/stack" + "github.com/graph-guard/ggproxy/utilities/xxhash" gqlscan "github.com/graph-guard/gqlscan" gqt "github.com/graph-guard/gqt/v4" @@ -91,9 +92,9 @@ const ( func (s *PathScanner) InTokens( operation gqlscan.Token, tokens []gqlparse.Token, - gqtVarPaths map[string]int, - onStructural func(path []byte) (stop bool), - onArg, onGQTVarVal func(path []byte, i int) (stop bool), + gqtVarPaths map[uint64][]gqlparse.Token, + onStructural func(pathHash uint64) (stop bool), + onArg, onGQTVarVal func(pathHash uint64, i int) (stop bool), ) { s.valPathBuf = s.valPathBuf[:0] s.valStack.Reset() @@ -151,9 +152,10 @@ func (s *PathScanner) InTokens( } s.argBuf = append(s.argBuf, tokens[i].Value) s.valWithDiv(divArgList, tokens[i].Value) - onArg(s.valPathBuf, i) - if _, ok := gqtVarPaths[string(s.valPathBuf)]; ok { - if onGQTVarVal(s.valPathBuf, i+1) { + h := Hash(s.valPathBuf) + onArg(h, i) + if _, ok := gqtVarPaths[h]; ok { + if onGQTVarVal(h, i+1) { return } } @@ -172,8 +174,9 @@ func (s *PathScanner) InTokens( s.valPop() } s.valWithDiv(divObjField, tokens[i].Value) - if _, ok := gqtVarPaths[string(s.valPathBuf)]; ok { - if onGQTVarVal(s.valPathBuf, i+1) { + h := Hash(s.valPathBuf) + if _, ok := gqtVarPaths[h]; ok { + if onGQTVarVal(h, i+1) { return } } @@ -197,7 +200,7 @@ func (s *PathScanner) InTokens( // Check for leaf if tokens[i+1].ID != gqlscan.TokenSet { - if onStructural(s.structuralPathBuf) { + if onStructural(Hash(s.structuralPathBuf)) { return } s.structuralPop() @@ -208,7 +211,7 @@ func (s *PathScanner) InTokens( s.structuralStack.Push(l) default: s.structuralStack.Push(l) - if onStructural(s.structuralPathBuf) { + if onStructural(Hash(s.structuralPathBuf)) { return } s.structuralPop() @@ -258,21 +261,37 @@ func (s *PathScanner) structuralPop() { // argument. func InAST( o *gqt.Operation, - onStructural func(path []byte, e gqt.Expression) (stop bool), - onArg func(path []byte, e gqt.Expression) (stop bool), - onVariable func(path []byte, e gqt.Expression) (stop bool), -) { + onStructural func(pathHash uint64, e gqt.Expression) (stop bool), + onArg func(pathHash uint64, e gqt.Expression) (stop bool), + onVariable func(pathHash uint64, e *gqt.VariableDeclaration) (stop bool), +) (errs []error) { + hashes := map[uint64]string{} + hash := func(path []byte) uint64 { + h := Hash(path) + s := string(path) + if x, ok := hashes[h]; ok { + if x != s { + errs = append(errs, fmt.Errorf( + "hash collision between %q and %q (%d)", + x, s, h, + )) + } + } + hashes[h] = s + return h + } + travgqt.Traverse(o, func(e gqt.Expression) (stop, skipChildren bool) { switch e := e.(type) { case *gqt.SelectionField: for _, a := range e.Arguments { p := makePathVar(a) - if onArg(p, a) { + if onArg(hash(p), a) { return true, true } if a.AssociatedVariable != nil { p := makePathVar(a) - if onVariable(p, e) { + if onVariable(hash(p), a.AssociatedVariable) { return true, true } } @@ -281,19 +300,21 @@ func InAST( break } p := makePathStructural(e) - if onStructural(p, e) { + hashes[hash(p)] = string(p) + if onStructural(hash(p), e) { return true, true } case *gqt.ObjectField: if e.AssociatedVariable != nil { p := makePathVar(e) - if onVariable(p, e) { + if onVariable(hash(p), e.AssociatedVariable) { return true, true } } } return false, false // Continue traversal }) + return errs } // makePathStructural generates a structural path for the given expression. @@ -374,3 +395,9 @@ func makePathVar(e gqt.Expression) []byte { } return s.Bytes() } + +func Hash[B []byte | string](b B) uint64 { + h := xxhash.New(0) + xxhash.Write(&h, b) + return h.Sum64() +} diff --git a/engine/playmon/internal/pathscan/pathscan_test.go b/engine/playmon/internal/pathscan/pathscan_test.go index 111b6ce..fdac9e9 100644 --- a/engine/playmon/internal/pathscan/pathscan_test.go +++ b/engine/playmon/internal/pathscan/pathscan_test.go @@ -1,6 +1,7 @@ package pathscan_test import ( + "fmt" "sort" "testing" @@ -9,6 +10,8 @@ import ( "github.com/graph-guard/gqlscan" "github.com/graph-guard/gqt/v4" "github.com/stretchr/testify/require" + "golang.org/x/exp/constraints" + "golang.org/x/exp/slices" ) var testsInTokens = []struct { @@ -255,12 +258,12 @@ func TestInTokens(t *testing.T) { t.Run(tt.Name, func(t *testing.T) { ps := pathscan.New(0, 0) p := gqlparse.NewParser() - actualStructuralPaths := []string{} - actualArgPaths := make(map[string]int) - actualVarPaths := make(map[string]int) - variablePaths := make(map[string]int, len(tt.VariablePaths)) + actualStructuralPaths := []uint64{} + actualArgPaths := make(map[uint64]int) + actualVarPaths := make(map[uint64]int) + variablePaths := make(map[uint64][]gqlparse.Token, len(tt.VariablePaths)) for _, p := range tt.VariablePaths { - variablePaths[p] = 0 + variablePaths[pathscan.Hash(p)] = nil } p.Parse( []byte(tt.GraphQLOperation), nil, nil, @@ -272,18 +275,18 @@ func TestInTokens(t *testing.T) { operation[0].ID, selectionSet, variablePaths, - func(path []byte) (stop bool) { // On structural + func(pathHash uint64) (stop bool) { // On structural actualStructuralPaths = append( - actualStructuralPaths, string(path), + actualStructuralPaths, pathHash, ) return false }, - func(path []byte, i int) (stop bool) { // On argument - actualArgPaths[string(path)] = i + func(pathHash uint64, i int) (stop bool) { // On argument + actualArgPaths[pathHash] = i return false }, - func(path []byte, i int) (stop bool) { // On GQT variable - actualVarPaths[string(path)] = i + func(pathHash uint64, i int) (stop bool) { // On GQT variable + actualVarPaths[pathHash] = i return false }, ) @@ -292,18 +295,25 @@ func TestInTokens(t *testing.T) { t.Fatalf("unexpected GraphQL parsing error: %v", err) }, ) - require.Equal( - t, tt.ExpectedStructural, actualStructuralPaths, - "structural paths", + compareStringsByHash( + t, tt.ExpectedStructural, actualStructuralPaths, "structural path", ) - if tt.ExpectedVarVal == nil { - tt.ExpectedVarVal = map[string]int{} + + argPathHash := map[uint64]int{} + if tt.ExpectedArg != nil { + for k, v := range tt.ExpectedArg { + argPathHash[pathscan.Hash(k)] = v + } } - if tt.ExpectedArg == nil { - tt.ExpectedArg = map[string]int{} + require.Equal(t, argPathHash, actualArgPaths, "argument paths") + + varPathHash := map[uint64]int{} + if tt.ExpectedVarVal != nil { + for k, v := range tt.ExpectedVarVal { + varPathHash[pathscan.Hash(k)] = v + } } - require.Equal(t, tt.ExpectedArg, actualArgPaths, "argument paths") - require.Equal(t, tt.ExpectedVarVal, actualVarPaths, "variable paths") + require.Equal(t, varPathHash, actualVarPaths, "variable paths") }) } } @@ -318,18 +328,16 @@ func TestInTokensPanic(t *testing.T) { {ID: gqlscan.TokenField, Value: []byte("foo")}, {ID: gqlscan.TokenSetEnd}, }, - map[string]int{ - // No variable paths - }, - func(path []byte) (stop bool) { // On structural + map[uint64][]gqlparse.Token{ /* No variable paths */ }, + func(pathHash uint64) (stop bool) { // On structural t.Fatal("this function isn't expected to be called") return false }, - func(path []byte, i int) (stop bool) { // On argument + func(pathHash uint64, i int) (stop bool) { // On argument t.Fatal("this function isn't expected to be called") return false }, - func(path []byte, i int) (stop bool) { // On variable + func(pathHash uint64, i int) (stop bool) { // On variable t.Fatal("this function isn't expected to be called") return false }, @@ -542,39 +550,83 @@ var testsInAST = []struct { }, } -func TestInAst(t *testing.T) { +func TestInAST(t *testing.T) { for _, tt := range testsInAST { t.Run(tt.Name, func(t *testing.T) { + x := tt.Name + fmt.Println(x) o, _, errs := gqt.Parse([]byte(tt.GQTTemplateSrc)) require.Nil(t, errs) - var actualPaths, actualArgPaths, actualVarPaths []string - pathscan.InAST(o, func(path []byte, e gqt.Expression) (stop bool) { - // On structural - actualPaths = append(actualPaths, string(path)) - require.NotNil(t, e) - return false - }, func(path []byte, e gqt.Expression) (stop bool) { - // On argument - actualArgPaths = append(actualArgPaths, string(path)) - return false - }, func(path []byte, e gqt.Expression) (stop bool) { - // On variable - actualVarPaths = append(actualVarPaths, string(path)) - return false - }) + var actualPaths, actualArgPaths []uint64 + actualVarPaths := map[uint64]string{} // Hash -> variable name + errsP := pathscan.InAST( + o, + func(pathHash uint64, e gqt.Expression) (stop bool) { + // On structural + actualPaths = append(actualPaths, pathHash) + require.NotNil(t, e) + return false + }, func(pathHash uint64, e gqt.Expression) (stop bool) { + // On argument + actualArgPaths = append(actualArgPaths, pathHash) + return false + }, func(pathHash uint64, e *gqt.VariableDeclaration) (stop bool) { + // On variable + actualVarPaths[pathHash] = e.Name + return false + }, + ) + require.Nil(t, errsP) - sort.Strings(tt.ExpectStructural) - sort.Strings(actualPaths) - require.Equal(t, tt.ExpectStructural, actualPaths, "structural paths") + compareStringsByHash( + t, tt.ExpectStructural, actualPaths, "structural paths", + ) + compareStringsByHash( + t, tt.ExpectedArgPaths, actualArgPaths, "argument paths", + ) + compareStringsByHash( + t, tt.ExpectedVarPaths, mapKeys(actualVarPaths), "variable paths", + ) + }) + } +} + +func compareStringsByHash( + t *testing.T, + expected []string, + actual []uint64, + msg string, +) { + e := hashStrings(expected) + sort.Slice(e, func(i, j int) bool { + return e[i] < e[j] + }) + sort.Slice(actual, func(i, j int) bool { + return actual[i] < actual[j] + }) + require.Equal(t, e, actual, msg) +} - sort.Strings(tt.ExpectedArgPaths) - sort.Strings(actualArgPaths) - require.Equal(t, tt.ExpectedArgPaths, actualArgPaths, "argument paths") +func hashStrings(s []string) []uint64 { + if len(s) < 1 { + return nil + } + h := make([]uint64, len(s)) + for i, s := range s { + h[i] = pathscan.Hash(s) + } + return h +} - sort.Strings(tt.ExpectedVarPaths) - sort.Strings(actualVarPaths) - require.Equal(t, tt.ExpectedVarPaths, actualVarPaths, "variable paths") - }) +func mapKeys[K constraints.Ordered, T any](m map[K]T) []K { + if len(m) < 1 { + return nil + } + ks := make([]K, 0, len(m)) + for k := range m { + ks = append(ks, k) } + slices.Sort(ks) + return ks } diff --git a/engine/playmon/playmon.go b/engine/playmon/playmon.go index c3c3fc0..feb7e47 100644 --- a/engine/playmon/playmon.go +++ b/engine/playmon/playmon.go @@ -1,8 +1,6 @@ package playmon import ( - "fmt" - "github.com/graph-guard/ggproxy/config" "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck" "github.com/graph-guard/ggproxy/engine/playmon/internal/pathmatch" @@ -12,14 +10,20 @@ import ( "github.com/graph-guard/gqt/v4" ) +type arg struct { + PathHash uint64 + Value []gqlparse.Token +} + type Engine struct { - pathScanner *pathscan.PathScanner - matcher *pathmatch.Matcher - templates []*template + pathScanner *pathscan.PathScanner + matcher *pathmatch.Matcher + templates map[string]*template + argumentPaths map[uint64]struct{} - structuralPaths [][]byte - gqtVarPaths map[string]int - inputsByPath map[string][]gqlparse.Token + structuralPaths []uint64 + varValues map[uint64][]gqlparse.Token + argumentsSet []arg } type template struct { @@ -32,11 +36,13 @@ type template struct { // New expects templates to be initialized and valid. func New(s *config.Service) *Engine { e := &Engine{ - pathScanner: pathscan.New(128, 2048), - templates: make([]*template, len(s.Templates)), - gqtVarPaths: make(map[string]int), + pathScanner: pathscan.New(128, 2048), + templates: make(map[string]*template, len(s.Templates)), + argumentPaths: make(map[uint64]struct{}), - inputsByPath: make(map[string][]gqlparse.Token), + structuralPaths: make([]uint64, 1024), + varValues: make(map[uint64][]gqlparse.Token), + argumentsSet: make([]arg, 1024), } idCounter := 0 for _, t := range s.Templates { @@ -47,18 +53,24 @@ func New(s *config.Service) *Engine { GQTOpr: t.GQTTemplate, ConstraintChecker: c, } - e.templates[idCounter] = tmpl - pathscan.InAST( + e.templates[tmpl.ID] = tmpl + if errs := pathscan.InAST( tmpl.GQTOpr, - func(path []byte, e gqt.Expression) (stop bool) { + func(path uint64, e gqt.Expression) (stop bool) { + // Structural return false - }, func(path []byte, _ gqt.Expression) (stop bool) { + }, func(path uint64, _ gqt.Expression) (stop bool) { + // Argument + e.argumentPaths[path] = struct{}{} return false - }, func(path []byte, _ gqt.Expression) (stop bool) { - e.gqtVarPaths[string(path)] = 0 + }, func(path uint64, _ *gqt.VariableDeclaration) (stop bool) { + // Variable + e.varValues[path] = nil return false }, - ) + ); errs != nil { + panic(errs) + } idCounter++ } e.matcher = pathmatch.New(s) @@ -67,11 +79,9 @@ func New(s *config.Service) *Engine { func (e *Engine) reset() { e.structuralPaths = e.structuralPaths[:0] - for i := range e.gqtVarPaths { - e.gqtVarPaths[i] = 0 - } - for path := range e.inputsByPath { - e.gqtVarPaths[path] = 0 + e.argumentsSet = e.argumentsSet[:0] + for path := range e.varValues { + e.varValues[path] = nil } } @@ -82,51 +92,47 @@ func (e *Engine) Match( selectionSet []gqlparse.Token, ) (id string) { e.reset() - fmt.Println(selectionSet) + var mismatch bool e.pathScanner.InTokens( queryType, selectionSet, - e.gqtVarPaths, - func(path []byte) (stop bool) { + e.varValues, + func(path uint64) (stop bool) { // Structural path e.structuralPaths = append(e.structuralPaths, path) return false }, - func(path []byte, i int) (stop bool) { + func(path uint64, i int) (stop bool) { // Argument + if _, ok := e.argumentPaths[path]; !ok { + mismatch = true + return false + } + e.argumentsSet = append(e.argumentsSet, arg{ + PathHash: path, + Value: selectionSet[i+1:], + }) return false }, - func(path []byte, i int) (stop bool) { - fmt.Println("FUCK") - e.inputsByPath[string(path)] = selectionSet[i:] + func(path uint64, i int) (stop bool) { // Variable value + e.varValues[path] = selectionSet[i:] return false }, ) - fmt.Println("STR", e.structuralPaths) - for i := range e.structuralPaths { - fmt.Println(" ", string(e.structuralPaths[i])) + if mismatch { + return "" } e.matcher.Match(e.structuralPaths, func(t *config.Template) (stop bool) { - fmt.Println("STRUCTURAL MATCH ", t.ID) - fmt.Println(e.inputsByPath) + tm := e.templates[t.ID] + for i := range e.argumentsSet { + if !tm.ConstraintChecker.Check( + e.varValues, e.argumentsSet[i].PathHash, e.argumentsSet[i].Value, + ) { + return false + } + } id = t.ID return false }) - // e.currentMask.Visit(func(i int) (skip bool) { - // t := e.templates[i] - // match := true - // t.ConstraintChecker.VisitPaths(func(path string) (stop bool) { - // if !t.ConstraintChecker.Check(path) { - // match = false - // return true - // } - // return false - // }) - // if match { - // id = t.ID - // return true - // } - // return false - // }) return id } diff --git a/go.mod b/go.mod index 10380e9..2bea375 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/google/uuid v1.3.0 github.com/graph-guard/backend v0.0.0-20220826171348-e3dcd100c82b github.com/graph-guard/gqlscan v1.0.0 - github.com/graph-guard/gqt v0.0.0-20220830090241-b646278952f9 github.com/graph-guard/gqt/v4 v4.0.7 github.com/phuslu/log v1.0.81 github.com/pierrec/xxHash v0.1.5 diff --git a/go.sum b/go.sum index 02344bb..b57485f 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,6 @@ github.com/graph-guard/backend v0.0.0-20220826171348-e3dcd100c82b h1:MTZ+FdD6qIy github.com/graph-guard/backend v0.0.0-20220826171348-e3dcd100c82b/go.mod h1:x2XK0889DedSc5iB1AZzg0dJdWaHx8UiRo1vnv55kNc= github.com/graph-guard/gqlscan v1.0.0 h1:hUEDqnJQJ7nV9S8hCPJuYI/H97KP3unu0Gu7wd0wV8A= github.com/graph-guard/gqlscan v1.0.0/go.mod h1:A7TeIFxoiP39gw/r4sWjDnTZJnNy+4kaytP9Kh7Luuk= -github.com/graph-guard/gqt v0.0.0-20220830090241-b646278952f9 h1:aVvTdG6rHXs4ZzE0BMK83P37zacC9t6I7LOhFb79DVI= -github.com/graph-guard/gqt v0.0.0-20220830090241-b646278952f9/go.mod h1:LSgSzLS6fBQ3uD5lREaytQu4qsSkM2EN50Ds1acdu0U= github.com/graph-guard/gqt/v4 v4.0.7 h1:xp07pamaP7wPqOTRp7+m4wUdpZ78unbisyh44SFnUtE= github.com/graph-guard/gqt/v4 v4.0.7/go.mod h1:tXBLe4Gr3S/y8pM6ABkcIWFcspyU1c1YfrsXdBf0hKE= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= diff --git a/server/server_test.go b/server/server_test.go index 33abc5b..8ac6f42 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -1,75 +1,25 @@ package server_test import ( - "embed" "encoding/json" "fmt" - "io/fs" "net" - "path/filepath" "regexp" - "strings" "sync" "testing" "time" - "github.com/graph-guard/ggproxy/config" "github.com/graph-guard/ggproxy/server" + "github.com/graph-guard/ggproxy/testsetup" plog "github.com/phuslu/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" "github.com/valyala/fasthttp/fasthttputil" - "gopkg.in/yaml.v3" ) -//go:embed tests -var testsFS embed.FS - -type TestModel struct { - Client struct { - Input struct { - Method string `yaml:"method"` - Endpoint string `yaml:"endpoint"` - Body string `yaml:"body"` - BodyJSON map[string]any `yaml:"body(JSON)"` - } `yaml:"input"` - ExpectResponse struct { - Status int `yaml:"status"` - Headers map[string]string `yaml:"headers"` // Key -> Regexp - Body string `yaml:"body"` - BodyJSON map[string]any `yaml:"body(JSON)"` - } `yaml:"expect-response"` - } `yaml:"client"` - Destination *struct { - ExpectForwarded struct { - Headers map[string]string `yaml:"headers"` // Key -> Regexp - Body string `yaml:"body"` - BodyJSON map[string]any `yaml:"body(JSON)"` - } `yaml:"expect-forwarded"` - Response struct { - Status int `yaml:"status"` - Headers map[string]string `yaml:"headers"` - Body string `yaml:"body"` - BodyJSON map[string]any `yaml:"body(JSON)"` - } - } `yaml:"destination"` - Logs []map[string]any `yaml:"logs"` -} - -type Setup struct { - Name string - Config *config.Config - Tests []Test -} - -type Test struct { - Name string - TestModel -} - func TestProxy(t *testing.T) { - setups := GetSetups(t, testsFS, "tests") + setups := GetSetups(t) for _, setup := range setups { t.Run(setup.Name, func(t *testing.T) { clientProxy, forwarded, respSetter, logs := launchSetup(t, setup) @@ -183,88 +133,12 @@ func TestProxy(t *testing.T) { } } -func GetSetups(t *testing.T, filesystem fs.FS, path string) []Setup { - var setups []Setup - - d, err := fs.ReadDir(filesystem, path) - require.NoError(t, err) - for _, setupDir := range d { - if !setupDir.IsDir() { - continue - } - n := setupDir.Name() - if !strings.HasPrefix(n, "setup_") { - t.Logf("ignoring %q", filepath.Join(n)) - continue - } - - c, err := config.New(filepath.Join(path, n, "config.yaml")) - require.NoError(t, err) - - tests := GetTests(t, filesystem, filepath.Join(path, n)) - - setups = append(setups, Setup{ - Name: n, - Config: c, - Tests: tests, - }) +func GetSetups(t *testing.T) []testsetup.Setup { + return []testsetup.Setup{ + testsetup.Starwars(), + testsetup.Test1(), + testsetup.Test2(), } - - return setups -} - -func GetTests(t *testing.T, filesystem fs.FS, root string) []Test { - var tests []Test - d, err := fs.ReadDir(filesystem, root) - require.NoError(t, err) - for _, testDir := range d { - n := testDir.Name() - if !strings.HasPrefix(n, "test_") || !strings.HasSuffix(n, ".yaml") { - continue - } - - f, err := filesystem.Open(filepath.Join(root, n)) - require.NoError(t, err) - defer f.Close() - var m TestModel - d := yaml.NewDecoder(f) - d.KnownFields(true) - err = d.Decode(&m) - require.NoError(t, err) - - isXOR(t, - m.Client.Input.Body, - m.Client.Input.BodyJSON, - "client.input.body", - "client.input.body(JSON)", - ) - isXOR(t, - m.Client.ExpectResponse.Body, - m.Client.ExpectResponse.BodyJSON, - "client.expect-response.body", - "client.expect-response.body(JSON)", - ) - if m.Destination != nil { - isXOR(t, - m.Destination.ExpectForwarded.Body, - m.Destination.ExpectForwarded.BodyJSON, - "destination.expect-forwarded.body", - "destination.expect-forwarded.body(JSON)", - ) - isXOR(t, - m.Destination.Response.Body, - m.Destination.Response.BodyJSON, - "destination.expect-forwarded.body", - "destination.expect-forwarded.body(JSON)", - ) - } - - tests = append(tests, Test{ - Name: n, - TestModel: m, - }) - } - return tests } type SendResponse struct { @@ -277,7 +151,7 @@ type ReceivedRequest struct { Headers map[string]string } -func launchSetup(t *testing.T, s Setup) ( +func launchSetup(t *testing.T, s testsetup.Setup) ( clientProxy *fasthttp.Client, forwarded <-chan ReceivedRequest, resp *Syncronized[*SendResponse], @@ -484,20 +358,6 @@ func copyMap[K comparable, V any](m map[K]V) (copy map[K]V) { return copy } -func isXOR( - t *testing.T, - a string, b map[string]any, - aTitle, bTitle string, -) { - if (a != "" && b == nil) || (a == "" && b != nil) { - return - } - t.Fatalf(`"%s" (%q) and "%s" (%v) are mutually exclusive, `+ - `make sure you're using either of them, not both at the same time!`, - aTitle, a, bTitle, b, - ) -} - // func checkLogs(t *testing.T, expected, actual []map[string]any) { // for i, x := range expected { // if i >= len(expected) { diff --git a/server/tests/README.md b/server/tests/README.md deleted file mode 100644 index 164ca4a..0000000 --- a/server/tests/README.md +++ /dev/null @@ -1,99 +0,0 @@ -# Tests - -Directory `tests` must contain all declarative test setups where each setup directory must have the prefix `setup_` followed by the name of the setup. -The setup directory must contain the full server configuration and test declarations which must have the prefix `test_` followed by the name of the test and the `.yaml` file extension. - -A test defines the clients inputs and expectations: -- `client.input.method` -- `client.input.endpoint` -- `client.input.body` -- `client.expect-response.status` -- `client.expect-response.headers` -- `client.expect-response.body` - -...and optionally, the destination server's outputs and expectations: -- `destination.expect-forwarded.headers` -- `destination.expect-forwarded.body` -- `destination.response.status` -- `destination.response.headers` -- `destination.response.body` - -An example test structure: - -``` -tests -├─ setup_A -│ ├─ config.yaml -│ ├─ services_enabled -│ │ └─ service_1 -│ │ ├─ config.yaml -│ │ └─ templates_enabled -│ │ └─ template_1.gqt -│ └─ test_1.yaml -└─ setup_B - ├─ config.yaml - ├─ services_enabled - │ └─ service_1 - │ ├─ config.yaml - │ └─ templates_enabled - │ └─ template_1.gqt - └─ test_1.yaml -``` - -An example test file: -```yaml -client: - input: - method: POST - endpoint: "/service_a" - body(JSON): - this: will - be: "sent to ggproxy" - expect-response: - status: 200 - headers: - Content-Length: ^74$ - Content-Type: ^text/plain; charset=utf-8$ - Server: "fasthttp" - Date: . - X-Custom-Header: value - body: > - this is what we expect to get from ggproxy - -destination: - expect-forwarded: - headers: - Host: ^http://localhost:8081/service_a$ - Content-Length: ^103$ - Content-Type: ^application/json$ - User-Agent: "fasthttp" - Date: . - body: > - this is what we expect to receive - on the destination server - response: - status: 200 - headers: - X-Custom-Header: value - body: > - this is what the destination server - will respond with - -logs: - - level: info - host: localhost:8080 - message: "listening" - services: - - service_a - - level: info - path: "/service_a" - message: "handling request" - - level: debug - path: "/service_a" - query: > - { - "query": "mutation X { a { a0(a0_0: [ 0 ]) } }", - "operationName": "X", - "variables": {} - } -``` \ No newline at end of file diff --git a/server/tests/setup_0/enabled-services/testservice.yml b/server/tests/setup_0/enabled-services/testservice.yml deleted file mode 120000 index a761b41..0000000 --- a/server/tests/setup_0/enabled-services/testservice.yml +++ /dev/null @@ -1 +0,0 @@ -../all-services/testservice.yaml \ No newline at end of file diff --git a/server/tests/setup_0/enabled-templates/testservice/template_mut.gqt b/server/tests/setup_0/enabled-templates/testservice/template_mut.gqt deleted file mode 120000 index e4de39b..0000000 --- a/server/tests/setup_0/enabled-templates/testservice/template_mut.gqt +++ /dev/null @@ -1 +0,0 @@ -../../all-templates/testservice/template_mut.gqt \ No newline at end of file diff --git a/server/tests/setup_0/enabled-templates/testservice/template_qry.gqt b/server/tests/setup_0/enabled-templates/testservice/template_qry.gqt deleted file mode 120000 index 01e483d..0000000 --- a/server/tests/setup_0/enabled-templates/testservice/template_qry.gqt +++ /dev/null @@ -1 +0,0 @@ -../../all-templates/testservice/template_qry.gqt \ No newline at end of file diff --git a/server/tests/setup_1/config.yaml b/server/tests/setup_1/config.yaml deleted file mode 100644 index 76a0490..0000000 --- a/server/tests/setup_1/config.yaml +++ /dev/null @@ -1,4 +0,0 @@ -proxy: - host: localhost:8080 -all-services: all-services -enabled-services: enabled-services diff --git a/server/tests/setup_1/enabled-services/service_a.yaml b/server/tests/setup_1/enabled-services/service_a.yaml deleted file mode 120000 index 953dca3..0000000 --- a/server/tests/setup_1/enabled-services/service_a.yaml +++ /dev/null @@ -1 +0,0 @@ -../all-services/service_a.yml \ No newline at end of file diff --git a/server/tests/setup_1/enabled-templates/service_a/template_a.gqt b/server/tests/setup_1/enabled-templates/service_a/template_a.gqt deleted file mode 120000 index a796d97..0000000 --- a/server/tests/setup_1/enabled-templates/service_a/template_a.gqt +++ /dev/null @@ -1 +0,0 @@ -../../all-templates/service_a/template_a.gqt \ No newline at end of file diff --git a/testsetup/starwars/config.yml b/testsetup/starwars/config.yml new file mode 100644 index 0000000..f5f9715 --- /dev/null +++ b/testsetup/starwars/config.yml @@ -0,0 +1,13 @@ +proxy: + # Address and port of the proxy server. + host: localhost:8000 + # Optional, in bytes, default: 4MiB. + max-request-body-size: 4096 + +# Optional, enables API server. +api: + # Address and port of the API server. + host: localhost:3000 + +all-services: services_enabled +enabled-services: services_enabled diff --git a/testsetup/starwars/services_enabled/starwars.yml b/testsetup/starwars/services_enabled/starwars.yml new file mode 100644 index 0000000..2f8f239 --- /dev/null +++ b/testsetup/starwars/services_enabled/starwars.yml @@ -0,0 +1,13 @@ +# The service's display name +name: "Starwars" +# Source URL path +path: "/starwars" +# Destination URL (where to proxy requests to) +forward-url: "http://localhost:8080/starwars" + +# false for forwarding the original request, +# true for the reduced version. +forward-reduced: true + +all-templates: starwars/templates_enabled +enabled-templates: starwars/templates_enabled diff --git a/testsetup/starwars/services_enabled/starwars/templates_enabled/a.gqt b/testsetup/starwars/services_enabled/starwars/templates_enabled/a.gqt new file mode 100644 index 0000000..dcdbdfa --- /dev/null +++ b/testsetup/starwars/services_enabled/starwars/templates_enabled/a.gqt @@ -0,0 +1,13 @@ +--- +name: "A" +--- +query { + hero(episode: *) { + id + name + friends { + id + name + } + } +} diff --git a/testsetup/starwars/services_enabled/starwars/templates_enabled/b.gqt b/testsetup/starwars/services_enabled/starwars/templates_enabled/b.gqt new file mode 100644 index 0000000..47fefd9 --- /dev/null +++ b/testsetup/starwars/services_enabled/starwars/templates_enabled/b.gqt @@ -0,0 +1,20 @@ +--- +name: "B" +--- +query { + hero(episode: EMPIRE || JEDI) { + id + name + appearsIn + friends { + id + name + appearsIn + friends { + id + name + appearsIn + } + } + } +} diff --git a/testsetup/starwars/services_enabled/starwars/templates_enabled/c.gqt b/testsetup/starwars/services_enabled/starwars/templates_enabled/c.gqt new file mode 100644 index 0000000..6cbb20e --- /dev/null +++ b/testsetup/starwars/services_enabled/starwars/templates_enabled/c.gqt @@ -0,0 +1,28 @@ +--- +name: "C" +--- +query { + hero(episode: EMPIRE || JEDI) { + id + name + appearsIn + friends { + id + name + appearsIn + friends { + id + name + appearsIn + } + } + friendsConnection(first: >= 0, after: len > 0) { + totalCount + friends { + id + name + appearsIn + } + } + } +} diff --git a/testsetup/starwars/services_enabled/starwars/templates_enabled/d.gqt b/testsetup/starwars/services_enabled/starwars/templates_enabled/d.gqt new file mode 100644 index 0000000..f5aafe8 --- /dev/null +++ b/testsetup/starwars/services_enabled/starwars/templates_enabled/d.gqt @@ -0,0 +1,20 @@ +--- +name: "D" +--- +query { + hero(episode: EMPIRE || JEDI) { + id + name + appearsIn + friends { + id + name + appearsIn + } + } + reviews(episode: EMPIRE || JEDI) { + stars + commentary + time + } +} diff --git a/testsetup/starwars/test_qry_ok.yaml b/testsetup/starwars/test_qry_ok.yaml new file mode 100644 index 0000000..1638a60 --- /dev/null +++ b/testsetup/starwars/test_qry_ok.yaml @@ -0,0 +1,96 @@ +client: + input: + method: POST + endpoint: /testservice + body(JSON): + query: > + query { + hero(episode: EMPIRE) { + id + name + friends { + id + name + } + } + } + + expect-response: + status: 200 + headers: + Content-Length: ^166$ + Content-Type: ^text/plain; charset=utf-8$ + Server: ^fasthttp$ + Date: . + X-Custom-Header: value + body(JSON): + data: + hero: + id: "1000" + name: "Luke Skywalker" + friends: + - id: "1002" + name: "Han Solo" + - id: "1003" + name: "Leia Organa" + - id: "2000" + name: "C-3PO" + - id: "2001" + name: "R2-D2" + +destination: + expect-forwarded: + headers: + X-Forwarded-Host: ^localhost:8000$ + X-Forwarded-For: 0.0.0.0 + X-Forwarded-Proto: ^HTTP/1.1$ + Host: ^localhost:8081$ + Content-Length: ^97$ + Content-Type: ^application/json$ + User-Agent: ^fasthttp$ + Date: . + body(JSON): + query: > + query { + hero(episode: EMPIRE) { + id + name + friends { + id + name + } + } + } + + response: + status: 200 + headers: + X-Custom-Header: value + body(JSON): + data: + hero: + id: "1000" + name: "Luke Skywalker" + friends: + - id: "1002" + name: "Han Solo" + - id: "1003" + name: "Leia Organa" + - id: "2000" + name: "C-3PO" + - id: "2001" + name: "R2-D2" + +logs: + - level: info + message: 'listening' + host: localhost:8080 + tls: false + services: + - testservice + - level: info + message: 'handling request' + path: /testservice + - level: debug + path: /testservice + query: '{"query":"query { queryFirstField { queryFirstSubfield querySecondSubfield } querySecondField }"}' diff --git a/server/tests/setup_0/all-services/testservice.yaml b/testsetup/test1/all_services/testservice.yaml similarity index 52% rename from server/tests/setup_0/all-services/testservice.yaml rename to testsetup/test1/all_services/testservice.yaml index cfac56d..fde9ed5 100644 --- a/server/tests/setup_0/all-services/testservice.yaml +++ b/testsetup/test1/all_services/testservice.yaml @@ -2,5 +2,5 @@ name: "Test Service" path: "/testservice" forward-url: "http://localhost:8081/test" forward-reduced: false -all-templates: ../all-templates/testservice -enabled-templates: ../enabled-templates/testservice +all-templates: ../all_templates +enabled-templates: ../enabled_templates diff --git a/server/tests/setup_0/all-templates/testservice/template_mut.gqt b/testsetup/test1/all_templates/template_mut.gqt similarity index 70% rename from server/tests/setup_0/all-templates/testservice/template_mut.gqt rename to testsetup/test1/all_templates/template_mut.gqt index 6eee3f9..8c4c934 100644 --- a/server/tests/setup_0/all-templates/testservice/template_mut.gqt +++ b/testsetup/test1/all_templates/template_mut.gqt @@ -5,8 +5,8 @@ tags: --- mutation { someMutations( - firstArg: val = "first" - secondArg: val = "second" + firstArg: "first", + secondArg: "second", ) { fieldA fieldB { diff --git a/server/tests/setup_0/all-templates/testservice/template_qry.gqt b/testsetup/test1/all_templates/template_qry.gqt similarity index 100% rename from server/tests/setup_0/all-templates/testservice/template_qry.gqt rename to testsetup/test1/all_templates/template_qry.gqt diff --git a/testsetup/test1/config.yml b/testsetup/test1/config.yml new file mode 100644 index 0000000..543e016 --- /dev/null +++ b/testsetup/test1/config.yml @@ -0,0 +1,5 @@ +proxy: + host: localhost:8080 + +all-services: all_services +enabled-services: enabled_services diff --git a/testsetup/test1/enabled_services/testservice.yaml b/testsetup/test1/enabled_services/testservice.yaml new file mode 100644 index 0000000..fde9ed5 --- /dev/null +++ b/testsetup/test1/enabled_services/testservice.yaml @@ -0,0 +1,6 @@ +name: "Test Service" +path: "/testservice" +forward-url: "http://localhost:8081/test" +forward-reduced: false +all-templates: ../all_templates +enabled-templates: ../enabled_templates diff --git a/testsetup/test1/enabled_templates/template_mut.gqt b/testsetup/test1/enabled_templates/template_mut.gqt new file mode 100644 index 0000000..8c4c934 --- /dev/null +++ b/testsetup/test1/enabled_templates/template_mut.gqt @@ -0,0 +1,16 @@ +--- +name: "Template Mutation" +tags: + - mutation +--- +mutation { + someMutations( + firstArg: "first", + secondArg: "second", + ) { + fieldA + fieldB { + subFieldC + } + } +} diff --git a/testsetup/test1/enabled_templates/template_qry.gqt b/testsetup/test1/enabled_templates/template_qry.gqt new file mode 100644 index 0000000..be5a0f0 --- /dev/null +++ b/testsetup/test1/enabled_templates/template_qry.gqt @@ -0,0 +1,12 @@ +--- +name: "Template Query" +tags: + - query +--- +query { + queryFirstField { + queryFirstSubfield + querySecondSubfield + } + querySecondField +} diff --git a/server/tests/setup_0/test_qry_ok.yaml b/testsetup/test1/test_qry_ok.yaml similarity index 100% rename from server/tests/setup_0/test_qry_ok.yaml rename to testsetup/test1/test_qry_ok.yaml diff --git a/server/tests/setup_0/x_test_mut_ok.yaml b/testsetup/test1/x_test_mut_ok.yaml similarity index 100% rename from server/tests/setup_0/x_test_mut_ok.yaml rename to testsetup/test1/x_test_mut_ok.yaml diff --git a/server/tests/setup_1/all-services/service_a.yml b/testsetup/test2/all-services/service_a.yaml similarity index 100% rename from server/tests/setup_1/all-services/service_a.yml rename to testsetup/test2/all-services/service_a.yaml diff --git a/server/tests/setup_1/all-templates/service_a/template_a.gqt b/testsetup/test2/all-templates/service_a/template_a.gqt similarity index 63% rename from server/tests/setup_1/all-templates/service_a/template_a.gqt rename to testsetup/test2/all-templates/service_a/template_a.gqt index cd9d980..b6b8e68 100644 --- a/server/tests/setup_1/all-templates/service_a/template_a.gqt +++ b/testsetup/test2/all-templates/service_a/template_a.gqt @@ -7,7 +7,7 @@ tags: mutation { a { a0( - a0_0: val = [ val <= 0 ] && val != [ val = -1 ] + a0_0: [ <= 0 ] && != [ -1 ] ) } } diff --git a/server/tests/setup_0/config.yaml b/testsetup/test2/config.yml similarity index 100% rename from server/tests/setup_0/config.yaml rename to testsetup/test2/config.yml diff --git a/testsetup/test2/enabled-services/service_a.yaml b/testsetup/test2/enabled-services/service_a.yaml new file mode 100644 index 0000000..4ce506f --- /dev/null +++ b/testsetup/test2/enabled-services/service_a.yaml @@ -0,0 +1,6 @@ +name: "Service A" +path: "/service_a" +forward-url: "http://localhost:8081/service_a" +forward-reduced: false +all-templates: ../all-templates/service_a +enabled-templates: ../enabled-templates/service_a diff --git a/testsetup/test2/enabled-templates/service_a/template_a.gqt b/testsetup/test2/enabled-templates/service_a/template_a.gqt new file mode 100644 index 0000000..b6b8e68 --- /dev/null +++ b/testsetup/test2/enabled-templates/service_a/template_a.gqt @@ -0,0 +1,13 @@ +--- +name: "Template A1" +tags: + - tag_a + - mutation +--- +mutation { + a { + a0( + a0_0: [ <= 0 ] && != [ -1 ] + ) + } +} diff --git a/server/tests/setup_1/test_1.yaml b/testsetup/test2/test_1.yaml similarity index 100% rename from server/tests/setup_1/test_1.yaml rename to testsetup/test2/test_1.yaml diff --git a/testsetup/testsetup.go b/testsetup/testsetup.go new file mode 100644 index 0000000..45672ec --- /dev/null +++ b/testsetup/testsetup.go @@ -0,0 +1,159 @@ +package testsetup + +import ( + "embed" + "fmt" + "io/fs" + "path/filepath" + "strings" + + "github.com/graph-guard/ggproxy/config" + "gopkg.in/yaml.v3" +) + +/* SPECIAL NOTE: *\ +\* Symlinks are not allowed in embedded filesystems! */ + +//go:embed starwars +var fsStarwars embed.FS + +//go:embed test1 +var fsTest1 embed.FS + +//go:embed test2 +var fsTest2 embed.FS + +func Starwars() Setup { return read(fsStarwars, "starwars") } +func Test1() Setup { return read(fsTest1, "test1") } +func Test2() Setup { return read(fsTest2, "test2") } + +func read(fsys fs.FS, root string) Setup { + c, err := config.Read(fsys, root, filepath.Join(root, "config.yml")) + panicOnErr(err) + t, err := readTests(fsys, root) + panicOnErr(err) + return Setup{ + Name: root, + Config: c, + Tests: t, + } +} + +func panicOnErr(err error) { + if err != nil { + panic(err) + } +} + +type TestModel struct { + Client struct { + Input struct { + Method string `yaml:"method"` + Endpoint string `yaml:"endpoint"` + Body string `yaml:"body"` + BodyJSON map[string]any `yaml:"body(JSON)"` + } `yaml:"input"` + ExpectResponse struct { + Status int `yaml:"status"` + Headers map[string]string `yaml:"headers"` // Key -> Regexp + Body string `yaml:"body"` + BodyJSON map[string]any `yaml:"body(JSON)"` + } `yaml:"expect-response"` + } `yaml:"client"` + Destination *struct { + ExpectForwarded struct { + Headers map[string]string `yaml:"headers"` // Key -> Regexp + Body string `yaml:"body"` + BodyJSON map[string]any `yaml:"body(JSON)"` + } `yaml:"expect-forwarded"` + Response struct { + Status int `yaml:"status"` + Headers map[string]string `yaml:"headers"` + Body string `yaml:"body"` + BodyJSON map[string]any `yaml:"body(JSON)"` + } + } `yaml:"destination"` + Logs []map[string]any `yaml:"logs"` +} + +type Setup struct { + Name string + Config *config.Config + Tests []Test +} + +type Test struct { + Name string + TestModel +} + +func readTests(fsys fs.FS, root string) ([]Test, error) { + var tests []Test + d, err := fs.ReadDir(fsys, root) + if err != nil { + return nil, fmt.Errorf("reading test dir: %w", err) + } + for _, testDir := range d { + n := testDir.Name() + if !strings.HasPrefix(n, "test_") || !strings.HasSuffix(n, ".yaml") { + continue + } + + p := filepath.Join(root, n) + f, err := fsys.Open(p) + if err != nil { + return nil, fmt.Errorf("reading test file %q: %w", p, err) + } + defer f.Close() + + var m TestModel + d := yaml.NewDecoder(f) + d.KnownFields(true) + if err := d.Decode(&m); err != nil { + return nil, fmt.Errorf("decoding YAML %q: %w", p, err) + } + + isXOR( + m.Client.Input.Body, + m.Client.Input.BodyJSON, + "client.input.body", + "client.input.body(JSON)", + ) + isXOR( + m.Client.ExpectResponse.Body, + m.Client.ExpectResponse.BodyJSON, + "client.expect-response.body", + "client.expect-response.body(JSON)", + ) + if m.Destination != nil { + isXOR( + m.Destination.ExpectForwarded.Body, + m.Destination.ExpectForwarded.BodyJSON, + "destination.expect-forwarded.body", + "destination.expect-forwarded.body(JSON)", + ) + isXOR( + m.Destination.Response.Body, + m.Destination.Response.BodyJSON, + "destination.expect-forwarded.body", + "destination.expect-forwarded.body(JSON)", + ) + } + + tests = append(tests, Test{ + Name: n, + TestModel: m, + }) + } + return tests, nil +} + +func isXOR(a string, b map[string]any, aTitle, bTitle string) error { + if (a != "" && b == nil) || (a == "" && b != nil) { + return nil + } + return fmt.Errorf(`%q (%q) and %q (%v) are mutually exclusive, `+ + `make sure you're using either of them, not both at the same time!`, + aTitle, a, bTitle, b, + ) +} diff --git a/testsetup/testsetup_test.go b/testsetup/testsetup_test.go new file mode 100644 index 0000000..4dcef7c --- /dev/null +++ b/testsetup/testsetup_test.go @@ -0,0 +1,18 @@ +package testsetup_test + +import ( + "testing" + + "github.com/graph-guard/ggproxy/testsetup" + "github.com/stretchr/testify/require" +) + +func TestStarwars(t *testing.T) { checkSetup(t, testsetup.Starwars()) } +func TestTest1(t *testing.T) { checkSetup(t, testsetup.Test1()) } +func TestTest2(t *testing.T) { checkSetup(t, testsetup.Test2()) } + +func checkSetup(t *testing.T, s testsetup.Setup) { + require.NotZero(t, s.Config) + require.NotZero(t, s.Name) + require.NotZero(t, s.Tests) +} From f700570fe25f9324c9c0629bc4bc8a88a5ac0738 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Mon, 20 Feb 2023 02:37:20 +0100 Subject: [PATCH 11/35] fix: Wrap fragment inlines when necessary --- api/graph/model/model.go | 2 + api/graph/resolver.go | 2 - api/graph/schema.resolvers.go | 4 +- engine/playmon/bench_test.go | 8 +- .../internal/pathscan/pathscan_test.go | 2 +- gqlparse/bench_test.go | 26 +- gqlparse/gqlparse.go | 194 ++++++++++++- gqlparse/gqlparse_test.go | 259 ++++++++++++++---- server/api.go | 3 +- server/proxy.go | 2 +- utilities/tokenwriter/bench_test.go | 2 +- utilities/tokenwriter/tokenwriter_test.go | 2 +- 12 files changed, 431 insertions(+), 75 deletions(-) diff --git a/api/graph/model/model.go b/api/graph/model/model.go index 0818d60..2e0197f 100644 --- a/api/graph/model/model.go +++ b/api/graph/model/model.go @@ -2,10 +2,12 @@ package model import ( "github.com/graph-guard/ggproxy/engine/playmon" + "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/ggproxy/statistics" ) type Service struct { + Parser *gqlparse.Parser Engine *playmon.Engine TemplatesByID map[string]*Template Stats *statistics.ServiceSync diff --git a/api/graph/resolver.go b/api/graph/resolver.go index e5064e8..3bd477b 100644 --- a/api/graph/resolver.go +++ b/api/graph/resolver.go @@ -5,7 +5,6 @@ import ( "github.com/graph-guard/ggproxy/api/graph/model" "github.com/graph-guard/ggproxy/config" - "github.com/graph-guard/ggproxy/gqlparse" plog "github.com/phuslu/log" ) @@ -15,7 +14,6 @@ type Resolver struct { Start time.Time Version string Conf *config.Config - Parser *gqlparse.Parser Services map[string]*model.Service Log plog.Logger } diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index f08e7ed..b190618 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -63,7 +63,7 @@ func (r *serviceResolver) MatchAll(ctx context.Context, obj *model.Service, quer } startParsing := time.Now() - r.Resolver.Parser.Parse( + obj.Parser.Parse( []byte(query), oprName, varsJSON, func( varVals [][]gqlparse.Token, @@ -118,7 +118,7 @@ func (r *serviceResolver) Match(ctx context.Context, obj *model.Service, query s } startParsing := time.Now() - r.Resolver.Parser.Parse( + obj.Parser.Parse( []byte(query), oprName, varsJSON, func( varVals [][]gqlparse.Token, diff --git a/engine/playmon/bench_test.go b/engine/playmon/bench_test.go index 8ec1ea8..84acb6c 100644 --- a/engine/playmon/bench_test.go +++ b/engine/playmon/bench_test.go @@ -13,14 +13,14 @@ var GS string func BenchmarkMatchStarwars(b *testing.B) { s := testsetup.Starwars() - e := playmon.New(s.Config.ServicesEnabled[0]) + service := s.Config.ServicesEnabled[0] + e := playmon.New(service) - p := gqlparse.NewParser() + p := gqlparse.NewParser(service.Schema) var varvals [][]gqlparse.Token var opr gqlscan.Token var selset []gqlparse.Token - src := s.Tests[0].Client.Input.BodyJSON["query"].(string) - b.Logf(src) + src := s.Tests[1].Client.Input.BodyJSON["query"].(string) p.Parse( []byte(src), nil, nil, func( diff --git a/engine/playmon/internal/pathscan/pathscan_test.go b/engine/playmon/internal/pathscan/pathscan_test.go index fdac9e9..aafbff9 100644 --- a/engine/playmon/internal/pathscan/pathscan_test.go +++ b/engine/playmon/internal/pathscan/pathscan_test.go @@ -257,7 +257,7 @@ func TestInTokens(t *testing.T) { for _, tt := range testsInTokens { t.Run(tt.Name, func(t *testing.T) { ps := pathscan.New(0, 0) - p := gqlparse.NewParser() + p := gqlparse.NewParser(nil) actualStructuralPaths := []uint64{} actualArgPaths := make(map[uint64]int) actualVarPaths := make(map[uint64]int) diff --git a/gqlparse/bench_test.go b/gqlparse/bench_test.go index 82adefa..6280667 100644 --- a/gqlparse/bench_test.go +++ b/gqlparse/bench_test.go @@ -4,13 +4,25 @@ import ( "testing" "github.com/graph-guard/ggproxy/gqlparse" + "github.com/vektah/gqlparser/v2" + "github.com/vektah/gqlparser/v2/ast" ) var GI int func BenchmarkParse(b *testing.B) { - r := gqlparse.NewParser() for _, td := range testdata { + var schema *ast.Schema + if td.Data.Schema != "" { + var err error + if schema, err = gqlparser.LoadSchema(&ast.Source{ + Name: "schema.graphqls", Input: td.Data.Schema, + }); err != nil { + b.Fatalf("parsing schema: %v", err) + } + } + + r := gqlparse.NewParser(schema) b.Run(td.Decl, func(b *testing.B) { src := []byte(td.Data.Src) opr := []byte(td.Data.OprName) @@ -32,8 +44,18 @@ func BenchmarkParse(b *testing.B) { } func BenchmarkParseErr(b *testing.B) { - r := gqlparse.NewParser() for _, td := range testdataErr { + var schema *ast.Schema + if td.Data.Schema != "" { + var err error + if schema, err = gqlparser.LoadSchema(&ast.Source{ + Name: "schema.graphqls", Input: td.Data.Schema, + }); err != nil { + b.Fatalf("parsing schema: %v", err) + } + } + + r := gqlparse.NewParser(schema) b.Run(td.Decl, func(b *testing.B) { src := []byte(td.Data.Src) opr := []byte(td.Data.OprName) diff --git a/gqlparse/gqlparse.go b/gqlparse/gqlparse.go index 7fdabe7..be36d3a 100644 --- a/gqlparse/gqlparse.go +++ b/gqlparse/gqlparse.go @@ -15,6 +15,7 @@ import ( "github.com/graph-guard/ggproxy/utilities/unsafe" "github.com/graph-guard/gqlscan" "github.com/tidwall/gjson" + "github.com/vektah/gqlparser/v2/ast" ) type Token struct { @@ -34,16 +35,31 @@ func (t Token) String() string { // VariableIndex returns the index of the varible value if // its a variable index token, otherwise returns -1. func (t Token) VariableIndex() (index int) { - if t.ID >= TokenTypeValIndexOffset { + if t.ID >= TokenTypeValIndexOffset && + t.ID < TokenTypeFragHostTypeIndexOffset { return int(t.ID - TokenTypeValIndexOffset) } return -1 } +// FragHostTypeIndex returns the index of the fragment host type, +// if it's a host-type reference token, otherwise returns -1. +func (t Token) FragHostTypeIndex() (index int) { + if t.ID >= TokenTypeFragHostTypeIndexOffset { + return int(t.ID - TokenTypeFragHostTypeIndexOffset) + } + return -1 +} + // TokenTypeValIndexOffset defines the offset that needs to be subtracted // from Token.ID in order to get the index of the value when Token.ID is > 99 const TokenTypeValIndexOffset = 100 +// TokenTypeFragHostTypeIndexOffset defines the offset that needs to be subtracted +// from Token.ID in order to get the index of the fragment host type +// when Token.ID is > 99999 +const TokenTypeFragHostTypeIndexOffset = 100000 + type indexRange struct { IndexStart int IndexEnd int @@ -74,8 +90,9 @@ type typeUnion struct { // NewParser creates a new parser instance. // It's adviced to create only one parser per goroutine // as calling (*Parser).Parse will reset it. -func NewParser() *Parser { +func NewParser(schema *ast.Schema) *Parser { return &Parser{ + schema: schema, gi: graph.NewInspector(), buffer: make([]Token, 0), bufferOpr: make([]Token, 0), @@ -99,6 +116,15 @@ func NewParser() *Parser { } type Parser struct { + schema *ast.Schema + + // schemaTypeStack is used during parsing to track the schema type + // of a particular token's context. + schemaTypeStack stack.Stack[*ast.Definition] + + // fragSpreadHostTypes associates fragment spreads with the host type. + fragSpreadHostTypes []*ast.Definition + // buffer holds the original source tokens buffer []Token @@ -145,6 +171,8 @@ type Parser struct { // fragmentGraphEdges buffers the edges that are passed to gi fragmentGraphEdges []graph.Edge + errTypeUndef ErrorTypeUndef + errFieldUndef ErrorFieldUndef errSyntax ErrorSyntax errOprAnonNonExcl ErrorOprAnonNonExcl errOprNotFound ErrorOprNotFound @@ -163,6 +191,7 @@ type Parser struct { } func (r *Parser) reset() { + r.schemaTypeStack.Reset() r.buffer = r.buffer[:0] r.bufferOpr = r.bufferOpr[:0] r.ordered = r.ordered[:0] @@ -206,18 +235,45 @@ func (r *Parser) Parse( }) switch i.Token() { case gqlscan.TokenDefMut: + if r.schema != nil { + if r.schema.Mutation == nil { + r.errTypeUndef.TypeName = "Mutation" + isErr = true + onError(&r.errTypeUndef) + return true + } + r.schemaTypeStack.Push(r.schema.Mutation) + } recentDef = gqlscan.TokenDefMut r.operations = append(r.operations, indexRange{ IndexStart: len(r.buffer) - 1, }) case gqlscan.TokenDefQry: + if r.schema != nil { + if r.schema.Query == nil { + r.errTypeUndef.TypeName = "Query" + isErr = true + onError(&r.errTypeUndef) + return true + } + r.schemaTypeStack.Push(r.schema.Query) + } recentDef = gqlscan.TokenDefQry r.operations = append(r.operations, indexRange{ IndexStart: len(r.buffer) - 1, }) case gqlscan.TokenDefSub: + if r.schema != nil { + if r.schema.Subscription == nil { + r.errTypeUndef.TypeName = "Subscription" + isErr = true + onError(&r.errTypeUndef) + return true + } + r.schemaTypeStack.Push(r.schema.Subscription) + } recentDef = gqlscan.TokenDefSub r.operations = append(r.operations, indexRange{ IndexStart: len(r.buffer) - 1, @@ -237,6 +293,36 @@ func (r *Parser) Parse( } stackCounter++ + beforeSet := r.buffer[len(r.buffer)-2] + if beforeSet.ID == gqlscan.TokenArgListEnd { + // Seek field before the argument list + i := len(r.buffer) - 3 + for ; r.buffer[i].ID != gqlscan.TokenArgList; i-- { + } + beforeSet = r.buffer[i-1] + } + switch beforeSet.ID { + case gqlscan.TokenField: + if r.schema != nil { + h := r.schemaTypeStack.Top() + f := fieldByName(h, beforeSet.Value) + if f == nil { + r.errFieldUndef.FieldName = beforeSet.Value + r.errFieldUndef.HostTypeName = h.Name + isErr = true + onError(&r.errTypeUndef) + return true + } + tp := r.schema.Types[f.Type.NamedType] + r.schemaTypeStack.Push(tp) + } + case gqlscan.TokenFragInline: + if r.schema != nil { + tp := r.schema.Types[string(beforeSet.Value)] + r.schemaTypeStack.Push(tp) + } + } + case gqlscan.TokenSetEnd: if fragStackCounter > 0 { r.buffer = r.buffer[:len(r.buffer)-1] @@ -259,6 +345,16 @@ func (r *Parser) Parse( recentDef, recentFragDef = 0, nil } case gqlscan.TokenNamedSpread: + if r.schema != nil { + hostType := r.schemaTypeStack.Top() + index := len(r.fragSpreadHostTypes) + r.fragSpreadHostTypes = append(r.fragSpreadHostTypes, hostType) + + // Override the spread token to encode + // the index of the host type in it. + r.buffer[len(r.buffer)-1].ID = makeFragSpreadHostTypeID(index) + } + if recentFragDef == nil { r.entryFrags.Set( r.buffer[len(r.buffer)-1].Value, struct{}{}, @@ -510,25 +606,58 @@ func (r *Parser) Parse( // Fill operation buffer selectionSetIndex := len(r.bufferOpr) for ; i < len(o); i++ { - switch o[i].ID { + switch id := o[i].ID; { default: r.bufferOpr = append(r.bufferOpr, o[i]) - case gqlscan.TokenNamedSpread: - // Inline fragment spread outside of fragment definitions + case id >= TokenTypeFragHostTypeIndexOffset && r.schema != nil: + fd, _ := r.fragDefs.Get(o[i].Value) + host := r.fragSpreadHostTypes[id-TokenTypeFragHostTypeIndexOffset] + fragType := r.schema.Types[string(r.buffer[fd.IndexStart+1].Value)] var fragContents []Token - r.fragDefs.GetFn(o[i].Value, func(v *fragDef) { - fragContents = r.buffer[v.IndexStart+3 : v.IndexEnd-1] - v.Used = true - }) + if isTypeDirectlyInlinable(r.schema, host, fragType) { + // No need to wrap the contents in a type condition. + // Inline fragment spread outside of fragment definitions. + if v := r.fragsConstructed.GetItems(o[i].Value); v != nil { + fragContents = v[2 : len(v)-1] + } else { + r.fragDefs.GetFn(o[i].Value, func(v *fragDef) { + fragContents = r.buffer[v.IndexStart+3 : v.IndexEnd] + fragContents[0].ID = gqlscan.TokenFragInline + v.Used = true + }) + } + } else { + // Wrap inlined fields in a type condition. + // Inline fragment spread outside of fragment definitions. + if v := r.fragsConstructed.GetItems(o[i].Value); v != nil { + fragContents = v + } else { + var fragContents []Token + r.fragDefs.GetFn(o[i].Value, func(v *fragDef) { + fragContents = r.buffer[v.IndexStart+1 : v.IndexEnd] + fragContents[0].ID = gqlscan.TokenFragInline + v.Used = true + }) + } + } + r.bufferOpr = append(r.bufferOpr, fragContents...) + case id == gqlscan.TokenNamedSpread: + // Inline fragment spread outside of fragment definitions if v := r.fragsConstructed.GetItems(o[i].Value); v != nil { r.bufferOpr = append(r.bufferOpr, v...) } else { + var fragContents []Token + r.fragDefs.GetFn(o[i].Value, func(v *fragDef) { + fragContents = r.buffer[v.IndexStart+1 : v.IndexEnd] + fragContents[0].ID = gqlscan.TokenFragInline + v.Used = true + }) r.bufferOpr = append(r.bufferOpr, fragContents...) } - case gqlscan.TokenVarRef: + case id == gqlscan.TokenVarRef: // Append variable value index value := r.varsConstructed.Get(o[i].Value) if value.Index == -1 { @@ -560,6 +689,24 @@ func (r *Parser) Parse( onSuccess(r.varValues, r.bufferOpr, r.bufferOpr[selectionSetIndex:]) } +type ErrorTypeUndef struct{ TypeName string } + +func (e *ErrorTypeUndef) Error() string { + return "undefined type: " + e.TypeName +} + +type ErrorFieldUndef struct { + HostTypeName string + FieldName []byte +} + +func (e *ErrorFieldUndef) Error() string { + return "undefined field (" + + string(e.FieldName) + + ") in type " + + e.HostTypeName +} + type ErrorSyntax struct { ScanErr gqlscan.Error } @@ -1068,7 +1215,7 @@ func (r *Parser) constructFrag( rn indexRange, onError func(err error), ) (err bool) { - fragSelections := r.buffer[rn.IndexStart+3 : rn.IndexEnd-1] + fragSelections := r.buffer[rn.IndexStart+1 : rn.IndexEnd] for _, t := range fragSelections { switch t.ID { case gqlscan.TokenNamedSpread: @@ -1086,6 +1233,11 @@ func (r *Parser) constructFrag( r.fragsConstructed.Append( makeVarValueRefTok(valueSeg.Index, t.Value), ) + case gqlscan.TokenFragTypeCond: + r.fragsConstructed.Append(Token{ + ID: gqlscan.TokenFragInline, + Value: t.Value, + }) default: r.fragsConstructed.Append(t) } @@ -1100,3 +1252,23 @@ func makeVarValueRefTok(index int, variableName []byte) Token { Value: variableName, } } + +func makeFragSpreadHostTypeID(index int) gqlscan.Token { + return gqlscan.Token(TokenTypeFragHostTypeIndexOffset + index) +} + +func fieldByName(d *ast.Definition, name []byte) *ast.FieldDefinition { + for _, f := range d.Fields { + if f.Name == string(name) { + return f + } + } + return nil +} + +// isTypeDirectlyInlinable returns true if properties from child +// are directly inlinable into type host without the need for a type condition, +// otherwise returns false. +func isTypeDirectlyInlinable(schema *ast.Schema, host, child *ast.Definition) bool { + return host.Name == child.Name +} diff --git a/gqlparse/gqlparse_test.go b/gqlparse/gqlparse_test.go index a783e72..294579e 100644 --- a/gqlparse/gqlparse_test.go +++ b/gqlparse/gqlparse_test.go @@ -10,10 +10,15 @@ import ( "github.com/graph-guard/ggproxy/utilities/decl" "github.com/graph-guard/ggproxy/utilities/testeq" "github.com/graph-guard/gqlscan" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/vektah/gqlparser/v2" + "github.com/vektah/gqlparser/v2/ast" ) var testdata = []decl.Declaration[TestSuccess]{ + /* SCHEMALESS */ + decl.New(TestSuccess{ Src: "query($e: Episode!) {hero(episode: $e) {id}}", VarsJSON: `{"e": "EMPIRE"}`, @@ -117,8 +122,11 @@ var testdata = []decl.Declaration[TestSuccess]{ ExpectOpr: []gqlparse.Token{ Token(gqlscan.TokenDefQry), Token(gqlscan.TokenSet), + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), Token(gqlscan.TokenField, "x"), Token(gqlscan.TokenSetEnd), + Token(gqlscan.TokenSetEnd), }, }), // Inline nested named fragments @@ -135,20 +143,65 @@ var testdata = []decl.Declaration[TestSuccess]{ ExpectOpr: []gqlparse.Token{ Token(gqlscan.TokenDefQry), Token(gqlscan.TokenSet), + + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), Token(gqlscan.TokenField, "x"), Token(gqlscan.TokenField, "y"), + Token(gqlscan.TokenSetEnd), + Token(gqlscan.TokenSetEnd), + Token(gqlscan.TokenSetEnd), + + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), Token(gqlscan.TokenField, "x2"), + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), Token(gqlscan.TokenField, "x"), Token(gqlscan.TokenField, "y"), + Token(gqlscan.TokenSetEnd), + Token(gqlscan.TokenSetEnd), + Token(gqlscan.TokenSetEnd), + + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), Token(gqlscan.TokenField, "x2"), + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), Token(gqlscan.TokenField, "x"), Token(gqlscan.TokenField, "y"), + Token(gqlscan.TokenSetEnd), + Token(gqlscan.TokenSetEnd), + Token(gqlscan.TokenSetEnd), + Token(gqlscan.TokenSetEnd), + Token(gqlscan.TokenField, "s"), Token(gqlscan.TokenSet), + + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), Token(gqlscan.TokenField, "x"), Token(gqlscan.TokenField, "y"), Token(gqlscan.TokenSetEnd), Token(gqlscan.TokenSetEnd), + Token(gqlscan.TokenSetEnd), + + Token(gqlscan.TokenSetEnd), + Token(gqlscan.TokenSetEnd), }, }), // Inline variables inside fragments @@ -173,11 +226,14 @@ var testdata = []decl.Declaration[TestSuccess]{ Token(gqlscan.TokenVarListEnd), Token(gqlscan.TokenSet), + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), Token(gqlscan.TokenField, "foo"), Token(gqlscan.TokenArgList), Token(gqlscan.TokenArgName, "bar"), MakeVariableIndexToken(0, "v"), Token(gqlscan.TokenArgListEnd), + Token(gqlscan.TokenSetEnd), Token(gqlscan.TokenSetEnd), }, @@ -205,29 +261,43 @@ var testdata = []decl.Declaration[TestSuccess]{ Token(gqlscan.TokenVarListEnd), Token(gqlscan.TokenSet), + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), + Token(gqlscan.TokenField, "foo"), Token(gqlscan.TokenArgList), Token(gqlscan.TokenArgName, "bar"), MakeVariableIndexToken(0, "v"), Token(gqlscan.TokenArgListEnd), + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), Token(gqlscan.TokenField, "bar"), Token(gqlscan.TokenArgList), Token(gqlscan.TokenArgName, "baz"), MakeVariableIndexToken(0, "v"), Token(gqlscan.TokenArgListEnd), + Token(gqlscan.TokenSetEnd), + Token(gqlscan.TokenSetEnd), + + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), Token(gqlscan.TokenField, "bar"), Token(gqlscan.TokenArgList), Token(gqlscan.TokenArgName, "baz"), MakeVariableIndexToken(0, "v"), Token(gqlscan.TokenArgListEnd), + Token(gqlscan.TokenSetEnd), + Token(gqlscan.TokenFragInline, "Query"), + Token(gqlscan.TokenSet), Token(gqlscan.TokenField, "baz"), Token(gqlscan.TokenArgList), Token(gqlscan.TokenArgName, "fuz"), MakeVariableIndexToken(0, "v"), Token(gqlscan.TokenArgListEnd), + Token(gqlscan.TokenSetEnd), Token(gqlscan.TokenSetEnd), }, @@ -918,6 +988,75 @@ var testdata = []decl.Declaration[TestSuccess]{ Token(gqlscan.TokenSetEnd), }, }), + + /* SCHEMA AWARE */ + + decl.New(TestSuccess{ + Schema: "type Query { i:Int! }", + Src: ` + query { ...f } + fragment f on Query { i } + `, + ExpectOpr: []gqlparse.Token{ + Token(gqlscan.TokenDefQry), + Token(gqlscan.TokenSet), + Token(gqlscan.TokenField, "i"), + Token(gqlscan.TokenSetEnd), + }, + }), + decl.New(TestSuccess{ + Schema: ` + type Query { + hero(episode: Episode!):Character! + } + enum Episode { JEDI, EMPIRE } + interface Character { id:ID!, name:String! } + type Droid implements Character { + id:ID!, name:String!, primaryFunction:String! } + type Human implements Character { + id:ID!, name:String!, mass:Float + } + `, + Src: ` + query { + hero(episode: EMPIRE) { + ...onChar + ...onDroid + ...onHuman + } + } + fragment onChar on Character { id name } + fragment onDroid on Droid { primaryFunction } + fragment onHuman on Human { mass } + `, + ExpectOpr: []gqlparse.Token{ + Token(gqlscan.TokenDefQry), + Token(gqlscan.TokenSet), + Token(gqlscan.TokenField, "hero"), + Token(gqlscan.TokenArgList), + Token(gqlscan.TokenArgName, "episode"), + Token(gqlscan.TokenEnumVal, "EMPIRE"), + Token(gqlscan.TokenArgListEnd), + Token(gqlscan.TokenSet), + + // Inlined directly + Token(gqlscan.TokenField, "id"), + Token(gqlscan.TokenField, "name"), + + // Type-cond wrapped inlines + Token(gqlscan.TokenFragInline, "Droid"), + Token(gqlscan.TokenSet), + Token(gqlscan.TokenField, "primaryFunction"), + Token(gqlscan.TokenSetEnd), + Token(gqlscan.TokenFragInline, "Human"), + Token(gqlscan.TokenSet), + Token(gqlscan.TokenField, "mass"), + Token(gqlscan.TokenSetEnd), + + Token(gqlscan.TokenSetEnd), + Token(gqlscan.TokenSetEnd), + }, + }), } func TestOK(t *testing.T) { @@ -932,7 +1071,17 @@ func TestOK(t *testing.T) { }) require.False(t, err.IsErr()) - gqlparse.NewParser().Parse( + var schema *ast.Schema + if td.Data.Schema != "" { + var err error + schema, err = gqlparser.LoadSchema(&ast.Source{ + Name: "schema.graphqls", + Input: td.Data.Schema, + }) + require.NoError(t, err, "parsing schema") + } + + gqlparse.NewParser(schema).Parse( []byte(td.Data.Src), []byte(td.Data.OprName), []byte(td.Data.VarsJSON), @@ -941,53 +1090,55 @@ func TestOK(t *testing.T) { operation []gqlparse.Token, selectionSet []gqlparse.Token, ) { - // fmt.Printf("expected: (%d)\n", len(td.Data.ExpectOpr)) - // for i, x := range td.Data.ExpectOpr { - // fmt.Printf(" %d: ", i) - // if i := x.VariableIndex(); i > -1 { - // fmt.Printf("variable value identifier (%d)", i) - // } else { - // fmt.Printf(" %v", x.ID) - // } - // if x.Value == nil { - // fmt.Print("\n") - // } else { - // fmt.Printf(" (%q)\n", string(x.Value)) - // } - // } - // fmt.Println(" ") - // fmt.Printf("operation: (%d)\n", len(operation)) - // for i, x := range operation { - // fmt.Printf(" %d: ", i) - // if i := x.VariableIndex(); i > -1 { - // fmt.Printf("variable value identifier (%d)", i) - // } else { - // fmt.Printf(" %v", x.ID) - // } - // if x.Value == nil { - // fmt.Print("\n") - // } else { - // fmt.Printf(" (%q)\n", string(x.Value)) - // } - // } - // fmt.Println(" ") - - testeq.Slices( - t, "token", - td.Data.ExpectOpr, operation, - func(expected, actual gqlparse.Token) (errMsg string) { - if expected.ID != actual.ID || - string(expected.Value) != string(actual.Value) { - return fmt.Sprintf( - "expected {%s}; received: {%s}", - stringifyToken(expected), - stringifyToken(actual), - ) + if !assert.ObjectsAreEqual(td.Data.ExpectOpr, operation) { + fmt.Printf("expected: (%d)\n", len(td.Data.ExpectOpr)) + for i, x := range td.Data.ExpectOpr { + fmt.Printf(" %d: ", i) + if i := x.VariableIndex(); i > -1 { + fmt.Printf("variable value identifier (%d)", i) + } else { + fmt.Printf(" %v", x.ID) } - return "" - }, - stringifyToken, - ) + if x.Value == nil { + fmt.Print("\n") + } else { + fmt.Printf(" (%q)\n", string(x.Value)) + } + } + fmt.Println(" ") + fmt.Printf("operation: (%d)\n", len(operation)) + for i, x := range operation { + fmt.Printf(" %d: ", i) + if i := x.VariableIndex(); i > -1 { + fmt.Printf("variable value identifier (%d)", i) + } else { + fmt.Printf(" %v", x.ID) + } + if x.Value == nil { + fmt.Print("\n") + } else { + fmt.Printf(" (%q)\n", string(x.Value)) + } + } + t.Error("unexpected tokens") + } + + // testeq.Slices( + // t, "token", + // td.Data.ExpectOpr, operation, + // func(expected, actual gqlparse.Token) (errMsg string) { + // if expected.ID != actual.ID || + // string(expected.Value) != string(actual.Value) { + // return fmt.Sprintf( + // "expected {%s}; received: {%s}", + // stringifyToken(expected), + // stringifyToken(actual), + // ) + // } + // return "" + // }, + // stringifyToken, + // ) variableValues := make(map[string][]gqlparse.Token) for _, t := range operation { @@ -1812,7 +1963,17 @@ var testdataErr = []decl.Declaration[TestError]{ func TestErr(t *testing.T) { for _, td := range testdataErr { t.Run(td.Decl, func(t *testing.T) { - gqlparse.NewParser().Parse( + var schema *ast.Schema + if td.Data.Schema != "" { + var err error + schema, err = gqlparser.LoadSchema(&ast.Source{ + Name: "schema.graphqls", + Input: td.Data.Schema, + }) + require.NoError(t, err, "parsing schema") + } + + gqlparse.NewParser(schema).Parse( []byte(td.Data.Src), []byte(td.Data.OprName), []byte(td.Data.VarsJSON), @@ -1833,6 +1994,7 @@ func TestErr(t *testing.T) { } type TestSuccess struct { + Schema string Src string VarsJSON string OprName string @@ -1869,6 +2031,7 @@ func Token(t gqlscan.Token, value ...string) gqlparse.Token { } type TestError struct { + Schema string Src string VarsJSON string OprName string diff --git a/server/api.go b/server/api.go index 38d1866..67f3a75 100644 --- a/server/api.go +++ b/server/api.go @@ -203,14 +203,12 @@ func makeGraphServer( conf *config.Config, proxyServer *Proxy, ) *handler.Server { - parser := gqlparse.NewParser() services := makeServices(conf, proxyServer) s := handler.NewDefaultServer( generated.NewExecutableSchema( generated.Config{Resolvers: &graph.Resolver{ Start: start, Conf: conf, - Parser: parser, Services: services, Log: proxyServer.log, }}, @@ -253,6 +251,7 @@ func makeService( ) *model.Service { stats := proxyServer.GetServiceStatistics(s.ID) service := &model.Service{ + Parser: gqlparse.NewParser(s.Schema), Stats: stats, TemplatesByID: make(map[string]*model.Template, len(s.Templates)), ID: s.ID, diff --git a/server/proxy.go b/server/proxy.go index 52ffe1c..c7039af 100644 --- a/server/proxy.go +++ b/server/proxy.go @@ -97,7 +97,7 @@ func NewProxy( for _, t := range s.TemplatesEnabled { d[t.ID] = t.GQTTemplate } - p := gqlparse.NewParser() + p := gqlparse.NewParser(s.Schema) e := playmon.New(s) return &engine{ Parser: p, diff --git a/utilities/tokenwriter/bench_test.go b/utilities/tokenwriter/bench_test.go index ab2e1ac..c1a09cc 100644 --- a/utilities/tokenwriter/bench_test.go +++ b/utilities/tokenwriter/bench_test.go @@ -12,7 +12,7 @@ func BenchmarkWrite(b *testing.B) { for _, td := range testdata { b.Run("", func(b *testing.B) { var opr []gqlparse.Token - r := gqlparse.NewParser() + r := gqlparse.NewParser(nil) r.Parse( []byte(td.Request), []byte(td.OperationName), diff --git a/utilities/tokenwriter/tokenwriter_test.go b/utilities/tokenwriter/tokenwriter_test.go index 5877941..16525e5 100644 --- a/utilities/tokenwriter/tokenwriter_test.go +++ b/utilities/tokenwriter/tokenwriter_test.go @@ -180,7 +180,7 @@ var testdata = []struct { func TestWrite(t *testing.T) { for _, td := range testdata { t.Run("", func(t *testing.T) { - r := gqlparse.NewParser() + r := gqlparse.NewParser(nil) r.Parse( []byte(td.Request), []byte(td.OperationName), From a638035ee833e9dbc85aff72149bd0d5863c0240 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Tue, 21 Feb 2023 01:29:20 +0100 Subject: [PATCH 12/35] fix: Fix bugs and adapt to new engine interface --- api/graph/generated/generated.go | 35 +-- api/graph/model/model.go | 9 +- api/graph/schema.resolvers.go | 28 ++- engine/playmon/bench_test.go | 40 +++- engine/playmon/internal/pathscan/pathscan.go | 10 - .../internal/pathscan/pathscan_test.go | 3 - engine/playmon/playmon.go | 19 +- go.mod | 9 +- go.sum | 33 ++- server/api.go | 11 +- server/proxy.go | 13 +- .../services_enabled/starwars.graphqls | 131 +++++++++++ .../starwars/services_enabled/starwars.yml | 2 + .../starwars/templates_enabled/d.gqt | 2 +- .../starwars/templates_enabled/e.gqt | 26 +++ .../starwars/templates_enabled/f.gqt | 14 ++ testsetup/starwars/test_qry2_ok.yaml | 212 ++++++++++++++++++ testsetup/testsetup.go | 24 +- 18 files changed, 510 insertions(+), 111 deletions(-) create mode 100644 testsetup/starwars/services_enabled/starwars.graphqls create mode 100644 testsetup/starwars/services_enabled/starwars/templates_enabled/e.gqt create mode 100644 testsetup/starwars/services_enabled/starwars/templates_enabled/f.gqt create mode 100644 testsetup/starwars/test_qry2_ok.yaml diff --git a/api/graph/generated/generated.go b/api/graph/generated/generated.go index 55b600d..a87305f 100644 --- a/api/graph/generated/generated.go +++ b/api/graph/generated/generated.go @@ -65,9 +65,9 @@ type ComplexityRoot struct { ForwardReduced func(childComplexity int) int ForwardURL func(childComplexity int) int ID func(childComplexity int) int - ProxyURL func(childComplexity int) int Match func(childComplexity int, query string, operationName *string, variablesJSON *string) int MatchAll func(childComplexity int, query string, operationName *string, variablesJSON *string) int + ProxyURL func(childComplexity int) int Statistics func(childComplexity int) int TemplatesDisabled func(childComplexity int) int TemplatesEnabled func(childComplexity int) int @@ -223,13 +223,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Service.ID(childComplexity), true - case "Service.proxyURL": - if e.complexity.Service.ProxyURL == nil { - break - } - - return e.complexity.Service.ProxyURL(childComplexity), true - case "Service.match": if e.complexity.Service.Match == nil { break @@ -254,6 +247,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Service.MatchAll(childComplexity, args["query"].(string), args["operationName"].(*string), args["variablesJSON"].(*string)), true + case "Service.proxyURL": + if e.complexity.Service.ProxyURL == nil { + break + } + + return e.complexity.Service.ProxyURL(childComplexity), true + case "Service.statistics": if e.complexity.Service.Statistics == nil { break @@ -968,7 +968,6 @@ func (ec *executionContext) _Query_uptime(ctx context.Context, field graphql.Col }) if err != nil { ec.Error(ctx, err) - return graphql.Null } if resTmp == nil { if !graphql.HasFieldError(ctx, fc) { @@ -1012,7 +1011,6 @@ func (ec *executionContext) _Query_version(ctx context.Context, field graphql.Co }) if err != nil { ec.Error(ctx, err) - return graphql.Null } if resTmp == nil { if !graphql.HasFieldError(ctx, fc) { @@ -1056,7 +1054,6 @@ func (ec *executionContext) _Query_service(ctx context.Context, field graphql.Co }) if err != nil { ec.Error(ctx, err) - return graphql.Null } if resTmp == nil { return graphql.Null @@ -1130,7 +1127,6 @@ func (ec *executionContext) _Query_services(ctx context.Context, field graphql.C }) if err != nil { ec.Error(ctx, err) - return graphql.Null } if resTmp == nil { if !graphql.HasFieldError(ctx, fc) { @@ -1196,7 +1192,6 @@ func (ec *executionContext) _Query___type(ctx context.Context, field graphql.Col }) if err != nil { ec.Error(ctx, err) - return graphql.Null } if resTmp == nil { return graphql.Null @@ -1270,7 +1265,6 @@ func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.C }) if err != nil { ec.Error(ctx, err) - return graphql.Null } if resTmp == nil { return graphql.Null @@ -4587,7 +4581,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr }) out := graphql.NewFieldSet(fields) - var invalids uint32 for i, field := range fields { innerCtx := graphql.WithRootFieldContext(ctx, &graphql.RootFieldContext{ Object: field.Name, @@ -4607,9 +4600,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } }() res = ec._Query_uptime(ctx, field) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } return res } @@ -4630,9 +4620,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } }() res = ec._Query_version(ctx, field) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } return res } @@ -4673,9 +4660,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } }() res = ec._Query_services(ctx, field) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } return res } @@ -4703,9 +4687,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } } out.Dispatch() - if invalids > 0 { - return graphql.Null - } return out } diff --git a/api/graph/model/model.go b/api/graph/model/model.go index 2e0197f..49f7803 100644 --- a/api/graph/model/model.go +++ b/api/graph/model/model.go @@ -1,16 +1,17 @@ package model import ( + "github.com/graph-guard/ggproxy/config" "github.com/graph-guard/ggproxy/engine/playmon" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/ggproxy/statistics" ) type Service struct { - Parser *gqlparse.Parser - Engine *playmon.Engine - TemplatesByID map[string]*Template - Stats *statistics.ServiceSync + Parser *gqlparse.Parser + Engine *playmon.Engine + Templates map[*config.Template]*Template + Stats *statistics.ServiceSync ID string `json:"id"` TemplatesEnabled []*Template `json:"templatesEnabled"` diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index b190618..d993c15 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -2,6 +2,7 @@ package graph // This file will be automatically regenerated based on the schema, any resolver implementations // will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.24 import ( "bytes" @@ -10,6 +11,7 @@ import ( "github.com/graph-guard/ggproxy/api/graph/generated" "github.com/graph-guard/ggproxy/api/graph/model" + "github.com/graph-guard/ggproxy/config" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/ggproxy/utilities/tokenwriter" ) @@ -72,13 +74,13 @@ func (r *serviceResolver) MatchAll(ctx context.Context, obj *model.Service, quer ) { m.TimeParsingNs = nsToF64(time.Since(startParsing).Nanoseconds()) startMatching := time.Now() - obj.Engine.MatchAll( + obj.Engine.Match( varVals, operation[0].ID, selectionSet, - func(id string) { - t := obj.TemplatesByID[id] - m.Templates = append(m.Templates, t) + func(t *config.Template) (stop bool) { + m.Templates = append(m.Templates, obj.Templates[t]) + return false }, ) m.TimeMatchingNs = nsToF64(time.Since(startMatching).Nanoseconds()) @@ -127,9 +129,13 @@ func (r *serviceResolver) Match(ctx context.Context, obj *model.Service, query s ) { m.TimeParsingNs = nsToF64(time.Since(startParsing).Nanoseconds()) startMatching := time.Now() - if id := obj.Engine.Match(varVals, operation[0].ID, selectionSet); id != "" { - m.Templates = []*model.Template{obj.TemplatesByID[id]} - } + obj.Engine.Match( + varVals, operation[0].ID, selectionSet, + func(t *config.Template) (stop bool) { + m.Templates = []*model.Template{obj.Templates[t]} + return true + }, + ) m.TimeMatchingNs = nsToF64(time.Since(startMatching).Nanoseconds()) var forwarded bytes.Buffer if err = tokenwriter.Write(&forwarded, operation); err != nil { @@ -188,8 +194,6 @@ func (r *Resolver) Service() generated.ServiceResolver { return &serviceResolver // Template returns generated.TemplateResolver implementation. func (r *Resolver) Template() generated.TemplateResolver { return &templateResolver{r} } -type ( - queryResolver struct{ *Resolver } - serviceResolver struct{ *Resolver } - templateResolver struct{ *Resolver } -) +type queryResolver struct{ *Resolver } +type serviceResolver struct{ *Resolver } +type templateResolver struct{ *Resolver } diff --git a/engine/playmon/bench_test.go b/engine/playmon/bench_test.go index 84acb6c..b9d7f7a 100644 --- a/engine/playmon/bench_test.go +++ b/engine/playmon/bench_test.go @@ -3,6 +3,7 @@ package playmon_test import ( "testing" + "github.com/graph-guard/ggproxy/config" "github.com/graph-guard/ggproxy/engine/playmon" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/ggproxy/testsetup" @@ -41,7 +42,44 @@ func BenchmarkMatchStarwars(b *testing.B) { b.ResetTimer() for n := 0; n < b.N; n++ { - GS = e.Match(varvals, opr, selset) + e.Match( + varvals, opr, selset, + func(t *config.Template) (stop bool) { + GS = t.ID + return false + }, + ) + } +} + +func BenchmarkMatchStarwarsWithParser(b *testing.B) { + s := testsetup.Starwars() + service := s.Config.ServicesEnabled[0] + e := playmon.New(service) + + p := gqlparse.NewParser(service.Schema) + src := []byte(s.Tests[1].Client.Input.BodyJSON["query"].(string)) + + b.ResetTimer() + for n := 0; n < b.N; n++ { + p.Parse( + src, nil, nil, + func( + varValues [][]gqlparse.Token, + operation, selectionSet []gqlparse.Token, + ) { + e.Match( + varValues, operation[0].ID, selectionSet, + func(t *config.Template) (stop bool) { + GS = t.ID + return false + }, + ) + }, + func(err error) { + b.Fatalf("parsing request: %v", err) + }, + ) } } diff --git a/engine/playmon/internal/pathscan/pathscan.go b/engine/playmon/internal/pathscan/pathscan.go index dd48a19..faa94e4 100644 --- a/engine/playmon/internal/pathscan/pathscan.go +++ b/engine/playmon/internal/pathscan/pathscan.go @@ -221,11 +221,6 @@ func (s *PathScanner) InTokens( } } -func (s *PathScanner) valByte(element byte) { - s.valStack.Push(1) - s.valPathBuf = append(s.valPathBuf, element) -} - func (s *PathScanner) valWithDiv(div byte, element []byte) { s.valStack.Push(1 + len(element)) s.valPathBuf = append(s.valPathBuf, div) @@ -238,11 +233,6 @@ func (s *PathScanner) valPop() { s.valStack.Pop() } -func (s *PathScanner) structuralByte(element byte) { - s.structuralStack.Push(1) - s.structuralPathBuf = append(s.structuralPathBuf, element) -} - func (s *PathScanner) structuralWithDiv(div byte, element []byte) { s.structuralStack.Push(1 + len(element)) s.structuralPathBuf = append(s.structuralPathBuf, div) diff --git a/engine/playmon/internal/pathscan/pathscan_test.go b/engine/playmon/internal/pathscan/pathscan_test.go index aafbff9..672fc93 100644 --- a/engine/playmon/internal/pathscan/pathscan_test.go +++ b/engine/playmon/internal/pathscan/pathscan_test.go @@ -1,7 +1,6 @@ package pathscan_test import ( - "fmt" "sort" "testing" @@ -553,8 +552,6 @@ var testsInAST = []struct { func TestInAST(t *testing.T) { for _, tt := range testsInAST { t.Run(tt.Name, func(t *testing.T) { - x := tt.Name - fmt.Println(x) o, _, errs := gqt.Parse([]byte(tt.GQTTemplateSrc)) require.Nil(t, errs) diff --git a/engine/playmon/playmon.go b/engine/playmon/playmon.go index feb7e47..7788e8e 100644 --- a/engine/playmon/playmon.go +++ b/engine/playmon/playmon.go @@ -90,7 +90,8 @@ func (e *Engine) Match( variableValues [][]gqlparse.Token, queryType gqlscan.Token, selectionSet []gqlparse.Token, -) (id string) { + onMatch func(*config.Template) (stop bool), +) { e.reset() var mismatch bool e.pathScanner.InTokens( @@ -118,7 +119,7 @@ func (e *Engine) Match( }, ) if mismatch { - return "" + return } e.matcher.Match(e.structuralPaths, func(t *config.Template) (stop bool) { @@ -130,18 +131,6 @@ func (e *Engine) Match( return false } } - id = t.ID - return false + return onMatch(t) }) - return id -} - -// MatchAll calls fn for every matching template. -func (e *Engine) MatchAll( - variableValues [][]gqlparse.Token, - queryType gqlscan.Token, - selectionSet []gqlparse.Token, - fn func(id string), -) { - panic("todo") } diff --git a/go.mod b/go.mod index 2bea375..e1a9088 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,14 @@ module github.com/graph-guard/ggproxy go 1.18 require ( - github.com/99designs/gqlgen v0.17.13 + github.com/99designs/gqlgen v0.17.24 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dustin/go-humanize v1.0.0 github.com/google/go-cmp v0.5.8 github.com/google/uuid v1.3.0 github.com/graph-guard/backend v0.0.0-20220826171348-e3dcd100c82b github.com/graph-guard/gqlscan v1.0.0 - github.com/graph-guard/gqt/v4 v4.0.7 + github.com/graph-guard/gqt/v4 v4.0.9 github.com/phuslu/log v1.0.81 github.com/pierrec/xxHash v0.1.5 github.com/stretchr/testify v1.7.2 @@ -41,8 +41,7 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect + golang.org/x/text v0.3.8 // indirect golang.org/x/tools v0.1.12 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index b57485f..b81be1a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/99designs/gqlgen v0.17.13 h1:ETUEqvRg5Zvr1lXtpoRdj026fzVay0ZlJPwI33qXLIw= -github.com/99designs/gqlgen v0.17.13/go.mod h1:w1brbeOdqVyNJI553BGwtwdVcYu1LKeYE1opLWN9RgQ= +github.com/99designs/gqlgen v0.17.24 h1:pcd/HFIoSdRvyADYQG2dHvQN2KZqX/nXzlVm6TMMq7E= +github.com/99designs/gqlgen v0.17.24/go.mod h1:BMhYIhe4bp7OlCo5I2PnowSK/Wimpv/YlxfNkqZGwLo= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= @@ -34,8 +34,8 @@ github.com/graph-guard/backend v0.0.0-20220826171348-e3dcd100c82b h1:MTZ+FdD6qIy github.com/graph-guard/backend v0.0.0-20220826171348-e3dcd100c82b/go.mod h1:x2XK0889DedSc5iB1AZzg0dJdWaHx8UiRo1vnv55kNc= github.com/graph-guard/gqlscan v1.0.0 h1:hUEDqnJQJ7nV9S8hCPJuYI/H97KP3unu0Gu7wd0wV8A= github.com/graph-guard/gqlscan v1.0.0/go.mod h1:A7TeIFxoiP39gw/r4sWjDnTZJnNy+4kaytP9Kh7Luuk= -github.com/graph-guard/gqt/v4 v4.0.7 h1:xp07pamaP7wPqOTRp7+m4wUdpZ78unbisyh44SFnUtE= -github.com/graph-guard/gqt/v4 v4.0.7/go.mod h1:tXBLe4Gr3S/y8pM6ABkcIWFcspyU1c1YfrsXdBf0hKE= +github.com/graph-guard/gqt/v4 v4.0.9 h1:rqCwhrns1u65a3GVutFQEPXWxO8Y6R3fv+t8oPsFhVU= +github.com/graph-guard/gqt/v4 v4.0.9/go.mod h1:tXBLe4Gr3S/y8pM6ABkcIWFcspyU1c1YfrsXdBf0hKE= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM= @@ -51,9 +51,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc= github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/phuslu/log v1.0.81 h1:07l/g+gde7oD77iCHXst/0iSu40BbcEkvv4pTeQCe+c= @@ -84,7 +83,6 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC github.com/valyala/fasthttp v1.38.0 h1:yTjSSNjuDi2PPvXY2836bIwLmiTS2T4T9p1coQshpco= github.com/valyala/fasthttp v1.38.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -github.com/vektah/gqlparser/v2 v2.4.6/go.mod h1:flJWIR04IMQPGz+BXLrORkrARBxv/rtyIAFvd/MceW0= github.com/vektah/gqlparser/v2 v2.5.1 h1:ZGu+bquAY23jsxDRcYpWjttRZrUz07LbiY77gUOHcr4= github.com/vektah/gqlparser/v2 v2.5.1/go.mod h1:mPgqFBu/woKTVYWyNk8cO3kh4S/f4aRFZrvOnp3hmCs= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= @@ -92,50 +90,49 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr github.com/yourbasic/bit v0.0.0-20180313074424-45a4409f4082 h1:AWIZQ6fJPAAZdCUElj007LvHa/ER8nOn3CHWajn+1QY= github.com/yourbasic/bit v0.0.0-20180313074424-45a4409f4082/go.mod h1:SC4yTthuwUIud4hT6D7kJGIYmhnskaQnm3VD2VYM8EM= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20220823124025-807a23277127 h1:S4NrSKDfihhl3+4jSTgwoIevKxX9p7Iv9x++OEIptDo= golang.org/x/exp v0.0.0-20220823124025-807a23277127/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 h1:v1W7bwXHsnLLloWYTVEdvGvA7BHMeBYsPcF0GLDxIRs= -golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/server/api.go b/server/api.go index 67f3a75..257927c 100644 --- a/server/api.go +++ b/server/api.go @@ -251,9 +251,12 @@ func makeService( ) *model.Service { stats := proxyServer.GetServiceStatistics(s.ID) service := &model.Service{ - Parser: gqlparse.NewParser(s.Schema), - Stats: stats, - TemplatesByID: make(map[string]*model.Template, len(s.Templates)), + Parser: gqlparse.NewParser(s.Schema), + Stats: stats, + Templates: make( + map[*config.Template]*model.Template, + len(s.Templates), + ), ID: s.ID, ForwardURL: s.ForwardURL, Enabled: s.Enabled, @@ -277,7 +280,7 @@ func makeService( Enabled: t.Enabled, } service.TemplatesEnabled = append(service.TemplatesEnabled, tm) - service.TemplatesByID[t.ID] = tm + service.Templates[t] = tm } service.Engine = playmon.New(s) diff --git a/server/proxy.go b/server/proxy.go index c7039af..48891e1 100644 --- a/server/proxy.go +++ b/server/proxy.go @@ -193,8 +193,15 @@ func (s *Proxy) handle(ctx *fasthttp.RequestCtx) { operation []gqlparse.Token, selectionSet []gqlparse.Token, ) { - templateID := m.Engine.Match(varVals, operation[0].ID, selectionSet) - if templateID == "" { + var tmpl *config.Template + m.Engine.Match( + varVals, operation[0].ID, selectionSet, + func(t *config.Template) (stop bool) { + tmpl = t + return false + }, + ) + if tmpl == nil { timeProcessing := time.Since(start) service.statistics.Update( len(body), 0, @@ -207,7 +214,7 @@ func (s *Proxy) handle(ctx *fasthttp.RequestCtx) { return } - templateStatistics := service.templateStatistics[templateID] + templateStatistics := service.templateStatistics[tmpl.ID] timeProcessing := time.Since(start) startForward := time.Now() diff --git a/testsetup/starwars/services_enabled/starwars.graphqls b/testsetup/starwars/services_enabled/starwars.graphqls new file mode 100644 index 0000000..48af8e5 --- /dev/null +++ b/testsetup/starwars/services_enabled/starwars.graphqls @@ -0,0 +1,131 @@ +# The query type, represents all of the entry points into our object graph +type Query { + hero(episode: Episode = NEWHOPE): Character + reviews(episode: Episode!, since: Time): [Review!]! + search(text: String!): [SearchResult!]! + character(id: ID!): Character + droid(id: ID!): Droid + human(id: ID!): Human + starship(id: ID!): Starship +} +# The mutation type, represents all updates we can make to our data +type Mutation { + createReview(episode: Episode!, review: ReviewInput!): Review +} +# The episodes in the Star Wars trilogy +enum Episode { + # Star Wars Episode IV: A New Hope, released in 1977. + NEWHOPE + # Star Wars Episode V: The Empire Strikes Back, released in 1980. + EMPIRE + # Star Wars Episode VI: Return of the Jedi, released in 1983. + JEDI +} +# A character from the Star Wars universe +interface Character { + # The ID of the character + id: ID! + # The name of the character + name: String! + # The friends of the character, or an empty list if they have none + friends: [Character!] + # The friends of the character exposed as a connection with edges + friendsConnection(first: Int, after: ID): FriendsConnection! + # The movies this character appears in + appearsIn: [Episode!]! +} +# Units of height +enum LengthUnit { + # The standard unit around the world + METER + # Primarily used in the United States + FOOT +} +# A humanoid creature from the Star Wars universe +type Human implements Character { + # The ID of the human + id: ID! + # What this human calls themselves + name: String! + # Height in the preferred unit, default is meters + height(unit: LengthUnit = METER): Float! + # Mass in kilograms, or null if unknown + mass: Float + # This human's friends, or an empty list if they have none + friends: [Character!] + # The friends of the human exposed as a connection with edges + friendsConnection(first: Int, after: ID): FriendsConnection! + # The movies this human appears in + appearsIn: [Episode!]! + # A list of starships this person has piloted, or an empty list if none + starships: [Starship!] +} +# An autonomous mechanical character in the Star Wars universe +type Droid implements Character { + # The ID of the droid + id: ID! + # What others call this droid + name: String! + # This droid's friends, or an empty list if they have none + friends: [Character!] + # The friends of the droid exposed as a connection with edges + friendsConnection(first: Int, after: ID): FriendsConnection! + # The movies this droid appears in + appearsIn: [Episode!]! + # This droid's primary function + primaryFunction: String +} +# A connection object for a character's friends +type FriendsConnection { + # The total number of friends + totalCount: Int! + # The edges for each of the character's friends. + edges: [FriendsEdge!] + # A list of the friends, as a convenience when edges are not needed. + friends: [Character!] + # Information for paginating this connection + pageInfo: PageInfo! +} +# An edge object for a character's friends +type FriendsEdge { + # A cursor used for pagination + cursor: ID! + # The character represented by this friendship edge + node: Character +} +# Information for paginating this connection +type PageInfo { + startCursor: ID! + endCursor: ID! + hasNextPage: Boolean! +} +# Represents a review for a movie +type Review { + # The number of stars this review gave, 1-5 + stars: Int! + # Comment about the movie + commentary: String + # when the review was posted + time: Time +} +# The input object sent when someone is creating a new review +input ReviewInput { + # 0-5 stars + stars: Int! + # Comment about the movie, optional + commentary: String + # when the review was posted + time: Time +} +type Starship { + # The ID of the starship + id: ID! + # The name of the starship + name: String! + # Length of the starship, along the longest axis + length(unit: LengthUnit = METER): Float! + # coordinates tracking this ship + history: [[Int!]!]! +} +union SearchResult = Human | Droid | Starship +scalar Time diff --git a/testsetup/starwars/services_enabled/starwars.yml b/testsetup/starwars/services_enabled/starwars.yml index 2f8f239..8342501 100644 --- a/testsetup/starwars/services_enabled/starwars.yml +++ b/testsetup/starwars/services_enabled/starwars.yml @@ -9,5 +9,7 @@ forward-url: "http://localhost:8080/starwars" # true for the reduced version. forward-reduced: true +schema: starwars.graphqls + all-templates: starwars/templates_enabled enabled-templates: starwars/templates_enabled diff --git a/testsetup/starwars/services_enabled/starwars/templates_enabled/d.gqt b/testsetup/starwars/services_enabled/starwars/templates_enabled/d.gqt index f5aafe8..24217ab 100644 --- a/testsetup/starwars/services_enabled/starwars/templates_enabled/d.gqt +++ b/testsetup/starwars/services_enabled/starwars/templates_enabled/d.gqt @@ -2,7 +2,7 @@ name: "D" --- query { - hero(episode: EMPIRE || JEDI) { + hero(episode: JEDI) { id name appearsIn diff --git a/testsetup/starwars/services_enabled/starwars/templates_enabled/e.gqt b/testsetup/starwars/services_enabled/starwars/templates_enabled/e.gqt new file mode 100644 index 0000000..4c0fda6 --- /dev/null +++ b/testsetup/starwars/services_enabled/starwars/templates_enabled/e.gqt @@ -0,0 +1,26 @@ +--- +name: "E" +tags: + - constraint equal + - constraint not equal + - constraint any + - type conditions +--- +query { + hero(episode: EMPIRE) { + id + name + ... on Droid { primaryFunction } + ... on Human { height(unit: *) mass } + appearsIn + friendsConnection { + totalCount + friends { + id name appearsIn + ...on Droid { primaryFunction } + ... on Human { height(unit: *) mass } + } + } + } + starship(id:"3001") { name length(unit: != METER) } +} diff --git a/testsetup/starwars/services_enabled/starwars/templates_enabled/f.gqt b/testsetup/starwars/services_enabled/starwars/templates_enabled/f.gqt new file mode 100644 index 0000000..91cd3be --- /dev/null +++ b/testsetup/starwars/services_enabled/starwars/templates_enabled/f.gqt @@ -0,0 +1,14 @@ +--- +name: "F" +--- +query { + hero(episode: JEDI) { + id + appearsIn + } + reviews(episode: EMPIRE || JEDI) { + stars + commentary + time + } +} diff --git a/testsetup/starwars/test_qry2_ok.yaml b/testsetup/starwars/test_qry2_ok.yaml new file mode 100644 index 0000000..bbd1728 --- /dev/null +++ b/testsetup/starwars/test_qry2_ok.yaml @@ -0,0 +1,212 @@ +client: + input: + method: POST + endpoint: /testservice + body(JSON): + query: > + { + hero(episode: EMPIRE) { + id, name, appearsIn + ...droidFrag + ...humanFrag + friendsConnection { + totalCount + friends { + id, name, appearsIn + ...droidFrag + ...humanFrag + } + } + } + starship(id:"3001") { name, length(unit:FOOT) } + } + fragment droidFrag on Droid { primaryFunction } + fragment humanFrag on Human { height(unit: FOOT), mass } + + + expect-response: + status: 200 + headers: + Content-Length: ^166$ + Content-Type: ^text/plain; charset=utf-8$ + Server: ^fasthttp$ + Date: . + X-Custom-Header: value + body(JSON): + data: + "hero": { + "id": "1000", + "name": "Luke Skywalker", + "height": 5.6430448, + "mass": 77, + "appearsIn": [ + "NEWHOPE", + "EMPIRE", + "JEDI" + ], + "friendsConnection": { + "totalCount": 4, + "friends": [ + { + "id": "1002", + "name": "Han Solo", + "appearsIn": [ + "NEWHOPE", + "EMPIRE", + "JEDI" + ], + "height": 5.905512, + "mass": 80 + }, + { + "id": "1003", + "name": "Leia Organa", + "appearsIn": [ + "NEWHOPE", + "EMPIRE", + "JEDI" + ], + "height": 4.92126, + "mass": 49 + }, + { + "id": "2000", + "name": "C-3PO", + "appearsIn": [ + "NEWHOPE", + "EMPIRE", + "JEDI" + ], + "primaryFunction": "Protocol" + }, + { + "id": "2001", + "name": "R2-D2", + "appearsIn": [ + "NEWHOPE", + "EMPIRE", + "JEDI" + ], + "primaryFunction": "Astromech" + } + ] + } + } + "starship": { + "name": "X-Wing", + "length": 41.0105 + } + +destination: + expect-forwarded: + headers: + X-Forwarded-Host: ^localhost:8000$ + X-Forwarded-For: 0.0.0.0 + X-Forwarded-Proto: ^HTTP/1.1$ + Host: ^localhost:8081$ + Content-Length: ^97$ + Content-Type: ^application/json$ + User-Agent: ^fasthttp$ + Date: . + body(JSON): + query: > + { + hero(episode: EMPIRE) { + id, name, appearsIn + ...droidFrag + ...humanFrag + friendsConnection { + totalCount + friends { + id, name, appearsIn + ...droidFrag + ...humanFrag + } + } + } + starship(id:"3001") { name, length(unit:FOOT) } + } + fragment droidFrag on Droid { primaryFunction } + fragment humanFrag on Human { height(unit: FOOT), mass } + + response: + status: 200 + headers: + X-Custom-Header: value + body(JSON): + data: + "hero": { + "id": "1000", + "name": "Luke Skywalker", + "height": 5.6430448, + "mass": 77, + "appearsIn": [ + "NEWHOPE", + "EMPIRE", + "JEDI" + ], + "friendsConnection": { + "totalCount": 4, + "friends": [ + { + "id": "1002", + "name": "Han Solo", + "appearsIn": [ + "NEWHOPE", + "EMPIRE", + "JEDI" + ], + "height": 5.905512, + "mass": 80 + }, + { + "id": "1003", + "name": "Leia Organa", + "appearsIn": [ + "NEWHOPE", + "EMPIRE", + "JEDI" + ], + "height": 4.92126, + "mass": 49 + }, + { + "id": "2000", + "name": "C-3PO", + "appearsIn": [ + "NEWHOPE", + "EMPIRE", + "JEDI" + ], + "primaryFunction": "Protocol" + }, + { + "id": "2001", + "name": "R2-D2", + "appearsIn": [ + "NEWHOPE", + "EMPIRE", + "JEDI" + ], + "primaryFunction": "Astromech" + } + ] + } + } + "starship": { + "name": "X-Wing", + "length": 41.0105 + } +logs: + - level: info + message: 'listening' + host: localhost:8080 + tls: false + services: + - testservice + - level: info + message: 'handling request' + path: /testservice + - level: debug + path: /testservice + query: '{"query":"query { queryFirstField { queryFirstSubfield querySecondSubfield } querySecondField }"}' diff --git a/testsetup/testsetup.go b/testsetup/testsetup.go index 45672ec..e69e022 100644 --- a/testsetup/testsetup.go +++ b/testsetup/testsetup.go @@ -113,31 +113,39 @@ func readTests(fsys fs.FS, root string) ([]Test, error) { return nil, fmt.Errorf("decoding YAML %q: %w", p, err) } - isXOR( + if err := isXOR( m.Client.Input.Body, m.Client.Input.BodyJSON, "client.input.body", "client.input.body(JSON)", - ) - isXOR( + ); err != nil { + return nil, err + } + if err := isXOR( m.Client.ExpectResponse.Body, m.Client.ExpectResponse.BodyJSON, "client.expect-response.body", "client.expect-response.body(JSON)", - ) + ); err != nil { + return nil, err + } if m.Destination != nil { - isXOR( + if err := isXOR( m.Destination.ExpectForwarded.Body, m.Destination.ExpectForwarded.BodyJSON, "destination.expect-forwarded.body", "destination.expect-forwarded.body(JSON)", - ) - isXOR( + ); err != nil { + return nil, err + } + if err := isXOR( m.Destination.Response.Body, m.Destination.Response.BodyJSON, "destination.expect-forwarded.body", "destination.expect-forwarded.body(JSON)", - ) + ); err != nil { + return nil, err + } } tests = append(tests, Test{ From 2ebf67f1fa6081f324932688fc80822da5426f5e Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Thu, 23 Feb 2023 00:45:55 +0100 Subject: [PATCH 13/35] feat: Properly stringify placeholder tokens --- gqlparse/gqlparse.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gqlparse/gqlparse.go b/gqlparse/gqlparse.go index be36d3a..ef77f83 100644 --- a/gqlparse/gqlparse.go +++ b/gqlparse/gqlparse.go @@ -26,6 +26,12 @@ type Token struct { } func (t Token) String() string { + if t.ID >= TokenTypeValIndexOffset && + t.ID < TokenTypeFragHostTypeIndexOffset { + return fmt.Sprintf("gqlparse_var_index [%d]", t.VariableIndex()) + } else if t.ID >= TokenTypeFragHostTypeIndexOffset { + return fmt.Sprintf("gqlparse_fht_index [%d]", t.FragHostTypeIndex()) + } if t.Value == nil { return t.ID.String() } From f7d01355b2b5991f34b9a2ae86eff9103f711a99 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Thu, 23 Feb 2023 00:49:34 +0100 Subject: [PATCH 14/35] fix: Read GraphQL variables in engine --- .../internal/constrcheck/constrcheck.go | 47 +++++++++++++------ .../internal/constrcheck/iswrongtype.go | 15 +++++- .../internal/constrcheck/iswrongtype_test.go | 30 +++++++++++- engine/playmon/playmon.go | 5 +- 4 files changed, 77 insertions(+), 20 deletions(-) diff --git a/engine/playmon/internal/constrcheck/constrcheck.go b/engine/playmon/internal/constrcheck/constrcheck.go index 58d9b48..65995fa 100644 --- a/engine/playmon/internal/constrcheck/constrcheck.go +++ b/engine/playmon/internal/constrcheck/constrcheck.go @@ -26,6 +26,9 @@ type Checker struct { checkers map[uint64]check pathByVarDecl map[*gqt.VariableDeclaration]uint64 + // gqlVarVals is set in every call to Check. + gqlVarVals [][]gqlparse.Token + // varValues is set in every call to Check. varValues map[uint64][]gqlparse.Token @@ -120,6 +123,7 @@ func (c *Checker) pushStackBool(v bool) { // Check returns true if the value for the given path is accepted, // otherwise returns false. func (c *Checker) Check( + gqlVarVals [][]gqlparse.Token, varVals map[uint64][]gqlparse.Token, path uint64, value []gqlparse.Token, @@ -129,7 +133,10 @@ func (c *Checker) Check( if cf == nil { return false } - c.varValues, c.inputValue, c.checkedValue = varVals, value, value + c.gqlVarVals = gqlVarVals + c.varValues = varVals + c.inputValue = value + c.checkedValue = value return cf(c) } @@ -460,7 +467,9 @@ func makeCheck( switch e := e.(type) { case *gqt.ConstrAny: return func(c *Checker) (match bool) { - if wrong, _ := isWrongType(expect, c.checkedValue, schema); wrong { + if wrong, _ := isWrongType( + c.gqlVarVals, expect, c.checkedValue, schema, + ); wrong { return false } @@ -850,7 +859,9 @@ func makeCheck( } itemCheck := makeCheck(e.Constraint, expectItem, schema) return func(c *Checker) (match bool) { - if wrong, _ := isWrongType(expect, c.checkedValue, schema); wrong { + if wrong, _ := isWrongType( + c.gqlVarVals, expect, c.checkedValue, schema, + ); wrong { return false } for c.checkedValue[0].ID != gqlscan.TokenArr { @@ -1018,7 +1029,7 @@ func Designation(c *Checker, e gqt.Expression) string { func (c *Checker) expectOrNum(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(expect, c.checkedValue, c.schema) + wrongType, _ = isWrongType(c.gqlVarVals, expect, c.checkedValue, c.schema) return wrongType } return c.checkedValue[0].ID != gqlscan.TokenInt && @@ -1027,7 +1038,7 @@ func (c *Checker) expectOrNum(expect *gqlast.Type) (wrongType bool) { func (c *Checker) expectOrInt(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(expect, c.checkedValue, c.schema) + wrongType, _ = isWrongType(c.gqlVarVals, expect, c.checkedValue, c.schema) return wrongType } return c.checkedValue[0].ID != gqlscan.TokenInt @@ -1035,7 +1046,7 @@ func (c *Checker) expectOrInt(expect *gqlast.Type) (wrongType bool) { func (c *Checker) expectOrFloat(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(expect, c.checkedValue, c.schema) + wrongType, _ = isWrongType(c.gqlVarVals, expect, c.checkedValue, c.schema) return wrongType } return c.checkedValue[0].ID != gqlscan.TokenFloat @@ -1043,7 +1054,7 @@ func (c *Checker) expectOrFloat(expect *gqlast.Type) (wrongType bool) { func (c *Checker) expectOrString(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(expect, c.checkedValue, c.schema) + wrongType, _ = isWrongType(c.gqlVarVals, expect, c.checkedValue, c.schema) return wrongType } return c.checkedValue[0].ID != gqlscan.TokenStr && @@ -1052,7 +1063,7 @@ func (c *Checker) expectOrString(expect *gqlast.Type) (wrongType bool) { func (c *Checker) expectOrEnum(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(expect, c.checkedValue, c.schema) + wrongType, _ = isWrongType(c.gqlVarVals, expect, c.checkedValue, c.schema) return wrongType } return c.checkedValue[0].ID != gqlscan.TokenEnumVal @@ -1060,7 +1071,7 @@ func (c *Checker) expectOrEnum(expect *gqlast.Type) (wrongType bool) { func (c *Checker) expectOrBool(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(expect, c.checkedValue, c.schema) + wrongType, _ = isWrongType(c.gqlVarVals, expect, c.checkedValue, c.schema) return wrongType } return c.checkedValue[0].ID != gqlscan.TokenTrue && @@ -1071,7 +1082,7 @@ func (c *Checker) expectOrHasLen( expect *gqlast.Type, ) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(expect, c.checkedValue, c.schema) + wrongType, _ = isWrongType(c.gqlVarVals, expect, c.checkedValue, c.schema) return wrongType } return c.checkedValue[0].ID != gqlscan.TokenArr && @@ -1286,7 +1297,7 @@ func makeEqNull( schema *gqlast.Schema, ) check { return func(c *Checker) (match bool) { - if wrong, _ := isWrongType(expect, c.checkedValue, schema); wrong { + if wrong, _ := isWrongType(c.gqlVarVals, expect, c.checkedValue, schema); wrong { return false } t := c.checkedValue[0].ID @@ -1300,7 +1311,9 @@ func makeNotEqNull( schema *gqlast.Schema, ) check { return func(c *Checker) (match bool) { - if wrong, _ := isWrongType(expect, c.checkedValue, schema); wrong { + if wrong, _ := isWrongType( + c.gqlVarVals, expect, c.checkedValue, schema, + ); wrong { return false } t := c.checkedValue[0].ID @@ -1367,7 +1380,7 @@ func makeEqObject( fieldChecked[v.Name.Name] = false } return func(c *Checker) (match bool) { - if wrong, _ := isWrongType(expect, c.checkedValue, schema); wrong { + if wrong, _ := isWrongType(c.gqlVarVals, expect, c.checkedValue, schema); wrong { return false } if c.checkedValue[0].ID != gqlscan.TokenObj { @@ -1433,7 +1446,9 @@ func makeNotEqObject( fieldChecked[v.Name.Name] = false } return func(c *Checker) (match bool) { - if wrong, _ := isWrongType(expect, c.checkedValue, schema); wrong { + if wrong, _ := isWrongType( + c.gqlVarVals, expect, c.checkedValue, schema, + ); wrong { return false } if c.checkedValue[0].ID != gqlscan.TokenObj { @@ -1487,7 +1502,9 @@ func makeEqExpression( schema *gqlast.Schema, ) check { return func(c *Checker) (match bool) { - if wrong, _ := isWrongType(expect, c.checkedValue, schema); wrong { + if wrong, _ := isWrongType( + c.gqlVarVals, expect, c.checkedValue, schema, + ); wrong { return false } c.resolveExpr(v) diff --git a/engine/playmon/internal/constrcheck/iswrongtype.go b/engine/playmon/internal/constrcheck/iswrongtype.go index e04584d..ee21b1e 100644 --- a/engine/playmon/internal/constrcheck/iswrongtype.go +++ b/engine/playmon/internal/constrcheck/iswrongtype.go @@ -9,6 +9,7 @@ import ( // isWrongType returns true if the value represented in input // doesn't correspond to the type defined by expect. func isWrongType( + gqlVarValues [][]gqlparse.Token, expect *gqlast.Type, input []gqlparse.Token, schema *gqlast.Schema, @@ -18,6 +19,16 @@ func isWrongType( def = schema.Types[expect.NamedType] } + if input[0].ID >= gqlparse.TokenTypeValIndexOffset && + input[0].ID < gqlparse.TokenTypeFragHostTypeIndexOffset { + return isWrongType( + gqlVarValues, + expect, + gqlVarValues[input[0].VariableIndex()], + schema, + ) + } + switch input[0].ID { case gqlscan.TokenNull: if expect == nil { @@ -38,7 +49,7 @@ func isWrongType( return false, input } var wrongType bool - wrongType, input = isWrongType(expect, input, schema) + wrongType, input = isWrongType(gqlVarValues, expect, input, schema) if wrongType { return true, nil } @@ -131,7 +142,7 @@ func isWrongType( // Check field value var wrongType bool wrongType, input = isWrongType( - def.Fields[i].Type, input[1:], schema, + gqlVarValues, def.Fields[i].Type, input[1:], schema, ) if wrongType { return true, nil diff --git a/engine/playmon/internal/constrcheck/iswrongtype_test.go b/engine/playmon/internal/constrcheck/iswrongtype_test.go index a85a39f..090bea0 100644 --- a/engine/playmon/internal/constrcheck/iswrongtype_test.go +++ b/engine/playmon/internal/constrcheck/iswrongtype_test.go @@ -14,6 +14,7 @@ func TestIsWrongType_False(t *testing.T) { for _, tt := range []struct { Name string Schema string + GQLVarVals [][]gqlparse.Token ExpectType *gqlast.Type Input []gqlparse.Token }{ @@ -334,6 +335,18 @@ func TestIsWrongType_False(t *testing.T) { {ID: gqlscan.TokenInt, Value: []byte("42")}, }, }, + { + Name: "expect_float_get_int", + Schema: `type Query { x:Int }`, + ExpectType: &gqlast.Type{NamedType: "Float"}, + GQLVarVals: [][]gqlparse.Token{ + { /*Not to be used*/ }, + {{ID: gqlscan.TokenInt, Value: []byte("42")}}, + }, + Input: []gqlparse.Token{ + {ID: gqlparse.TokenTypeValIndexOffset + 1}, + }, + }, } { t.Run(tt.Name, func(t *testing.T) { var s *gqlast.Schema @@ -351,7 +364,7 @@ func TestIsWrongType_False(t *testing.T) { ) } - r, _ := isWrongType(tt.ExpectType, tt.Input, s) + r, _ := isWrongType(tt.GQLVarVals, tt.ExpectType, tt.Input, s) require.False(t, r) }) } @@ -361,9 +374,22 @@ func TestIsWrongType_True(t *testing.T) { for _, tt := range []struct { Name string Schema string + GQLVarVals [][]gqlparse.Token ExpectType *gqlast.Type Input []gqlparse.Token }{ + { + Name: "expect_non-null_int_get_null", + Schema: `type Query { x:Int }`, + GQLVarVals: [][]gqlparse.Token{ + { /*Not used*/ }, + {{ID: gqlscan.TokenNull}}, + }, + ExpectType: &gqlast.Type{NamedType: "Int", NonNull: true}, + Input: []gqlparse.Token{ + {ID: gqlparse.TokenTypeValIndexOffset + 1}, + }, + }, { Name: "expect_non-null_int_get_null", Schema: `type Query { x:Int }`, @@ -500,7 +526,7 @@ func TestIsWrongType_True(t *testing.T) { "type expectations are always schema-aware", ) } - r, _ := isWrongType(tt.ExpectType, tt.Input, s) + r, _ := isWrongType(tt.GQLVarVals, tt.ExpectType, tt.Input, s) require.True(t, r) }) } diff --git a/engine/playmon/playmon.go b/engine/playmon/playmon.go index 7788e8e..8753b68 100644 --- a/engine/playmon/playmon.go +++ b/engine/playmon/playmon.go @@ -126,7 +126,10 @@ func (e *Engine) Match( tm := e.templates[t.ID] for i := range e.argumentsSet { if !tm.ConstraintChecker.Check( - e.varValues, e.argumentsSet[i].PathHash, e.argumentsSet[i].Value, + variableValues, + e.varValues, + e.argumentsSet[i].PathHash, + e.argumentsSet[i].Value, ) { return false } From d00826e9a9efe01794746d9b44245254ad7891f4 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Thu, 23 Feb 2023 10:51:04 +0100 Subject: [PATCH 15/35] fix: Accept enum values as variable default value --- gqlparse/gqlparse.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gqlparse/gqlparse.go b/gqlparse/gqlparse.go index ef77f83..81536b1 100644 --- a/gqlparse/gqlparse.go +++ b/gqlparse/gqlparse.go @@ -942,6 +942,14 @@ func (r *Parser) validateAndIndexVars( string(expect.TypeName) != "ID" { isErr = true } + case gqlscan.TokenEnumVal: + i++ + if r.schema != nil { + if t := r.schema.Types[string(expect.TypeName)]; t != nil && + t.Kind != ast.Enum { + isErr = true + } + } case gqlscan.TokenObj: i++ SKIP_OBJ_INTERNALS: From aa9081d797fce4a36d41b286b5ebda1fd31900c6 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Fri, 3 Mar 2023 00:23:19 +0100 Subject: [PATCH 16/35] feat: Check types in gqlscan in schema-aware mode --- .../internal/constrcheck/constrcheck.go | 425 +++++++++--------- .../internal/constrcheck/iswrongtype.go | 98 ++-- .../internal/constrcheck/iswrongtype_test.go | 5 +- .../internal/countval}/count_values.go | 19 +- .../internal/countval}/count_values_test.go | 82 ++-- .../playmon/internal/scanval/length.go | 23 +- .../playmon/internal/scanval/length_test.go | 8 +- .../playmon/internal/scanval}/scan_values.go | 43 +- .../scanval}/scan_values_in_arrays.go | 61 +-- .../scanval}/scan_values_in_arrays_test.go | 50 ++- .../internal/scanval}/scan_values_test.go | 18 +- .../internal/tokenreader/tokenreader.go | 55 +++ .../internal/tokenreader/tokenreader_test.go | 168 +++++++ engine/playmon/playmon.go | 3 + gqlparse/gqlparse.go | 203 +++++++-- gqlparse/gqlparse_test.go | 265 ++++++++++- server/server_test.go | 2 +- .../all-services/service_a.yaml | 1 + .../all-templates/service_a/mut_arr_str.gqt | 10 + testsetup/{test2 => inputs_schema}/config.yml | 0 .../enabled-services/service_a.yaml | 1 + .../service_a/mut_arr_str.gqt | 10 + testsetup/inputs_schema/service_a.graphqls | 3 + .../test_arr_str_eq.yaml} | 32 +- .../starwars/templates_enabled/max_1.gqt | 14 + .../all-templates/service_a/template_a.gqt | 13 - .../service_a/template_a.gqt | 13 - testsetup/testsetup.go | 10 +- testsetup/testsetup_test.go | 6 +- utilities/stack/stack.go | 12 +- utilities/stack/stack_test.go | 40 ++ 31 files changed, 1186 insertions(+), 507 deletions(-) rename {gqlparse => engine/playmon/internal/countval}/count_values.go (67%) rename {gqlparse => engine/playmon/internal/countval}/count_values_test.go (86%) rename gqlparse/getvallen.go => engine/playmon/internal/scanval/length.go (65%) rename gqlparse/getvallen_test.go => engine/playmon/internal/scanval/length_test.go (94%) rename {gqlparse => engine/playmon/internal/scanval}/scan_values.go (59%) rename {gqlparse => engine/playmon/internal/scanval}/scan_values_in_arrays.go (57%) rename {gqlparse => engine/playmon/internal/scanval}/scan_values_in_arrays_test.go (89%) rename {gqlparse => engine/playmon/internal/scanval}/scan_values_test.go (96%) create mode 100644 engine/playmon/internal/tokenreader/tokenreader.go create mode 100644 engine/playmon/internal/tokenreader/tokenreader_test.go rename testsetup/{test2 => inputs_schema}/all-services/service_a.yaml (86%) create mode 100644 testsetup/inputs_schema/all-templates/service_a/mut_arr_str.gqt rename testsetup/{test2 => inputs_schema}/config.yml (100%) rename testsetup/{test2 => inputs_schema}/enabled-services/service_a.yaml (86%) create mode 100644 testsetup/inputs_schema/enabled-templates/service_a/mut_arr_str.gqt create mode 100644 testsetup/inputs_schema/service_a.graphqls rename testsetup/{test2/test_1.yaml => inputs_schema/test_arr_str_eq.yaml} (63%) create mode 100644 testsetup/starwars/services_enabled/starwars/templates_enabled/max_1.gqt delete mode 100644 testsetup/test2/all-templates/service_a/template_a.gqt delete mode 100644 testsetup/test2/enabled-templates/service_a/template_a.gqt diff --git a/engine/playmon/internal/constrcheck/constrcheck.go b/engine/playmon/internal/constrcheck/constrcheck.go index 65995fa..c37540f 100644 --- a/engine/playmon/internal/constrcheck/constrcheck.go +++ b/engine/playmon/internal/constrcheck/constrcheck.go @@ -7,7 +7,10 @@ import ( "strings" "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck/internal/union" + "github.com/graph-guard/ggproxy/engine/playmon/internal/countval" "github.com/graph-guard/ggproxy/engine/playmon/internal/pathscan" + "github.com/graph-guard/ggproxy/engine/playmon/internal/scanval" + "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/ggproxy/utilities/atoi" "github.com/graph-guard/gqlscan" @@ -26,16 +29,13 @@ type Checker struct { checkers map[uint64]check pathByVarDecl map[*gqt.VariableDeclaration]uint64 - // gqlVarVals is set in every call to Check. - gqlVarVals [][]gqlparse.Token - // varValues is set in every call to Check. varValues map[uint64][]gqlparse.Token // inputValue is set in every call to Check. inputValue []gqlparse.Token - // checkedValue is set in every call to Check. - checkedValue []gqlparse.Token + + reader *tokenreader.Reader // stack is reset in every call to Check. stack []union.Union @@ -133,10 +133,14 @@ func (c *Checker) Check( if cf == nil { return false } - c.gqlVarVals = gqlVarVals + c.reader.Vars = gqlVarVals c.varValues = varVals c.inputValue = value - c.checkedValue = value + c.reader.Main = value + // fmt.Println("CHECKED VALUE: ", len(c.checkedValue)) + // for i, v := range c.checkedValue { + // fmt.Printf(" %d: %s\n", i, v) + // } return cf(c) } @@ -430,6 +434,7 @@ func New(o *gqt.Operation, s *gqlast.Schema) *Checker { checkers: make(map[uint64]check), stack: make([]union.Union, 1024), pathByVarDecl: make(map[*gqt.VariableDeclaration]uint64), + reader: &tokenreader.Reader{}, } if errs := pathscan.InAST( o, @@ -467,30 +472,31 @@ func makeCheck( switch e := e.(type) { case *gqt.ConstrAny: return func(c *Checker) (match bool) { - if wrong, _ := isWrongType( - c.gqlVarVals, expect, c.checkedValue, schema, - ); wrong { + rBefore := *c.reader + if isWrongType(c.reader, expect, schema) { return false } + *c.reader = rBefore // Make sure the value is semantically valid. - return !gqlparse.ScanValuesInArrays(c.checkedValue, func(t []gqlparse.Token) (stop bool) { - if t[0].ID != gqlscan.TokenObj { - return false - } - t = t[1:] - checkedFields := map[string]struct{}{} - for t[0].ID != gqlscan.TokenObjEnd { - fieldName := t[0].Value - if _, ok := checkedFields[string(fieldName)]; ok { - // Duplicate field! Invalid object value. - return true + return !scanval.InArrays( + c.reader, + func(r *tokenreader.Reader) (stop bool) { + if r.ReadOne().ID != gqlscan.TokenObj { + return false } - checkedFields[string(fieldName)] = struct{}{} - t = t[1:] - } - return false - }) + checkedFields := map[string]struct{}{} + for r.PeekOne().ID != gqlscan.TokenObjEnd { + fieldName := r.ReadOne().Value + if _, ok := checkedFields[string(fieldName)]; ok { + // Duplicate field! Invalid object value. + return true + } + checkedFields[string(fieldName)] = struct{}{} + } + return false + }, + ) } case *gqt.ExprParentheses: return makeCheck(e.Expression, expect, schema) @@ -500,13 +506,13 @@ func makeCheck( exprCheckers[i] = makeCheck(e, expect, schema) } return func(c *Checker) (match bool) { - r := c.checkedValue + rBefore := *c.reader for _, e := range exprCheckers { if e(c) { return true } // Reset value to start checking from the same index - c.checkedValue = r + *c.reader = rBefore } return false } @@ -516,13 +522,13 @@ func makeCheck( exprCheckers[i] = makeCheck(e, expect, schema) } return func(c *Checker) (match bool) { - r := c.checkedValue + rBefore := *c.reader for _, e := range exprCheckers { if !e(c) { return false } // Reset value to start checking from the same index - c.checkedValue = r + *c.reader = rBefore } return true } @@ -533,9 +539,9 @@ func makeCheck( } c.resolveExpr(e.Value) u := c.popStack() - switch c.checkedValue[0].ID { + switch read := c.reader.ReadOne(); read.ID { case gqlscan.TokenInt: - i := atoi.MustI32(c.checkedValue[0].Value) + i := atoi.MustI32(read.Value) if u, ok := u.Int(); ok != union.ValueNone { match = i > u break @@ -543,11 +549,10 @@ func makeCheck( u, _ := u.Float() match = float64(i) > u case gqlscan.TokenFloat: - i := atoi.MustF64(c.checkedValue[0].Value) + i := atoi.MustF64(read.Value) u, _ := u.Float() match = i > u } - c.checkedValue = c.checkedValue[1:] return match } case *gqt.ConstrGreaterOrEqual: @@ -557,9 +562,9 @@ func makeCheck( } c.resolveExpr(e.Value) u := c.popStack() - switch c.checkedValue[0].ID { + switch read := c.reader.ReadOne(); read.ID { case gqlscan.TokenInt: - i := atoi.MustI32(c.checkedValue[0].Value) + i := atoi.MustI32(read.Value) if u, ok := u.Int(); ok != union.ValueNone { match = i >= u break @@ -567,11 +572,10 @@ func makeCheck( u, _ := u.Float() match = float64(i) >= u case gqlscan.TokenFloat: - i := atoi.MustF64(c.checkedValue[0].Value) + i := atoi.MustF64(read.Value) u, _ := u.Float() match = i >= u } - c.checkedValue = c.checkedValue[1:] return match } case *gqt.ConstrLess: @@ -581,9 +585,9 @@ func makeCheck( } c.resolveExpr(e.Value) u := c.popStack() - switch c.checkedValue[0].ID { + switch read := c.reader.ReadOne(); read.ID { case gqlscan.TokenInt: - i := atoi.MustI32(c.checkedValue[0].Value) + i := atoi.MustI32(read.Value) if u, ok := u.Int(); ok != union.ValueNone { match = i < u break @@ -591,11 +595,10 @@ func makeCheck( u, _ := u.Float() match = float64(i) < u case gqlscan.TokenFloat: - i := atoi.MustF64(c.checkedValue[0].Value) + i := atoi.MustF64(read.Value) u, _ := u.Float() match = i < u } - c.checkedValue = c.checkedValue[1:] return match } case *gqt.ConstrLessOrEqual: @@ -605,9 +608,9 @@ func makeCheck( } c.resolveExpr(e.Value) u := c.popStack() - switch c.checkedValue[0].ID { + switch read := c.reader.ReadOne(); read.ID { case gqlscan.TokenInt: - i := atoi.MustI32(c.checkedValue[0].Value) + i := atoi.MustI32(read.Value) if u, ok := u.Int(); ok != union.ValueNone { match = i <= u break @@ -615,11 +618,10 @@ func makeCheck( u, _ := u.Float() match = float64(i) <= u case gqlscan.TokenFloat: - i := atoi.MustF64(c.checkedValue[0].Value) + i := atoi.MustF64(read.Value) u, _ := u.Float() match = i <= u } - c.checkedValue = c.checkedValue[1:] return match } case *gqt.ConstrEquals: @@ -673,18 +675,13 @@ func makeCheck( } var length int - switch c.checkedValue[0].ID { + switch read := c.reader.ReadOne(); read.ID { case gqlscan.TokenArr: - var tokensRead int - length, tokensRead = gqlparse.CountValuesUntil( - c.checkedValue[1:], - gqlscan.TokenArrEnd, - ) - c.checkedValue = c.checkedValue[tokensRead:] + length, _ = countval.Until(c.reader, gqlscan.TokenArrEnd) case gqlscan.TokenStr: - length = len(c.checkedValue[0].Value) + length = len(read.Value) case gqlscan.TokenStrBlock: - length = len(c.checkedValue[0].Value) + length = len(read.Value) } c.resolveExpr(e.Value) @@ -704,18 +701,15 @@ func makeCheck( } var length int - switch c.checkedValue[0].ID { + switch read := c.reader.ReadOne(); read.ID { case gqlscan.TokenArr: - var tokensRead int - length, tokensRead = gqlparse.CountValuesUntil( - c.checkedValue[1:], - gqlscan.TokenArrEnd, + length, _ = countval.Until( + c.reader, gqlscan.TokenArrEnd, ) - c.checkedValue = c.checkedValue[tokensRead:] case gqlscan.TokenStr: - length = len(c.checkedValue[0].Value) + length = len(read.Value) case gqlscan.TokenStrBlock: - length = len(c.checkedValue[0].Value) + length = len(read.Value) } c.resolveExpr(e.Value) @@ -735,18 +729,15 @@ func makeCheck( } var length int - switch c.checkedValue[0].ID { + switch read := c.reader.ReadOne(); read.ID { case gqlscan.TokenArr: - var tokensRead int - length, tokensRead = gqlparse.CountValuesUntil( - c.checkedValue[1:], - gqlscan.TokenArrEnd, + length, _ = countval.Until( + c.reader, gqlscan.TokenArrEnd, ) - c.checkedValue = c.checkedValue[tokensRead:] case gqlscan.TokenStr: - length = len(c.checkedValue[0].Value) + length = len(read.Value) case gqlscan.TokenStrBlock: - length = len(c.checkedValue[0].Value) + length = len(read.Value) } c.resolveExpr(e.Value) @@ -766,18 +757,15 @@ func makeCheck( } var length int - switch c.checkedValue[0].ID { + switch read := c.reader.ReadOne(); read.ID { case gqlscan.TokenArr: - var tokensRead int - length, tokensRead = gqlparse.CountValuesUntil( - c.checkedValue[1:], - gqlscan.TokenArrEnd, + length, _ = countval.Until( + c.reader, gqlscan.TokenArrEnd, ) - c.checkedValue = c.checkedValue[tokensRead:] case gqlscan.TokenStr: - length = len(c.checkedValue[0].Value) + length = len(read.Value) case gqlscan.TokenStrBlock: - length = len(c.checkedValue[0].Value) + length = len(read.Value) } c.resolveExpr(e.Value) @@ -797,18 +785,15 @@ func makeCheck( } var length int - switch c.checkedValue[0].ID { + switch read := c.reader.ReadOne(); read.ID { case gqlscan.TokenArr: - var tokensRead int - length, tokensRead = gqlparse.CountValuesUntil( - c.checkedValue[1:], - gqlscan.TokenArrEnd, + length, _ = countval.Until( + c.reader, gqlscan.TokenArrEnd, ) - c.checkedValue = c.checkedValue[tokensRead:] case gqlscan.TokenStr: - length = len(c.checkedValue[0].Value) + length = len(read.Value) case gqlscan.TokenStrBlock: - length = len(c.checkedValue[0].Value) + length = len(read.Value) } c.resolveExpr(e.Value) @@ -828,18 +813,15 @@ func makeCheck( } var length int - switch c.checkedValue[0].ID { + switch read := c.reader.ReadOne(); read.ID { case gqlscan.TokenArr: - var tokensRead int - length, tokensRead = gqlparse.CountValuesUntil( - c.checkedValue[1:], - gqlscan.TokenArrEnd, + length, _ = countval.Until( + c.reader, gqlscan.TokenArrEnd, ) - c.checkedValue = c.checkedValue[tokensRead:] case gqlscan.TokenStr: - length = len(c.checkedValue[0].Value) + length = len(read.Value) case gqlscan.TokenStrBlock: - length = len(c.checkedValue[0].Value) + length = len(read.Value) } c.resolveExpr(e.Value) @@ -859,21 +841,19 @@ func makeCheck( } itemCheck := makeCheck(e.Constraint, expectItem, schema) return func(c *Checker) (match bool) { - if wrong, _ := isWrongType( - c.gqlVarVals, expect, c.checkedValue, schema, - ); wrong { + rBefore := *c.reader + if isWrongType(c.reader, expect, schema) { return false } - for c.checkedValue[0].ID != gqlscan.TokenArr { + *c.reader = rBefore + for c.reader.PeekOne().ID != gqlscan.TokenArr { return false } - c.checkedValue = c.checkedValue[1:] - for c.checkedValue[0].ID != gqlscan.TokenArrEnd { + for c.reader.PeekOne().ID != gqlscan.TokenArrEnd { if !itemCheck(c) { return false } } - c.checkedValue = c.checkedValue[1:] return true } } @@ -1029,65 +1009,83 @@ func Designation(c *Checker, e gqt.Expression) string { func (c *Checker) expectOrNum(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(c.gqlVarVals, expect, c.checkedValue, c.schema) - return wrongType + rBefore := *c.reader + isWrongType := isWrongType(c.reader, expect, c.schema) + *c.reader = rBefore + return isWrongType } - return c.checkedValue[0].ID != gqlscan.TokenInt && - c.checkedValue[0].ID != gqlscan.TokenFloat + v := c.reader.PeekOne() + return v.ID != gqlscan.TokenInt && v.ID != gqlscan.TokenFloat } func (c *Checker) expectOrInt(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(c.gqlVarVals, expect, c.checkedValue, c.schema) - return wrongType + rBefore := *c.reader + isWrongType := isWrongType(c.reader, expect, c.schema) + *c.reader = rBefore + return isWrongType } - return c.checkedValue[0].ID != gqlscan.TokenInt + v := c.reader.PeekOne() + return v.ID != gqlscan.TokenInt } func (c *Checker) expectOrFloat(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(c.gqlVarVals, expect, c.checkedValue, c.schema) - return wrongType + rBefore := *c.reader + isWrongType := isWrongType(c.reader, expect, c.schema) + *c.reader = rBefore + return isWrongType } - return c.checkedValue[0].ID != gqlscan.TokenFloat + v := c.reader.PeekOne() + return v.ID != gqlscan.TokenFloat } func (c *Checker) expectOrString(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(c.gqlVarVals, expect, c.checkedValue, c.schema) - return wrongType + rBefore := *c.reader + isWrongType := isWrongType(c.reader, expect, c.schema) + *c.reader = rBefore + return isWrongType } - return c.checkedValue[0].ID != gqlscan.TokenStr && - c.checkedValue[0].ID != gqlscan.TokenStrBlock + v := c.reader.PeekOne() + return v.ID != gqlscan.TokenStr && v.ID != gqlscan.TokenStrBlock } func (c *Checker) expectOrEnum(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(c.gqlVarVals, expect, c.checkedValue, c.schema) - return wrongType + rBefore := *c.reader + isWrongType := isWrongType(c.reader, expect, c.schema) + *c.reader = rBefore + return isWrongType } - return c.checkedValue[0].ID != gqlscan.TokenEnumVal + v := c.reader.PeekOne() + return v.ID != gqlscan.TokenEnumVal } func (c *Checker) expectOrBool(expect *gqlast.Type) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(c.gqlVarVals, expect, c.checkedValue, c.schema) - return wrongType + rBefore := *c.reader + isWrongType := isWrongType(c.reader, expect, c.schema) + *c.reader = rBefore + return isWrongType } - return c.checkedValue[0].ID != gqlscan.TokenTrue && - c.checkedValue[0].ID != gqlscan.TokenFalse + v := c.reader.PeekOne() + return v.ID != gqlscan.TokenTrue && v.ID != gqlscan.TokenFalse } func (c *Checker) expectOrHasLen( expect *gqlast.Type, ) (wrongType bool) { if expect != nil { - wrongType, _ = isWrongType(c.gqlVarVals, expect, c.checkedValue, c.schema) - return wrongType + rBefore := *c.reader + isWrongType := isWrongType(c.reader, expect, c.schema) + *c.reader = rBefore + return isWrongType } - return c.checkedValue[0].ID != gqlscan.TokenArr && - c.checkedValue[0].ID != gqlscan.TokenStr && - c.checkedValue[0].ID != gqlscan.TokenStrBlock + v := c.reader.PeekOne() + return v.ID != gqlscan.TokenArr && + v.ID != gqlscan.TokenStr && + v.ID != gqlscan.TokenStrBlock } func makeEqArray( @@ -1104,12 +1102,15 @@ func makeEqArray( checks[i] = makeCheck(v.Items[i], expect, schema) } return func(c *Checker) (match bool) { - if c.checkedValue[0].ID != gqlscan.TokenArr { + if c.reader.ReadOne().ID != gqlscan.TokenArr { return false } - c.checkedValue = c.checkedValue[1:] count := 0 - for ; c.checkedValue[0].ID != gqlscan.TokenArrEnd; count++ { + for ; ; count++ { + t := c.reader.ReadOne() + if t.ID != gqlscan.TokenArrEnd { + break + } if count >= len(checks) { return false } @@ -1117,11 +1118,7 @@ func makeEqArray( return false } } - if count != len(checks) { - return false - } - c.checkedValue = c.checkedValue[1:] - return true + return count == len(checks) } } @@ -1139,30 +1136,27 @@ func makeNotEqArray( checks[i] = makeCheck(v.Items[i], expect, schema) } return func(c *Checker) (match bool) { - if c.checkedValue[0].ID != gqlscan.TokenArr { + if c.reader.ReadOne().ID != gqlscan.TokenArr { return false } - c.checkedValue = c.checkedValue[1:] count := 0 - for ; c.checkedValue[0].ID != gqlscan.TokenArrEnd; count++ { + for ; c.reader.PeekOne().ID != gqlscan.TokenArrEnd; count++ { if count >= len(checks) { // Skip all following values to return correct tokenRead - for c.checkedValue[0].ID != gqlscan.TokenArrEnd { - l := gqlparse.GetValLen(c.checkedValue) - c.checkedValue = c.checkedValue[l:] + for c.reader.PeekOne().ID != gqlscan.TokenArrEnd { + scanval.Length(c.reader) } return true } if !checks[count](c) { // Skip all following values to return correct tokenRead - for c.checkedValue[0].ID != gqlscan.TokenArrEnd { - l := gqlparse.GetValLen(c.checkedValue) - c.checkedValue = c.checkedValue[l:] + for c.reader.PeekOne().ID != gqlscan.TokenArrEnd { + scanval.Length(c.reader) } return true } } - c.checkedValue = c.checkedValue[1:] + c.reader.ReadOne() // All item checks were matched, finally check length return count != len(checks) } @@ -1178,8 +1172,7 @@ func makeEqNumber( if c.expectOrInt(expect) { return false } - a := atoi.MustI32(c.checkedValue[0].Value) - c.checkedValue = c.checkedValue[1:] + a := atoi.MustI32(c.reader.ReadOne().Value) return int32(i) == a } } @@ -1188,8 +1181,7 @@ func makeEqNumber( if c.expectOrFloat(expect) { return false } - a := atoi.MustF64(c.checkedValue[0].Value) - c.checkedValue = c.checkedValue[1:] + a := atoi.MustF64(c.reader.ReadOne().Value) return f == a } } @@ -1204,8 +1196,7 @@ func makeNotEqNumber( if c.expectOrInt(expect) { return false } - a := atoi.MustI32(c.checkedValue[0].Value) - c.checkedValue = c.checkedValue[1:] + a := atoi.MustI32(c.reader.ReadOne().Value) return int32(i) != a } } @@ -1214,8 +1205,7 @@ func makeNotEqNumber( if c.expectOrFloat(expect) { return false } - a := atoi.MustF64(c.checkedValue[0].Value) - c.checkedValue = c.checkedValue[1:] + a := atoi.MustF64(c.reader.ReadOne().Value) return f != a } } @@ -1229,8 +1219,7 @@ func makeEqString( if c.expectOrString(expect) { return false } - t := c.checkedValue[0] - c.checkedValue = c.checkedValue[1:] + t := c.reader.ReadOne() switch t.ID { case gqlscan.TokenStr: return v.Value == string(t.Value) @@ -1250,8 +1239,7 @@ func makeNotEqString( if c.expectOrString(expect) { return false } - t := c.checkedValue[0] - c.checkedValue = c.checkedValue[1:] + t := c.reader.ReadOne() switch t.ID { case gqlscan.TokenStr: return v.Value != string(t.Value) @@ -1271,8 +1259,7 @@ func makeEqEnum( if c.expectOrEnum(expect) { return false } - b := c.checkedValue[0].Value - c.checkedValue = c.checkedValue[1:] + b := c.reader.ReadOne().Value return v.Value == string(b) } } @@ -1286,8 +1273,7 @@ func makeNotEqEnum( if c.expectOrEnum(expect) { return false } - b := c.checkedValue[0].Value - c.checkedValue = c.checkedValue[1:] + b := c.reader.ReadOne().Value return v.Value != string(b) } } @@ -1297,11 +1283,12 @@ func makeEqNull( schema *gqlast.Schema, ) check { return func(c *Checker) (match bool) { - if wrong, _ := isWrongType(c.gqlVarVals, expect, c.checkedValue, schema); wrong { + rBefore := *c.reader + if isWrongType(c.reader, expect, c.schema) { return false } - t := c.checkedValue[0].ID - c.checkedValue = c.checkedValue[1:] + *c.reader = rBefore + t := c.reader.ReadOne().ID return t == gqlscan.TokenNull } } @@ -1311,13 +1298,12 @@ func makeNotEqNull( schema *gqlast.Schema, ) check { return func(c *Checker) (match bool) { - if wrong, _ := isWrongType( - c.gqlVarVals, expect, c.checkedValue, schema, - ); wrong { + rBefore := *c.reader + if isWrongType(c.reader, expect, c.schema) { return false } - t := c.checkedValue[0].ID - c.checkedValue = c.checkedValue[1:] + *c.reader = rBefore + t := c.reader.ReadOne().ID return t != gqlscan.TokenNull } } @@ -1335,8 +1321,7 @@ func makeEqBool( if c.expectOrBool(expect) { return false } - t := c.checkedValue[0].ID - c.checkedValue = c.checkedValue[1:] + t := c.reader.ReadOne().ID return t == v } } @@ -1354,8 +1339,7 @@ func makeNotEqBool( if c.expectOrBool(expect) { return false } - t := c.checkedValue[0].ID - c.checkedValue = c.checkedValue[1:] + t := c.reader.ReadOne().ID return t != v } } @@ -1380,13 +1364,14 @@ func makeEqObject( fieldChecked[v.Name.Name] = false } return func(c *Checker) (match bool) { - if wrong, _ := isWrongType(c.gqlVarVals, expect, c.checkedValue, schema); wrong { + rBefore := *c.reader + if isWrongType(c.reader, expect, c.schema) { return false } - if c.checkedValue[0].ID != gqlscan.TokenObj { + *c.reader = rBefore + if c.reader.ReadOne().ID != gqlscan.TokenObj { return false } - c.checkedValue = c.checkedValue[1:] // Reset check status for k := range fieldChecked { @@ -1394,24 +1379,25 @@ func makeEqObject( } count := 0 - for ; c.checkedValue[0].ID != gqlscan.TokenObjEnd; count++ { - if count >= len(checks) { + for ; ; count++ { + read := c.reader.ReadOne() + if read.ID == gqlscan.TokenObjEnd { + break + } else if count >= len(checks) { return false } - check := checks[string(c.checkedValue[0].Value)] + check := checks[string(read.Value)] if check == nil { // Unknown field, wrong object type return false } - if fieldChecked[string(c.checkedValue[0].Value)] { + if fieldChecked[string(read.Value)] { // Field provided twice, invalid object return false } - fieldChecked[string(c.checkedValue[0].Value)] = true - - c.checkedValue = c.checkedValue[1:] + fieldChecked[string(read.Value)] = true if !check(c) { return false @@ -1421,7 +1407,6 @@ func makeEqObject( // Not all required fields were provided, wrong object type return false } - c.checkedValue = c.checkedValue[1:] return true } } @@ -1446,15 +1431,14 @@ func makeNotEqObject( fieldChecked[v.Name.Name] = false } return func(c *Checker) (match bool) { - if wrong, _ := isWrongType( - c.gqlVarVals, expect, c.checkedValue, schema, - ); wrong { + rBefore := *c.reader + if isWrongType(c.reader, expect, c.schema) { return false } - if c.checkedValue[0].ID != gqlscan.TokenObj { + *c.reader = rBefore + if c.reader.ReadOne().ID != gqlscan.TokenObj { return false } - c.checkedValue = c.checkedValue[1:] // Reset check status for k := range fieldChecked { @@ -1462,24 +1446,25 @@ func makeNotEqObject( } count := 0 - for ; c.checkedValue[0].ID != gqlscan.TokenObjEnd; count++ { - if count >= len(checks) { + for ; ; count++ { + read := c.reader.ReadOne() + if read.ID == gqlscan.TokenObjEnd { + break + } else if count >= len(checks) { return false } - check := checks[string(c.checkedValue[0].Value)] + check := checks[string(read.Value)] if check == nil { // Unknown field, wrong object type return false } - if fieldChecked[string(c.checkedValue[0].Value)] { + if fieldChecked[string(read.Value)] { // Field provided twice, invalid object return false } - fieldChecked[string(c.checkedValue[0].Value)] = true - - c.checkedValue = c.checkedValue[1:] + fieldChecked[string(read.Value)] = true if check(c) { // Don't return just yet as we're not sure whether the @@ -1491,7 +1476,6 @@ func makeNotEqObject( // Not all required fields were provided, wrong object type return false } - c.checkedValue = c.checkedValue[1:] return !match } } @@ -1502,62 +1486,63 @@ func makeEqExpression( schema *gqlast.Schema, ) check { return func(c *Checker) (match bool) { - if wrong, _ := isWrongType( - c.gqlVarVals, expect, c.checkedValue, schema, - ); wrong { + rBefore := *c.reader + if isWrongType(c.reader, expect, c.schema) { return false } + *c.reader = rBefore c.resolveExpr(v) u := c.popStack() switch u.Type() { case union.TypeNull: - return c.checkedValue[0].ID == gqlscan.TokenNull + return c.reader.ReadOne().ID == gqlscan.TokenNull case union.TypeBoolean: b, _ := u.Bool() if b { - return c.checkedValue[0].ID == gqlscan.TokenTrue + return c.reader.ReadOne().ID == gqlscan.TokenTrue } - return c.checkedValue[0].ID == gqlscan.TokenFalse + return c.reader.ReadOne().ID == gqlscan.TokenFalse case union.TypeInt: - if c.checkedValue[0].ID == gqlscan.TokenInt { + if read := c.reader.ReadOne(); read.ID == gqlscan.TokenInt { u, _ := u.Int() - return u == atoi.MustI32(c.checkedValue[0].Value) + return u == atoi.MustI32(read.Value) } return false case union.TypeFloat: - if c.checkedValue[0].ID == gqlscan.TokenFloat { + if read := c.reader.ReadOne(); read.ID == gqlscan.TokenFloat { u, _ := u.Float() - return u == atoi.MustF64(c.checkedValue[0].Value) + return u == atoi.MustF64(read.Value) } return false case union.TypeString: - if c.checkedValue[0].ID == gqlscan.TokenStr { + if read := c.reader.ReadOne(); read.ID == gqlscan.TokenStr { u, _ := u.String() - return u == string(c.checkedValue[0].Value) + return u == string(read.Value) } return false case union.TypeEnum: - if c.checkedValue[0].ID == gqlscan.TokenEnumVal { + if read := c.reader.ReadOne(); read.ID == gqlscan.TokenEnumVal { u, _ := u.Enum() - return u == string(c.checkedValue[0].Value) + return u == string(read.Value) } return false case union.TypeTokens: - if len(u.Tokens()) != len(c.checkedValue) { - return false - } - for i := range c.checkedValue { - if c.checkedValue[i].ID != u.Tokens()[i].ID { + // if len(u.Tokens()) != len(c.checkedValue) { + // return false + // } + for i := 0; !c.reader.EOF(); i++ { + read := c.reader.ReadOne() + if read.ID != u.Tokens()[i].ID { return false } - switch c.checkedValue[i].ID { + switch read.ID { case gqlscan.TokenStrBlock: - if string(c.checkedValue[i].Value) != string(u.Tokens()[i].Value) { + if string(read.Value) != string(u.Tokens()[i].Value) { return false } default: - if string(c.checkedValue[i].Value) != string(u.Tokens()[i].Value) { + if string(read.Value) != string(u.Tokens()[i].Value) { return false } } diff --git a/engine/playmon/internal/constrcheck/iswrongtype.go b/engine/playmon/internal/constrcheck/iswrongtype.go index ee21b1e..a5c00a1 100644 --- a/engine/playmon/internal/constrcheck/iswrongtype.go +++ b/engine/playmon/internal/constrcheck/iswrongtype.go @@ -1,7 +1,7 @@ package constrcheck import ( - "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" "github.com/graph-guard/gqlscan" gqlast "github.com/vektah/gqlparser/v2/ast" ) @@ -9,49 +9,35 @@ import ( // isWrongType returns true if the value represented in input // doesn't correspond to the type defined by expect. func isWrongType( - gqlVarValues [][]gqlparse.Token, + r *tokenreader.Reader, expect *gqlast.Type, - input []gqlparse.Token, schema *gqlast.Schema, -) (bool, []gqlparse.Token) { +) bool { var def *gqlast.Definition if expect != nil { def = schema.Types[expect.NamedType] } - if input[0].ID >= gqlparse.TokenTypeValIndexOffset && - input[0].ID < gqlparse.TokenTypeFragHostTypeIndexOffset { - return isWrongType( - gqlVarValues, - expect, - gqlVarValues[input[0].VariableIndex()], - schema, - ) - } - - switch input[0].ID { + switch read := r.ReadOne(); read.ID { case gqlscan.TokenNull: if expect == nil { - return false, input[1:] + return false } - return expect.NonNull, input[1:] + return expect.NonNull case gqlscan.TokenArr: if expect != nil { if expect.Elem == nil { - return true, nil + return true } expect = expect.Elem } - input = input[1:] for { - if input[0].ID == gqlscan.TokenArrEnd { - input = input[1:] - return false, input + rBefore := r + if r.ReadOne().ID == gqlscan.TokenArrEnd { + return false } - var wrongType bool - wrongType, input = isWrongType(gqlVarValues, expect, input, schema) - if wrongType { - return true, nil + if isWrongType(rBefore, expect, schema) { + return true } } case gqlscan.TokenTrue, gqlscan.TokenFalse: @@ -60,36 +46,36 @@ func isWrongType( def.Name != "Int" && def.Name != "Float" && def.Name != "String") { - return false, input[1:] + return false } - return expect.NamedType != "Boolean", input[1:] + return expect.NamedType != "Boolean" case gqlscan.TokenInt: if expect == nil || (def != nil && def.Kind == gqlast.Scalar && def.Name != "ID" && def.Name != "String" && def.Name != "Boolean") { - return false, input[1:] + return false } return expect.NamedType != "Int" && - expect.NamedType != "Float", input[1:] + expect.NamedType != "Float" case gqlscan.TokenFloat: if expect == nil || (def != nil && def.Kind == gqlast.Scalar && def.Name != "ID" && def.Name != "Int" && def.Name != "String" && def.Name != "Boolean") { - return false, input[1:] + return false } - return expect.NamedType != "Float", input[1:] + return expect.NamedType != "Float" case gqlscan.TokenStr, gqlscan.TokenStrBlock: if expect == nil || (def != nil && def.Kind == gqlast.Scalar && def.Name != "Int" && def.Name != "Float" && def.Name != "Boolean") { - return false, input[1:] + return false } return expect.NamedType != "String" && - expect.NamedType != "ID", input[1:] + expect.NamedType != "ID" case gqlscan.TokenEnumVal: if def == nil || def.Kind == gqlast.Scalar && def.Name != "ID" && @@ -98,63 +84,53 @@ func isWrongType( def.Name != "String" && def.Name != "Boolean" { // No expectation or custom scalar. - return false, input[1:] + return false } for i := range def.EnumValues { - if def.EnumValues[i].Name == string(input[0].Value) { - return false, input[1:] + if def.EnumValues[i].Name == string(read.Value) { + return false } } - return true, nil + return true case gqlscan.TokenObj: if def == nil || def.Kind == gqlast.Scalar { // No expectation or custom scalar type. - input = input[1:] + // r.ReadOne() SKIP_OBJECT: - for levelObj := 1; ; input = input[1:] { - switch input[0].ID { + for levelObj := 1; ; { + switch r.ReadOne().ID { case gqlscan.TokenObj: levelObj++ case gqlscan.TokenObjEnd: levelObj-- if levelObj < 1 { - input = input[1:] break SKIP_OBJECT } } } - return false, input + return false } else if def.Kind != gqlast.InputObject { - return true, nil + return true } - - input = input[1:] SCAN_OBJECT: - for len(input) > 0 { - if input[0].ID == gqlscan.TokenObjEnd { - input = input[1:] + for !r.EOF() { + if read = r.ReadOne(); read.ID == gqlscan.TokenObjEnd { break SCAN_OBJECT } - field := input[0] for i := range def.Fields { - if def.Fields[i].Name == string(field.Value) { + if def.Fields[i].Name == string(read.Value) { // Check field value - var wrongType bool - wrongType, input = isWrongType( - gqlVarValues, def.Fields[i].Type, input[1:], schema, - ) - if wrongType { - return true, nil + if isWrongType(r, def.Fields[i].Type, schema) { + return true } continue SCAN_OBJECT } } // Field not found in expected input object type - return true, nil + return true } - return false, input + return false } - - return true, nil + return true } diff --git a/engine/playmon/internal/constrcheck/iswrongtype_test.go b/engine/playmon/internal/constrcheck/iswrongtype_test.go index 090bea0..465d407 100644 --- a/engine/playmon/internal/constrcheck/iswrongtype_test.go +++ b/engine/playmon/internal/constrcheck/iswrongtype_test.go @@ -3,6 +3,7 @@ package constrcheck import ( "testing" + "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/gqlscan" "github.com/stretchr/testify/require" @@ -364,7 +365,7 @@ func TestIsWrongType_False(t *testing.T) { ) } - r, _ := isWrongType(tt.GQLVarVals, tt.ExpectType, tt.Input, s) + r := isWrongType(&tokenreader.Reader{Main: tt.Input}, tt.ExpectType, s) require.False(t, r) }) } @@ -526,7 +527,7 @@ func TestIsWrongType_True(t *testing.T) { "type expectations are always schema-aware", ) } - r, _ := isWrongType(tt.GQLVarVals, tt.ExpectType, tt.Input, s) + r := isWrongType(&tokenreader.Reader{Main: tt.Input}, tt.ExpectType, s) require.True(t, r) }) } diff --git a/gqlparse/count_values.go b/engine/playmon/internal/countval/count_values.go similarity index 67% rename from gqlparse/count_values.go rename to engine/playmon/internal/countval/count_values.go index def81b3..6e4a37c 100644 --- a/gqlparse/count_values.go +++ b/engine/playmon/internal/countval/count_values.go @@ -1,13 +1,16 @@ -package gqlparse +package countval -import "github.com/graph-guard/gqlscan" +import ( + "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" + "github.com/graph-guard/gqlscan" +) -// CountValuesUntil counts the number of tokens and values +// Until counts the number of tokens and values // until term is reached. // Immediately returns true if any call to fn returned true as well. -func CountValuesUntil(a []Token, term gqlscan.Token) (values, tokens int) { - for ; tokens < len(a) && a[tokens].ID != term; tokens++ { - switch a[tokens].ID { +func Until(r *tokenreader.Reader, term gqlscan.Token) (values, tokens int) { + for ; !r.EOF() && r.PeekOne().ID != term; tokens++ { + switch r.ReadOne().ID { case gqlscan.TokenTrue, gqlscan.TokenFalse, gqlscan.TokenStr, @@ -21,7 +24,7 @@ func CountValuesUntil(a []Token, term gqlscan.Token) (values, tokens int) { tokens++ SCAN_OBJ: for levelObj := 1; ; tokens++ { - switch a[tokens].ID { + switch r.ReadOne().ID { case gqlscan.TokenObj: levelObj++ case gqlscan.TokenObjEnd: @@ -36,7 +39,7 @@ func CountValuesUntil(a []Token, term gqlscan.Token) (values, tokens int) { tokens++ SCAN_ARR: for levelArr := 1; ; tokens++ { - switch a[tokens].ID { + switch r.ReadOne().ID { case gqlscan.TokenArr: levelArr++ case gqlscan.TokenArrEnd: diff --git a/gqlparse/count_values_test.go b/engine/playmon/internal/countval/count_values_test.go similarity index 86% rename from gqlparse/count_values_test.go rename to engine/playmon/internal/countval/count_values_test.go index 6876bc9..540c1f8 100644 --- a/gqlparse/count_values_test.go +++ b/engine/playmon/internal/countval/count_values_test.go @@ -1,8 +1,10 @@ -package gqlparse_test +package countval_test import ( "testing" + "github.com/graph-guard/ggproxy/engine/playmon/internal/countval" + "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/gqlscan" "github.com/stretchr/testify/require" @@ -11,7 +13,7 @@ import ( func TestCountValuesUntil(t *testing.T) { for _, tt := range []struct { Name string - Input []gqlparse.Token + Input *tokenreader.Reader Term gqlscan.Token ExpectValues int ExpectTokens int @@ -19,43 +21,45 @@ func TestCountValuesUntil(t *testing.T) { { Name: "sequence", Term: gqlscan.TokenArrEnd, - Input: []gqlparse.Token{ - {ID: gqlscan.TokenStr, Value: []byte("text")}, - {ID: gqlscan.TokenEnumVal, Value: []byte("red")}, - {ID: gqlscan.TokenInt, Value: []byte("42")}, - {ID: gqlscan.TokenFloat, Value: []byte("3.1415")}, - {ID: gqlscan.TokenTrue}, - {ID: gqlscan.TokenFalse}, - {ID: gqlscan.TokenNull}, - {ID: gqlscan.TokenStrBlock}, - // {o:{f:[[null]]}} - {ID: gqlscan.TokenObj}, - {ID: gqlscan.TokenObjField, Value: []byte("o")}, - {ID: gqlscan.TokenObj}, - {ID: gqlscan.TokenObjField, Value: []byte("f")}, - {ID: gqlscan.TokenArr}, - {ID: gqlscan.TokenArr}, - {ID: gqlscan.TokenNull}, - {ID: gqlscan.TokenArrEnd}, - {ID: gqlscan.TokenArrEnd}, - {ID: gqlscan.TokenObjEnd}, - {ID: gqlscan.TokenObjEnd}, - // {f:42} - {ID: gqlscan.TokenObj}, - {ID: gqlscan.TokenObjField, Value: []byte("f")}, - {ID: gqlscan.TokenInt, Value: []byte("42")}, - {ID: gqlscan.TokenObjEnd}, - // [] - {ID: gqlscan.TokenArr}, - {ID: gqlscan.TokenArrEnd}, - // [[]] - {ID: gqlscan.TokenArr}, - {ID: gqlscan.TokenArr}, - {ID: gqlscan.TokenArrEnd}, - {ID: gqlscan.TokenArrEnd}, + Input: &tokenreader.Reader{ + Main: []gqlparse.Token{ + {ID: gqlscan.TokenStr, Value: []byte("text")}, + {ID: gqlscan.TokenEnumVal, Value: []byte("red")}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenFloat, Value: []byte("3.1415")}, + {ID: gqlscan.TokenTrue}, + {ID: gqlscan.TokenFalse}, + {ID: gqlscan.TokenNull}, + {ID: gqlscan.TokenStrBlock}, + // {o:{f:[[null]]}} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("o")}, + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("f")}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenNull}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenObjEnd}, + {ID: gqlscan.TokenObjEnd}, + // {f:42} + {ID: gqlscan.TokenObj}, + {ID: gqlscan.TokenObjField, Value: []byte("f")}, + {ID: gqlscan.TokenInt, Value: []byte("42")}, + {ID: gqlscan.TokenObjEnd}, + // [] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArrEnd}, + // [[]] + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArr}, + {ID: gqlscan.TokenArrEnd}, + {ID: gqlscan.TokenArrEnd}, - // Term - {ID: gqlscan.TokenArrEnd}, + // Term + {ID: gqlscan.TokenArrEnd}, + }, }, ExpectValues: 12, ExpectTokens: 29, @@ -383,7 +387,7 @@ func TestCountValuesUntil(t *testing.T) { // }, } { t.Run(tt.Name, func(t *testing.T) { - values, tokens := gqlparse.CountValuesUntil(tt.Input, tt.Term) + values, tokens := countval.Until(tt.Input, tt.Term) require.Equal(t, tt.ExpectValues, values) require.Equal(t, tt.ExpectTokens, tokens) }) diff --git a/gqlparse/getvallen.go b/engine/playmon/internal/scanval/length.go similarity index 65% rename from gqlparse/getvallen.go rename to engine/playmon/internal/scanval/length.go index 8297a09..fb885df 100644 --- a/gqlparse/getvallen.go +++ b/engine/playmon/internal/scanval/length.go @@ -1,26 +1,28 @@ -package gqlparse +package scanval -import "github.com/graph-guard/gqlscan" +import ( + "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" + "github.com/graph-guard/gqlscan" +) -// GetValLen reads the length in tokens from the first value in t. +// Length reads the length in tokens from the first value in t. // For example: // // `[1,2,3]` has a length of 5 tokens // `{x:""}` has a length of 4 tokens // `null` has a length of 1 tokens -func GetValLen(t []Token) (length int) { - switch t[0].ID { +func Length(r *tokenreader.Reader) (length int) { + switch r.ReadOne().ID { case gqlscan.TokenNull, gqlscan.TokenInt, gqlscan.TokenFloat, gqlscan.TokenStr, gqlscan.TokenStrBlock, gqlscan.TokenEnumVal, gqlscan.TokenTrue, gqlscan.TokenFalse: return 1 case gqlscan.TokenArr: - t = t[1:] length += 1 SCAN_ARR: - for levelArr := 1; ; t = t[1:] { + for levelArr := 1; ; { length++ - switch t[0].ID { + switch r.ReadOne().ID { case gqlscan.TokenArr: levelArr++ case gqlscan.TokenArrEnd: @@ -31,12 +33,11 @@ func GetValLen(t []Token) (length int) { } } case gqlscan.TokenObj: - t = t[1:] length += 1 SCAN_OBJ: - for levelObj := 1; ; t = t[1:] { + for levelObj := 1; !r.EOF(); { length++ - switch t[0].ID { + switch r.ReadOne().ID { case gqlscan.TokenObj: levelObj++ case gqlscan.TokenObjEnd: diff --git a/gqlparse/getvallen_test.go b/engine/playmon/internal/scanval/length_test.go similarity index 94% rename from gqlparse/getvallen_test.go rename to engine/playmon/internal/scanval/length_test.go index 547b13c..c84d802 100644 --- a/gqlparse/getvallen_test.go +++ b/engine/playmon/internal/scanval/length_test.go @@ -1,14 +1,16 @@ -package gqlparse_test +package scanval_test import ( "testing" + "github.com/graph-guard/ggproxy/engine/playmon/internal/scanval" + "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/gqlscan" "github.com/stretchr/testify/require" ) -func TestGetValLen(t *testing.T) { +func TestLength(t *testing.T) { for _, tt := range []struct { Name string Input []gqlparse.Token @@ -169,7 +171,7 @@ func TestGetValLen(t *testing.T) { }, } { t.Run(tt.Name, func(t *testing.T) { - a := gqlparse.GetValLen(tt.Input) + a := scanval.Length(&tokenreader.Reader{Main: tt.Input}) require.Equal(t, tt.Expect, a) }) } diff --git a/gqlparse/scan_values.go b/engine/playmon/internal/scanval/scan_values.go similarity index 59% rename from gqlparse/scan_values.go rename to engine/playmon/internal/scanval/scan_values.go index e1c6ebb..074475f 100644 --- a/gqlparse/scan_values.go +++ b/engine/playmon/internal/scanval/scan_values.go @@ -1,15 +1,19 @@ -package gqlparse +package scanval -import "github.com/graph-guard/gqlscan" +import ( + "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" + "github.com/graph-guard/gqlscan" +) // ScanValues calls fn for every value in a. // Immediately returns true if any call to fn returned true as well. func ScanValues( - a []Token, - fn func([]Token) (stop bool), + r *tokenreader.Reader, + fn func(r *tokenreader.Reader) (stop bool), ) (stopped bool) { - for i := 0; i < len(a); i++ { - switch a[i].ID { + for !r.EOF() { + rBefore := *r + switch r.ReadOne().ID { case gqlscan.TokenTrue, gqlscan.TokenFalse, gqlscan.TokenStr, @@ -18,15 +22,16 @@ func ScanValues( gqlscan.TokenInt, gqlscan.TokenEnumVal, gqlscan.TokenNull: - if fn(a[i : i+1]) { + rAfter := *r + *r = rBefore + if fn(r) { return true } + *r = rAfter case gqlscan.TokenObj: - s := i - i++ SCAN_OBJ: - for levelObj := 1; ; i++ { - switch a[i].ID { + for levelObj := 1; ; { + switch r.ReadOne().ID { case gqlscan.TokenObj: levelObj++ case gqlscan.TokenObjEnd: @@ -36,15 +41,16 @@ func ScanValues( } } } - if fn(a[s : i+1]) { + rAfter := *r + *r = rBefore + if fn(r) { return true } + *r = rAfter case gqlscan.TokenArr: - s := i - i++ SCAN_ARR: - for levelArr := 1; ; i++ { - switch a[i].ID { + for levelArr := 1; ; { + switch r.ReadOne().ID { case gqlscan.TokenArr: levelArr++ case gqlscan.TokenArrEnd: @@ -54,9 +60,12 @@ func ScanValues( } } } - if fn(a[s : i+1]) { + rAfter := *r + *r = rBefore + if fn(r) { return true } + *r = rAfter } } return false diff --git a/gqlparse/scan_values_in_arrays.go b/engine/playmon/internal/scanval/scan_values_in_arrays.go similarity index 57% rename from gqlparse/scan_values_in_arrays.go rename to engine/playmon/internal/scanval/scan_values_in_arrays.go index 8323453..1c2cee5 100644 --- a/gqlparse/scan_values_in_arrays.go +++ b/engine/playmon/internal/scanval/scan_values_in_arrays.go @@ -1,15 +1,19 @@ -package gqlparse +package scanval -import "github.com/graph-guard/gqlscan" +import ( + "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" + "github.com/graph-guard/gqlscan" +) -// ScanValuesInArrays calls fn for every non-array value contained in x recursively. +// InArrays calls fn for every non-array value contained in x recursively. // Immediately returns true if any call to fn returned true as well. -func ScanValuesInArrays( - x []Token, - fn func([]Token) (stop bool), +func InArrays( + r *tokenreader.Reader, + fn func(r *tokenreader.Reader) (stop bool), ) (stopped bool) { - for levelArr, i := 0, 0; i < len(x); i++ { - switch x[i].ID { + for levelArr := 0; !r.EOF(); { + rBefore := *r + switch r.ReadOne().ID { case gqlscan.TokenTrue, gqlscan.TokenFalse, gqlscan.TokenStr, @@ -18,68 +22,71 @@ func ScanValuesInArrays( gqlscan.TokenInt, gqlscan.TokenEnumVal, gqlscan.TokenNull: - if fn(x[i : i+1]) { + rAfter := *r + *r = rBefore + if fn(r) { return true } + *r = rAfter case gqlscan.TokenObj: - s := i - i++ OBJ_SCAN_1: - for levelObj := 1; ; i++ { - switch x[i].ID { + for levelObj := 1; ; { + switch r.ReadOne().ID { case gqlscan.TokenObj: levelObj++ case gqlscan.TokenObjEnd: levelObj-- if levelObj < 1 { - i++ break OBJ_SCAN_1 } } } - if fn(x[s:i]) { + rAfter := *r + *r = rBefore + if fn(r) { return true } + *r = rAfter case gqlscan.TokenArr: levelArr++ - i++ VAL_SCAN: for levelArr > 0 { - switch x[i].ID { + rBefore := *r + switch r.ReadOne().ID { case gqlscan.TokenArrEnd: - i++ levelArr-- if levelArr < 1 { break VAL_SCAN } goto VAL_SCAN case gqlscan.TokenArr: - i++ levelArr++ case gqlscan.TokenObj: - s := i - i++ OBJ_SCAN_2: - for levelObj := 1; ; i++ { - switch x[i].ID { + for levelObj := 1; ; { + switch r.ReadOne().ID { case gqlscan.TokenObj: levelObj++ case gqlscan.TokenObjEnd: levelObj-- if levelObj < 1 { - i++ break OBJ_SCAN_2 } } } - if fn(x[s:i]) { + rAfter := *r + *r = rBefore + if fn(r) { return true } + *r = rAfter default: - if fn(x[i : i+1]) { + rAfter := *r + *r = rBefore + if fn(r) { return true } - i++ + *r = rAfter } } } diff --git a/gqlparse/scan_values_in_arrays_test.go b/engine/playmon/internal/scanval/scan_values_in_arrays_test.go similarity index 89% rename from gqlparse/scan_values_in_arrays_test.go rename to engine/playmon/internal/scanval/scan_values_in_arrays_test.go index 55e4b55..8fb6e8f 100644 --- a/gqlparse/scan_values_in_arrays_test.go +++ b/engine/playmon/internal/scanval/scan_values_in_arrays_test.go @@ -1,9 +1,11 @@ -package gqlparse_test +package scanval_test import ( "fmt" "testing" + "github.com/graph-guard/ggproxy/engine/playmon/internal/scanval" + "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/gqlscan" "github.com/stretchr/testify/assert" @@ -27,7 +29,7 @@ func TestInValues(t *testing.T) { }, }, { - Name: "string block", + Name: "string_block", Input: []gqlparse.Token{ // """text""" {ID: gqlscan.TokenStrBlock}, @@ -86,7 +88,7 @@ func TestInValues(t *testing.T) { }, }, { - Name: "object with array inside", + Name: "object_with_array_inside", Input: []gqlparse.Token{ // {field:["text"]} {ID: gqlscan.TokenObj}, @@ -108,7 +110,7 @@ func TestInValues(t *testing.T) { }, }, { - Name: "object nested", + Name: "object_nested", Input: []gqlparse.Token{ // {x:{y:{z:1}}} {ID: gqlscan.TokenObj}, @@ -138,7 +140,7 @@ func TestInValues(t *testing.T) { }, }, { - Name: "empty array", + Name: "empty_array", Input: []gqlparse.Token{ // [] {ID: gqlscan.TokenArr}, @@ -147,7 +149,7 @@ func TestInValues(t *testing.T) { Expect: [][]gqlparse.Token{}, }, { - Name: "array with 1 int", + Name: "array_with_1_int", Input: []gqlparse.Token{ // [42] {ID: gqlscan.TokenArr}, @@ -159,7 +161,7 @@ func TestInValues(t *testing.T) { }, }, { - Name: "array with 3 int", + Name: "array_with_3_int", Input: []gqlparse.Token{ // [42, 0, 100500] {ID: gqlscan.TokenArr}, @@ -175,7 +177,7 @@ func TestInValues(t *testing.T) { }, }, { - Name: "array with 1 string", + Name: "array_with_1_string", Input: []gqlparse.Token{ // ["text"] {ID: gqlscan.TokenArr}, @@ -187,7 +189,7 @@ func TestInValues(t *testing.T) { }, }, { - Name: "array with 1 string block", + Name: "array_with_1_string_block", Input: []gqlparse.Token{ // ["""text"""] {ID: gqlscan.TokenArr}, @@ -199,7 +201,7 @@ func TestInValues(t *testing.T) { }, }, { - Name: "array with 1 float", + Name: "array_with_1_float", Input: []gqlparse.Token{ // [3.14] {ID: gqlscan.TokenArr}, @@ -211,7 +213,7 @@ func TestInValues(t *testing.T) { }, }, { - Name: "array with 1 enum", + Name: "array_with_1_enum", Input: []gqlparse.Token{ // [red] {ID: gqlscan.TokenArr}, @@ -223,7 +225,7 @@ func TestInValues(t *testing.T) { }, }, { - Name: "array with 1 boolean(true)", + Name: "array_with_1_boolean(true)", Input: []gqlparse.Token{ // [true] {ID: gqlscan.TokenArr}, @@ -235,7 +237,7 @@ func TestInValues(t *testing.T) { }, }, { - Name: "array with 1 boolean(false)", + Name: "array_with_1_boolean(false)", Input: []gqlparse.Token{ // [false] {ID: gqlscan.TokenArr}, @@ -247,7 +249,7 @@ func TestInValues(t *testing.T) { }, }, { - Name: "array with 1 null", + Name: "array_with_1_null", Input: []gqlparse.Token{ // [null] {ID: gqlscan.TokenArr}, @@ -259,7 +261,7 @@ func TestInValues(t *testing.T) { }, }, { - Name: "array with 1 object", + Name: "array_with_1_object", Input: []gqlparse.Token{ // [{x:0}] {ID: gqlscan.TokenArr}, @@ -279,7 +281,7 @@ func TestInValues(t *testing.T) { }, }, { - Name: "3d array string", + Name: "3d_array_string", Input: []gqlparse.Token{ // [[["1"],["2"]],[["3"]]] {ID: gqlscan.TokenArr}, @@ -305,7 +307,7 @@ func TestInValues(t *testing.T) { }, }, { - Name: "array with null and nested object with array inside", + Name: "array_with_null_and_nested_object_with_array_inside", Input: []gqlparse.Token{ // [null, {object:{array:["text"]}}] {ID: gqlscan.TokenArr}, @@ -339,12 +341,16 @@ func TestInValues(t *testing.T) { } { t.Run(tt.Name, func(t *testing.T) { actual := [][]gqlparse.Token{} - stopped := gqlparse.ScanValuesInArrays( - tt.Input, - func(t []gqlparse.Token) (stop bool) { - cp := make([]gqlparse.Token, len(t)) - copy(cp, t) + actualCounter := 0 + stopped := scanval.InArrays( + &tokenreader.Reader{Main: tt.Input}, + func(r *tokenreader.Reader) (stop bool) { + var cp []gqlparse.Token + for i := 0; !r.EOF() && i < len(tt.Expect[actualCounter]); i++ { + cp = append(cp, r.ReadOne()) + } actual = append(actual, cp) + actualCounter++ return false }, ) diff --git a/gqlparse/scan_values_test.go b/engine/playmon/internal/scanval/scan_values_test.go similarity index 96% rename from gqlparse/scan_values_test.go rename to engine/playmon/internal/scanval/scan_values_test.go index ae8ca93..f0b92d1 100644 --- a/gqlparse/scan_values_test.go +++ b/engine/playmon/internal/scanval/scan_values_test.go @@ -1,9 +1,11 @@ -package gqlparse_test +package scanval_test import ( "fmt" "testing" + "github.com/graph-guard/ggproxy/engine/playmon/internal/scanval" + "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/gqlscan" "github.com/stretchr/testify/assert" @@ -433,12 +435,16 @@ func TestScanValues(t *testing.T) { } { t.Run(tt.Name, func(t *testing.T) { actual := [][]gqlparse.Token{} - stopped := gqlparse.ScanValues( - tt.Input, - func(t []gqlparse.Token) (stop bool) { - cp := make([]gqlparse.Token, len(t)) - copy(cp, t) + actualCounter := 0 + stopped := scanval.ScanValues( + &tokenreader.Reader{Main: tt.Input}, + func(r *tokenreader.Reader) (stop bool) { + var cp []gqlparse.Token + for i := 0; !r.EOF() && i < len(tt.Expect[actualCounter]); i++ { + cp = append(cp, r.ReadOne()) + } actual = append(actual, cp) + actualCounter++ return false }, ) diff --git a/engine/playmon/internal/tokenreader/tokenreader.go b/engine/playmon/internal/tokenreader/tokenreader.go new file mode 100644 index 0000000..0db1f97 --- /dev/null +++ b/engine/playmon/internal/tokenreader/tokenreader.go @@ -0,0 +1,55 @@ +// Package tokenreader provides Reader which reads a continuous +// stream of tokens respecting variable index tokens. +package tokenreader + +import ( + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/gqlscan" +) + +// Reader reads a continuous stream of tokens respecting +// variable index tokens. +type Reader struct { + Vars [][]gqlparse.Token + Var, Main []gqlparse.Token +} + +// ReadOne advances the reader by one token and returns it. +func (r *Reader) ReadOne() (t gqlparse.Token) { + if len(r.Var) > 0 { + t, r.Var = r.Var[0], r.Var[1:] + return t + } + if vi := r.Main[0].VariableIndex(); vi > -1 { + t, r.Var, r.Main = r.Vars[vi][0], r.Vars[vi][1:], r.Main[1:] + return t + } + t, r.Main = r.Main[0], r.Main[1:] + return t +} + +// SkipUntil skips all tokens until (including) the first token +// that has the given id. +func (r *Reader) SkipUntil(id gqlscan.Token) { + for r.ReadOne().ID != id { + } +} + +// EOF returns true if the reader is currently at the end of the file, +// otherwise returns false. +func (r *Reader) EOF() bool { + return len(r.Var) < 1 && len(r.Main) < 1 +} + +// PeekOne returns true if the reader is currently at the end of the file, +// otherwise returns false. +func (r *Reader) PeekOne() gqlparse.Token { + if len(r.Var) > 0 { + return r.Var[0] + } + if vi := r.Main[0].VariableIndex(); vi > -1 { + r.Var, r.Main = r.Vars[vi], r.Main[1:] + return r.Var[0] + } + return r.Main[0] +} diff --git a/engine/playmon/internal/tokenreader/tokenreader_test.go b/engine/playmon/internal/tokenreader/tokenreader_test.go new file mode 100644 index 0000000..7b74bec --- /dev/null +++ b/engine/playmon/internal/tokenreader/tokenreader_test.go @@ -0,0 +1,168 @@ +package tokenreader_test + +import ( + "testing" + + "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" + "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/gqlscan" + "github.com/stretchr/testify/require" +) + +// No need to test the case when Main is exhausted, it will never happen. + +func TestReadOne(t *testing.T) { + r := &tokenreader.Reader{ + Vars: [][]gqlparse.Token{ + {T(gqlscan.TokenStr, "var1_first"), T(gqlscan.TokenStr, "var1_second")}, + {T(gqlscan.TokenStr, "var2_first"), T(gqlscan.TokenStr, "var2_second")}, + }, + Main: []gqlparse.Token{ + T(gqlparse.TokenTypeValIndexOffset+1, "irrelevant_text"), + T(gqlscan.TokenStrBlock, "intermediate"), + T(gqlparse.TokenTypeValIndexOffset, "irrelevant_text2"), + T(gqlscan.TokenStrBlock, "last_main"), + }, + } + + require.False(t, r.EOF()) + TokEq(t, T(gqlscan.TokenStr, "var2_first"), r.PeekOne()) + TokEq(t, T(gqlscan.TokenStr, "var2_first"), r.ReadOne()) + require.False(t, r.EOF()) + TokEq(t, T(gqlscan.TokenStr, "var2_second"), r.PeekOne()) + TokEq(t, T(gqlscan.TokenStr, "var2_second"), r.ReadOne()) + require.False(t, r.EOF()) + TokEq(t, T(gqlscan.TokenStrBlock, "intermediate"), r.PeekOne()) + TokEq(t, T(gqlscan.TokenStrBlock, "intermediate"), r.ReadOne()) + require.False(t, r.EOF()) + TokEq(t, T(gqlscan.TokenStr, "var1_first"), r.PeekOne()) + TokEq(t, T(gqlscan.TokenStr, "var1_first"), r.ReadOne()) + require.False(t, r.EOF()) + TokEq(t, T(gqlscan.TokenStr, "var1_second"), r.PeekOne()) + TokEq(t, T(gqlscan.TokenStr, "var1_second"), r.ReadOne()) + require.False(t, r.EOF()) + TokEq(t, T(gqlscan.TokenStrBlock, "last_main"), r.PeekOne()) + TokEq(t, T(gqlscan.TokenStrBlock, "last_main"), r.ReadOne()) + require.True(t, r.EOF()) + require.True(t, r.EOF()) +} + +func TestSkipUntil(t *testing.T) { + r := &tokenreader.Reader{ + Vars: [][]gqlparse.Token{ + {T(gqlscan.TokenStr, "var1_first"), T(gqlscan.TokenStr, "var1_second")}, + {T(gqlscan.TokenStr, "var2_first"), T(gqlscan.TokenStr, "var2_second")}, + }, + Main: []gqlparse.Token{ + T(gqlparse.TokenTypeValIndexOffset+1, "irrelevant_text"), + T(gqlscan.TokenStrBlock, "intermediate"), + T(gqlparse.TokenTypeValIndexOffset, "irrelevant_text2"), + T(gqlscan.TokenArrEnd, "last_main"), + }, + } + + r.SkipUntil(gqlscan.TokenArrEnd) + require.Equal(t, &tokenreader.Reader{ + Vars: [][]gqlparse.Token{ + {T(gqlscan.TokenStr, "var1_first"), T(gqlscan.TokenStr, "var1_second")}, + {T(gqlscan.TokenStr, "var2_first"), T(gqlscan.TokenStr, "var2_second")}, + }, + Var: []gqlparse.Token{}, + Main: []gqlparse.Token{}, + }, r) +} + +func TestSkipUntil_InVarVal(t *testing.T) { + r := &tokenreader.Reader{ + Vars: [][]gqlparse.Token{ + {T(gqlscan.TokenStr, "var1_first"), T(gqlscan.TokenStr, "var1_second")}, + {T(gqlscan.TokenEnumVal, "var2_first"), T(gqlscan.TokenStr, "var2_second")}, + }, + Main: []gqlparse.Token{ + T(gqlparse.TokenTypeValIndexOffset+1, "irrelevant_text"), + T(gqlscan.TokenStrBlock, "intermediate"), + T(gqlparse.TokenTypeValIndexOffset, "irrelevant_text2"), + T(gqlscan.TokenArrEnd, "last_main"), + }, + } + + r.SkipUntil(gqlscan.TokenEnumVal) + require.Equal(t, &tokenreader.Reader{ + Vars: [][]gqlparse.Token{ + {T(gqlscan.TokenStr, "var1_first"), T(gqlscan.TokenStr, "var1_second")}, + {T(gqlscan.TokenEnumVal, "var2_first"), T(gqlscan.TokenStr, "var2_second")}, + }, + Var: []gqlparse.Token{ + T(gqlscan.TokenStr, "var2_second"), + }, + Main: []gqlparse.Token{ + T(gqlscan.TokenStrBlock, "intermediate"), + T(gqlparse.TokenTypeValIndexOffset, "irrelevant_text2"), + T(gqlscan.TokenArrEnd, "last_main"), + }, + }, r) +} + +func TokEq(t *testing.T, expect, actual gqlparse.Token) { + t.Helper() + require.Equal(t, expect.String(), actual.String()) +} + +func T(id gqlscan.Token, value string) gqlparse.Token { + var v []byte + if value != "" { + v = []byte(value) + } + return gqlparse.Token{ID: gqlscan.Token(id), Value: v} +} + +var GT gqlparse.Token + +func BenchmarkReadOne(b *testing.B) { + r := &tokenreader.Reader{ + Vars: [][]gqlparse.Token{ + {T(gqlscan.TokenStr, "var1_first"), T(gqlscan.TokenStr, "var1_second")}, + {T(gqlscan.TokenStr, "var2_first"), T(gqlscan.TokenStr, "var2_second")}, + }, + Main: []gqlparse.Token{ + T(gqlparse.TokenTypeValIndexOffset+1, "irrelevant_text"), + T(gqlscan.TokenStrBlock, "intermediate"), + T(gqlparse.TokenTypeValIndexOffset, "irrelevant_text2"), + T(gqlscan.TokenStrBlock, "last_main"), + }, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + resetTo := *r + GT = r.ReadOne() // var2_first + GT = r.ReadOne() // var2_second + GT = r.ReadOne() // intermediate + GT = r.ReadOne() // var1_first + GT = r.ReadOne() // var1_second + GT = r.ReadOne() // last_main + *r = resetTo // Reset reader + } +} + +func BenchmarkSkipUntil(b *testing.B) { + r := &tokenreader.Reader{ + Vars: [][]gqlparse.Token{ + {T(gqlscan.TokenStr, "var1_first"), T(gqlscan.TokenStr, "var1_second")}, + {T(gqlscan.TokenStr, "var2_first"), T(gqlscan.TokenStr, "var2_second")}, + }, + Main: []gqlparse.Token{ + T(gqlparse.TokenTypeValIndexOffset+1, "irrelevant_text"), + T(gqlscan.TokenStrBlock, "intermediate"), + T(gqlparse.TokenTypeValIndexOffset, "irrelevant_text2"), + T(gqlscan.TokenArrEnd, "last_main"), + }, + } + + b.ResetTimer() + for n := 0; n < b.N; n++ { + resetTo := *r + r.SkipUntil(gqlscan.TokenArrEnd) + *r = resetTo // Reset reader + } +} diff --git a/engine/playmon/playmon.go b/engine/playmon/playmon.go index 8753b68..bdb3f88 100644 --- a/engine/playmon/playmon.go +++ b/engine/playmon/playmon.go @@ -1,6 +1,8 @@ package playmon import ( + "fmt" + "github.com/graph-guard/ggproxy/config" "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck" "github.com/graph-guard/ggproxy/engine/playmon/internal/pathmatch" @@ -124,6 +126,7 @@ func (e *Engine) Match( e.matcher.Match(e.structuralPaths, func(t *config.Template) (stop bool) { tm := e.templates[t.ID] + fmt.Println("\nTEMPORARILY MATCHED ", tm.ID) for i := range e.argumentsSet { if !tm.ConstraintChecker.Check( variableValues, diff --git a/gqlparse/gqlparse.go b/gqlparse/gqlparse.go index 81536b1..de99bdd 100644 --- a/gqlparse/gqlparse.go +++ b/gqlparse/gqlparse.go @@ -121,12 +121,17 @@ func NewParser(schema *ast.Schema) *Parser { } } +type typeStackFrame struct { + HostType *ast.Definition + FieldType *ast.Definition +} + type Parser struct { schema *ast.Schema // schemaTypeStack is used during parsing to track the schema type // of a particular token's context. - schemaTypeStack stack.Stack[*ast.Definition] + schemaTypeStack stack.Stack[typeStackFrame] // fragSpreadHostTypes associates fragment spreads with the host type. fragSpreadHostTypes []*ast.Definition @@ -179,6 +184,7 @@ type Parser struct { errTypeUndef ErrorTypeUndef errFieldUndef ErrorFieldUndef + errCantBeOfType ErrorCantBeOfType errSyntax ErrorSyntax errOprAnonNonExcl ErrorOprAnonNonExcl errOprNotFound ErrorOprNotFound @@ -212,6 +218,12 @@ func (r *Parser) reset() { r.fragmentGraphEdges = r.fragmentGraphEdges[:0] } +var ( + typeNameQuery = []byte("Query") + typeNameMutation = []byte("Mutation") + typeNameSubscription = []byte("Subscription") +) + // Parse calls onSuccess in case of success where operation // only contains the relevant set of tokens. // onError is called in case of an error. @@ -233,22 +245,25 @@ func (r *Parser) Parse( var stackCounter int var fragStackCounter int var recentFragDef []byte + var recentHost Token if serr := gqlscan.Scan(src, func(i *gqlscan.Iterator) bool { - r.buffer = append(r.buffer, Token{ + tk := Token{ ID: i.Token(), Value: i.Value(), - }) + } + r.buffer = append(r.buffer, tk) switch i.Token() { case gqlscan.TokenDefMut: if r.schema != nil { if r.schema.Mutation == nil { - r.errTypeUndef.TypeName = "Mutation" + r.errTypeUndef.Location = locFromItr(i) + r.errTypeUndef.TypeName = typeNameMutation isErr = true onError(&r.errTypeUndef) return true } - r.schemaTypeStack.Push(r.schema.Mutation) + recentHost = tk } recentDef = gqlscan.TokenDefMut r.operations = append(r.operations, indexRange{ @@ -258,12 +273,13 @@ func (r *Parser) Parse( case gqlscan.TokenDefQry: if r.schema != nil { if r.schema.Query == nil { - r.errTypeUndef.TypeName = "Query" + r.errTypeUndef.Location = locFromItr(i) + r.errTypeUndef.TypeName = typeNameQuery isErr = true onError(&r.errTypeUndef) return true } - r.schemaTypeStack.Push(r.schema.Query) + recentHost = tk } recentDef = gqlscan.TokenDefQry r.operations = append(r.operations, indexRange{ @@ -273,24 +289,66 @@ func (r *Parser) Parse( case gqlscan.TokenDefSub: if r.schema != nil { if r.schema.Subscription == nil { - r.errTypeUndef.TypeName = "Subscription" + r.errTypeUndef.Location = locFromItr(i) + r.errTypeUndef.TypeName = typeNameSubscription isErr = true onError(&r.errTypeUndef) return true } - r.schemaTypeStack.Push(r.schema.Subscription) + recentHost = tk } recentDef = gqlscan.TokenDefSub r.operations = append(r.operations, indexRange{ IndexStart: len(r.buffer) - 1, }) + case gqlscan.TokenField: + recentHost = tk + name := i.Value() + if r.schema != nil { + h := r.schemaTypeStack.Top() + f := fieldByName(h.HostType, name) + if f == nil { + r.errFieldUndef.Location = locFromItr(i) + r.errFieldUndef.FieldName = name + r.errFieldUndef.HostTypeName = h.HostType.Name + isErr = true + onError(&r.errFieldUndef) + return true + } + tp := r.schema.Types[f.Type.NamedType] + r.schemaTypeStack.TopOffsetFn(0, func(f *typeStackFrame) { + f.FieldType = tp + }) + } + case gqlscan.TokenFragInline: - if i.Value() != nil { - break + recentHost.ID = 0 + if typeName := i.Value(); typeName == nil { + // Anonymous fragment + r.buffer = r.buffer[:len(r.buffer)-1] + fragStackCounter++ + } else if r.schema != nil { + tp := r.schema.Types[string(typeName)] + if tp == nil { + r.errTypeUndef.Location = locFromItr(i) + r.errTypeUndef.TypeName = typeName + isErr = true + onError(&r.errTypeUndef) + return true + } + host := r.schemaTypeStack.Top().HostType + if !isPossibleType(r.schema, typeName, host) { + r.errCantBeOfType.Location = locFromItr(i) + r.errCantBeOfType.Kind = astTypeKindToString(host) + r.errCantBeOfType.HostTypeName = host.Name + r.errCantBeOfType.SpreadTypeName = typeName + isErr = true + onError(&r.errCantBeOfType) + return true + } + r.schemaTypeStack.Push(typeStackFrame{HostType: tp}) } - r.buffer = r.buffer[:len(r.buffer)-1] - fragStackCounter++ case gqlscan.TokenSet: if fragStackCounter > 0 { @@ -298,38 +356,30 @@ func (r *Parser) Parse( break } stackCounter++ - - beforeSet := r.buffer[len(r.buffer)-2] - if beforeSet.ID == gqlscan.TokenArgListEnd { - // Seek field before the argument list - i := len(r.buffer) - 3 - for ; r.buffer[i].ID != gqlscan.TokenArgList; i-- { - } - beforeSet = r.buffer[i-1] - } - switch beforeSet.ID { - case gqlscan.TokenField: - if r.schema != nil { - h := r.schemaTypeStack.Top() - f := fieldByName(h, beforeSet.Value) - if f == nil { - r.errFieldUndef.FieldName = beforeSet.Value - r.errFieldUndef.HostTypeName = h.Name - isErr = true - onError(&r.errTypeUndef) - return true - } - tp := r.schema.Types[f.Type.NamedType] - r.schemaTypeStack.Push(tp) - } - case gqlscan.TokenFragInline: - if r.schema != nil { - tp := r.schema.Types[string(beforeSet.Value)] - r.schemaTypeStack.Push(tp) + if r.schema != nil { + switch recentHost.ID { + case gqlscan.TokenDefQry: + r.schemaTypeStack.Push(typeStackFrame{ + HostType: r.schema.Query, + }) + case gqlscan.TokenDefMut: + r.schemaTypeStack.Push(typeStackFrame{ + HostType: r.schema.Mutation, + }) + case gqlscan.TokenDefSub: + r.schemaTypeStack.Push(typeStackFrame{ + HostType: r.schema.Subscription, + }) + case gqlscan.TokenField, gqlscan.TokenFragInline: + r.schemaTypeStack.Push(typeStackFrame{ + HostType: r.schemaTypeStack.Top().FieldType, + }) } } case gqlscan.TokenSetEnd: + recentHost.ID = 0 + r.schemaTypeStack.Pop() if fragStackCounter > 0 { r.buffer = r.buffer[:len(r.buffer)-1] fragStackCounter-- @@ -352,7 +402,12 @@ func (r *Parser) Parse( } case gqlscan.TokenNamedSpread: if r.schema != nil { - hostType := r.schemaTypeStack.Top() + t := r.schemaTypeStack.Top() + r.schemaTypeStack.Push(typeStackFrame{ + HostType: t.HostType, + }) + + hostType := r.schemaTypeStack.Top().HostType index := len(r.fragSpreadHostTypes) r.fragSpreadHostTypes = append(r.fragSpreadHostTypes, hostType) @@ -392,12 +447,27 @@ func (r *Parser) Parse( IndexStart: len(r.buffer) - 1, }, }) + case gqlscan.TokenFragTypeCond: + if r.schema != nil { + r.schemaTypeStack.Reset() + d := r.schema.Types[string(i.Value())] + if d == nil { + r.errTypeUndef.Location = locFromItr(i) + r.errTypeUndef.TypeName = i.Value() + isErr = true + onError(&r.errTypeUndef) + return true + } + r.schemaTypeStack.Push(typeStackFrame{HostType: d}) + } } return false }); serr.IsErr() { if isErr { return } + r.errSyntax.Location.IndexHead = serr.Index + r.errSyntax.Location.IndexTail = serr.Index + 1 r.errSyntax.ScanErr = serr onError(&r.errSyntax) return @@ -695,13 +765,23 @@ func (r *Parser) Parse( onSuccess(r.varValues, r.bufferOpr, r.bufferOpr[selectionSetIndex:]) } -type ErrorTypeUndef struct{ TypeName string } +type Location struct{ IndexHead, IndexTail int } + +func locFromItr(i *gqlscan.Iterator) Location { + return Location{IndexHead: i.IndexHead(), IndexTail: i.IndexTail()} +} + +type ErrorTypeUndef struct { + Location + TypeName []byte +} func (e *ErrorTypeUndef) Error() string { - return "undefined type: " + e.TypeName + return "undefined type: " + string(e.TypeName) } type ErrorFieldUndef struct { + Location HostTypeName string FieldName []byte } @@ -713,7 +793,22 @@ func (e *ErrorFieldUndef) Error() string { e.HostTypeName } +type ErrorCantBeOfType struct { + Location + Kind string + HostTypeName string + SpreadTypeName []byte +} + +func (e *ErrorCantBeOfType) Error() string { + return e.Kind + " " + + e.HostTypeName + + " can never be of type " + + string(e.SpreadTypeName) +} + type ErrorSyntax struct { + Location ScanErr gqlscan.Error } @@ -1286,3 +1381,23 @@ func fieldByName(d *ast.Definition, name []byte) *ast.FieldDefinition { func isTypeDirectlyInlinable(schema *ast.Schema, host, child *ast.Definition) bool { return host.Name == child.Name } + +func isPossibleType(schema *ast.Schema, typeName []byte, host *ast.Definition) bool { + possibleTypes := schema.PossibleTypes[host.Name] + for i := range possibleTypes { + if possibleTypes[i].Name == string(typeName) { + return true + } + } + return false +} + +func astTypeKindToString(d *ast.Definition) string { + switch d.Kind { + case ast.Union: + return "union" + case ast.Interface: + return "interface" + } + return string(d.Kind) +} diff --git a/gqlparse/gqlparse_test.go b/gqlparse/gqlparse_test.go index 294579e..3972135 100644 --- a/gqlparse/gqlparse_test.go +++ b/gqlparse/gqlparse_test.go @@ -1958,11 +1958,273 @@ var testdataErr = []decl.Declaration[TestError]{ require.Equal(t, "fragment limit (128) exceeded", err.Error()) }, }), + + /* TYPE ERRORS */ + + decl.New(TestError{ + Name: "undefined_field_in_query_type", + Schema: "type Query { foo:String! }", + Src: `{ bar }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorFieldUndef{ + Location: gqlparse.Location{IndexTail: 2, IndexHead: 5}, + FieldName: []byte("bar"), + HostTypeName: "Query", + }, err) + require.Equal(t, `undefined field (bar) in type Query`, err.Error()) + }, + }), + decl.New(TestError{ + Name: "undefined_field_in_mutation_type", + Schema: "type Mutation { foo:String! }", + Src: `mutation { bar }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorFieldUndef{ + Location: gqlparse.Location{IndexTail: 11, IndexHead: 14}, + FieldName: []byte("bar"), + HostTypeName: "Mutation", + }, err) + require.Equal(t, `undefined field (bar) in type Mutation`, err.Error()) + }, + }), + decl.New(TestError{ + Name: "undefined_field_in_subscription_type", + Schema: "type Subscription { foo:String! }", + Src: `subscription { bar }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorFieldUndef{ + Location: gqlparse.Location{IndexTail: 15, IndexHead: 18}, + FieldName: []byte("bar"), + HostTypeName: "Subscription", + }, err) + require.Equal(t, `undefined field (bar) in type Subscription`, err.Error()) + }, + }), + decl.New(TestError{ + Name: "undefined_field_in_named_type", + Schema: ` + type Query { foo:Foo! } + type Foo { bar:String! } + `, + Src: `{ foo { bazz } }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorFieldUndef{ + Location: gqlparse.Location{IndexTail: 8, IndexHead: 12}, + FieldName: []byte("bazz"), + HostTypeName: "Foo", + }, err) + require.Equal(t, `undefined field (bazz) in type Foo`, err.Error()) + }, + }), + decl.New(TestError{ + Name: "undefined_field_in_named_subtype", + Schema: ` + type Query { foo:Foo! } + type Foo { bar:Bar! } + type Bar { bar:String! } + `, + Src: `{ foo { bar { bazz } } }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorFieldUndef{ + Location: gqlparse.Location{IndexTail: 14, IndexHead: 18}, + FieldName: []byte("bazz"), + HostTypeName: "Bar", + }, err) + require.Equal(t, `undefined field (bazz) in type Bar`, err.Error()) + }, + }), + decl.New(TestError{ + Name: "undefined_field_in_interface", + Schema: ` + type Query { interface:Interface! x:X! } + interface Interface { bar:String! } + type X implements Interface { bar:String! } + `, + Src: `{ interface { bazz } }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorFieldUndef{ + Location: gqlparse.Location{IndexTail: 14, IndexHead: 18}, + FieldName: []byte("bazz"), + HostTypeName: "Interface", + }, err) + require.Equal(t, `undefined field (bazz) in type Interface`, err.Error()) + }, + }), + decl.New(TestError{ + Name: "undefined_field_in_inline_frag", + Schema: ` + type Query { u:U } + union U = Foo | Bar + type Foo { foo:String } + type Bar { bar:String } + `, + Src: `{ u { ... on Foo { bar } } }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorFieldUndef{ + Location: gqlparse.Location{IndexTail: 19, IndexHead: 22}, + FieldName: []byte("bar"), + HostTypeName: "Foo", + }, err) + require.Equal(t, `undefined field (bar) in type Foo`, err.Error()) + }, + }), + decl.New(TestError{ + Name: "undefined_field_in_sametype_inline_frag_inside_query", + Schema: ` + type Query { foo:String! } + `, + Src: `{ ... on Query { bar } }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorFieldUndef{ + Location: gqlparse.Location{IndexTail: 17, IndexHead: 20}, + FieldName: []byte("bar"), + HostTypeName: "Query", + }, err) + require.Equal(t, `undefined field (bar) in type Query`, err.Error()) + }, + }), + decl.New(TestError{ + Name: "undefined_field_in_2d_sametype_inline_frag_inside_query", + Schema: ` + type Query { foo:String! } + `, + Src: `{ ... on Query { ... on Query { bar } } }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorFieldUndef{ + Location: gqlparse.Location{IndexTail: 32, IndexHead: 35}, + FieldName: []byte("bar"), + HostTypeName: "Query", + }, err) + require.Equal(t, `undefined field (bar) in type Query`, err.Error()) + }, + }), + decl.New(TestError{ + Name: "undefined_field_in_anonymous_inline_frag_inside_query", + Schema: ` + type Query { foo:String! } + `, + Src: `{ ... { bar } }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorFieldUndef{ + Location: gqlparse.Location{IndexTail: 8, IndexHead: 11}, + FieldName: []byte("bar"), + HostTypeName: "Query", + }, err) + require.Equal(t, `undefined field (bar) in type Query`, err.Error()) + }, + }), + decl.New(TestError{ + Name: "undefined_field_in_2d_anonymous_inline_frag_inside_query", + Schema: ` + type Query { foo:String! } + `, + Src: `{ ... { ... { bar } } }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorFieldUndef{ + Location: gqlparse.Location{IndexTail: 14, IndexHead: 17}, + FieldName: []byte("bar"), + HostTypeName: "Query", + }, err) + require.Equal(t, `undefined field (bar) in type Query`, err.Error()) + }, + }), + + decl.New(TestError{ + Name: "undefined_type_in_inline_fragment", + Schema: ` + type Query { u: U! } + union U = Bar | Bazz + type Bar { bar:String! } + type Bazz { bazz:String! } + `, + Src: `{ u { ... on Foo { foo } } }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorTypeUndef{ + Location: gqlparse.Location{IndexTail: 13, IndexHead: 16}, + TypeName: []byte("Foo"), + }, err) + require.Equal(t, `undefined type: Foo`, err.Error()) + }, + }), + decl.New(TestError{ + Name: "undefined_type_in_fragment", + Schema: ` + type Query { foo:String } + `, + Src: `{ ...f } fragment f on Foo { foo }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorTypeUndef{ + Location: gqlparse.Location{IndexTail: 23, IndexHead: 26}, + TypeName: []byte("Foo"), + }, err) + require.Equal(t, `undefined type: Foo`, err.Error()) + }, + }), + + decl.New(TestError{ + Name: "unsupported_type_in_union", + Schema: ` + type Query { u: U! } + union U = Bar | Baz + type Foo { foo:String! } + type Bar { bar:String! } + type Baz { baz:String! } + `, + Src: `{ u { ... on Foo { foo } } }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorCantBeOfType{ + Location: gqlparse.Location{IndexTail: 13, IndexHead: 16}, + Kind: "union", + HostTypeName: "U", + SpreadTypeName: []byte("Foo"), + }, err) + require.Equal(t, `union U can never be of type Foo`, err.Error()) + }, + }), + decl.New(TestError{ + Name: "unsupported_type_in_interface", + Schema: ` + type Query { i:Iface! } + interface Iface { x:String! } + type Foo { x:String! } + type Bar implements Iface { x:String! } + type Baz implements Iface { x:String! } + `, + Src: `{ i { ... on Foo { x } } }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorCantBeOfType{ + Location: gqlparse.Location{IndexTail: 13, IndexHead: 16}, + Kind: "interface", + HostTypeName: "Iface", + SpreadTypeName: []byte("Foo"), + }, err) + require.Equal(t, `interface Iface can never be of type Foo`, err.Error()) + }, + }), } func TestErr(t *testing.T) { for _, td := range testdataErr { - t.Run(td.Decl, func(t *testing.T) { + name := td.Decl + if td.Data.Name != "" { + name = td.Data.Name + } + t.Run(name, func(t *testing.T) { var schema *ast.Schema if td.Data.Schema != "" { var err error @@ -2031,6 +2293,7 @@ func Token(t gqlscan.Token, value ...string) gqlparse.Token { } type TestError struct { + Name string Schema string Src string VarsJSON string diff --git a/server/server_test.go b/server/server_test.go index 8ac6f42..3349a82 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -137,7 +137,7 @@ func GetSetups(t *testing.T) []testsetup.Setup { return []testsetup.Setup{ testsetup.Starwars(), testsetup.Test1(), - testsetup.Test2(), + testsetup.InputsSchema(), } } diff --git a/testsetup/test2/all-services/service_a.yaml b/testsetup/inputs_schema/all-services/service_a.yaml similarity index 86% rename from testsetup/test2/all-services/service_a.yaml rename to testsetup/inputs_schema/all-services/service_a.yaml index 4ce506f..d7d5fba 100644 --- a/testsetup/test2/all-services/service_a.yaml +++ b/testsetup/inputs_schema/all-services/service_a.yaml @@ -4,3 +4,4 @@ forward-url: "http://localhost:8081/service_a" forward-reduced: false all-templates: ../all-templates/service_a enabled-templates: ../enabled-templates/service_a +schema: ../service_a.graphqls diff --git a/testsetup/inputs_schema/all-templates/service_a/mut_arr_str.gqt b/testsetup/inputs_schema/all-templates/service_a/mut_arr_str.gqt new file mode 100644 index 0000000..46a9c75 --- /dev/null +++ b/testsetup/inputs_schema/all-templates/service_a/mut_arr_str.gqt @@ -0,0 +1,10 @@ +--- +name: "Mutation Array of Strings" +tags: + - mutation + - array + - string +--- +mutation { + arr_str_eq(a: ["foo", "bar"]) +} diff --git a/testsetup/test2/config.yml b/testsetup/inputs_schema/config.yml similarity index 100% rename from testsetup/test2/config.yml rename to testsetup/inputs_schema/config.yml diff --git a/testsetup/test2/enabled-services/service_a.yaml b/testsetup/inputs_schema/enabled-services/service_a.yaml similarity index 86% rename from testsetup/test2/enabled-services/service_a.yaml rename to testsetup/inputs_schema/enabled-services/service_a.yaml index 4ce506f..d7d5fba 100644 --- a/testsetup/test2/enabled-services/service_a.yaml +++ b/testsetup/inputs_schema/enabled-services/service_a.yaml @@ -4,3 +4,4 @@ forward-url: "http://localhost:8081/service_a" forward-reduced: false all-templates: ../all-templates/service_a enabled-templates: ../enabled-templates/service_a +schema: ../service_a.graphqls diff --git a/testsetup/inputs_schema/enabled-templates/service_a/mut_arr_str.gqt b/testsetup/inputs_schema/enabled-templates/service_a/mut_arr_str.gqt new file mode 100644 index 0000000..46a9c75 --- /dev/null +++ b/testsetup/inputs_schema/enabled-templates/service_a/mut_arr_str.gqt @@ -0,0 +1,10 @@ +--- +name: "Mutation Array of Strings" +tags: + - mutation + - array + - string +--- +mutation { + arr_str_eq(a: ["foo", "bar"]) +} diff --git a/testsetup/inputs_schema/service_a.graphqls b/testsetup/inputs_schema/service_a.graphqls new file mode 100644 index 0000000..e91228e --- /dev/null +++ b/testsetup/inputs_schema/service_a.graphqls @@ -0,0 +1,3 @@ +type Mutation { + arr_str_eq(a: [String!]!): [String!]! +} diff --git a/testsetup/test2/test_1.yaml b/testsetup/inputs_schema/test_arr_str_eq.yaml similarity index 63% rename from testsetup/test2/test_1.yaml rename to testsetup/inputs_schema/test_arr_str_eq.yaml index d42ea98..ee600b2 100644 --- a/testsetup/test2/test_1.yaml +++ b/testsetup/inputs_schema/test_arr_str_eq.yaml @@ -3,8 +3,13 @@ client: method: POST endpoint: /service_a body(JSON): - query: 'mutation X { a { a0(a0_0: [ 0 ]) } }' - operationName: 'X' + query: > + mutation( + $foo:String! = "foo", + $bar:String! = "bar", + ) { + arr_str_eq(a: [$foo, $bar]) + } expect-response: status: 200 @@ -16,8 +21,7 @@ client: X-Custom-Header: value body(JSON): data: - a: - a0: 'foo' + arr_str_eq: ["foo", "bar"] destination: expect-forwarded: @@ -31,8 +35,13 @@ destination: User-Agent: ^fasthttp$ Date: . body(JSON): - query: 'mutation X { a { a0(a0_0: [ 0 ]) } }' - operationName: 'X' + query: > + mutation( + $foo:String! = "foo", + $bar:String! = "bar", + ) { + arr_str_eq(a: [$foo, $bar]) + } response: status: 200 @@ -40,8 +49,7 @@ destination: X-Custom-Header: value body(JSON): data: - a: - a0: 'foo' + arr_str_eq: ["foo", "bar"] logs: - level: info @@ -55,4 +63,10 @@ logs: path: /service_a - level: debug path: /service_a - query: '{"operationName":"X","query":"mutation X { a { a0(a0_0: [ 0 ]) } }"}' + query: > + mutation( + $foo:String! = "foo", + $bar:String! = "bar", + ) { + arr_str_eq(a: [$foo, $bar]) + } \ No newline at end of file diff --git a/testsetup/starwars/services_enabled/starwars/templates_enabled/max_1.gqt b/testsetup/starwars/services_enabled/starwars/templates_enabled/max_1.gqt new file mode 100644 index 0000000..49286f2 --- /dev/null +++ b/testsetup/starwars/services_enabled/starwars/templates_enabled/max_1.gqt @@ -0,0 +1,14 @@ +--- +name: "Max1" +tags: + - max_selection +--- +query { + human(id: *) { + max 2 { + id + name + appearsIn + } + } +} diff --git a/testsetup/test2/all-templates/service_a/template_a.gqt b/testsetup/test2/all-templates/service_a/template_a.gqt deleted file mode 100644 index b6b8e68..0000000 --- a/testsetup/test2/all-templates/service_a/template_a.gqt +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: "Template A1" -tags: - - tag_a - - mutation ---- -mutation { - a { - a0( - a0_0: [ <= 0 ] && != [ -1 ] - ) - } -} diff --git a/testsetup/test2/enabled-templates/service_a/template_a.gqt b/testsetup/test2/enabled-templates/service_a/template_a.gqt deleted file mode 100644 index b6b8e68..0000000 --- a/testsetup/test2/enabled-templates/service_a/template_a.gqt +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: "Template A1" -tags: - - tag_a - - mutation ---- -mutation { - a { - a0( - a0_0: [ <= 0 ] && != [ -1 ] - ) - } -} diff --git a/testsetup/testsetup.go b/testsetup/testsetup.go index e69e022..c8f8796 100644 --- a/testsetup/testsetup.go +++ b/testsetup/testsetup.go @@ -20,12 +20,12 @@ var fsStarwars embed.FS //go:embed test1 var fsTest1 embed.FS -//go:embed test2 -var fsTest2 embed.FS +//go:embed inputs_schema +var fsInputsSchema embed.FS -func Starwars() Setup { return read(fsStarwars, "starwars") } -func Test1() Setup { return read(fsTest1, "test1") } -func Test2() Setup { return read(fsTest2, "test2") } +func Starwars() Setup { return read(fsStarwars, "starwars") } +func Test1() Setup { return read(fsTest1, "test1") } +func InputsSchema() Setup { return read(fsInputsSchema, "inputs_schema") } func read(fsys fs.FS, root string) Setup { c, err := config.Read(fsys, root, filepath.Join(root, "config.yml")) diff --git a/testsetup/testsetup_test.go b/testsetup/testsetup_test.go index 4dcef7c..c115ff0 100644 --- a/testsetup/testsetup_test.go +++ b/testsetup/testsetup_test.go @@ -7,9 +7,9 @@ import ( "github.com/stretchr/testify/require" ) -func TestStarwars(t *testing.T) { checkSetup(t, testsetup.Starwars()) } -func TestTest1(t *testing.T) { checkSetup(t, testsetup.Test1()) } -func TestTest2(t *testing.T) { checkSetup(t, testsetup.Test2()) } +func TestStarwars(t *testing.T) { checkSetup(t, testsetup.Starwars()) } +func TestTest1(t *testing.T) { checkSetup(t, testsetup.Test1()) } +func TestInputsSchema(t *testing.T) { checkSetup(t, testsetup.InputsSchema()) } func checkSetup(t *testing.T, s testsetup.Setup) { require.NotZero(t, s.Config) diff --git a/utilities/stack/stack.go b/utilities/stack/stack.go index 4db49d6..1c710a5 100644 --- a/utilities/stack/stack.go +++ b/utilities/stack/stack.go @@ -56,10 +56,18 @@ func (s *Stack[T]) Top() (top T) { return top } +// TopOffset returns the last stack element relative to the offset. +func (s *Stack[T]) TopOffset(offset int) (top T) { + if l := len(s.s) - (1 + offset); l >= 0 { + return (s.s)[l] + } + return top +} + // TopOffsetFn calls fn with the last stack element at offset. func (s *Stack[T]) TopOffsetFn(offset int, fn func(*T)) { - if l := len(s.s) - 1; l >= 0 { - fn(&(s.s)[l-offset]) + if l := len(s.s) - (1 + offset); l >= 0 { + fn(&(s.s)[l]) } } diff --git a/utilities/stack/stack_test.go b/utilities/stack/stack_test.go index aa34e8e..3831e38 100644 --- a/utilities/stack/stack_test.go +++ b/utilities/stack/stack_test.go @@ -59,6 +59,34 @@ func TestTop(t *testing.T) { require.Equal(t, 0, st.Top()) } +func TestTopOffset(t *testing.T) { + st := stack.New[int](2) + st.Push(1) + st.Push(2) + st.Push(3) + require.Equal(t, 3, st.TopOffset(0)) + require.Equal(t, 2, st.TopOffset(1)) + require.Equal(t, 1, st.TopOffset(2)) + require.Zero(t, st.TopOffset(3)) + st.Pop() + require.Equal(t, 2, st.TopOffset(0)) + require.Equal(t, 1, st.TopOffset(1)) + require.Zero(t, st.TopOffset(2)) + require.Zero(t, st.TopOffset(3)) +} + +func TestTopOffsetNeg(t *testing.T) { + st := stack.New[int](2) + st.Push(1) + st.Push(2) + require.Panics(t, func() { + require.Zero(t, st.TopOffset(-1)) + }) + require.Panics(t, func() { + require.Zero(t, st.TopOffset(-2)) + }) +} + func TestTopOffsetFn(t *testing.T) { st := stack.New[int](2) st.Push(0) @@ -76,6 +104,18 @@ func TestTopOffsetFn(t *testing.T) { require.Equal(t, 0, st.Top()) } +func TestTopOffsetFnNeg(t *testing.T) { + st := stack.New[int](2) + st.Push(1) + st.Push(2) + require.Panics(t, func() { + st.TopOffsetFn(-1, func(t *int) {}) + }) + require.Panics(t, func() { + st.TopOffsetFn(-2, func(t *int) {}) + }) +} + func TestGet(t *testing.T) { st := stack.New[int](2) st.Push(0) From 8f7e788e25e082d0251be93d901bf84644e2758a Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Fri, 3 Mar 2023 00:31:33 +0100 Subject: [PATCH 17/35] test: Add test --- gqlparse/gqlparse_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/gqlparse/gqlparse_test.go b/gqlparse/gqlparse_test.go index 3972135..847b1da 100644 --- a/gqlparse/gqlparse_test.go +++ b/gqlparse/gqlparse_test.go @@ -2216,6 +2216,25 @@ var testdataErr = []decl.Declaration[TestError]{ require.Equal(t, `interface Iface can never be of type Foo`, err.Error()) }, }), + decl.New(TestError{ + Name: "unsupported_type_in_object", + Schema: ` + type Query { foo:Foo! } + type Foo { foo:String! } + type Bar { bar:String! } + `, + Src: `{ foo { ... on Bar { bar } } }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorCantBeOfType{ + Location: gqlparse.Location{IndexTail: 15, IndexHead: 18}, + Kind: "OBJECT", + HostTypeName: "Foo", + SpreadTypeName: []byte("Bar"), + }, err) + require.Equal(t, `OBJECT Foo can never be of type Bar`, err.Error()) + }, + }), } func TestErr(t *testing.T) { From ae336ce1ab14de0079123cea6a652058c2c7c83d Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Fri, 3 Mar 2023 00:57:23 +0100 Subject: [PATCH 18/35] test: Name individual tests --- gqlparse/bench_test.go | 14 +- gqlparse/gqlparse_test.go | 380 ++++++++++++++++++++++---------------- 2 files changed, 224 insertions(+), 170 deletions(-) diff --git a/gqlparse/bench_test.go b/gqlparse/bench_test.go index 6280667..b991f0c 100644 --- a/gqlparse/bench_test.go +++ b/gqlparse/bench_test.go @@ -44,22 +44,22 @@ func BenchmarkParse(b *testing.B) { } func BenchmarkParseErr(b *testing.B) { - for _, td := range testdataErr { + for _, td := range testsErr { var schema *ast.Schema - if td.Data.Schema != "" { + if td.Schema != "" { var err error if schema, err = gqlparser.LoadSchema(&ast.Source{ - Name: "schema.graphqls", Input: td.Data.Schema, + Name: "schema.graphqls", Input: td.Schema, }); err != nil { b.Fatalf("parsing schema: %v", err) } } r := gqlparse.NewParser(schema) - b.Run(td.Decl, func(b *testing.B) { - src := []byte(td.Data.Src) - opr := []byte(td.Data.OprName) - varJSON := []byte(td.Data.VarsJSON) + b.Run(td.Name, func(b *testing.B) { + src := []byte(td.Src) + opr := []byte(td.OprName) + varJSON := []byte(td.VarsJSON) b.ResetTimer() for n := 0; n < b.N; n++ { r.Parse(src, opr, varJSON, func( diff --git a/gqlparse/gqlparse_test.go b/gqlparse/gqlparse_test.go index 847b1da..f9a3334 100644 --- a/gqlparse/gqlparse_test.go +++ b/gqlparse/gqlparse_test.go @@ -1197,18 +1197,20 @@ func TestOK(t *testing.T) { } } -var testdataErr = []decl.Declaration[TestError]{ +var testsErr = []TestError{ // Operation not found - decl.New(TestError{ - Src: `query A {x}, query B {x}`, + { + Name: "operation_not_found_empty_operation_name", + Src: `query A {x}, query B {x}`, Check: func(t *testing.T, err error) { require.Equal(t, &gqlparse.ErrorOprNotFound{ OperationName: []byte(""), }, err) require.Equal(t, `operation "" not found`, err.Error()) }, - }), - decl.New(TestError{ + }, + { + Name: "operation_not_found", Src: `query A {x}, query B {x}`, OprName: "C", Check: func(t *testing.T, err error) { @@ -1217,8 +1219,9 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `operation "C" not found`, err.Error()) }, - }), - decl.New(TestError{ + }, + { + Name: "operation_not_found_single_anonymous", Src: `{x}`, OprName: "A", Check: func(t *testing.T, err error) { @@ -1227,62 +1230,77 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `operation "A" not found`, err.Error()) }, - }), + }, // Non-exclusive anonymous operation - decl.New(TestError{ - Src: `{x}, query{x}`, + { + Name: "non_exclusive_anonymous_operation_query_1", + Src: `{x}, query{x}`, Check: func(t *testing.T, err error) { require.Equal(t, &gqlparse.ErrorOprAnonNonExcl{}, err) require.Equal(t, `non-exclusive anonymous operation`, err.Error()) }, - }), - decl.New(TestError{ - Src: `{x}, mutation M {x}`, + }, + { + Name: "non_exclusive_anonymous_operation_query_2", + Src: `query A {x}, query {x}`, Check: func(t *testing.T, err error) { require.Equal(t, &gqlparse.ErrorOprAnonNonExcl{}, err) require.Equal(t, `non-exclusive anonymous operation`, err.Error()) }, - }), - decl.New(TestError{ - Src: `query A {x}, query {x}`, + }, + { + Name: "non_exclusive_anonymous_operation_mutation", + Src: `{x}, mutation M {x}`, Check: func(t *testing.T, err error) { require.Equal(t, &gqlparse.ErrorOprAnonNonExcl{}, err) require.Equal(t, `non-exclusive anonymous operation`, err.Error()) }, - }), + }, + { + Name: "non_exclusive_anonymous_operation_subscription", + Src: `{x}, subscription S {x}`, + Check: func(t *testing.T, err error) { + require.Equal(t, &gqlparse.ErrorOprAnonNonExcl{}, err) + require.Equal(t, `non-exclusive anonymous operation`, err.Error()) + }, + }, // Redeclared operation - decl.New(TestError{ - Src: `query A {x}, query A {x}`, + { + Name: "redeclared_operation_query", + Src: `query A {x}, query A {x}`, Check: func(t *testing.T, err error) { require.Equal(t, &gqlparse.ErrorRedecOpr{ OperationName: []byte("A"), }, err) require.Equal(t, `operation "A" redeclared`, err.Error()) }, - }), - decl.New(TestError{ - Src: `query M {x}, mutation M {x}`, + }, + { + Name: "redeclared_operation_query_mutation", + Src: `query M {x}, mutation M {x}`, Check: func(t *testing.T, err error) { require.Equal(t, &gqlparse.ErrorRedecOpr{ OperationName: []byte("M"), }, err) require.Equal(t, `operation "M" redeclared`, err.Error()) }, - }), - decl.New(TestError{ - Src: `query S {x}, subscription S {x}`, + }, + { + Name: "redeclared_operation_subscription", + Src: `query S {x}, subscription S {x}`, Check: func(t *testing.T, err error) { require.Equal(t, &gqlparse.ErrorRedecOpr{ OperationName: []byte("S"), }, err) require.Equal(t, `operation "S" redeclared`, err.Error()) }, - }), + }, // Redeclared fragment - decl.New(TestError{ + { + Name: "redeclared_fragment", Src: `{...f} fragment f on Query {x} fragment f on Query {x}`, @@ -1292,10 +1310,11 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `fragment "f" redeclared`, err.Error()) }, - }), + }, // Unused fragment - decl.New(TestError{ + { + Name: "unused_fragment", Src: `{...f} fragment f on Query {x} fragment a on Query {x}`, @@ -1305,10 +1324,11 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `fragment "a" unused`, err.Error()) }, - }), + }, // Recursive fragment - decl.New(TestError{ + { + Name: "recursive_fragment", Src: `{...a} fragment a on Query {...a}`, Check: func(t *testing.T, err error) { @@ -1322,8 +1342,9 @@ var testdataErr = []decl.Declaration[TestError]{ t, `fragment recursion detected at: a.a`, err.Error(), ) }, - }), - decl.New(TestError{ + }, + { + Name: "redeclared_fragment_level2", Src: `{...a} fragment a on Query {...b} fragment b on Query {...a}`, @@ -1339,8 +1360,9 @@ var testdataErr = []decl.Declaration[TestError]{ t, `fragment recursion detected at: a.b.a`, err.Error(), ) }, - }), - decl.New(TestError{ + }, + { + Name: "redeclared_fragment_complex", Src: `{...a, ...a1} fragment a on Query {...b} fragment a1 on Query {...b} @@ -1359,19 +1381,21 @@ var testdataErr = []decl.Declaration[TestError]{ t, `fragment recursion detected at: b.c.a1.b`, err.Error(), ) }, - }), + }, // Redeclared variable - decl.New(TestError{ - Src: `query($v1:String, $v1:Int){f(a:$v1)}`, + { + Name: "redeclared_variable", + Src: `query($v1:String, $v1:Int){f(a:$v1)}`, Check: func(t *testing.T, err error) { require.Equal(t, &gqlparse.ErrorRedeclVar{ VariableName: []byte("v1"), }, err) require.Equal(t, `variable "v1" redeclared`, err.Error()) }, - }), - decl.New(TestError{ + }, + { + Name: "redeclared_variable_in_named_query", Src: `query Q ($v2:String, $v2:Int){f(a:$v2)}`, OprName: "Q", Check: func(t *testing.T, err error) { @@ -1380,11 +1404,12 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `variable "v2" redeclared`, err.Error()) }, - }), + }, // Default value wrong type - decl.New(TestError{ - Src: `query ($v:String=true) { f(a:$v) }`, + { + Name: "default_value_wrong_type_boolean_as_string", + Src: `query ($v:String=true) { f(a:$v) }`, Check: func(t *testing.T, err error) { require.Error(t, err) require.IsType(t, &gqlparse.ErrorUnexpValType{}, err) @@ -1396,9 +1421,11 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ - Src: `query ($v:[String]="okay") { f(a:$v) }`, + }, + { + // TODO: make sure this is really illegal and not a case of legal coersion + Name: "default_value_wrong_type_string_as_array_string", + Src: `query ($v:[String]="okay") { f(a:$v) }`, Check: func(t *testing.T, err error) { require.Error(t, err) require.IsType(t, &gqlparse.ErrorUnexpValType{}, err) @@ -1410,9 +1437,10 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ - Src: `query ($v:Int=42.5) { f(a:$v) }`, + }, + { + Name: "default_value_wrong_type_float_as_int", + Src: `query ($v:Int=42.5) { f(a:$v) }`, Check: func(t *testing.T, err error) { require.Error(t, err) require.IsType(t, &gqlparse.ErrorUnexpValType{}, err) @@ -1424,9 +1452,10 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ - Src: `query ($v:Float=false) { f(a:$v) }`, + }, + { + Name: "default_value_wrong_type_boolean_as_float", + Src: `query ($v:Float=false) { f(a:$v) }`, Check: func(t *testing.T, err error) { require.Error(t, err) require.IsType(t, &gqlparse.ErrorUnexpValType{}, err) @@ -1438,9 +1467,10 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ - Src: `query ($v:Boolean=1) { f(a:$v) }`, + }, + { + Name: "default_value_wrong_type_int_as_boolean", + Src: `query ($v:Boolean=1) { f(a:$v) }`, Check: func(t *testing.T, err error) { require.Error(t, err) require.IsType(t, &gqlparse.ErrorUnexpValType{}, err) @@ -1452,9 +1482,10 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ - Src: `query ($v:Input=1) { f(a:$v) }`, + }, + { + Name: "default_value_wrong_type_int_as_input", + Src: `query ($v:Input=1) { f(a:$v) }`, Check: func(t *testing.T, err error) { require.Error(t, err) require.IsType(t, &gqlparse.ErrorUnexpValType{}, err) @@ -1466,9 +1497,10 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ - Src: `query ($v:Int={foo:"bar", baz:42}) { f(a:$v) }`, + }, + { + Name: "default_value_wrong_type_input_as_int", + Src: `query ($v:Int={foo:"bar", baz:42}) { f(a:$v) }`, Check: func(t *testing.T, err error) { require.Error(t, err) require.IsType(t, &gqlparse.ErrorUnexpValType{}, err) @@ -1480,9 +1512,10 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ - Src: `query ($v:Int=[]) { f(a:$v) }`, + }, + { + Name: "default_value_wrong_type_array_empty_as_int", + Src: `query ($v:Int=[]) { f(a:$v) }`, Check: func(t *testing.T, err error) { require.Error(t, err) require.IsType(t, &gqlparse.ErrorUnexpValType{}, err) @@ -1494,9 +1527,10 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ - Src: `query ($v:Int=[{x:2,y:4}, null, {x:8,y:8}]) { f(a:$v) }`, + }, + { + Name: "default_value_wrong_type_array_input_as_int", + Src: `query ($v:Int=[{x:2,y:4}, null, {x:8,y:8}]) { f(a:$v) }`, Check: func(t *testing.T, err error) { require.Error(t, err) require.IsType(t, &gqlparse.ErrorUnexpValType{}, err) @@ -1508,9 +1542,10 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ - Src: `query ($v:Int! = null) { f(a:$v) }`, + }, + { + Name: "default_value_wrong_type_null_as_int_notnull", + Src: `query ($v:Int! = null) { f(a:$v) }`, Check: func(t *testing.T, err error) { require.Error(t, err) require.IsType(t, &gqlparse.ErrorUnexpValType{}, err) @@ -1522,10 +1557,11 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), + }, // JSON variable wrong type - decl.New(TestError{ + { + Name: "json_variable_wrong_type_boolean_as_string", Src: `query ($v:String) { f(a:$v) }`, VarsJSON: `{"v":true}`, Check: func(t *testing.T, err error) { @@ -1539,8 +1575,10 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ + }, + { + // TODO: make sure this is really illegal and not a case of legal coersion + Name: "json_variable_wrong_type_string_as_array_string", Src: `query ($v:[String]) { f(a:$v) }`, VarsJSON: `{"v":"okay"}`, Check: func(t *testing.T, err error) { @@ -1554,8 +1592,9 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ + }, + { + Name: "json_variable_wrong_type_float_as_int", Src: `query ($v:Int) { f(a:$v) }`, VarsJSON: `{"v":42.5}`, Check: func(t *testing.T, err error) { @@ -1569,8 +1608,9 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ + }, + { + Name: "json_variable_wrong_type_boolean_as_float", Src: `query ($v:Float) { f(a:$v) }`, VarsJSON: `{"v":false}`, Check: func(t *testing.T, err error) { @@ -1584,8 +1624,9 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ + }, + { + Name: "json_variable_wrong_type_int_as_boolean", Src: `query ($v:Boolean) { f(a:$v) }`, VarsJSON: `{"v":1}`, Check: func(t *testing.T, err error) { @@ -1599,8 +1640,9 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ + }, + { + Name: "json_variable_wrong_type_int_as_input", Src: `query ($v:Input) { f(a:$v) }`, VarsJSON: `{"v":1}`, Check: func(t *testing.T, err error) { @@ -1614,8 +1656,9 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ + }, + { + Name: "json_variable_wrong_type_input_as_int", Src: `query ($v:Int) { f(a:$v) }`, VarsJSON: `{"v":{"foo":"bar","baz":42}}`, Check: func(t *testing.T, err error) { @@ -1629,8 +1672,9 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ + }, + { + Name: "json_variable_wrong_type_array_empty_as_int", Src: `query ($v:Int) { f(a:$v) }`, VarsJSON: `{"v":[]}`, Check: func(t *testing.T, err error) { @@ -1644,8 +1688,9 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ + }, + { + Name: "json_variable_wrong_type_array_input_as_int", Src: `query ($v:Int) { f(a:$v) }`, VarsJSON: `{"v":[{"x":2,"y":4}, null, {"x":8,"y":8}]}`, Check: func(t *testing.T, err error) { @@ -1659,8 +1704,9 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), - decl.New(TestError{ + }, + { + Name: "json_variable_wrong_type_null_as_int_notnull", Src: `query ($v:Int!) { f(a:$v) }`, VarsJSON: `{"v":null}`, Check: func(t *testing.T, err error) { @@ -1674,10 +1720,11 @@ var testdataErr = []decl.Declaration[TestError]{ err.Error(), ) }, - }), + }, // Invalid variables JSON (non-object) - decl.New(TestError{ + { + Name: "invalid_variable_json_array_string", Src: `query ($v:String) { f(a:$v) }`, VarsJSON: `["okay"]`, Check: func(t *testing.T, err error) { @@ -1686,8 +1733,9 @@ var testdataErr = []decl.Declaration[TestError]{ require.Equal(t, `expected JSON object for variables, `+ `received: ["okay"]`, err.Error()) }, - }), - decl.New(TestError{ + }, + { + Name: "invalid_variable_json_int", Src: `query ($v:Int!) { f(a:$v) }`, VarsJSON: `42`, Check: func(t *testing.T, err error) { @@ -1696,21 +1744,23 @@ var testdataErr = []decl.Declaration[TestError]{ require.Equal(t, `expected JSON object for variables, `+ `received: 42`, err.Error()) }, - }), + }, // Query syntax error - decl.New(TestError{ - Src: `query ($v:String = ) { f(a:$v) }`, + { + Name: "invalid_query_syntax", + Src: `query ($v:String = ) { f(a:$v) }`, Check: func(t *testing.T, err error) { require.Error(t, err) require.IsType(t, &gqlparse.ErrorSyntax{}, err) require.Equal(t, `syntax error: error at index 19 (')'):`+ ` unexpected token; expected enum value`, err.Error()) }, - }), + }, // Invalid variables JSON (syntax error) - decl.New(TestError{ + { + Name: "invalid_variable_json_syntax_missing_comma", Src: `query ($v:String) { f(a:$v) }`, VarsJSON: `{"v":"first" "missing-comma": "second"}`, Check: func(t *testing.T, err error) { @@ -1718,8 +1768,9 @@ var testdataErr = []decl.Declaration[TestError]{ require.IsType(t, &gqlparse.ErrorVarJSONSyntax{}, err) require.Equal(t, `variables JSON syntax error`, err.Error()) }, - }), - decl.New(TestError{ + }, + { + Name: "invalid_variable_json_syntax_noquotes_key", Src: `query ($v:Int!) { f(a:$v) }`, VarsJSON: `{v:42}`, Check: func(t *testing.T, err error) { @@ -1727,8 +1778,9 @@ var testdataErr = []decl.Declaration[TestError]{ require.IsType(t, &gqlparse.ErrorVarJSONSyntax{}, err) require.Equal(t, `variables JSON syntax error`, err.Error()) }, - }), - decl.New(TestError{ + }, + { + Name: "invalid_variable_json_syntax_noquotes_value", Src: `query ($v:String!) { f(a:$v) }`, VarsJSON: `{"v": missing_quotes}`, Check: func(t *testing.T, err error) { @@ -1736,10 +1788,11 @@ var testdataErr = []decl.Declaration[TestError]{ require.IsType(t, &gqlparse.ErrorVarJSONSyntax{}, err) require.Equal(t, `variables JSON syntax error`, err.Error()) }, - }), + }, // Undeclared variable - decl.New(TestError{ + { + Name: "undeclared_variable", Src: `{ f(a:$u) }`, VarsJSON: `{"u":42}`, Check: func(t *testing.T, err error) { @@ -1747,8 +1800,9 @@ var testdataErr = []decl.Declaration[TestError]{ require.IsType(t, &gqlparse.ErrorVarUndeclared{}, err) require.Equal(t, `variable "u" undeclared`, err.Error()) }, - }), - decl.New(TestError{ + }, + { + Name: "undeclared_variable_json", Src: `query ($v:String!) { f(a:$u) }`, VarsJSON: `{"v":"okay","u":42}`, Check: func(t *testing.T, err error) { @@ -1756,21 +1810,23 @@ var testdataErr = []decl.Declaration[TestError]{ require.IsType(t, &gqlparse.ErrorVarUndeclared{}, err) require.Equal(t, `variable "u" undeclared`, err.Error()) }, - }), + }, // Undefined variable value - decl.New(TestError{ - Src: `query ($v:String!) { f(a:$v) }`, + { + Name: "undefined_variable_value", + Src: `query ($v:String!) { f(a:$v) }`, Check: func(t *testing.T, err error) { require.Error(t, err) require.IsType(t, &gqlparse.ErrorVarUndefined{}, err) require.Equal(t, `variable "v" undefined`, err.Error()) }, - }), + }, // Fragment undefined - decl.New(TestError{ - Src: `{ ...f }`, + { + Name: "fragment_undefined", + Src: `{ ...f }`, Check: func(t *testing.T, err error) { require.Error(t, err) require.Equal(t, &gqlparse.ErrorFragUndefined{ @@ -1778,9 +1834,10 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `fragment "f" undefined`, err.Error()) }, - }), - decl.New(TestError{ - Src: `{ ...f }, fragment f on Query { ...x }`, + }, + { + Name: "fragment_undefined_level2", + Src: `{ ...f }, fragment f on Query { ...x }`, Check: func(t *testing.T, err error) { require.Error(t, err) require.Equal(t, &gqlparse.ErrorFragUndefined{ @@ -1788,10 +1845,11 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `fragment "x" undefined`, err.Error()) }, - }), + }, // Fragment limit exceeded - decl.New(TestError{ + { + Name: "fragment_limit_exceeded", Src: `{ ...f0, ...f1, ...f2, ...f3, ...f4, ...f5, ...f6, ...f7, ...f8, ...f9, @@ -1957,11 +2015,11 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, "fragment limit (128) exceeded", err.Error()) }, - }), + }, /* TYPE ERRORS */ - decl.New(TestError{ + { Name: "undefined_field_in_query_type", Schema: "type Query { foo:String! }", Src: `{ bar }`, @@ -1974,8 +2032,8 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `undefined field (bar) in type Query`, err.Error()) }, - }), - decl.New(TestError{ + }, + { Name: "undefined_field_in_mutation_type", Schema: "type Mutation { foo:String! }", Src: `mutation { bar }`, @@ -1988,8 +2046,8 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `undefined field (bar) in type Mutation`, err.Error()) }, - }), - decl.New(TestError{ + }, + { Name: "undefined_field_in_subscription_type", Schema: "type Subscription { foo:String! }", Src: `subscription { bar }`, @@ -2002,8 +2060,8 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `undefined field (bar) in type Subscription`, err.Error()) }, - }), - decl.New(TestError{ + }, + { Name: "undefined_field_in_named_type", Schema: ` type Query { foo:Foo! } @@ -2019,8 +2077,8 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `undefined field (bazz) in type Foo`, err.Error()) }, - }), - decl.New(TestError{ + }, + { Name: "undefined_field_in_named_subtype", Schema: ` type Query { foo:Foo! } @@ -2037,8 +2095,8 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `undefined field (bazz) in type Bar`, err.Error()) }, - }), - decl.New(TestError{ + }, + { Name: "undefined_field_in_interface", Schema: ` type Query { interface:Interface! x:X! } @@ -2055,8 +2113,8 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `undefined field (bazz) in type Interface`, err.Error()) }, - }), - decl.New(TestError{ + }, + { Name: "undefined_field_in_inline_frag", Schema: ` type Query { u:U } @@ -2074,8 +2132,8 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `undefined field (bar) in type Foo`, err.Error()) }, - }), - decl.New(TestError{ + }, + { Name: "undefined_field_in_sametype_inline_frag_inside_query", Schema: ` type Query { foo:String! } @@ -2090,8 +2148,8 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `undefined field (bar) in type Query`, err.Error()) }, - }), - decl.New(TestError{ + }, + { Name: "undefined_field_in_2d_sametype_inline_frag_inside_query", Schema: ` type Query { foo:String! } @@ -2106,8 +2164,8 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `undefined field (bar) in type Query`, err.Error()) }, - }), - decl.New(TestError{ + }, + { Name: "undefined_field_in_anonymous_inline_frag_inside_query", Schema: ` type Query { foo:String! } @@ -2122,8 +2180,8 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `undefined field (bar) in type Query`, err.Error()) }, - }), - decl.New(TestError{ + }, + { Name: "undefined_field_in_2d_anonymous_inline_frag_inside_query", Schema: ` type Query { foo:String! } @@ -2138,9 +2196,9 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `undefined field (bar) in type Query`, err.Error()) }, - }), + }, - decl.New(TestError{ + { Name: "undefined_type_in_inline_fragment", Schema: ` type Query { u: U! } @@ -2157,8 +2215,8 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `undefined type: Foo`, err.Error()) }, - }), - decl.New(TestError{ + }, + { Name: "undefined_type_in_fragment", Schema: ` type Query { foo:String } @@ -2172,9 +2230,9 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `undefined type: Foo`, err.Error()) }, - }), + }, - decl.New(TestError{ + { Name: "unsupported_type_in_union", Schema: ` type Query { u: U! } @@ -2194,8 +2252,8 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `union U can never be of type Foo`, err.Error()) }, - }), - decl.New(TestError{ + }, + { Name: "unsupported_type_in_interface", Schema: ` type Query { i:Iface! } @@ -2215,8 +2273,8 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `interface Iface can never be of type Foo`, err.Error()) }, - }), - decl.New(TestError{ + }, + { Name: "unsupported_type_in_object", Schema: ` type Query { foo:Foo! } @@ -2234,30 +2292,26 @@ var testdataErr = []decl.Declaration[TestError]{ }, err) require.Equal(t, `OBJECT Foo can never be of type Bar`, err.Error()) }, - }), + }, } func TestErr(t *testing.T) { - for _, td := range testdataErr { - name := td.Decl - if td.Data.Name != "" { - name = td.Data.Name - } - t.Run(name, func(t *testing.T) { + for _, td := range testsErr { + t.Run(td.Name, func(t *testing.T) { var schema *ast.Schema - if td.Data.Schema != "" { + if td.Schema != "" { var err error schema, err = gqlparser.LoadSchema(&ast.Source{ Name: "schema.graphqls", - Input: td.Data.Schema, + Input: td.Schema, }) require.NoError(t, err, "parsing schema") } gqlparse.NewParser(schema).Parse( - []byte(td.Data.Src), - []byte(td.Data.OprName), - []byte(td.Data.VarsJSON), + []byte(td.Src), + []byte(td.OprName), + []byte(td.VarsJSON), func( varVals [][]gqlparse.Token, operation []gqlparse.Token, @@ -2267,7 +2321,7 @@ func TestErr(t *testing.T) { }, func(err error) { require.Error(t, err) - td.Data.Check(t, err) + td.Check(t, err) }, ) }) From ab3a49c7874f6cc9c1bc6ab3d1cede572655a3db Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Fri, 3 Mar 2023 23:32:16 +0100 Subject: [PATCH 19/35] feat: Check for undefined arguments --- gqlparse/gqlparse.go | 50 +++++++++++++++++++++++++++++--- gqlparse/gqlparse_test.go | 61 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 4 deletions(-) diff --git a/gqlparse/gqlparse.go b/gqlparse/gqlparse.go index de99bdd..308ce3d 100644 --- a/gqlparse/gqlparse.go +++ b/gqlparse/gqlparse.go @@ -184,6 +184,7 @@ type Parser struct { errTypeUndef ErrorTypeUndef errFieldUndef ErrorFieldUndef + errArgUndef ErrorArgUndef errCantBeOfType ErrorCantBeOfType errSyntax ErrorSyntax errOprAnonNonExcl ErrorOprAnonNonExcl @@ -246,6 +247,7 @@ func (r *Parser) Parse( var fragStackCounter int var recentFragDef []byte var recentHost Token + var recentField *ast.FieldDefinition if serr := gqlscan.Scan(src, func(i *gqlscan.Iterator) bool { tk := Token{ @@ -302,13 +304,28 @@ func (r *Parser) Parse( IndexStart: len(r.buffer) - 1, }) + case gqlscan.TokenArgName: + if r.schema != nil { + name := i.Value() + h := r.schemaTypeStack.Top() + a := argByName(recentField, name) + if a == nil { + r.errArgUndef.Location = locFromItr(i) + r.errArgUndef.ArgName = name + r.errArgUndef.FieldName = recentField.Name + r.errArgUndef.HostTypeName = h.HostType.Name + isErr = true + onError(&r.errArgUndef) + return true + } + } + case gqlscan.TokenField: recentHost = tk - name := i.Value() if r.schema != nil { + name := i.Value() h := r.schemaTypeStack.Top() - f := fieldByName(h.HostType, name) - if f == nil { + if recentField = fieldByName(h.HostType, name); recentField == nil { r.errFieldUndef.Location = locFromItr(i) r.errFieldUndef.FieldName = name r.errFieldUndef.HostTypeName = h.HostType.Name @@ -316,7 +333,7 @@ func (r *Parser) Parse( onError(&r.errFieldUndef) return true } - tp := r.schema.Types[f.Type.NamedType] + tp := r.schema.Types[recentField.Type.NamedType] r.schemaTypeStack.TopOffsetFn(0, func(f *typeStackFrame) { f.FieldType = tp }) @@ -793,6 +810,22 @@ func (e *ErrorFieldUndef) Error() string { e.HostTypeName } +type ErrorArgUndef struct { + Location + ArgName []byte + HostTypeName string + FieldName string +} + +func (e *ErrorArgUndef) Error() string { + return "undefined argument (" + + string(e.ArgName) + + ") on field " + + e.HostTypeName + + "." + + string(e.FieldName) +} + type ErrorCantBeOfType struct { Location Kind string @@ -1375,6 +1408,15 @@ func fieldByName(d *ast.Definition, name []byte) *ast.FieldDefinition { return nil } +func argByName(d *ast.FieldDefinition, name []byte) *ast.ArgumentDefinition { + for _, f := range d.Arguments { + if f.Name == string(name) { + return f + } + } + return nil +} + // isTypeDirectlyInlinable returns true if properties from child // are directly inlinable into type host without the need for a type condition, // otherwise returns false. diff --git a/gqlparse/gqlparse_test.go b/gqlparse/gqlparse_test.go index f9a3334..76bd0e2 100644 --- a/gqlparse/gqlparse_test.go +++ b/gqlparse/gqlparse_test.go @@ -2198,6 +2198,67 @@ var testsErr = []TestError{ }, }, + { + Name: "undefined_argument_1", + Schema: "type Query { foo(ok:String!):String! }", + Src: `{ foo(ok:"ok", inexistent:"inexistent") }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorArgUndef{ + Location: gqlparse.Location{IndexTail: 15, IndexHead: 25}, + ArgName: []byte("inexistent"), + FieldName: "foo", + HostTypeName: "Query", + }, err) + require.Equal( + t, + `undefined argument (inexistent) on field Query.foo`, + err.Error(), + ) + }, + }, + { + Name: "undefined_argument_2", + Schema: ` + type Query { foo:Foo! } + type Foo { i:Int! } + `, + Src: `{ foo { i(x:null) } }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorArgUndef{ + Location: gqlparse.Location{IndexTail: 10, IndexHead: 11}, + ArgName: []byte("x"), + FieldName: "i", + HostTypeName: "Foo", + }, err) + require.Equal( + t, `undefined argument (x) on field Foo.i`, err.Error(), + ) + }, + }, + { + Name: "undefined_argument_2", + Schema: ` + type Query { mainCharacter:Character! } + interface Character { name: String! } + type Droid implements Character { name: String! } + `, + Src: `{ mainCharacter { ... on Droid { name(lang:DE) } } }`, + Check: func(t *testing.T, err error) { + require.Error(t, err) + require.Equal(t, &gqlparse.ErrorArgUndef{ + Location: gqlparse.Location{IndexTail: 38, IndexHead: 42}, + ArgName: []byte("lang"), + FieldName: "name", + HostTypeName: "Droid", + }, err) + require.Equal( + t, `undefined argument (lang) on field Droid.name`, err.Error(), + ) + }, + }, + { Name: "undefined_type_in_inline_fragment", Schema: ` From 8d75d7e8dec04799134579b16ee758272fbc7dbe Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sat, 4 Mar 2023 12:09:05 +0100 Subject: [PATCH 20/35] fix: Fix isWrongType --- engine/playmon/internal/constrcheck/iswrongtype.go | 3 ++- .../internal/constrcheck/iswrongtype_test.go | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/engine/playmon/internal/constrcheck/iswrongtype.go b/engine/playmon/internal/constrcheck/iswrongtype.go index a5c00a1..0ce8e62 100644 --- a/engine/playmon/internal/constrcheck/iswrongtype.go +++ b/engine/playmon/internal/constrcheck/iswrongtype.go @@ -33,7 +33,8 @@ func isWrongType( } for { rBefore := r - if r.ReadOne().ID == gqlscan.TokenArrEnd { + if r.PeekOne().ID == gqlscan.TokenArrEnd { + r.ReadOne() return false } if isWrongType(rBefore, expect, schema) { diff --git a/engine/playmon/internal/constrcheck/iswrongtype_test.go b/engine/playmon/internal/constrcheck/iswrongtype_test.go index 465d407..91dd5b7 100644 --- a/engine/playmon/internal/constrcheck/iswrongtype_test.go +++ b/engine/playmon/internal/constrcheck/iswrongtype_test.go @@ -365,7 +365,12 @@ func TestIsWrongType_False(t *testing.T) { ) } - r := isWrongType(&tokenreader.Reader{Main: tt.Input}, tt.ExpectType, s) + r := isWrongType( + &tokenreader.Reader{ + Main: tt.Input, + Vars: tt.GQLVarVals, + }, tt.ExpectType, s, + ) require.False(t, r) }) } @@ -527,7 +532,12 @@ func TestIsWrongType_True(t *testing.T) { "type expectations are always schema-aware", ) } - r := isWrongType(&tokenreader.Reader{Main: tt.Input}, tt.ExpectType, s) + r := isWrongType( + &tokenreader.Reader{ + Main: tt.Input, + Vars: tt.GQLVarVals, + }, tt.ExpectType, s, + ) require.True(t, r) }) } From 13e2a1cb536dab360f528b27d8297f08ccc32a27 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sat, 4 Mar 2023 21:12:42 +0100 Subject: [PATCH 21/35] refactor: Remove unused code --- .../playmon/internal/constrcheck/internal/union/union.go | 2 +- .../internal/constrcheck/internal/unsafe/unsafe.go | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 engine/playmon/internal/constrcheck/internal/unsafe/unsafe.go diff --git a/engine/playmon/internal/constrcheck/internal/union/union.go b/engine/playmon/internal/constrcheck/internal/union/union.go index f147a1e..6153e57 100644 --- a/engine/playmon/internal/constrcheck/internal/union/union.go +++ b/engine/playmon/internal/constrcheck/internal/union/union.go @@ -1,9 +1,9 @@ package union import ( - "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck/internal/unsafe" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/ggproxy/utilities/atoi" + "github.com/graph-guard/ggproxy/utilities/unsafe" "github.com/graph-guard/gqlscan" ) diff --git a/engine/playmon/internal/constrcheck/internal/unsafe/unsafe.go b/engine/playmon/internal/constrcheck/internal/unsafe/unsafe.go deleted file mode 100644 index 5e05f78..0000000 --- a/engine/playmon/internal/constrcheck/internal/unsafe/unsafe.go +++ /dev/null @@ -1,8 +0,0 @@ -package unsafe - -import "unsafe" - -// B2S returns a string representation of b. -func B2S(b []byte) string { - return *(*string)(unsafe.Pointer(&b)) -} From 0c0877be19c1d2bd42ae74e9df1fbe183fab9e21 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sat, 4 Mar 2023 21:23:05 +0100 Subject: [PATCH 22/35] ci: Fix goreleaser config --- .goreleaser.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index fe421ac..7e60d66 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -17,15 +17,15 @@ builds: - linux - darwin goarch: - - 386 - - amd64 - - arm - - arm64 + - "386" + - "amd64" + - "arm" + - "arm64" archives: - id: ggproxy name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}" replacements: - 386: i386 + "386": "i386" files: - src: assets/etc/ info: @@ -39,7 +39,7 @@ snapshot: release: draft: false replace_existing_draft: true - prerelease: true + prerelease: auto mode: append header: | **[CHANGELOG.md](https://github.com/graph-guard/ggproxy/blob/ci/CHANGELOG.md#{{ .Env.COMPACT_TAG }})** From bd017ce039cb1adb6722fe5bb3185ab5bb8431e9 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sat, 4 Mar 2023 21:27:03 +0100 Subject: [PATCH 23/35] refactor: Remove LVS --- .goreleaser.yml | 2 - CHANGELOG.md | 4 -- cli/cli.go | 32 ++------- cli/cli_test.go | 153 +++++--------------------------------------- cmd/ggproxy/main.go | 10 +-- lvs/lvs.go | 94 --------------------------- lvs/lvs_test.go | 75 ---------------------- 7 files changed, 22 insertions(+), 348 deletions(-) delete mode 100644 lvs/lvs.go delete mode 100644 lvs/lvs_test.go diff --git a/.goreleaser.yml b/.goreleaser.yml index 7e60d66..a216106 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -11,8 +11,6 @@ builds: gobinary: garble env: - CGO_ENABLED=0 - ldflags: - - -X 'github.com/graph-guard/ggproxy/lvs.PublicKey={{ .Env.PUB_KEY }}' goos: - linux - darwin diff --git a/CHANGELOG.md b/CHANGELOG.md index 584da6e..11d66ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,6 @@ ### Fix * Fix comparison of byte slices -* Fix lvs failure on empty license * Reset segmented array index counter * Accept enum values * Fix broken pipe handling @@ -77,8 +76,6 @@ * Add 'null' support * Add processing of enums * Prepare to Beta release -* Use LVS validation and rename 'licence' to 'license' -* Add licence key environment variable * Add support for basic auth in API server * Add service and template statistics * Add ggproxy GraphQL API @@ -107,7 +104,6 @@ ### Refactor -* Refactor LVS ([#4](https://github.com/graph-guard/ggproxy/issues/4)) * Get rid of the matcher interface * Simplify code diff --git a/cli/cli.go b/cli/cli.go index 186ce43..a5f68c6 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -8,10 +8,11 @@ import ( "path/filepath" ) -const EnvAPIUsername = "GGPROXY_API_USERNAME" -const EnvAPIPassword = "GGPROXY_API_PASSWORD" -const EnvLicense = "GGPROXY_LICENSE" -const LinkDashboardDownload = "https://graphguard.io/dashboard#download" +const ( + EnvAPIUsername = "GGPROXY_API_USERNAME" + EnvAPIPassword = "GGPROXY_API_PASSWORD" + LinkDashboardDownload = "https://graphguard.io/dashboard#download" +) // Command can be any of: // @@ -23,7 +24,6 @@ type Command any type CommandServe struct { ConfigDirPath string - LicenseToken string APIUsername string APIPassword string } @@ -35,7 +35,6 @@ type CommandStop struct{} func Parse( w io.Writer, args []string, - validateLicenseToken func(string) error, ) (cmd Command) { fm := fmt.Sprintf @@ -73,7 +72,6 @@ func Parse( c := CommandServe{} c.APIUsername = os.Getenv(EnvAPIUsername) c.APIPassword = os.Getenv(EnvAPIPassword) - c.LicenseToken = os.Getenv(EnvLicense) flags.Usage = func() { writeLines(w, @@ -88,7 +86,6 @@ func Parse( fm("%s: API basic auth username "+ "(enables basic auth if set)", EnvAPIUsername), fm("%s: API basic auth password", EnvAPIPassword), - fm("%s: License key", EnvLicense), ) } @@ -97,25 +94,6 @@ func Parse( return nil } - err := validateLicenseToken(c.LicenseToken) - - if c.LicenseToken == "" { - writeLines(w, - EnvLicense+" isn't set.", - fm("You can get the license key at %s", LinkDashboardDownload), - ) - flags.Usage() - return nil - } else if err != nil { - writeLines(w, - err.Error(), - EnvLicense+" contains an invalid license key!", - fm("You can get a valid license key at %s", LinkDashboardDownload), - ) - flags.Usage() - return nil - } - if c.APIUsername != "" && c.APIPassword == "" { writeLines(w, EnvAPIPassword+" isn't set.", diff --git a/cli/cli_test.go b/cli/cli_test.go index a14b4fc..d08ab2f 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -25,48 +25,34 @@ func helpOutput(execName string) string { func TestNoArgs(t *testing.T) { out := new(bytes.Buffer) - c := cli.Parse(out, nil, func(s string) error { return nil }) + c := cli.Parse(out, nil) require.Nil(t, c) require.Equal(t, helpOutput("ggproxy"), out.String()) } func TestNoCommand(t *testing.T) { out := new(bytes.Buffer) - c := cli.Parse( - out, - []string{"execname"}, - func(s string) error { return nil }, - ) + c := cli.Parse(out, []string{"execname"}) require.Nil(t, c) require.Equal(t, helpOutput("execname"), out.String()) } func TestUnknownCommand(t *testing.T) { out := new(bytes.Buffer) - c := cli.Parse( - out, - []string{"execname", "unknown-command"}, - func(s string) error { return nil }, - ) + c := cli.Parse(out, []string{"execname", "unknown-command"}) require.Nil(t, c) require.Equal(t, helpOutput("execname"), out.String()) } func TestCommandServe(t *testing.T) { - os.Setenv(cli.EnvLicense, "TESTLICENSETOKEN") os.Setenv(cli.EnvAPIUsername, "testusername") os.Setenv(cli.EnvAPIPassword, "testpassword") t.Run("default_config_path", func(t *testing.T) { out := new(bytes.Buffer) - c := cli.Parse( - out, - []string{"ggproxy", "serve"}, - func(s string) error { return nil }, - ) + c := cli.Parse(out, []string{"ggproxy", "serve"}) require.Equal(t, cli.CommandServe{ ConfigDirPath: "/etc/ggproxy", - LicenseToken: "TESTLICENSETOKEN", APIUsername: "testusername", APIPassword: "testpassword", }, c) @@ -75,16 +61,11 @@ func TestCommandServe(t *testing.T) { t.Run("custom_config_path", func(t *testing.T) { out := new(bytes.Buffer) - c := cli.Parse( - out, - []string{ - "ggproxy", "serve", - "-config", "./custom_config", - }, - func(s string) error { return nil }, - ) + c := cli.Parse(out, []string{ + "ggproxy", "serve", + "-config", "./custom_config", + }) require.Equal(t, cli.CommandServe{ - LicenseToken: "TESTLICENSETOKEN", ConfigDirPath: "./custom_config", APIUsername: "testusername", APIPassword: "testpassword", @@ -94,14 +75,10 @@ func TestCommandServe(t *testing.T) { t.Run("unknown_flags", func(t *testing.T) { out := new(bytes.Buffer) - c := cli.Parse( - out, - []string{ - "ggproxy", "serve", - "-unknown", "foobar", - }, - func(s string) error { return nil }, - ) + c := cli.Parse(out, []string{ + "ggproxy", "serve", + "-unknown", "foobar", + }) require.Nil(t, c) require.Equal(t, lines( @@ -118,7 +95,6 @@ func TestCommandServe(t *testing.T) { "GGPROXY_API_USERNAME: API basic auth username "+ "(enables basic auth if set)", "GGPROXY_API_PASSWORD: API basic auth password", - "GGPROXY_LICENSE: License key", ), out.String(), ) @@ -129,11 +105,7 @@ func TestAPIPasswordNotSet(t *testing.T) { out := new(bytes.Buffer) os.Setenv(cli.EnvAPIUsername, "testusername") os.Setenv(cli.EnvAPIPassword, "") - c := cli.Parse( - out, - []string{"ggproxy", "serve"}, - func(s string) error { return nil }, - ) + c := cli.Parse(out, []string{"ggproxy", "serve"}) require.Nil(t, c) require.Equal(t, lines( @@ -154,87 +126,6 @@ func TestAPIPasswordNotSet(t *testing.T) { "GGPROXY_API_USERNAME: API basic auth username "+ "(enables basic auth if set)", "GGPROXY_API_PASSWORD: API basic auth password", - "GGPROXY_LICENSE: License key", - ), - out.String(), - ) -} - -func TestLicenseTokenNotSet(t *testing.T) { - out := new(bytes.Buffer) - os.Setenv(cli.EnvAPIUsername, "testusername") - os.Setenv(cli.EnvAPIPassword, "testpassword") - os.Setenv(cli.EnvLicense, "") - c := cli.Parse( - out, - []string{"ggproxy", "serve"}, - func(s string) error { return nil }, - ) - require.Nil(t, c) - - require.Equal(t, - lines( - fmt.Sprintf("%s isn't set.", cli.EnvLicense), - fmt.Sprintf( - "You can get the license key at %s", - cli.LinkDashboardDownload, - ), - "", - "usage: ggproxy serve [-config ]", - "", - "flags:", - "-config : "+ - "defines the configuration directory path "+ - "(default: /etc/ggproxy)", - "", - "environment variables:", - "GGPROXY_API_USERNAME: API basic auth username "+ - "(enables basic auth if set)", - "GGPROXY_API_PASSWORD: API basic auth password", - "GGPROXY_LICENSE: License key", - ), - out.String(), - ) -} - -func TestLicenseTokenInvalid(t *testing.T) { - out := new(bytes.Buffer) - os.Setenv(cli.EnvAPIUsername, "testusername") - os.Setenv(cli.EnvAPIPassword, "testpassword") - os.Setenv(cli.EnvLicense, "thiskeyisinvalid") - c := cli.Parse( - out, - []string{"ggproxy", "serve"}, - func(s string) error { - if s != "valid" { - return fmt.Errorf("invalid") - } - return nil - }, - ) - require.Nil(t, c) - - require.Equal(t, - lines( - "invalid", - fmt.Sprintf("%s contains an invalid license key!", cli.EnvLicense), - fmt.Sprintf( - "You can get a valid license key at %s", - cli.LinkDashboardDownload, - ), - "", - "usage: ggproxy serve [-config ]", - "", - "flags:", - "-config : "+ - "defines the configuration directory path "+ - "(default: /etc/ggproxy)", - "", - "environment variables:", - "GGPROXY_API_USERNAME: API basic auth username "+ - "(enables basic auth if set)", - "GGPROXY_API_PASSWORD: API basic auth password", - "GGPROXY_LICENSE: License key", ), out.String(), ) @@ -242,33 +133,21 @@ func TestLicenseTokenInvalid(t *testing.T) { func TestCommandReload(t *testing.T) { out := new(bytes.Buffer) - c := cli.Parse( - out, - []string{"execname", "reload"}, - func(s string) error { return nil }, - ) + c := cli.Parse(out, []string{"execname", "reload"}) require.Equal(t, cli.CommandReload{}, c) require.Equal(t, "", out.String()) } func TestCommandStop(t *testing.T) { out := new(bytes.Buffer) - c := cli.Parse( - out, - []string{"execname", "stop"}, - func(s string) error { return nil }, - ) + c := cli.Parse(out, []string{"execname", "stop"}) require.Equal(t, cli.CommandStop{}, c) require.Equal(t, "", out.String()) } func TestCommandHelp(t *testing.T) { out := new(bytes.Buffer) - c := cli.Parse( - out, - []string{"execname", "help"}, - func(s string) error { return nil }, - ) + c := cli.Parse(out, []string{"execname", "help"}) require.Nil(t, c) e := new(bytes.Buffer) diff --git a/cmd/ggproxy/main.go b/cmd/ggproxy/main.go index e013ff7..216ce5f 100644 --- a/cmd/ggproxy/main.go +++ b/cmd/ggproxy/main.go @@ -5,19 +5,11 @@ import ( "os" "github.com/graph-guard/ggproxy/cli" - "github.com/graph-guard/ggproxy/lvs" ) func main() { w := os.Stdout - switch c := cli.Parse( - w, - os.Args, - func(licenseToken string) error { - _, err := lvs.ValidateLicenseToken(licenseToken) - return err - }, - ).(type) { + switch c := cli.Parse(w, os.Args).(type) { case cli.CommandServe: serve(w, c) case cli.CommandReload: diff --git a/lvs/lvs.go b/lvs/lvs.go deleted file mode 100644 index 6708f4c..0000000 --- a/lvs/lvs.go +++ /dev/null @@ -1,94 +0,0 @@ -package lvs - -import ( - "crypto" - "crypto/x509" - _ "embed" - "encoding/pem" - "errors" - - jwt "github.com/dgrijalva/jwt-go" -) - -type Type uint16 -type Plan uint16 - -type LicenseTokenClaim struct { - jwt.StandardClaims - Type Type `json:"type"` - Plan Plan `json:"plan"` -} - -const ( - Beta Type = iota - Community - Commercial -) - -const ( - Tiny Plan = iota - Small - Medium - Big - Unlimited -) - -// Encoded public key -var PublicKey string - -var ErrFailParseClaims = errors.New("failed to parse license token claims") -var ErrLicenseExpired = errors.New("license expired") -var ErrLicenseMalformed = errors.New("license token malformed or empty") -var ErrNoPEMBlock = errors.New("no valid PEM block in public key") - -// ValidateLicenseToken verifies the license and return license key parameters as claims. -func ValidateLicenseToken(licenseToken string) (*LicenseTokenClaim, error) { - if PublicKey == "" { - panic("missing public key") - } - - decodedPublicKey, err := decodePublicKey([]byte(PublicKey)) - if err != nil { - panic(err) - } - - token, err := jwt.ParseWithClaims( - licenseToken, - &LicenseTokenClaim{}, - func(token *jwt.Token) (interface{}, error) { - return decodedPublicKey, nil - }, - ) - - e, ok := err.(*jwt.ValidationError) - if ok { - if e.Errors&jwt.ValidationErrorExpired != 0 { - return nil, ErrLicenseExpired - } - if e.Errors&jwt.ValidationErrorMalformed != 0 { - return nil, ErrLicenseMalformed - } - } - - claims, ok := token.Claims.(*LicenseTokenClaim) - if !ok { - return nil, ErrFailParseClaims - } - - return claims, err -} - -func decodePublicKey(pemEncoded []byte) (crypto.PublicKey, error) { - block, _ := pem.Decode(pemEncoded) - if block == nil { - return nil, ErrNoPEMBlock - } - x509Encoded := block.Bytes - genericPublicKey, err := x509.ParsePKIXPublicKey(x509Encoded) - if err != nil { - return nil, err - } - publicKey := genericPublicKey.(crypto.PublicKey) - - return publicKey, nil -} diff --git a/lvs/lvs_test.go b/lvs/lvs_test.go deleted file mode 100644 index 4f85a0d..0000000 --- a/lvs/lvs_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package lvs_test - -import ( - _ "embed" - "testing" - "time" - - "github.com/google/uuid" - blvs "github.com/graph-guard/backend/lvs" - "github.com/graph-guard/ggproxy/lvs" - "github.com/stretchr/testify/require" -) - -var privateKey = ` ------BEGIN EC PRIVATE KEY----- -MIHcAgEBBEIBK43Z3ATV+af8U8iMBODHssl4FsSvL70DePBubkthHlltuVPxu29X -T7/Q5zSICfpRD0Q8F9lGxum5KPl4T/n6IM2gBwYFK4EEACOhgYkDgYYABAHtTblG -M/FaKHDkVrBOSJ2SJe7+Spyxbn7DQOfZ0B4dVVALGc5j/G+TqeYt6DVO1GEOHL3/ -lMy0L827kdCU5iZopgHJrjeaM38n4HmG/dEh4x7R3P+rNDV0NQ7EJ4W8dY+HwtDb -ripL46GdA8fVwDgom/qe4btdpGJBQcJrmURLxQjAXw== ------END EC PRIVATE KEY----- -` - -var publicKey = ` ------BEGIN PUBLIC KEY----- -MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB7U25RjPxWihw5FawTkidkiXu/kqc -sW5+w0Dn2dAeHVVQCxnOY/xvk6nmLeg1TtRhDhy9/5TMtC/Nu5HQlOYmaKYBya43 -mjN/J+B5hv3RIeMe0dz/qzQ1dDUOxCeFvHWPh8LQ264qS+OhnQPH1cA4KJv6nuG7 -XaRiQUHCa5lES8UIwF8= ------END PUBLIC KEY----- -` - -func TestVerifyLicenseToken(t *testing.T) { - decodedLicenseToken, err := blvs.GenerateLicenseToken( - time.Now().Local(), - time.Now().Local().Add(time.Hour), - lvs.Beta, - lvs.Unlimited, - uuid.New(), - []byte(privateKey), - ) - require.NoError(t, err) - require.NotEmpty(t, decodedLicenseToken) - - lvs.PublicKey = publicKey - - claims, err := lvs.ValidateLicenseToken( - string(decodedLicenseToken), - ) - - require.NoError(t, err) - require.NotNil(t, claims) -} - -func TestLicenseTokenExpired(t *testing.T) { - decodedLicenseToken, err := blvs.GenerateLicenseToken( - time.Now().Local(), - time.Now().Local().Add(-time.Hour), - lvs.Beta, - lvs.Unlimited, - uuid.New(), - []byte(privateKey), - ) - require.NoError(t, err) - require.NotEmpty(t, decodedLicenseToken) - - lvs.PublicKey = publicKey - - claims, err := lvs.ValidateLicenseToken( - string(decodedLicenseToken), - ) - - require.Error(t, lvs.ErrLicenseExpired, err) - require.Nil(t, claims) -} From 5718aba1139b01fbd71db9fc569ae6df7fc5ef82 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sat, 4 Mar 2023 22:09:28 +0100 Subject: [PATCH 24/35] ci: Fix goreleaser configuration --- .goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index a216106..034b551 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -37,7 +37,7 @@ snapshot: release: draft: false replace_existing_draft: true - prerelease: auto + prerelease: "true" # Don't remove the quotes, it's supposed to be a string, not a boolean. mode: append header: | **[CHANGELOG.md](https://github.com/graph-guard/ggproxy/blob/ci/CHANGELOG.md#{{ .Env.COMPACT_TAG }})** From eb69360c43a4d8fbc9a37ba4cbf9bd89a6881714 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sat, 4 Mar 2023 23:59:31 +0100 Subject: [PATCH 25/35] !feat: return multiple match results in GQL API Refactor: make gqlparse an implementation defail of the Playmon engine. --- api/graph/generated/generated.go | 89 +++++++++++++++------ api/graph/model/model.go | 2 - api/graph/model/models_gen.go | 8 +- api/graph/schema.graphqls | 6 +- api/graph/schema.resolvers.go | 131 +++++++++++++++---------------- engine/playmon/bench_test.go | 74 ++--------------- engine/playmon/playmon.go | 37 +++++++-- go.mod | 3 - go.sum | 6 -- server/api.go | 4 +- server/proxy.go | 29 +++---- 11 files changed, 187 insertions(+), 202 deletions(-) diff --git a/api/graph/generated/generated.go b/api/graph/generated/generated.go index a87305f..9a347f0 100644 --- a/api/graph/generated/generated.go +++ b/api/graph/generated/generated.go @@ -48,7 +48,7 @@ type DirectiveRoot struct { type ComplexityRoot struct { MatchResult struct { Forwarded func(childComplexity int) int - Templates func(childComplexity int) int + Template func(childComplexity int) int TimeMatchingNs func(childComplexity int) int TimeParsingNs func(childComplexity int) int } @@ -110,7 +110,7 @@ type QueryResolver interface { Services(ctx context.Context) ([]*model.Service, error) } type ServiceResolver interface { - MatchAll(ctx context.Context, obj *model.Service, query string, operationName *string, variablesJSON *string) (*model.MatchResult, error) + MatchAll(ctx context.Context, obj *model.Service, query string, operationName *string, variablesJSON *string) ([]*model.MatchResult, error) Match(ctx context.Context, obj *model.Service, query string, operationName *string, variablesJSON *string) (*model.MatchResult, error) Statistics(ctx context.Context, obj *model.Service) (*model.ServiceStatistics, error) } @@ -141,12 +141,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.MatchResult.Forwarded(childComplexity), true - case "MatchResult.templates": - if e.complexity.MatchResult.Templates == nil { + case "MatchResult.template": + if e.complexity.MatchResult.Template == nil { break } - return e.complexity.MatchResult.Templates(childComplexity), true + return e.complexity.MatchResult.Template(childComplexity), true case "MatchResult.timeMatchingNS": if e.complexity.MatchResult.TimeMatchingNs == nil { @@ -518,7 +518,7 @@ type Service { query: String! operationName: String variablesJSON: String - ): MatchResult! + ): [MatchResult]! # match provides matching results for the given query. # It's similar to matchAll except that it matches one template only. @@ -533,9 +533,9 @@ type Service { } type MatchResult { - # templates provides all templates that matched the query. + # template provides the template that matched the query. # Provides an empty array if there was no match. - templates: [Template!]! + template: Template! # forwarded provides the forwarded query. # Provides null if there was no match. @@ -763,8 +763,8 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg // region **************************** field.gotpl ***************************** -func (ec *executionContext) _MatchResult_templates(ctx context.Context, field graphql.CollectedField, obj *model.MatchResult) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_MatchResult_templates(ctx, field) +func (ec *executionContext) _MatchResult_template(ctx context.Context, field graphql.CollectedField, obj *model.MatchResult) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_MatchResult_template(ctx, field) if err != nil { return graphql.Null } @@ -777,7 +777,7 @@ func (ec *executionContext) _MatchResult_templates(ctx context.Context, field gr }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Templates, nil + return obj.Template, nil }) if err != nil { ec.Error(ctx, err) @@ -789,12 +789,12 @@ func (ec *executionContext) _MatchResult_templates(ctx context.Context, field gr } return graphql.Null } - res := resTmp.([]*model.Template) + res := resTmp.(*model.Template) fc.Result = res - return ec.marshalNTemplate2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐTemplateᚄ(ctx, field.Selections, res) + return ec.marshalNTemplate2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐTemplate(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_MatchResult_templates(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_MatchResult_template(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "MatchResult", Field: field, @@ -1663,9 +1663,9 @@ func (ec *executionContext) _Service_matchAll(ctx context.Context, field graphql } return graphql.Null } - res := resTmp.(*model.MatchResult) + res := resTmp.([]*model.MatchResult) fc.Result = res - return ec.marshalNMatchResult2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐMatchResult(ctx, field.Selections, res) + return ec.marshalNMatchResult2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐMatchResult(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Service_matchAll(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -1676,8 +1676,8 @@ func (ec *executionContext) fieldContext_Service_matchAll(ctx context.Context, f IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { - case "templates": - return ec.fieldContext_MatchResult_templates(ctx, field) + case "template": + return ec.fieldContext_MatchResult_template(ctx, field) case "forwarded": return ec.fieldContext_MatchResult_forwarded(ctx, field) case "timeParsingNS": @@ -1741,8 +1741,8 @@ func (ec *executionContext) fieldContext_Service_match(ctx context.Context, fiel IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { - case "templates": - return ec.fieldContext_MatchResult_templates(ctx, field) + case "template": + return ec.fieldContext_MatchResult_template(ctx, field) case "forwarded": return ec.fieldContext_MatchResult_forwarded(ctx, field) case "timeParsingNS": @@ -4536,9 +4536,9 @@ func (ec *executionContext) _MatchResult(ctx context.Context, sel ast.SelectionS switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("MatchResult") - case "templates": + case "template": - out.Values[i] = ec._MatchResult_templates(ctx, field, obj) + out.Values[i] = ec._MatchResult_template(ctx, field, obj) if out.Values[i] == graphql.Null { invalids++ @@ -5431,6 +5431,44 @@ func (ec *executionContext) marshalNMatchResult2githubᚗcomᚋgraphᚑguardᚋg return ec._MatchResult(ctx, sel, &v) } +func (ec *executionContext) marshalNMatchResult2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐMatchResult(ctx context.Context, sel ast.SelectionSet, v []*model.MatchResult) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalOMatchResult2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐMatchResult(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + return ret +} + func (ec *executionContext) marshalNMatchResult2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐMatchResult(ctx context.Context, sel ast.SelectionSet, v *model.MatchResult) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { @@ -5922,6 +5960,13 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast return res } +func (ec *executionContext) marshalOMatchResult2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐMatchResult(ctx context.Context, sel ast.SelectionSet, v *model.MatchResult) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._MatchResult(ctx, sel, v) +} + func (ec *executionContext) marshalOService2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐService(ctx context.Context, sel ast.SelectionSet, v *model.Service) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/api/graph/model/model.go b/api/graph/model/model.go index 49f7803..f8b7e7a 100644 --- a/api/graph/model/model.go +++ b/api/graph/model/model.go @@ -3,12 +3,10 @@ package model import ( "github.com/graph-guard/ggproxy/config" "github.com/graph-guard/ggproxy/engine/playmon" - "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/ggproxy/statistics" ) type Service struct { - Parser *gqlparse.Parser Engine *playmon.Engine Templates map[*config.Template]*Template Stats *statistics.ServiceSync diff --git a/api/graph/model/models_gen.go b/api/graph/model/models_gen.go index 0ed5890..8e769bb 100644 --- a/api/graph/model/models_gen.go +++ b/api/graph/model/models_gen.go @@ -7,10 +7,10 @@ import ( ) type MatchResult struct { - Templates []*Template `json:"templates"` - Forwarded *string `json:"forwarded"` - TimeParsingNs float64 `json:"timeParsingNS"` - TimeMatchingNs float64 `json:"timeMatchingNS"` + Template *Template `json:"template"` + Forwarded *string `json:"forwarded"` + TimeParsingNs float64 `json:"timeParsingNS"` + TimeMatchingNs float64 `json:"timeMatchingNS"` } type ServiceStatistics struct { diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index c21893f..99bbfd4 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -49,7 +49,7 @@ type Service { query: String! operationName: String variablesJSON: String - ): MatchResult! + ): [MatchResult]! # match provides matching results for the given query. # It's similar to matchAll except that it matches one template only. @@ -64,9 +64,9 @@ type Service { } type MatchResult { - # templates provides all templates that matched the query. + # template provides the template that matched the query. # Provides an empty array if there was no match. - templates: [Template!]! + template: Template! # forwarded provides the forwarded query. # Provides null if there was no match. diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index d993c15..b853d24 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -48,109 +48,102 @@ func (r *queryResolver) Services(ctx context.Context) ([]*model.Service, error) } // MatchAll is the resolver for the matchAll field. -func (r *serviceResolver) MatchAll(ctx context.Context, obj *model.Service, query string, operationName *string, variablesJSON *string) (*model.MatchResult, error) { - // Declare here instead of using named return variables - // to avoid code generation overriding them. - m := new(model.MatchResult) +func (r *serviceResolver) MatchAll(ctx context.Context, obj *model.Service, query string, operationName *string, variablesJSON *string) ([]*model.MatchResult, error) { + m := []*model.MatchResult{} var err error - oprName := []byte(nil) + var oprName []byte if operationName != nil { oprName = []byte(*operationName) } - varsJSON := []byte(nil) + var varsJSON []byte if variablesJSON != nil { varsJSON = []byte(*variablesJSON) } - startParsing := time.Now() - obj.Parser.Parse( + start := time.Now() + var durParsing float64 + var forwarded string + obj.Engine.Match( []byte(query), oprName, varsJSON, - func( - varVals [][]gqlparse.Token, - operation []gqlparse.Token, - selectionSet []gqlparse.Token, - ) { - m.TimeParsingNs = nsToF64(time.Since(startParsing).Nanoseconds()) - startMatching := time.Now() - obj.Engine.Match( - varVals, - operation[0].ID, - selectionSet, - func(t *config.Template) (stop bool) { - m.Templates = append(m.Templates, obj.Templates[t]) - return false - }, - ) - m.TimeMatchingNs = nsToF64(time.Since(startMatching).Nanoseconds()) - var forwarded bytes.Buffer - if err = tokenwriter.Write(&forwarded, operation); err != nil { + func(operation, selectionSet []gqlparse.Token) (stop bool) { + durParsing = nsToF64(time.Since(start).Nanoseconds()) + + var b bytes.Buffer + if err = tokenwriter.Write(&b, operation); err != nil { r.Log.Error(). Err(err). Msg("writing parsed") - return + return true } - forwardStr := forwarded.String() - m.Forwarded = &forwardStr + forwarded = b.String() + + start = time.Now() + return false }, - func(errParser error) { - m.TimeParsingNs = nsToF64(time.Since(startParsing).Nanoseconds()) - err = errParser + func(template *config.Template) (stop bool) { + sinceStart := time.Since(start) + m = append(m, &model.MatchResult{ + Template: obj.Templates[template], + TimeMatchingNs: nsToF64(sinceStart.Nanoseconds()), + TimeParsingNs: durParsing, + Forwarded: &forwarded, + }) + + start = time.Now() + return false }, + func(errParser error) { err = errParser }, ) return m, err } // Match is the resolver for the match field. -func (r *serviceResolver) Match(ctx context.Context, obj *model.Service, query string, operationName *string, variablesJSON *string) (*model.MatchResult, error) { - // Declare here instead of using named return variables - // to avoid code generation overriding them. - m := new(model.MatchResult) - var err error +func (r *serviceResolver) Match(ctx context.Context, obj *model.Service, query string, operationName *string, variablesJSON *string) (m *model.MatchResult, err error) { + m = &model.MatchResult{} - oprName := []byte(nil) + var oprName []byte if operationName != nil { oprName = []byte(*operationName) } - varsJSON := []byte(nil) + var varsJSON []byte if variablesJSON != nil { varsJSON = []byte(*variablesJSON) } - startParsing := time.Now() - obj.Parser.Parse( + start := time.Now() + var durParsing float64 + var forwarded string + obj.Engine.Match( []byte(query), oprName, varsJSON, - func( - varVals [][]gqlparse.Token, - operation []gqlparse.Token, - selectionSet []gqlparse.Token, - ) { - m.TimeParsingNs = nsToF64(time.Since(startParsing).Nanoseconds()) - startMatching := time.Now() - obj.Engine.Match( - varVals, operation[0].ID, selectionSet, - func(t *config.Template) (stop bool) { - m.Templates = []*model.Template{obj.Templates[t]} - return true - }, - ) - m.TimeMatchingNs = nsToF64(time.Since(startMatching).Nanoseconds()) - var forwarded bytes.Buffer - if err = tokenwriter.Write(&forwarded, operation); err != nil { + func(operation, selectionSet []gqlparse.Token) (stop bool) { + durParsing = nsToF64(time.Since(start).Nanoseconds()) + + var b bytes.Buffer + if err = tokenwriter.Write(&b, operation); err != nil { r.Log.Error(). Err(err). Msg("writing parsed") - return + return true } - forwardStr := forwarded.String() - m.Forwarded = &forwardStr + forwarded = b.String() + + start = time.Now() + return false }, - func(errParser error) { - m.TimeParsingNs = nsToF64(time.Since(startParsing).Nanoseconds()) - err = errParser + func(template *config.Template) (stop bool) { + sinceStart := time.Since(start) + m = &model.MatchResult{ + Template: obj.Templates[template], + TimeMatchingNs: nsToF64(sinceStart.Nanoseconds()), + TimeParsingNs: durParsing, + Forwarded: &forwarded, + } + return true // Stop after first match }, + func(errParser error) { err = errParser }, ) return m, err } @@ -194,6 +187,8 @@ func (r *Resolver) Service() generated.ServiceResolver { return &serviceResolver // Template returns generated.TemplateResolver implementation. func (r *Resolver) Template() generated.TemplateResolver { return &templateResolver{r} } -type queryResolver struct{ *Resolver } -type serviceResolver struct{ *Resolver } -type templateResolver struct{ *Resolver } +type ( + queryResolver struct{ *Resolver } + serviceResolver struct{ *Resolver } + templateResolver struct{ *Resolver } +) diff --git a/engine/playmon/bench_test.go b/engine/playmon/bench_test.go index b9d7f7a..7278069 100644 --- a/engine/playmon/bench_test.go +++ b/engine/playmon/bench_test.go @@ -7,7 +7,6 @@ import ( "github.com/graph-guard/ggproxy/engine/playmon" "github.com/graph-guard/ggproxy/gqlparse" "github.com/graph-guard/ggproxy/testsetup" - "github.com/graph-guard/gqlscan" ) var GS string @@ -16,82 +15,21 @@ func BenchmarkMatchStarwars(b *testing.B) { s := testsetup.Starwars() service := s.Config.ServicesEnabled[0] e := playmon.New(service) - - p := gqlparse.NewParser(service.Schema) - var varvals [][]gqlparse.Token - var opr gqlscan.Token - var selset []gqlparse.Token - src := s.Tests[1].Client.Input.BodyJSON["query"].(string) - p.Parse( - []byte(src), nil, nil, - func( - varValues [][]gqlparse.Token, - operation, selectionSet []gqlparse.Token, - ) { - varvals = make([][]gqlparse.Token, len(varValues)) - for i, vv := range varValues { - varvals[i] = copyTokens(vv) - } - opr = operation[0].ID - selset = copyTokens(selectionSet) - }, - func(err error) { - b.Fatalf("parsing request: %v", err) - }, - ) - b.ResetTimer() for n := 0; n < b.N; n++ { e.Match( - varvals, opr, selset, + []byte(s.Tests[1].Client.Input.BodyJSON["query"].(string)), + nil, nil, + func(operation, selectionSet []gqlparse.Token) (stop bool) { + return false + }, func(t *config.Template) (stop bool) { GS = t.ID return false }, - ) - } -} - -func BenchmarkMatchStarwarsWithParser(b *testing.B) { - s := testsetup.Starwars() - service := s.Config.ServicesEnabled[0] - e := playmon.New(service) - - p := gqlparse.NewParser(service.Schema) - src := []byte(s.Tests[1].Client.Input.BodyJSON["query"].(string)) - - b.ResetTimer() - for n := 0; n < b.N; n++ { - p.Parse( - src, nil, nil, - func( - varValues [][]gqlparse.Token, - operation, selectionSet []gqlparse.Token, - ) { - e.Match( - varValues, operation[0].ID, selectionSet, - func(t *config.Template) (stop bool) { - GS = t.ID - return false - }, - ) - }, func(err error) { - b.Fatalf("parsing request: %v", err) + b.Fatal("unexpected error:", err) }, ) } } - -func copyTokens(original []gqlparse.Token) []gqlparse.Token { - cp := make([]gqlparse.Token, len(original)) - for i, t := range original { - v := make([]byte, len(t.Value)) - copy(v, t.Value) - cp[i] = gqlparse.Token{ - ID: t.ID, - Value: v, - } - } - return cp -} diff --git a/engine/playmon/playmon.go b/engine/playmon/playmon.go index bdb3f88..78281fb 100644 --- a/engine/playmon/playmon.go +++ b/engine/playmon/playmon.go @@ -1,8 +1,6 @@ package playmon import ( - "fmt" - "github.com/graph-guard/ggproxy/config" "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck" "github.com/graph-guard/ggproxy/engine/playmon/internal/pathmatch" @@ -18,6 +16,7 @@ type arg struct { } type Engine struct { + parser *gqlparse.Parser pathScanner *pathscan.PathScanner matcher *pathmatch.Matcher templates map[string]*template @@ -38,6 +37,7 @@ type template struct { // New expects templates to be initialized and valid. func New(s *config.Service) *Engine { e := &Engine{ + parser: gqlparse.NewParser(s.Schema), pathScanner: pathscan.New(128, 2048), templates: make(map[string]*template, len(s.Templates)), argumentPaths: make(map[uint64]struct{}), @@ -87,8 +87,8 @@ func (e *Engine) reset() { } } -// Match returns the ID of the first matching template or "" if none was matched. -func (e *Engine) Match( +// match returns the ID of the first matching template or "" if none was matched. +func (e *Engine) match( variableValues [][]gqlparse.Token, queryType gqlscan.Token, selectionSet []gqlparse.Token, @@ -126,7 +126,6 @@ func (e *Engine) Match( e.matcher.Match(e.structuralPaths, func(t *config.Template) (stop bool) { tm := e.templates[t.ID] - fmt.Println("\nTEMPORARILY MATCHED ", tm.ID) for i := range e.argumentsSet { if !tm.ConstraintChecker.Check( variableValues, @@ -140,3 +139,31 @@ func (e *Engine) Match( return onMatch(t) }) } + +// Match calls onMatch for every matched template until onMatch returns true. +// onErr is invoked in case of an error. +func (e *Engine) Match( + query, operationName, variablesJSON []byte, + onParsed func(operation, selectionSet []gqlparse.Token) (stop bool), + onMatch func(template *config.Template) (stop bool), + onErr func(err error), +) { + e.parser.Parse( + query, operationName, variablesJSON, + func( + varVals [][]gqlparse.Token, + operation, selectionSet []gqlparse.Token, + ) { + if onParsed(operation, selectionSet) { + return + } + e.match( + varVals, operation[0].ID, selectionSet, + func(t *config.Template) (stop bool) { + return onMatch(t) + }, + ) + }, + onErr, + ) +} diff --git a/go.mod b/go.mod index e1a9088..307c694 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,8 @@ go 1.18 require ( github.com/99designs/gqlgen v0.17.24 - github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dustin/go-humanize v1.0.0 github.com/google/go-cmp v0.5.8 - github.com/google/uuid v1.3.0 - github.com/graph-guard/backend v0.0.0-20220826171348-e3dcd100c82b github.com/graph-guard/gqlscan v1.0.0 github.com/graph-guard/gqt/v4 v4.0.9 github.com/phuslu/log v1.0.81 diff --git a/go.sum b/go.sum index b81be1a..079b5b1 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= @@ -26,12 +24,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/graph-guard/backend v0.0.0-20220826171348-e3dcd100c82b h1:MTZ+FdD6qIy0s0KgUecPGNJ8frrZ00Tqv4J81vdTv8w= -github.com/graph-guard/backend v0.0.0-20220826171348-e3dcd100c82b/go.mod h1:x2XK0889DedSc5iB1AZzg0dJdWaHx8UiRo1vnv55kNc= github.com/graph-guard/gqlscan v1.0.0 h1:hUEDqnJQJ7nV9S8hCPJuYI/H97KP3unu0Gu7wd0wV8A= github.com/graph-guard/gqlscan v1.0.0/go.mod h1:A7TeIFxoiP39gw/r4sWjDnTZJnNy+4kaytP9Kh7Luuk= github.com/graph-guard/gqt/v4 v4.0.9 h1:rqCwhrns1u65a3GVutFQEPXWxO8Y6R3fv+t8oPsFhVU= diff --git a/server/api.go b/server/api.go index 257927c..f1f0837 100644 --- a/server/api.go +++ b/server/api.go @@ -20,7 +20,6 @@ import ( "github.com/graph-guard/ggproxy/api/graph/model" "github.com/graph-guard/ggproxy/config" "github.com/graph-guard/ggproxy/engine/playmon" - "github.com/graph-guard/ggproxy/gqlparse" gqt "github.com/graph-guard/gqt/v4" plog "github.com/phuslu/log" "github.com/valyala/fasthttp" @@ -251,8 +250,7 @@ func makeService( ) *model.Service { stats := proxyServer.GetServiceStatistics(s.ID) service := &model.Service{ - Parser: gqlparse.NewParser(s.Schema), - Stats: stats, + Stats: stats, Templates: make( map[*config.Template]*model.Template, len(s.Templates), diff --git a/server/proxy.go b/server/proxy.go index 48891e1..65db17a 100644 --- a/server/proxy.go +++ b/server/proxy.go @@ -186,22 +186,15 @@ func (s *Proxy) handle(ctx *fasthttp.RequestCtx) { m := service.enginePool.Get().(*engine) defer service.enginePool.Put(m) - m.Parser.Parse( + var operation []gqlparse.Token + m.Engine.Match( query, operationName, variablesJSON, - func( - varVals [][]gqlparse.Token, - operation []gqlparse.Token, - selectionSet []gqlparse.Token, - ) { - var tmpl *config.Template - m.Engine.Match( - varVals, operation[0].ID, selectionSet, - func(t *config.Template) (stop bool) { - tmpl = t - return false - }, - ) - if tmpl == nil { + func(o, selectionSet []gqlparse.Token) (stop bool) { + operation = o + return false + }, + func(t *config.Template) (stop bool) { + if t == nil { timeProcessing := time.Since(start) service.statistics.Update( len(body), 0, @@ -214,7 +207,7 @@ func (s *Proxy) handle(ctx *fasthttp.RequestCtx) { return } - templateStatistics := service.templateStatistics[tmpl.ID] + templateStatistics := service.templateStatistics[t.ID] timeProcessing := time.Since(start) startForward := time.Now() @@ -231,8 +224,7 @@ func (s *Proxy) handle(ctx *fasthttp.RequestCtx) { if service.forwardReduced { if err := tokenwriter.Write( - ctx.Request.BodyWriter(), - operation, + ctx.Request.BodyWriter(), operation, ); err != nil { s.log.Error(). Err(err). @@ -280,6 +272,7 @@ func (s *Proxy) handle(ctx *fasthttp.RequestCtx) { templateStatistics.Update( timeProcessing, timeForwarding, ) + return false }, func(err error) { s.log.Error().Err(err).Msg("parser error") From fdf79e309a199a6d35d09112c24908c3dcef407b Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sun, 5 Mar 2023 00:29:18 +0100 Subject: [PATCH 26/35] refactor: Restructure packages --- .goreleaser.yml | 6 -- assets/etc/ggproxy/all-services/a.yml | 12 ---- .../all-templates/a/example_template.gqt | 20 ------- assets/etc/ggproxy/config.yml | 25 --------- assets/etc/ggproxy/enabled-services/a.yml | 1 - .../enabled-templates/a/example_template.gqt | 1 - cmd/ggproxy/main.go | 2 +- cmd/ggproxy/read_config.go | 2 +- cmd/ggproxy/reload_unix.go | 2 +- cmd/ggproxy/reload_windows.go | 2 +- cmd/ggproxy/serve_unix.go | 4 +- cmd/ggproxy/serve_windows.go | 2 +- cmd/ggproxy/stop_unix.go | 2 +- cmd/ggproxy/stop_windows.go | 2 +- {api => pkg/api}/gqlgen.yml | 6 +- {api => pkg/api}/graph/generated/generated.go | 56 +++++++++---------- {api => pkg/api}/graph/model/model.go | 6 +- {api => pkg/api}/graph/model/models_gen.go | 0 {api => pkg/api}/graph/resolver.go | 4 +- {api => pkg/api}/graph/schema.graphqls | 0 {api => pkg/api}/graph/schema.resolvers.go | 18 +++--- {api => pkg/api}/tools.go | 0 {utilities => pkg}/aset/aset.go | 2 +- {utilities => pkg}/aset/aset_test.go | 4 +- {utilities => pkg}/aset/benchmark_test.go | 2 +- {utilities => pkg}/atoi/atoi.go | 2 +- {utilities => pkg}/atoi/atoi_test.go | 2 +- {utilities => pkg}/bitmask/bench_test.go | 0 {utilities => pkg}/bitmask/bitmask.go | 0 {utilities => pkg}/bitmask/example_test.go | 0 .../bitmask/example_union_test.go | 0 {utilities => pkg}/bitmask/funcs.go | 0 {utilities => pkg}/bitmask/funcs_test.go | 0 {utilities => pkg}/bitmask/set_test.go | 0 {cli => pkg/cli}/cli.go | 0 {cli => pkg/cli}/cli_test.go | 2 +- {config => pkg/config}/config.go | 2 +- {config => pkg/config}/config_test.go | 4 +- {config => pkg/config}/metadata/metadata.go | 0 .../config}/metadata/metadata_test.go | 2 +- {utilities => pkg}/container/amap/amap.go | 2 +- .../container/amap/amap_test.go | 2 +- .../container/benchmark_test.go | 8 ++- {utilities => pkg}/container/container.go | 0 .../container/container_test.go | 8 +-- {utilities => pkg}/container/gomap/gomap.go | 0 {utilities => pkg}/container/hamap/hamap.go | 8 ++- .../container/hamap/hamap_test.go | 2 +- {utilities => pkg}/container/linear/linear.go | 0 {utilities => pkg}/decl/decl.go | 0 {engine => pkg/engine}/playmon/bench_test.go | 8 +-- .../internal/constrcheck/bench_test.go | 4 +- .../internal/constrcheck/constrcheck.go | 14 ++--- .../internal/constrcheck/constrcheck_test.go | 4 +- .../playmon/internal/constrcheck/doc.go | 0 .../constrcheck/internal/test/test.go | 2 +- .../constrcheck/internal/union/union.go | 6 +- .../constrcheck/internal/union/union_test.go | 4 +- .../internal/constrcheck/iswrongtype.go | 2 +- .../internal/constrcheck/iswrongtype_test.go | 4 +- .../constrcheck/tests/arithmetic_div.yml | 0 .../constrcheck/tests/arithmetic_mod.yml | 0 .../constrcheck/tests/arithmetic_mul.yml | 0 .../constrcheck/tests/arithmetic_sub.yml | 0 .../tests/equal_strblock_false.todo.yaml | 0 .../tests/false_arithmetic_add.yml | 0 .../constrcheck/tests/false_constr_and.yml | 0 .../constrcheck/tests/false_constr_or.yml | 0 .../constrcheck/tests/false_equal_arr_int.yml | 0 .../constrcheck/tests/false_equal_arr_str.yml | 0 .../constrcheck/tests/false_equal_bool.yml | 0 .../constrcheck/tests/false_equal_enum.yml | 0 .../constrcheck/tests/false_equal_float.yml | 0 .../constrcheck/tests/false_equal_int.yml | 0 .../constrcheck/tests/false_equal_obj.yml | 0 .../constrcheck/tests/false_equal_str.yml | 0 .../tests/false_equal_strblock.yml | 0 .../constrcheck/tests/false_interface.yml | 0 .../constrcheck/tests/false_len_array_int.yml | 0 .../constrcheck/tests/false_len_array_obj.yml | 0 .../constrcheck/tests/false_len_str_eq.yml | 0 .../constrcheck/tests/false_len_str_gr.yml | 0 .../constrcheck/tests/false_len_str_greq.yml | 0 .../constrcheck/tests/false_len_str_leeq.yml | 0 .../constrcheck/tests/false_len_str_neq.yml | 0 .../tests/false_len_strblock_eq.yml | 0 .../tests/false_len_strblock_gr.yml | 0 .../tests/false_len_strblock_greq.yml | 0 .../tests/false_len_strblock_le.yaml | 0 .../tests/false_len_strblock_le.yml | 0 .../tests/false_len_strblock_leeq.yml | 0 .../tests/false_len_strblock_neq.yml | 0 .../constrcheck/tests/false_logical_equal.yml | 0 .../tests/false_logical_expr_relational.yml | 0 .../false_logical_expr_relational_float.yml | 0 .../tests/false_logical_notequal.yml | 0 .../constrcheck/tests/false_logical_or_eq.yml | 0 .../tests/false_logical_or_gr_le.yml | 0 .../constrcheck/tests/false_map_2d.yml | 0 .../tests/false_map_array_object.yml | 0 .../tests/false_map_var_arithmetic.yml | 0 .../tests/false_not_equal_arr_int.yml | 0 .../tests/false_not_equal_arr_str.yml | 0 .../constrcheck/tests/false_not_equal_obj.yml | 0 .../tests/false_relational_float.yml | 0 .../tests/false_relational_int.yml | 0 .../constrcheck/tests/false_union.yml | 0 .../constrcheck/tests/false_var_bool.yml | 0 .../constrcheck/tests/false_var_str.yml | 0 .../constrcheck/tests/false_var_strblock.yml | 0 .../tests/invalid_obj_duplicate_inputs.yml | 0 .../invalid_obj_duplicate_inputs_in_any.yml | 0 .../constrcheck/tests/logical_negation.yml | 0 .../constrcheck/tests/numeric_negation.yml | 0 .../constrcheck/tests/true_arithmetic_add.yml | 0 .../constrcheck/tests/true_constr_and.yml | 0 .../constrcheck/tests/true_constr_or.yml | 0 .../constrcheck/tests/true_equal_arr_int.yml | 0 .../constrcheck/tests/true_equal_arr_str.yml | 0 .../constrcheck/tests/true_equal_bool.yml | 0 .../constrcheck/tests/true_equal_enum.yml | 0 .../constrcheck/tests/true_equal_float.yml | 0 .../constrcheck/tests/true_equal_int.yml | 0 .../constrcheck/tests/true_equal_obj.yml | 0 .../constrcheck/tests/true_equal_str.yml | 0 .../constrcheck/tests/true_equal_strblock.yml | 0 .../constrcheck/tests/true_interface.yml | 0 .../constrcheck/tests/true_len_array_int.yml | 0 .../constrcheck/tests/true_len_array_obj.yml | 0 .../constrcheck/tests/true_len_str.yml | 0 .../constrcheck/tests/true_len_str_eq.yml | 0 .../constrcheck/tests/true_len_str_gr.yml | 0 .../constrcheck/tests/true_len_str_greq.yml | 0 .../constrcheck/tests/true_len_str_le.yml | 0 .../constrcheck/tests/true_len_str_leeq.yml | 0 .../constrcheck/tests/true_len_str_neq.yml | 0 .../constrcheck/tests/true_len_strblock.yml | 0 .../tests/true_len_strblock_eq.yml | 0 .../tests/true_len_strblock_gr.yml | 0 .../tests/true_len_strblock_greq.yml | 0 .../tests/true_len_strblock_le.yml | 0 .../tests/true_len_strblock_leeq.yml | 0 .../tests/true_len_strblock_neq.yml | 0 .../constrcheck/tests/true_logical_equal.yml | 0 .../tests/true_logical_expr_relational.yml | 0 .../true_logical_expr_relational_float.yml | 0 .../tests/true_logical_notequal.yml | 0 .../constrcheck/tests/true_logical_or_eq.yml | 0 .../tests/true_logical_or_gr_le.yml | 0 .../constrcheck/tests/true_map_2d.yml | 0 .../tests/true_map_array_object.yml | 0 .../tests/true_map_var_arithmetic.yml | 0 .../tests/true_not_equal_arr_int.yml | 0 .../tests/true_not_equal_arr_str.yml | 0 .../constrcheck/tests/true_not_equal_obj.yml | 0 .../tests/true_relational_float.yml | 0 .../constrcheck/tests/true_relational_int.yml | 0 .../internal/constrcheck/tests/true_union.yml | 0 .../constrcheck/tests/true_var_bool.yml | 0 .../constrcheck/tests/true_var_str.yml | 0 .../constrcheck/tests/true_var_strblock.yml | 0 .../tests/var_equal_nonexistent.yml | 0 .../constrcheck/tests/wrong_type_equal.yml | 0 .../tests/wrong_type_equal_arrint.yml | 0 .../tests/wrong_type_equal_bool_f.yml | 0 .../tests/wrong_type_equal_bool_t.yml | 0 .../tests/wrong_type_equal_enum.yml | 0 .../tests/wrong_type_equal_float.yml | 0 .../tests/wrong_type_equal_int.yml | 0 .../tests/wrong_type_equal_missing_field.yml | 0 .../tests/wrong_type_equal_null.yml | 0 .../tests/wrong_type_equal_str.yml | 0 .../wrong_type_false_equal_missing_field.yml | 0 ...ong_type_false_not_equal_missing_field.yml | 0 .../tests/wrong_type_len_arr_int.yml | 0 .../tests/wrong_type_len_arr_obj.yml | 0 .../constrcheck/tests/wrong_type_len_str.yml | 0 .../constrcheck/tests/wrong_type_map_2d.yml | 0 .../wrong_type_map_obj_too_many_fields.yml | 0 .../tests/wrong_type_not_equal.yml | 0 .../tests/wrong_type_obj_too_many_inputs.yml | 0 .../tests/wrong_type_relational_float.yml | 0 .../playmon/internal/countval/count_values.go | 2 +- .../internal/countval/count_values_test.go | 6 +- .../playmon/internal/pathmatch/bench_test.go | 4 +- .../playmon/internal/pathmatch/pathmatch.go | 6 +- .../internal/pathmatch/pathmatch_test.go | 6 +- .../pathmatch/test_setups/starwars/config.yml | 0 .../starwars/services_enabled/starwars.yml | 0 .../starwars/templates_enabled/a.gqt | 0 .../starwars/templates_enabled/b.gqt | 0 .../starwars/templates_enabled/c.gqt | 0 .../starwars/templates_enabled/d.gqt | 0 .../playmon/internal/pathscan/pathscan.go | 8 +-- .../internal/pathscan/pathscan_test.go | 4 +- .../playmon/internal/scanval/length.go | 2 +- .../playmon/internal/scanval/length_test.go | 6 +- .../playmon/internal/scanval/scan_values.go | 2 +- .../internal/scanval/scan_values_in_arrays.go | 2 +- .../scanval/scan_values_in_arrays_test.go | 6 +- .../internal/scanval/scan_values_test.go | 6 +- .../internal/tokenreader/tokenreader.go | 2 +- .../internal/tokenreader/tokenreader_test.go | 4 +- .../playmon/internal/travgqt/travgqt.go | 0 {engine => pkg/engine}/playmon/playmon.go | 10 ++-- .../assets/benchassets/_bench_deep/bench.yml | 0 .../benchassets/bench_average/bench.yml | 0 .../assets/benchassets/bench_big/bench.yml | 0 .../tests/assets/benchassets/templates/0.gqt | 0 .../tests/assets/benchassets/templates/1.gqt | 0 .../tests/assets/benchassets/templates/2.gqt | 0 .../tests/assets/benchassets/templates/3.gqt | 0 .../tests/assets/benchassets/templates/4.gqt | 0 .../tests/assets/benchassets/templates/5.gqt | 0 .../tests/assets/benchassets/templates/6.gqt | 0 .../tests/assets/benchassets/templates/7.gqt | 0 .../tests/assets/benchassets/templates/8.tgqt | 0 .../testassets/test_arguments_merge_00/0.gqt | 0 .../testassets/test_arguments_merge_00/1.gqt | 0 .../testassets/test_arguments_merge_00/2.gqt | 0 .../testassets/test_arguments_merge_00/3.gqt | 0 .../test_arguments_merge_00/test.yml | 0 .../testassets/test_arguments_merge_01/0.gqt | 0 .../testassets/test_arguments_merge_01/1.gqt | 0 .../testassets/test_arguments_merge_01/2.gqt | 0 .../testassets/test_arguments_merge_01/3.gqt | 0 .../test_arguments_merge_01/test.yml | 0 .../assets/testassets/test_complex_00/0.gqt | 0 .../assets/testassets/test_complex_00/1.gqt | 0 .../assets/testassets/test_complex_00/2.gqt | 0 .../assets/testassets/test_complex_00/3.gqt | 0 .../assets/testassets/test_complex_00/4.gqt | 0 .../assets/testassets/test_complex_00/5.gqt | 0 .../testassets/test_complex_00/test.yml | 0 .../testassets/test_conditions_00/0.gqt | 0 .../testassets/test_conditions_00/test.yml | 0 .../testassets/test_conditions_01/0.gqt | 0 .../testassets/test_conditions_01/1.gqt | 0 .../testassets/test_conditions_01/test.yml | 0 .../testassets/test_conditions_02/0.gqt | 0 .../testassets/test_conditions_02/1.gqt | 0 .../testassets/test_conditions_02/test.yml | 0 .../testassets/test_conditions_03/0.gqt | 0 .../testassets/test_conditions_03/test.yml | 0 .../testassets/test_conditions_04/0.gqt | 0 .../testassets/test_conditions_04/1.gqt | 0 .../testassets/test_conditions_04/test.yml | 0 .../assets/testassets/test_enum_00/0.gqt | 0 .../assets/testassets/test_enum_00/1.gqt | 0 .../assets/testassets/test_enum_00/test.yml | 0 .../assets/testassets/test_fragments_00/0.gqt | 0 .../testassets/test_fragments_00/test.yml | 0 .../assets/testassets/test_fragments_01/0.gqt | 0 .../testassets/test_fragments_01/test.yml | 0 .../assets/testassets/test_fragments_02/0.gqt | 0 .../testassets/test_fragments_02/test.yml | 0 .../assets/testassets/test_fragments_03/0.gqt | 0 .../testassets/test_fragments_03/test.yml | 0 .../testassets/test_general_logic_00/0.gqt | 0 .../testassets/test_general_logic_00/test.yml | 0 .../testassets/test_general_logic_01/0.gqt | 0 .../testassets/test_general_logic_01/test.yml | 0 .../testassets/test_general_logic_02/0.gqt | 0 .../testassets/test_general_logic_02/test.yml | 0 .../testassets/test_general_logic_03/0.gqt | 0 .../testassets/test_general_logic_03/test.yml | 0 .../testassets/test_general_logic_04/0.gqt | 0 .../testassets/test_general_logic_04/test.yml | 0 .../testassets/test_general_logic_05/0.gqt | 0 .../testassets/test_general_logic_05/1.gqt | 0 .../testassets/test_general_logic_05/test.yml | 0 .../testassets/test_general_logic_06/0.gqt | 0 .../testassets/test_general_logic_06/1.gqt | 0 .../testassets/test_general_logic_06/test.yml | 0 .../testassets/test_general_logic_07/0.gqt | 0 .../testassets/test_general_logic_07/test.yml | 0 .../testassets/test_general_logic_08/0.gqt | 0 .../testassets/test_general_logic_08/test.yml | 0 .../testassets/test_general_logic_09/0.gqt | 0 .../testassets/test_general_logic_09/test.yml | 0 .../testassets/test_general_logic_10/0.gqt | 0 .../testassets/test_general_logic_10/1.gqt | 0 .../testassets/test_general_logic_10/2.gqt | 0 .../testassets/test_general_logic_10/test.yml | 0 .../testassets/test_general_logic_11/0.gqt | 0 .../testassets/test_general_logic_11/1.gqt | 0 .../testassets/test_general_logic_11/2.gqt | 0 .../testassets/test_general_logic_11/test.yml | 0 .../testassets/test_identical_fields_00/0.gqt | 0 .../test_identical_fields_00/test.yml | 0 .../tests/assets/testassets/test_map_00/0.gqt | 0 .../assets/testassets/test_map_00/test.yml | 0 .../tests/assets/testassets/test_max_00/0.gqt | 0 .../assets/testassets/test_max_00/test.yml | 0 .../tests/assets/testassets/test_max_02/0.gqt | 0 .../tests/assets/testassets/test_max_02/1.gqt | 0 .../assets/testassets/test_max_02/test.yml | 0 .../tests/assets/testassets/test_max_03/0.gqt | 0 .../assets/testassets/test_max_03/test.yml | 0 .../testassets/test_max_complex_00/0.gqt | 0 .../testassets/test_max_complex_00/test.yml | 0 .../assets/testassets/test_negation_00/0.gqt | 0 .../testassets/test_negation_00/test.yml | 0 .../assets/testassets/test_negation_01/0.gqt | 0 .../testassets/test_negation_01/test.yml | 0 .../assets/testassets/test_nesting_00/0.gqt | 0 .../testassets/test_nesting_00/test.yml | 0 .../testassets/test_special_fields_00/0.gqt | 0 .../test_special_fields_00/test.yml | 0 .../testassets/test_token_null_00/0.gqt | 0 .../testassets/test_token_null_00/test.yml | 0 .../assets/testassets/test_variables_00/0.gqt | 0 .../testassets/test_variables_00/test.yml | 0 .../engine}/tests/benchmark_test.go | 2 +- {engine => pkg/engine}/tests/engine_test.go | 10 ++-- {gqlparse => pkg/gqlparse}/bench_test.go | 2 +- {gqlparse => pkg/gqlparse}/gqlparse.go | 10 ++-- {gqlparse => pkg/gqlparse}/gqlparse_test.go | 6 +- .../gqlparse}/internal/graph/bench_test.go | 8 ++- .../gqlparse}/internal/graph/graph.go | 4 +- .../gqlparse}/internal/graph/graph_test.go | 4 +- {utilities => pkg}/math/math.go | 0 {utilities => pkg}/math/math_test.go | 2 +- {utilities => pkg}/mhstore/benchmark_test.go | 2 +- {utilities => pkg}/mhstore/mhstore.go | 2 +- {utilities => pkg}/mhstore/mhstore_test.go | 2 +- {utilities => pkg}/segmented/segmented.go | 2 +- .../segmented/segmented_test.go | 2 +- {server => pkg/server}/api.go | 10 ++-- {server => pkg/server}/proxy.go | 10 ++-- {server => pkg/server}/server_test.go | 4 +- {utilities => pkg}/set/bench_test.go | 2 +- {utilities => pkg}/set/set.go | 0 {utilities => pkg}/set/set_test.go | 2 +- {utilities => pkg}/stack/stack.go | 0 {utilities => pkg}/stack/stack_test.go | 2 +- {statistics => pkg/statistics}/statistics.go | 0 .../statistics}/statistics_test.go | 2 +- {utilities => pkg}/testeq/testeq.go | 0 {utilities => pkg}/testeq/testeq_test.go | 2 +- .../inputs_schema/all-services/service_a.yaml | 0 .../all-templates/service_a/mut_arr_str.gqt | 0 .../testsetup}/inputs_schema/config.yml | 0 .../enabled-services/service_a.yaml | 0 .../service_a/mut_arr_str.gqt | 0 .../inputs_schema/service_a.graphqls | 0 .../inputs_schema/test_arr_str_eq.yaml | 0 .../testsetup}/starwars/config.yml | 0 .../services_enabled/starwars.graphqls | 0 .../starwars/services_enabled/starwars.yml | 0 .../starwars/templates_enabled/a.gqt | 0 .../starwars/templates_enabled/b.gqt | 0 .../starwars/templates_enabled/c.gqt | 0 .../starwars/templates_enabled/d.gqt | 0 .../starwars/templates_enabled/e.gqt | 0 .../starwars/templates_enabled/f.gqt | 0 .../starwars/templates_enabled/max_1.gqt | 0 .../testsetup}/starwars/test_qry2_ok.yaml | 0 .../testsetup}/starwars/test_qry_ok.yaml | 0 .../test1/all_services/testservice.yaml | 0 .../test1/all_templates/template_mut.gqt | 0 .../test1/all_templates/template_qry.gqt | 0 {testsetup => pkg/testsetup}/test1/config.yml | 0 .../test1/enabled_services/testservice.yaml | 0 .../test1/enabled_templates/template_mut.gqt | 0 .../test1/enabled_templates/template_qry.gqt | 0 .../testsetup}/test1/test_qry_ok.yaml | 0 .../testsetup}/test1/x_test_mut_ok.yaml | 0 {testsetup => pkg/testsetup}/testsetup.go | 2 +- .../testsetup}/testsetup_test.go | 2 +- {utilities => pkg}/tokenwriter/bench_test.go | 4 +- {utilities => pkg}/tokenwriter/tokenwriter.go | 46 +++++++-------- .../tokenwriter/tokenwriter_test.go | 4 +- {utilities => pkg}/unsafe/unsafe.go | 0 {utilities => pkg}/unsafe/unsafe_test.go | 2 +- {utilities => pkg}/xxhash/bench_test.go | 3 +- {utilities => pkg}/xxhash/xxhash.go | 0 {utilities => pkg}/xxhash/xxhash_test.go | 2 +- 378 files changed, 224 insertions(+), 282 deletions(-) delete mode 100644 assets/etc/ggproxy/all-services/a.yml delete mode 100644 assets/etc/ggproxy/all-templates/a/example_template.gqt delete mode 100644 assets/etc/ggproxy/config.yml delete mode 120000 assets/etc/ggproxy/enabled-services/a.yml delete mode 120000 assets/etc/ggproxy/enabled-templates/a/example_template.gqt rename {api => pkg/api}/gqlgen.yml (91%) rename {api => pkg/api}/graph/generated/generated.go (98%) rename {api => pkg/api}/graph/model/model.go (83%) rename {api => pkg/api}/graph/model/models_gen.go (100%) rename {api => pkg/api}/graph/resolver.go (83%) rename {api => pkg/api}/graph/schema.graphqls (100%) rename {api => pkg/api}/graph/schema.resolvers.go (93%) rename {api => pkg/api}/tools.go (100%) rename {utilities => pkg}/aset/aset.go (97%) rename {utilities => pkg}/aset/aset_test.go (94%) rename {utilities => pkg}/aset/benchmark_test.go (96%) rename {utilities => pkg}/atoi/atoi.go (95%) rename {utilities => pkg}/atoi/atoi_test.go (98%) rename {utilities => pkg}/bitmask/bench_test.go (100%) rename {utilities => pkg}/bitmask/bitmask.go (100%) rename {utilities => pkg}/bitmask/example_test.go (100%) rename {utilities => pkg}/bitmask/example_union_test.go (100%) rename {utilities => pkg}/bitmask/funcs.go (100%) rename {utilities => pkg}/bitmask/funcs_test.go (100%) rename {utilities => pkg}/bitmask/set_test.go (100%) rename {cli => pkg/cli}/cli.go (100%) rename {cli => pkg/cli}/cli_test.go (98%) rename {config => pkg/config}/config.go (99%) rename {config => pkg/config}/config_test.go (99%) rename {config => pkg/config}/metadata/metadata.go (100%) rename {config => pkg/config}/metadata/metadata_test.go (98%) rename {utilities => pkg}/container/amap/amap.go (98%) rename {utilities => pkg}/container/amap/amap_test.go (98%) rename {utilities => pkg}/container/benchmark_test.go (96%) rename {utilities => pkg}/container/container.go (100%) rename {utilities => pkg}/container/container_test.go (93%) rename {utilities => pkg}/container/gomap/gomap.go (100%) rename {utilities => pkg}/container/hamap/hamap.go (98%) rename {utilities => pkg}/container/hamap/hamap_test.go (99%) rename {utilities => pkg}/container/linear/linear.go (100%) rename {utilities => pkg}/decl/decl.go (100%) rename {engine => pkg/engine}/playmon/bench_test.go (74%) rename {engine => pkg/engine}/playmon/internal/constrcheck/bench_test.go (97%) rename {engine => pkg/engine}/playmon/internal/constrcheck/constrcheck.go (98%) rename {engine => pkg/engine}/playmon/internal/constrcheck/constrcheck_test.go (94%) rename {engine => pkg/engine}/playmon/internal/constrcheck/doc.go (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/internal/test/test.go (98%) rename {engine => pkg/engine}/playmon/internal/constrcheck/internal/union/union.go (98%) rename {engine => pkg/engine}/playmon/internal/constrcheck/internal/union/union_test.go (97%) rename {engine => pkg/engine}/playmon/internal/constrcheck/iswrongtype.go (97%) rename {engine => pkg/engine}/playmon/internal/constrcheck/iswrongtype_test.go (99%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/arithmetic_div.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/arithmetic_mod.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/arithmetic_mul.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/arithmetic_sub.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/equal_strblock_false.todo.yaml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_arithmetic_add.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_constr_and.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_constr_or.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_equal_arr_int.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_equal_arr_str.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_equal_bool.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_equal_enum.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_equal_float.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_equal_int.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_equal_obj.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_equal_str.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_equal_strblock.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_interface.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_len_array_int.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_len_array_obj.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_len_str_eq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_len_str_gr.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_len_str_greq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_len_str_leeq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_len_str_neq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_len_strblock_eq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_len_strblock_gr.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_len_strblock_greq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_len_strblock_le.yaml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_len_strblock_le.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_len_strblock_leeq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_len_strblock_neq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_logical_equal.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_logical_expr_relational.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_logical_expr_relational_float.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_logical_notequal.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_logical_or_eq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_logical_or_gr_le.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_map_2d.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_map_array_object.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_map_var_arithmetic.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_not_equal_arr_int.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_not_equal_arr_str.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_not_equal_obj.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_relational_float.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_relational_int.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_union.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_var_bool.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_var_str.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/false_var_strblock.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs_in_any.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/logical_negation.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/numeric_negation.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_arithmetic_add.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_constr_and.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_constr_or.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_equal_arr_int.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_equal_arr_str.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_equal_bool.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_equal_enum.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_equal_float.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_equal_int.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_equal_obj.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_equal_str.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_equal_strblock.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_interface.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_len_array_int.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_len_array_obj.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_len_str.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_len_str_eq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_len_str_gr.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_len_str_greq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_len_str_le.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_len_str_leeq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_len_str_neq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_len_strblock.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_len_strblock_eq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_len_strblock_gr.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_len_strblock_greq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_len_strblock_le.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_len_strblock_leeq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_len_strblock_neq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_logical_equal.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_logical_expr_relational.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_logical_expr_relational_float.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_logical_notequal.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_logical_or_eq.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_logical_or_gr_le.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_map_2d.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_map_array_object.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_map_var_arithmetic.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_not_equal_arr_int.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_not_equal_arr_str.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_not_equal_obj.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_relational_float.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_relational_int.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_union.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_var_bool.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_var_str.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/true_var_strblock.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/var_equal_nonexistent.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_equal.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_equal_arrint.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_equal_bool_f.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_equal_bool_t.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_equal_enum.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_equal_float.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_equal_int.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_equal_missing_field.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_equal_null.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_equal_str.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_false_equal_missing_field.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_false_not_equal_missing_field.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_len_arr_int.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_len_arr_obj.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_len_str.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_map_2d.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_map_obj_too_many_fields.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_not_equal.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_obj_too_many_inputs.yml (100%) rename {engine => pkg/engine}/playmon/internal/constrcheck/tests/wrong_type_relational_float.yml (100%) rename {engine => pkg/engine}/playmon/internal/countval/count_values.go (93%) rename {engine => pkg/engine}/playmon/internal/countval/count_values_test.go (98%) rename {engine => pkg/engine}/playmon/internal/pathmatch/bench_test.go (80%) rename {engine => pkg/engine}/playmon/internal/pathmatch/pathmatch.go (93%) rename {engine => pkg/engine}/playmon/internal/pathmatch/pathmatch_test.go (95%) rename {engine => pkg/engine}/playmon/internal/pathmatch/test_setups/starwars/config.yml (100%) rename {engine => pkg/engine}/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars.yml (100%) rename {engine => pkg/engine}/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/a.gqt (100%) rename {engine => pkg/engine}/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/b.gqt (100%) rename {engine => pkg/engine}/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/c.gqt (100%) rename {engine => pkg/engine}/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/d.gqt (100%) rename {engine => pkg/engine}/playmon/internal/pathscan/pathscan.go (98%) rename {engine => pkg/engine}/playmon/internal/pathscan/pathscan_test.go (99%) rename {engine => pkg/engine}/playmon/internal/scanval/length.go (93%) rename {engine => pkg/engine}/playmon/internal/scanval/length_test.go (95%) rename {engine => pkg/engine}/playmon/internal/scanval/scan_values.go (94%) rename {engine => pkg/engine}/playmon/internal/scanval/scan_values_in_arrays.go (95%) rename {engine => pkg/engine}/playmon/internal/scanval/scan_values_in_arrays_test.go (98%) rename {engine => pkg/engine}/playmon/internal/scanval/scan_values_test.go (98%) rename {engine => pkg/engine}/playmon/internal/tokenreader/tokenreader.go (96%) rename {engine => pkg/engine}/playmon/internal/tokenreader/tokenreader_test.go (97%) rename {engine => pkg/engine}/playmon/internal/travgqt/travgqt.go (100%) rename {engine => pkg/engine}/playmon/playmon.go (92%) rename {engine => pkg/engine}/tests/assets/benchassets/_bench_deep/bench.yml (100%) rename {engine => pkg/engine}/tests/assets/benchassets/bench_average/bench.yml (100%) rename {engine => pkg/engine}/tests/assets/benchassets/bench_big/bench.yml (100%) rename {engine => pkg/engine}/tests/assets/benchassets/templates/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/benchassets/templates/1.gqt (100%) rename {engine => pkg/engine}/tests/assets/benchassets/templates/2.gqt (100%) rename {engine => pkg/engine}/tests/assets/benchassets/templates/3.gqt (100%) rename {engine => pkg/engine}/tests/assets/benchassets/templates/4.gqt (100%) rename {engine => pkg/engine}/tests/assets/benchassets/templates/5.gqt (100%) rename {engine => pkg/engine}/tests/assets/benchassets/templates/6.gqt (100%) rename {engine => pkg/engine}/tests/assets/benchassets/templates/7.gqt (100%) rename {engine => pkg/engine}/tests/assets/benchassets/templates/8.tgqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_arguments_merge_00/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_arguments_merge_00/1.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_arguments_merge_00/2.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_arguments_merge_00/3.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_arguments_merge_00/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_arguments_merge_01/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_arguments_merge_01/1.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_arguments_merge_01/2.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_arguments_merge_01/3.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_arguments_merge_01/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_complex_00/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_complex_00/1.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_complex_00/2.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_complex_00/3.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_complex_00/4.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_complex_00/5.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_complex_00/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_conditions_00/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_conditions_00/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_conditions_01/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_conditions_01/1.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_conditions_01/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_conditions_02/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_conditions_02/1.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_conditions_02/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_conditions_03/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_conditions_03/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_conditions_04/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_conditions_04/1.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_conditions_04/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_enum_00/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_enum_00/1.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_enum_00/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_fragments_00/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_fragments_00/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_fragments_01/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_fragments_01/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_fragments_02/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_fragments_02/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_fragments_03/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_fragments_03/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_00/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_00/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_01/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_01/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_02/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_02/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_03/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_03/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_04/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_04/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_05/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_05/1.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_05/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_06/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_06/1.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_06/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_07/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_07/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_08/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_08/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_09/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_09/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_10/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_10/1.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_10/2.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_10/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_11/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_11/1.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_11/2.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_general_logic_11/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_identical_fields_00/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_identical_fields_00/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_map_00/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_map_00/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_max_00/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_max_00/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_max_02/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_max_02/1.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_max_02/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_max_03/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_max_03/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_max_complex_00/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_max_complex_00/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_negation_00/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_negation_00/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_negation_01/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_negation_01/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_nesting_00/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_nesting_00/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_special_fields_00/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_special_fields_00/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_token_null_00/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_token_null_00/test.yml (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_variables_00/0.gqt (100%) rename {engine => pkg/engine}/tests/assets/testassets/test_variables_00/test.yml (100%) rename {engine => pkg/engine}/tests/benchmark_test.go (96%) rename {engine => pkg/engine}/tests/engine_test.go (98%) rename {gqlparse => pkg/gqlparse}/bench_test.go (97%) rename {gqlparse => pkg/gqlparse}/gqlparse.go (99%) rename {gqlparse => pkg/gqlparse}/gqlparse_test.go (99%) rename {gqlparse => pkg/gqlparse}/internal/graph/bench_test.go (90%) rename {gqlparse => pkg/gqlparse}/internal/graph/graph.go (95%) rename {gqlparse => pkg/gqlparse}/internal/graph/graph_test.go (98%) rename {utilities => pkg}/math/math.go (100%) rename {utilities => pkg}/math/math_test.go (86%) rename {utilities => pkg}/mhstore/benchmark_test.go (92%) rename {utilities => pkg}/mhstore/mhstore.go (96%) rename {utilities => pkg}/mhstore/mhstore_test.go (94%) rename {utilities => pkg}/segmented/segmented.go (97%) rename {utilities => pkg}/segmented/segmented_test.go (98%) rename {server => pkg/server}/api.go (96%) rename {server => pkg/server}/proxy.go (97%) rename {server => pkg/server}/server_test.go (98%) rename {utilities => pkg}/set/bench_test.go (96%) rename {utilities => pkg}/set/set.go (100%) rename {utilities => pkg}/set/set_test.go (97%) rename {utilities => pkg}/stack/stack.go (100%) rename {utilities => pkg}/stack/stack_test.go (97%) rename {statistics => pkg/statistics}/statistics.go (100%) rename {statistics => pkg/statistics}/statistics_test.go (98%) rename {utilities => pkg}/testeq/testeq.go (100%) rename {utilities => pkg}/testeq/testeq_test.go (98%) rename {testsetup => pkg/testsetup}/inputs_schema/all-services/service_a.yaml (100%) rename {testsetup => pkg/testsetup}/inputs_schema/all-templates/service_a/mut_arr_str.gqt (100%) rename {testsetup => pkg/testsetup}/inputs_schema/config.yml (100%) rename {testsetup => pkg/testsetup}/inputs_schema/enabled-services/service_a.yaml (100%) rename {testsetup => pkg/testsetup}/inputs_schema/enabled-templates/service_a/mut_arr_str.gqt (100%) rename {testsetup => pkg/testsetup}/inputs_schema/service_a.graphqls (100%) rename {testsetup => pkg/testsetup}/inputs_schema/test_arr_str_eq.yaml (100%) rename {testsetup => pkg/testsetup}/starwars/config.yml (100%) rename {testsetup => pkg/testsetup}/starwars/services_enabled/starwars.graphqls (100%) rename {testsetup => pkg/testsetup}/starwars/services_enabled/starwars.yml (100%) rename {testsetup => pkg/testsetup}/starwars/services_enabled/starwars/templates_enabled/a.gqt (100%) rename {testsetup => pkg/testsetup}/starwars/services_enabled/starwars/templates_enabled/b.gqt (100%) rename {testsetup => pkg/testsetup}/starwars/services_enabled/starwars/templates_enabled/c.gqt (100%) rename {testsetup => pkg/testsetup}/starwars/services_enabled/starwars/templates_enabled/d.gqt (100%) rename {testsetup => pkg/testsetup}/starwars/services_enabled/starwars/templates_enabled/e.gqt (100%) rename {testsetup => pkg/testsetup}/starwars/services_enabled/starwars/templates_enabled/f.gqt (100%) rename {testsetup => pkg/testsetup}/starwars/services_enabled/starwars/templates_enabled/max_1.gqt (100%) rename {testsetup => pkg/testsetup}/starwars/test_qry2_ok.yaml (100%) rename {testsetup => pkg/testsetup}/starwars/test_qry_ok.yaml (100%) rename {testsetup => pkg/testsetup}/test1/all_services/testservice.yaml (100%) rename {testsetup => pkg/testsetup}/test1/all_templates/template_mut.gqt (100%) rename {testsetup => pkg/testsetup}/test1/all_templates/template_qry.gqt (100%) rename {testsetup => pkg/testsetup}/test1/config.yml (100%) rename {testsetup => pkg/testsetup}/test1/enabled_services/testservice.yaml (100%) rename {testsetup => pkg/testsetup}/test1/enabled_templates/template_mut.gqt (100%) rename {testsetup => pkg/testsetup}/test1/enabled_templates/template_qry.gqt (100%) rename {testsetup => pkg/testsetup}/test1/test_qry_ok.yaml (100%) rename {testsetup => pkg/testsetup}/test1/x_test_mut_ok.yaml (100%) rename {testsetup => pkg/testsetup}/testsetup.go (98%) rename {testsetup => pkg/testsetup}/testsetup_test.go (90%) rename {utilities => pkg}/tokenwriter/bench_test.go (88%) rename {utilities => pkg}/tokenwriter/tokenwriter.go (89%) rename {utilities => pkg}/tokenwriter/tokenwriter_test.go (98%) rename {utilities => pkg}/unsafe/unsafe.go (100%) rename {utilities => pkg}/unsafe/unsafe_test.go (85%) rename {utilities => pkg}/xxhash/bench_test.go (91%) rename {utilities => pkg}/xxhash/xxhash.go (100%) rename {utilities => pkg}/xxhash/xxhash_test.go (96%) diff --git a/.goreleaser.yml b/.goreleaser.yml index 034b551..f81c178 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -24,12 +24,6 @@ archives: name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}" replacements: "386": "i386" - files: - - src: assets/etc/ - info: - owner: root - group: ggproxy - mode: 0664 checksum: name_template: "{{ .ProjectName }}-{{ .Tag }}-checksums.txt" snapshot: diff --git a/assets/etc/ggproxy/all-services/a.yml b/assets/etc/ggproxy/all-services/a.yml deleted file mode 100644 index f7b4d32..0000000 --- a/assets/etc/ggproxy/all-services/a.yml +++ /dev/null @@ -1,12 +0,0 @@ -# Source URL path -path: "/path" - -# Destination URL (where to proxy requests to) -forward-url: "http://localhost:8080/path" - -# false for forwarding the original request, -# true for the reduced version. -forward-reduced: true - -all-templates: ../all-templates/a -enabled-templates: ../enabled-templates/a diff --git a/assets/etc/ggproxy/all-templates/a/example_template.gqt b/assets/etc/ggproxy/all-templates/a/example_template.gqt deleted file mode 100644 index cd16428..0000000 --- a/assets/etc/ggproxy/all-templates/a/example_template.gqt +++ /dev/null @@ -1,20 +0,0 @@ ---- -# The template's display name -name: "Example Template" - -# Arbitrary tags -tags: - - query - - products - - related_products ---- -query { - products(limit: val <= 10, after: any) { - id - name - relatedProducts(type: val = "tea" || val = "juice") { - id - name - } - } -} diff --git a/assets/etc/ggproxy/config.yml b/assets/etc/ggproxy/config.yml deleted file mode 100644 index 60060dc..0000000 --- a/assets/etc/ggproxy/config.yml +++ /dev/null @@ -1,25 +0,0 @@ -proxy: - # Address and port of the proxy server. - host: localhost:8000 - # Optional, enables HTTPS. - #tls: - # Certificate file path. - #cert-file: proxy.cert - # Private key file path. - #key-file: proxy.key - # Optional, in bytes, default: 4MiB. - #max-request-body-size: 1024 - -# Optional, enables API server. -api: - # Address and port of the API server. - host: localhost:3000 - # Optional, enables HTTPS. - #tls: - # Certificate file path. - #cert-file: api.cert - # Private key file path. - #key-file: api.key - -all-services: ./all-services -enabled-services: ./enabled-services diff --git a/assets/etc/ggproxy/enabled-services/a.yml b/assets/etc/ggproxy/enabled-services/a.yml deleted file mode 120000 index 1fcb412..0000000 --- a/assets/etc/ggproxy/enabled-services/a.yml +++ /dev/null @@ -1 +0,0 @@ -../all-services/a.yml \ No newline at end of file diff --git a/assets/etc/ggproxy/enabled-templates/a/example_template.gqt b/assets/etc/ggproxy/enabled-templates/a/example_template.gqt deleted file mode 120000 index 007c8d3..0000000 --- a/assets/etc/ggproxy/enabled-templates/a/example_template.gqt +++ /dev/null @@ -1 +0,0 @@ -../../all-templates/a/example_template.gqt \ No newline at end of file diff --git a/cmd/ggproxy/main.go b/cmd/ggproxy/main.go index 216ce5f..d5c7e55 100644 --- a/cmd/ggproxy/main.go +++ b/cmd/ggproxy/main.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/graph-guard/ggproxy/cli" + "github.com/graph-guard/ggproxy/pkg/cli" ) func main() { diff --git a/cmd/ggproxy/read_config.go b/cmd/ggproxy/read_config.go index 0f0f22d..daa1b7a 100644 --- a/cmd/ggproxy/read_config.go +++ b/cmd/ggproxy/read_config.go @@ -6,7 +6,7 @@ import ( "os" "path/filepath" - "github.com/graph-guard/ggproxy/config" + "github.com/graph-guard/ggproxy/pkg/config" ) func ReadConfig( diff --git a/cmd/ggproxy/reload_unix.go b/cmd/ggproxy/reload_unix.go index 63aa935..3bc0ae2 100644 --- a/cmd/ggproxy/reload_unix.go +++ b/cmd/ggproxy/reload_unix.go @@ -4,7 +4,7 @@ import ( "fmt" "io" - "github.com/graph-guard/ggproxy/cli" + "github.com/graph-guard/ggproxy/pkg/cli" ) func reload(w io.Writer, c cli.CommandReload) { diff --git a/cmd/ggproxy/reload_windows.go b/cmd/ggproxy/reload_windows.go index abd1fdf..e7385ad 100644 --- a/cmd/ggproxy/reload_windows.go +++ b/cmd/ggproxy/reload_windows.go @@ -3,7 +3,7 @@ package main import ( "io" - "github.com/graph-guard/ggproxy/cli" + "github.com/graph-guard/ggproxy/pkg/cli" ) func reload(w io.Writer, c cli.CommandReload) { diff --git a/cmd/ggproxy/serve_unix.go b/cmd/ggproxy/serve_unix.go index dfb93e1..9196dbd 100644 --- a/cmd/ggproxy/serve_unix.go +++ b/cmd/ggproxy/serve_unix.go @@ -5,8 +5,8 @@ import ( "sync" "time" - "github.com/graph-guard/ggproxy/cli" - "github.com/graph-guard/ggproxy/server" + "github.com/graph-guard/ggproxy/pkg/cli" + "github.com/graph-guard/ggproxy/pkg/server" "github.com/phuslu/log" ) diff --git a/cmd/ggproxy/serve_windows.go b/cmd/ggproxy/serve_windows.go index 63ee5a2..6a99db0 100644 --- a/cmd/ggproxy/serve_windows.go +++ b/cmd/ggproxy/serve_windows.go @@ -3,7 +3,7 @@ package main import ( "io" - "github.com/graph-guard/ggproxy/cli" + "github.com/graph-guard/ggproxy/pkg/cli" ) func serve(w io.Writer, c cli.CommandServe) { diff --git a/cmd/ggproxy/stop_unix.go b/cmd/ggproxy/stop_unix.go index f2ff351..8ba793d 100644 --- a/cmd/ggproxy/stop_unix.go +++ b/cmd/ggproxy/stop_unix.go @@ -4,7 +4,7 @@ import ( "fmt" "io" - "github.com/graph-guard/ggproxy/cli" + "github.com/graph-guard/ggproxy/pkg/cli" ) func stop(w io.Writer, c cli.CommandStop) { diff --git a/cmd/ggproxy/stop_windows.go b/cmd/ggproxy/stop_windows.go index 49d2a62..cc5d6df 100644 --- a/cmd/ggproxy/stop_windows.go +++ b/cmd/ggproxy/stop_windows.go @@ -3,7 +3,7 @@ package main import ( "io" - "github.com/graph-guard/ggproxy/cli" + "github.com/graph-guard/ggproxy/pkg/cli" ) func stop(w io.Writer, c cli.CommandStop) { diff --git a/api/gqlgen.yml b/pkg/api/gqlgen.yml similarity index 91% rename from api/gqlgen.yml rename to pkg/api/gqlgen.yml index c1d2533..14b6681 100644 --- a/api/gqlgen.yml +++ b/pkg/api/gqlgen.yml @@ -42,7 +42,7 @@ resolver: # gqlgen will search for any type names in the schema in these go packages # if they match it will use them, otherwise it will generate them. autobind: -# - "github.com/graph-guard/ggproxy/api/graph/model" +# - "github.com/graph-guard/ggproxy/pkg/api/graph/model" # This section declares type mapping between the GraphQL and go type systems # @@ -62,14 +62,14 @@ models: - github.com/99designs/gqlgen/graphql.Int64 - github.com/99designs/gqlgen/graphql.Int32 Service: - model: github.com/graph-guard/ggproxy/api/graph/model.Service + model: github.com/graph-guard/ggproxy/pkg/api/graph/model.Service fields: matchingTemplates: resolver: true statistics: resolver: true Template: - model: github.com/graph-guard/ggproxy/api/graph/model.Template + model: github.com/graph-guard/ggproxy/pkg/api/graph/model.Template fields: service: resolver: true diff --git a/api/graph/generated/generated.go b/pkg/api/graph/generated/generated.go similarity index 98% rename from api/graph/generated/generated.go rename to pkg/api/graph/generated/generated.go index 9a347f0..9862388 100644 --- a/api/graph/generated/generated.go +++ b/pkg/api/graph/generated/generated.go @@ -14,7 +14,7 @@ import ( "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/introspection" - "github.com/graph-guard/ggproxy/api/graph/model" + "github.com/graph-guard/ggproxy/pkg/api/graph/model" gqlparser "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" ) @@ -791,7 +791,7 @@ func (ec *executionContext) _MatchResult_template(ctx context.Context, field gra } res := resTmp.(*model.Template) fc.Result = res - return ec.marshalNTemplate2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐTemplate(ctx, field.Selections, res) + return ec.marshalNTemplate2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐTemplate(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_MatchResult_template(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -1060,7 +1060,7 @@ func (ec *executionContext) _Query_service(ctx context.Context, field graphql.Co } res := resTmp.(*model.Service) fc.Result = res - return ec.marshalOService2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐService(ctx, field.Selections, res) + return ec.marshalOService2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐService(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Query_service(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -1136,7 +1136,7 @@ func (ec *executionContext) _Query_services(ctx context.Context, field graphql.C } res := resTmp.([]*model.Service) fc.Result = res - return ec.marshalNService2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐServiceᚄ(ctx, field.Selections, res) + return ec.marshalNService2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐServiceᚄ(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Query_services(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -1373,7 +1373,7 @@ func (ec *executionContext) _Service_templatesEnabled(ctx context.Context, field } res := resTmp.([]*model.Template) fc.Result = res - return ec.marshalNTemplate2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐTemplateᚄ(ctx, field.Selections, res) + return ec.marshalNTemplate2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐTemplateᚄ(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Service_templatesEnabled(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -1431,7 +1431,7 @@ func (ec *executionContext) _Service_templatesDisabled(ctx context.Context, fiel } res := resTmp.([]*model.Template) fc.Result = res - return ec.marshalNTemplate2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐTemplateᚄ(ctx, field.Selections, res) + return ec.marshalNTemplate2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐTemplateᚄ(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Service_templatesDisabled(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -1665,7 +1665,7 @@ func (ec *executionContext) _Service_matchAll(ctx context.Context, field graphql } res := resTmp.([]*model.MatchResult) fc.Result = res - return ec.marshalNMatchResult2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐMatchResult(ctx, field.Selections, res) + return ec.marshalNMatchResult2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐMatchResult(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Service_matchAll(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -1730,7 +1730,7 @@ func (ec *executionContext) _Service_match(ctx context.Context, field graphql.Co } res := resTmp.(*model.MatchResult) fc.Result = res - return ec.marshalNMatchResult2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐMatchResult(ctx, field.Selections, res) + return ec.marshalNMatchResult2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐMatchResult(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Service_match(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -1795,7 +1795,7 @@ func (ec *executionContext) _Service_statistics(ctx context.Context, field graph } res := resTmp.(*model.ServiceStatistics) fc.Result = res - return ec.marshalNServiceStatistics2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐServiceStatistics(ctx, field.Selections, res) + return ec.marshalNServiceStatistics2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐServiceStatistics(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Service_statistics(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -2341,7 +2341,7 @@ func (ec *executionContext) _Template_statistics(ctx context.Context, field grap } res := resTmp.(*model.TemplateStatistics) fc.Result = res - return ec.marshalNTemplateStatistics2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐTemplateStatistics(ctx, field.Selections, res) + return ec.marshalNTemplateStatistics2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐTemplateStatistics(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Template_statistics(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -2399,7 +2399,7 @@ func (ec *executionContext) _Template_service(ctx context.Context, field graphql } res := resTmp.(*model.Service) fc.Result = res - return ec.marshalNService2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐService(ctx, field.Selections, res) + return ec.marshalNService2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐService(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Template_service(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -5427,11 +5427,11 @@ func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.Selecti return res } -func (ec *executionContext) marshalNMatchResult2githubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐMatchResult(ctx context.Context, sel ast.SelectionSet, v model.MatchResult) graphql.Marshaler { +func (ec *executionContext) marshalNMatchResult2githubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐMatchResult(ctx context.Context, sel ast.SelectionSet, v model.MatchResult) graphql.Marshaler { return ec._MatchResult(ctx, sel, &v) } -func (ec *executionContext) marshalNMatchResult2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐMatchResult(ctx context.Context, sel ast.SelectionSet, v []*model.MatchResult) graphql.Marshaler { +func (ec *executionContext) marshalNMatchResult2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐMatchResult(ctx context.Context, sel ast.SelectionSet, v []*model.MatchResult) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup isLen1 := len(v) == 1 @@ -5455,7 +5455,7 @@ func (ec *executionContext) marshalNMatchResult2ᚕᚖgithubᚗcomᚋgraphᚑgua if !isLen1 { defer wg.Done() } - ret[i] = ec.marshalOMatchResult2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐMatchResult(ctx, sel, v[i]) + ret[i] = ec.marshalOMatchResult2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐMatchResult(ctx, sel, v[i]) } if isLen1 { f(i) @@ -5469,7 +5469,7 @@ func (ec *executionContext) marshalNMatchResult2ᚕᚖgithubᚗcomᚋgraphᚑgua return ret } -func (ec *executionContext) marshalNMatchResult2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐMatchResult(ctx context.Context, sel ast.SelectionSet, v *model.MatchResult) graphql.Marshaler { +func (ec *executionContext) marshalNMatchResult2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐMatchResult(ctx context.Context, sel ast.SelectionSet, v *model.MatchResult) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { ec.Errorf(ctx, "the requested element is null which the schema does not allow") @@ -5479,11 +5479,11 @@ func (ec *executionContext) marshalNMatchResult2ᚖgithubᚗcomᚋgraphᚑguard return ec._MatchResult(ctx, sel, v) } -func (ec *executionContext) marshalNService2githubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐService(ctx context.Context, sel ast.SelectionSet, v model.Service) graphql.Marshaler { +func (ec *executionContext) marshalNService2githubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐService(ctx context.Context, sel ast.SelectionSet, v model.Service) graphql.Marshaler { return ec._Service(ctx, sel, &v) } -func (ec *executionContext) marshalNService2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐServiceᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.Service) graphql.Marshaler { +func (ec *executionContext) marshalNService2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐServiceᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.Service) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup isLen1 := len(v) == 1 @@ -5507,7 +5507,7 @@ func (ec *executionContext) marshalNService2ᚕᚖgithubᚗcomᚋgraphᚑguard if !isLen1 { defer wg.Done() } - ret[i] = ec.marshalNService2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐService(ctx, sel, v[i]) + ret[i] = ec.marshalNService2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐService(ctx, sel, v[i]) } if isLen1 { f(i) @@ -5527,7 +5527,7 @@ func (ec *executionContext) marshalNService2ᚕᚖgithubᚗcomᚋgraphᚑguard return ret } -func (ec *executionContext) marshalNService2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐService(ctx context.Context, sel ast.SelectionSet, v *model.Service) graphql.Marshaler { +func (ec *executionContext) marshalNService2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐService(ctx context.Context, sel ast.SelectionSet, v *model.Service) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { ec.Errorf(ctx, "the requested element is null which the schema does not allow") @@ -5537,11 +5537,11 @@ func (ec *executionContext) marshalNService2ᚖgithubᚗcomᚋgraphᚑguardᚋgg return ec._Service(ctx, sel, v) } -func (ec *executionContext) marshalNServiceStatistics2githubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐServiceStatistics(ctx context.Context, sel ast.SelectionSet, v model.ServiceStatistics) graphql.Marshaler { +func (ec *executionContext) marshalNServiceStatistics2githubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐServiceStatistics(ctx context.Context, sel ast.SelectionSet, v model.ServiceStatistics) graphql.Marshaler { return ec._ServiceStatistics(ctx, sel, &v) } -func (ec *executionContext) marshalNServiceStatistics2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐServiceStatistics(ctx context.Context, sel ast.SelectionSet, v *model.ServiceStatistics) graphql.Marshaler { +func (ec *executionContext) marshalNServiceStatistics2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐServiceStatistics(ctx context.Context, sel ast.SelectionSet, v *model.ServiceStatistics) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { ec.Errorf(ctx, "the requested element is null which the schema does not allow") @@ -5598,7 +5598,7 @@ func (ec *executionContext) marshalNString2ᚕstringᚄ(ctx context.Context, sel return ret } -func (ec *executionContext) marshalNTemplate2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐTemplateᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.Template) graphql.Marshaler { +func (ec *executionContext) marshalNTemplate2ᚕᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐTemplateᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.Template) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup isLen1 := len(v) == 1 @@ -5622,7 +5622,7 @@ func (ec *executionContext) marshalNTemplate2ᚕᚖgithubᚗcomᚋgraphᚑguard if !isLen1 { defer wg.Done() } - ret[i] = ec.marshalNTemplate2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐTemplate(ctx, sel, v[i]) + ret[i] = ec.marshalNTemplate2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐTemplate(ctx, sel, v[i]) } if isLen1 { f(i) @@ -5642,7 +5642,7 @@ func (ec *executionContext) marshalNTemplate2ᚕᚖgithubᚗcomᚋgraphᚑguard return ret } -func (ec *executionContext) marshalNTemplate2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐTemplate(ctx context.Context, sel ast.SelectionSet, v *model.Template) graphql.Marshaler { +func (ec *executionContext) marshalNTemplate2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐTemplate(ctx context.Context, sel ast.SelectionSet, v *model.Template) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { ec.Errorf(ctx, "the requested element is null which the schema does not allow") @@ -5652,11 +5652,11 @@ func (ec *executionContext) marshalNTemplate2ᚖgithubᚗcomᚋgraphᚑguardᚋg return ec._Template(ctx, sel, v) } -func (ec *executionContext) marshalNTemplateStatistics2githubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐTemplateStatistics(ctx context.Context, sel ast.SelectionSet, v model.TemplateStatistics) graphql.Marshaler { +func (ec *executionContext) marshalNTemplateStatistics2githubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐTemplateStatistics(ctx context.Context, sel ast.SelectionSet, v model.TemplateStatistics) graphql.Marshaler { return ec._TemplateStatistics(ctx, sel, &v) } -func (ec *executionContext) marshalNTemplateStatistics2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐTemplateStatistics(ctx context.Context, sel ast.SelectionSet, v *model.TemplateStatistics) graphql.Marshaler { +func (ec *executionContext) marshalNTemplateStatistics2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐTemplateStatistics(ctx context.Context, sel ast.SelectionSet, v *model.TemplateStatistics) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { ec.Errorf(ctx, "the requested element is null which the schema does not allow") @@ -5960,14 +5960,14 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast return res } -func (ec *executionContext) marshalOMatchResult2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐMatchResult(ctx context.Context, sel ast.SelectionSet, v *model.MatchResult) graphql.Marshaler { +func (ec *executionContext) marshalOMatchResult2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐMatchResult(ctx context.Context, sel ast.SelectionSet, v *model.MatchResult) graphql.Marshaler { if v == nil { return graphql.Null } return ec._MatchResult(ctx, sel, v) } -func (ec *executionContext) marshalOService2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋapiᚋgraphᚋmodelᚐService(ctx context.Context, sel ast.SelectionSet, v *model.Service) graphql.Marshaler { +func (ec *executionContext) marshalOService2ᚖgithubᚗcomᚋgraphᚑguardᚋggproxyᚋpkgᚋapiᚋgraphᚋmodelᚐService(ctx context.Context, sel ast.SelectionSet, v *model.Service) graphql.Marshaler { if v == nil { return graphql.Null } diff --git a/api/graph/model/model.go b/pkg/api/graph/model/model.go similarity index 83% rename from api/graph/model/model.go rename to pkg/api/graph/model/model.go index f8b7e7a..e379156 100644 --- a/api/graph/model/model.go +++ b/pkg/api/graph/model/model.go @@ -1,9 +1,9 @@ package model import ( - "github.com/graph-guard/ggproxy/config" - "github.com/graph-guard/ggproxy/engine/playmon" - "github.com/graph-guard/ggproxy/statistics" + "github.com/graph-guard/ggproxy/pkg/config" + "github.com/graph-guard/ggproxy/pkg/engine/playmon" + "github.com/graph-guard/ggproxy/pkg/statistics" ) type Service struct { diff --git a/api/graph/model/models_gen.go b/pkg/api/graph/model/models_gen.go similarity index 100% rename from api/graph/model/models_gen.go rename to pkg/api/graph/model/models_gen.go diff --git a/api/graph/resolver.go b/pkg/api/graph/resolver.go similarity index 83% rename from api/graph/resolver.go rename to pkg/api/graph/resolver.go index 3bd477b..5ad2d77 100644 --- a/api/graph/resolver.go +++ b/pkg/api/graph/resolver.go @@ -3,8 +3,8 @@ package graph import ( "time" - "github.com/graph-guard/ggproxy/api/graph/model" - "github.com/graph-guard/ggproxy/config" + "github.com/graph-guard/ggproxy/pkg/api/graph/model" + "github.com/graph-guard/ggproxy/pkg/config" plog "github.com/phuslu/log" ) diff --git a/api/graph/schema.graphqls b/pkg/api/graph/schema.graphqls similarity index 100% rename from api/graph/schema.graphqls rename to pkg/api/graph/schema.graphqls diff --git a/api/graph/schema.resolvers.go b/pkg/api/graph/schema.resolvers.go similarity index 93% rename from api/graph/schema.resolvers.go rename to pkg/api/graph/schema.resolvers.go index b853d24..46e239e 100644 --- a/api/graph/schema.resolvers.go +++ b/pkg/api/graph/schema.resolvers.go @@ -9,11 +9,11 @@ import ( "context" "time" - "github.com/graph-guard/ggproxy/api/graph/generated" - "github.com/graph-guard/ggproxy/api/graph/model" - "github.com/graph-guard/ggproxy/config" - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/ggproxy/utilities/tokenwriter" + "github.com/graph-guard/ggproxy/pkg/api/graph/generated" + "github.com/graph-guard/ggproxy/pkg/api/graph/model" + "github.com/graph-guard/ggproxy/pkg/config" + "github.com/graph-guard/ggproxy/pkg/gqlparse" + "github.com/graph-guard/ggproxy/pkg/tokenwriter" ) // Uptime is the resolver for the uptime field. @@ -187,8 +187,6 @@ func (r *Resolver) Service() generated.ServiceResolver { return &serviceResolver // Template returns generated.TemplateResolver implementation. func (r *Resolver) Template() generated.TemplateResolver { return &templateResolver{r} } -type ( - queryResolver struct{ *Resolver } - serviceResolver struct{ *Resolver } - templateResolver struct{ *Resolver } -) +type queryResolver struct{ *Resolver } +type serviceResolver struct{ *Resolver } +type templateResolver struct{ *Resolver } diff --git a/api/tools.go b/pkg/api/tools.go similarity index 100% rename from api/tools.go rename to pkg/api/tools.go diff --git a/utilities/aset/aset.go b/pkg/aset/aset.go similarity index 97% rename from utilities/aset/aset.go rename to pkg/aset/aset.go index ef9b584..f98ea20 100644 --- a/utilities/aset/aset.go +++ b/pkg/aset/aset.go @@ -1,6 +1,6 @@ package aset -import "github.com/graph-guard/ggproxy/utilities/math" +import "github.com/graph-guard/ggproxy/pkg/math" type ElementInterface interface { uint8 | uint16 | uint32 | uint64 | int | int8 | int16 | int32 | int64 | float32 | float64 diff --git a/utilities/aset/aset_test.go b/pkg/aset/aset_test.go similarity index 94% rename from utilities/aset/aset_test.go rename to pkg/aset/aset_test.go index b5a9e70..c4480f7 100644 --- a/utilities/aset/aset_test.go +++ b/pkg/aset/aset_test.go @@ -3,8 +3,8 @@ package aset_test import ( "testing" - "github.com/graph-guard/ggproxy/utilities/aset" - "github.com/graph-guard/ggproxy/utilities/math" + "github.com/graph-guard/ggproxy/pkg/aset" + "github.com/graph-guard/ggproxy/pkg/math" "github.com/stretchr/testify/require" ) diff --git a/utilities/aset/benchmark_test.go b/pkg/aset/benchmark_test.go similarity index 96% rename from utilities/aset/benchmark_test.go rename to pkg/aset/benchmark_test.go index 040ba22..ee847c4 100644 --- a/utilities/aset/benchmark_test.go +++ b/pkg/aset/benchmark_test.go @@ -6,7 +6,7 @@ import ( "fmt" "testing" - "github.com/graph-guard/ggproxy/utilities/aset" + "github.com/graph-guard/ggproxy/pkg/aset" ) func BenchmarkAdd(b *testing.B) { diff --git a/utilities/atoi/atoi.go b/pkg/atoi/atoi.go similarity index 95% rename from utilities/atoi/atoi.go rename to pkg/atoi/atoi.go index 2f6d00e..0ac82cf 100644 --- a/utilities/atoi/atoi.go +++ b/pkg/atoi/atoi.go @@ -6,7 +6,7 @@ import ( "fmt" "strconv" - "github.com/graph-guard/ggproxy/utilities/unsafe" + "github.com/graph-guard/ggproxy/pkg/unsafe" ) // MustI32 parses s assuming that it's a valid signed 32-bit integer. diff --git a/utilities/atoi/atoi_test.go b/pkg/atoi/atoi_test.go similarity index 98% rename from utilities/atoi/atoi_test.go rename to pkg/atoi/atoi_test.go index d4df3fa..4b01140 100644 --- a/utilities/atoi/atoi_test.go +++ b/pkg/atoi/atoi_test.go @@ -6,7 +6,7 @@ import ( "strconv" "testing" - "github.com/graph-guard/ggproxy/utilities/atoi" + "github.com/graph-guard/ggproxy/pkg/atoi" "github.com/stretchr/testify/require" ) diff --git a/utilities/bitmask/bench_test.go b/pkg/bitmask/bench_test.go similarity index 100% rename from utilities/bitmask/bench_test.go rename to pkg/bitmask/bench_test.go diff --git a/utilities/bitmask/bitmask.go b/pkg/bitmask/bitmask.go similarity index 100% rename from utilities/bitmask/bitmask.go rename to pkg/bitmask/bitmask.go diff --git a/utilities/bitmask/example_test.go b/pkg/bitmask/example_test.go similarity index 100% rename from utilities/bitmask/example_test.go rename to pkg/bitmask/example_test.go diff --git a/utilities/bitmask/example_union_test.go b/pkg/bitmask/example_union_test.go similarity index 100% rename from utilities/bitmask/example_union_test.go rename to pkg/bitmask/example_union_test.go diff --git a/utilities/bitmask/funcs.go b/pkg/bitmask/funcs.go similarity index 100% rename from utilities/bitmask/funcs.go rename to pkg/bitmask/funcs.go diff --git a/utilities/bitmask/funcs_test.go b/pkg/bitmask/funcs_test.go similarity index 100% rename from utilities/bitmask/funcs_test.go rename to pkg/bitmask/funcs_test.go diff --git a/utilities/bitmask/set_test.go b/pkg/bitmask/set_test.go similarity index 100% rename from utilities/bitmask/set_test.go rename to pkg/bitmask/set_test.go diff --git a/cli/cli.go b/pkg/cli/cli.go similarity index 100% rename from cli/cli.go rename to pkg/cli/cli.go diff --git a/cli/cli_test.go b/pkg/cli/cli_test.go similarity index 98% rename from cli/cli_test.go rename to pkg/cli/cli_test.go index d08ab2f..1e5edf3 100644 --- a/cli/cli_test.go +++ b/pkg/cli/cli_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/graph-guard/ggproxy/cli" + "github.com/graph-guard/ggproxy/pkg/cli" "github.com/stretchr/testify/require" ) diff --git a/config/config.go b/pkg/config/config.go similarity index 99% rename from config/config.go rename to pkg/config/config.go index bd16be6..28211eb 100644 --- a/config/config.go +++ b/pkg/config/config.go @@ -13,7 +13,7 @@ import ( "strings" "github.com/dustin/go-humanize" - "github.com/graph-guard/ggproxy/config/metadata" + "github.com/graph-guard/ggproxy/pkg/config/metadata" gqt "github.com/graph-guard/gqt/v4" gqlparser "github.com/vektah/gqlparser/v2" gqlast "github.com/vektah/gqlparser/v2/ast" diff --git a/config/config_test.go b/pkg/config/config_test.go similarity index 99% rename from config/config_test.go rename to pkg/config/config_test.go index c742e13..3160bcc 100644 --- a/config/config_test.go +++ b/pkg/config/config_test.go @@ -11,8 +11,8 @@ import ( "testing" "testing/fstest" - "github.com/graph-guard/ggproxy/config" - "github.com/graph-guard/ggproxy/config/metadata" + "github.com/graph-guard/ggproxy/pkg/config" + "github.com/graph-guard/ggproxy/pkg/config/metadata" "github.com/graph-guard/gqt/v4" "github.com/stretchr/testify/require" gqlparser "github.com/vektah/gqlparser/v2" diff --git a/config/metadata/metadata.go b/pkg/config/metadata/metadata.go similarity index 100% rename from config/metadata/metadata.go rename to pkg/config/metadata/metadata.go diff --git a/config/metadata/metadata_test.go b/pkg/config/metadata/metadata_test.go similarity index 98% rename from config/metadata/metadata_test.go rename to pkg/config/metadata/metadata_test.go index bc93a03..3c830f5 100644 --- a/config/metadata/metadata_test.go +++ b/pkg/config/metadata/metadata_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/graph-guard/ggproxy/config/metadata" + "github.com/graph-guard/ggproxy/pkg/config/metadata" "github.com/stretchr/testify/require" ) diff --git a/utilities/container/amap/amap.go b/pkg/container/amap/amap.go similarity index 98% rename from utilities/container/amap/amap.go rename to pkg/container/amap/amap.go index 795d201..2e99ec7 100644 --- a/utilities/container/amap/amap.go +++ b/pkg/container/amap/amap.go @@ -1,7 +1,7 @@ package amap import ( - "github.com/graph-guard/ggproxy/utilities/math" + "github.com/graph-guard/ggproxy/pkg/math" ) type KeyInterface interface { diff --git a/utilities/container/amap/amap_test.go b/pkg/container/amap/amap_test.go similarity index 98% rename from utilities/container/amap/amap_test.go rename to pkg/container/amap/amap_test.go index 4c8b534..587465b 100644 --- a/utilities/container/amap/amap_test.go +++ b/pkg/container/amap/amap_test.go @@ -3,7 +3,7 @@ package amap_test import ( "testing" - "github.com/graph-guard/ggproxy/utilities/container/amap" + "github.com/graph-guard/ggproxy/pkg/container/amap" "github.com/stretchr/testify/require" ) diff --git a/utilities/container/benchmark_test.go b/pkg/container/benchmark_test.go similarity index 96% rename from utilities/container/benchmark_test.go rename to pkg/container/benchmark_test.go index 06c7828..342f17e 100644 --- a/utilities/container/benchmark_test.go +++ b/pkg/container/benchmark_test.go @@ -5,7 +5,7 @@ import ( "math/rand" "testing" - "github.com/graph-guard/ggproxy/utilities/container" + "github.com/graph-guard/ggproxy/pkg/container" ) func forEachImplB( @@ -19,8 +19,10 @@ func forEachImplB( } } -var GI int -var GB bool +var ( + GI int + GB bool +) func BenchmarkAdd(b *testing.B) { for _, td := range []int{8, 64, 192, 512, 1024} { diff --git a/utilities/container/container.go b/pkg/container/container.go similarity index 100% rename from utilities/container/container.go rename to pkg/container/container.go diff --git a/utilities/container/container_test.go b/pkg/container/container_test.go similarity index 93% rename from utilities/container/container_test.go rename to pkg/container/container_test.go index 0021aa8..7fbf5b2 100644 --- a/utilities/container/container_test.go +++ b/pkg/container/container_test.go @@ -4,10 +4,10 @@ import ( "strconv" "testing" - "github.com/graph-guard/ggproxy/utilities/container" - "github.com/graph-guard/ggproxy/utilities/container/gomap" - "github.com/graph-guard/ggproxy/utilities/container/hamap" - "github.com/graph-guard/ggproxy/utilities/container/linear" + "github.com/graph-guard/ggproxy/pkg/container" + "github.com/graph-guard/ggproxy/pkg/container/gomap" + "github.com/graph-guard/ggproxy/pkg/container/hamap" + "github.com/graph-guard/ggproxy/pkg/container/linear" "github.com/stretchr/testify/require" ) diff --git a/utilities/container/gomap/gomap.go b/pkg/container/gomap/gomap.go similarity index 100% rename from utilities/container/gomap/gomap.go rename to pkg/container/gomap/gomap.go diff --git a/utilities/container/hamap/hamap.go b/pkg/container/hamap/hamap.go similarity index 98% rename from utilities/container/hamap/hamap.go rename to pkg/container/hamap/hamap.go index 6671b50..6229fcb 100644 --- a/utilities/container/hamap/hamap.go +++ b/pkg/container/hamap/hamap.go @@ -10,7 +10,7 @@ package hamap import ( "github.com/google/go-cmp/cmp" - "github.com/graph-guard/ggproxy/utilities/math" + "github.com/graph-guard/ggproxy/pkg/math" "github.com/zeebo/xxh3" ) @@ -53,8 +53,10 @@ func (h *HasherXXH3[K]) Hash(k K) uint64 { return xxh3.HashSeed([]byte(k), h.Seed) } -var defaultHasherS = &HasherXXH3[string]{} -var defaultHasherB = &HasherXXH3[[]byte]{} +var ( + defaultHasherS = &HasherXXH3[string]{} + defaultHasherB = &HasherXXH3[[]byte]{} +) // New creates a new map instance. func New[K KeyInterface, V any]( diff --git a/utilities/container/hamap/hamap_test.go b/pkg/container/hamap/hamap_test.go similarity index 99% rename from utilities/container/hamap/hamap_test.go rename to pkg/container/hamap/hamap_test.go index d539865..e174403 100644 --- a/utilities/container/hamap/hamap_test.go +++ b/pkg/container/hamap/hamap_test.go @@ -5,7 +5,7 @@ import ( "strconv" "testing" - "github.com/graph-guard/ggproxy/utilities/container/hamap" + "github.com/graph-guard/ggproxy/pkg/container/hamap" "github.com/stretchr/testify/require" ) diff --git a/utilities/container/linear/linear.go b/pkg/container/linear/linear.go similarity index 100% rename from utilities/container/linear/linear.go rename to pkg/container/linear/linear.go diff --git a/utilities/decl/decl.go b/pkg/decl/decl.go similarity index 100% rename from utilities/decl/decl.go rename to pkg/decl/decl.go diff --git a/engine/playmon/bench_test.go b/pkg/engine/playmon/bench_test.go similarity index 74% rename from engine/playmon/bench_test.go rename to pkg/engine/playmon/bench_test.go index 7278069..74acff8 100644 --- a/engine/playmon/bench_test.go +++ b/pkg/engine/playmon/bench_test.go @@ -3,10 +3,10 @@ package playmon_test import ( "testing" - "github.com/graph-guard/ggproxy/config" - "github.com/graph-guard/ggproxy/engine/playmon" - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/ggproxy/testsetup" + "github.com/graph-guard/ggproxy/pkg/config" + "github.com/graph-guard/ggproxy/pkg/engine/playmon" + "github.com/graph-guard/ggproxy/pkg/gqlparse" + "github.com/graph-guard/ggproxy/pkg/testsetup" ) var GS string diff --git a/engine/playmon/internal/constrcheck/bench_test.go b/pkg/engine/playmon/internal/constrcheck/bench_test.go similarity index 97% rename from engine/playmon/internal/constrcheck/bench_test.go rename to pkg/engine/playmon/internal/constrcheck/bench_test.go index 0a803b1..d444e01 100644 --- a/engine/playmon/internal/constrcheck/bench_test.go +++ b/pkg/engine/playmon/internal/constrcheck/bench_test.go @@ -5,8 +5,8 @@ package constrcheck_test // "strings" // "testing" -// "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck" -// "github.com/graph-guard/ggproxy/gqlparse" +// "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/constrcheck" +// "github.com/graph-guard/ggproxy/pkg/gqlparse" // "github.com/graph-guard/gqlscan" // "github.com/graph-guard/gqt/v4" // "github.com/vektah/gqlparser/v2" diff --git a/engine/playmon/internal/constrcheck/constrcheck.go b/pkg/engine/playmon/internal/constrcheck/constrcheck.go similarity index 98% rename from engine/playmon/internal/constrcheck/constrcheck.go rename to pkg/engine/playmon/internal/constrcheck/constrcheck.go index c37540f..7ec74e6 100644 --- a/engine/playmon/internal/constrcheck/constrcheck.go +++ b/pkg/engine/playmon/internal/constrcheck/constrcheck.go @@ -6,13 +6,13 @@ import ( "strconv" "strings" - "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck/internal/union" - "github.com/graph-guard/ggproxy/engine/playmon/internal/countval" - "github.com/graph-guard/ggproxy/engine/playmon/internal/pathscan" - "github.com/graph-guard/ggproxy/engine/playmon/internal/scanval" - "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/ggproxy/utilities/atoi" + "github.com/graph-guard/ggproxy/pkg/atoi" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/constrcheck/internal/union" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/countval" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/pathscan" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/scanval" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/tokenreader" + "github.com/graph-guard/ggproxy/pkg/gqlparse" "github.com/graph-guard/gqlscan" "github.com/graph-guard/gqt/v4" gqlast "github.com/vektah/gqlparser/v2/ast" diff --git a/engine/playmon/internal/constrcheck/constrcheck_test.go b/pkg/engine/playmon/internal/constrcheck/constrcheck_test.go similarity index 94% rename from engine/playmon/internal/constrcheck/constrcheck_test.go rename to pkg/engine/playmon/internal/constrcheck/constrcheck_test.go index 3c3b721..6b34b73 100644 --- a/engine/playmon/internal/constrcheck/constrcheck_test.go +++ b/pkg/engine/playmon/internal/constrcheck/constrcheck_test.go @@ -7,8 +7,8 @@ import ( "strings" "testing" - "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck" - "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck/internal/test" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/constrcheck" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/constrcheck/internal/test" "github.com/graph-guard/gqt/v4" "github.com/stretchr/testify/require" "github.com/vektah/gqlparser/v2" diff --git a/engine/playmon/internal/constrcheck/doc.go b/pkg/engine/playmon/internal/constrcheck/doc.go similarity index 100% rename from engine/playmon/internal/constrcheck/doc.go rename to pkg/engine/playmon/internal/constrcheck/doc.go diff --git a/engine/playmon/internal/constrcheck/internal/test/test.go b/pkg/engine/playmon/internal/constrcheck/internal/test/test.go similarity index 98% rename from engine/playmon/internal/constrcheck/internal/test/test.go rename to pkg/engine/playmon/internal/constrcheck/internal/test/test.go index 747eb22..6777ea6 100644 --- a/engine/playmon/internal/constrcheck/internal/test/test.go +++ b/pkg/engine/playmon/internal/constrcheck/internal/test/test.go @@ -6,7 +6,7 @@ import ( "fmt" "path/filepath" - "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/pkg/gqlparse" "github.com/graph-guard/gqlscan" "gopkg.in/yaml.v3" ) diff --git a/engine/playmon/internal/constrcheck/internal/union/union.go b/pkg/engine/playmon/internal/constrcheck/internal/union/union.go similarity index 98% rename from engine/playmon/internal/constrcheck/internal/union/union.go rename to pkg/engine/playmon/internal/constrcheck/internal/union/union.go index 6153e57..3710092 100644 --- a/engine/playmon/internal/constrcheck/internal/union/union.go +++ b/pkg/engine/playmon/internal/constrcheck/internal/union/union.go @@ -1,9 +1,9 @@ package union import ( - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/ggproxy/utilities/atoi" - "github.com/graph-guard/ggproxy/utilities/unsafe" + "github.com/graph-guard/ggproxy/pkg/atoi" + "github.com/graph-guard/ggproxy/pkg/gqlparse" + "github.com/graph-guard/ggproxy/pkg/unsafe" "github.com/graph-guard/gqlscan" ) diff --git a/engine/playmon/internal/constrcheck/internal/union/union_test.go b/pkg/engine/playmon/internal/constrcheck/internal/union/union_test.go similarity index 97% rename from engine/playmon/internal/constrcheck/internal/union/union_test.go rename to pkg/engine/playmon/internal/constrcheck/internal/union/union_test.go index 098c5ca..2db8656 100644 --- a/engine/playmon/internal/constrcheck/internal/union/union_test.go +++ b/pkg/engine/playmon/internal/constrcheck/internal/union/union_test.go @@ -3,8 +3,8 @@ package union_test import ( "testing" - "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck/internal/union" - "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/constrcheck/internal/union" + "github.com/graph-guard/ggproxy/pkg/gqlparse" "github.com/graph-guard/gqlscan" "github.com/stretchr/testify/require" ) diff --git a/engine/playmon/internal/constrcheck/iswrongtype.go b/pkg/engine/playmon/internal/constrcheck/iswrongtype.go similarity index 97% rename from engine/playmon/internal/constrcheck/iswrongtype.go rename to pkg/engine/playmon/internal/constrcheck/iswrongtype.go index 0ce8e62..63a4a62 100644 --- a/engine/playmon/internal/constrcheck/iswrongtype.go +++ b/pkg/engine/playmon/internal/constrcheck/iswrongtype.go @@ -1,7 +1,7 @@ package constrcheck import ( - "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/tokenreader" "github.com/graph-guard/gqlscan" gqlast "github.com/vektah/gqlparser/v2/ast" ) diff --git a/engine/playmon/internal/constrcheck/iswrongtype_test.go b/pkg/engine/playmon/internal/constrcheck/iswrongtype_test.go similarity index 99% rename from engine/playmon/internal/constrcheck/iswrongtype_test.go rename to pkg/engine/playmon/internal/constrcheck/iswrongtype_test.go index 91dd5b7..df25c34 100644 --- a/engine/playmon/internal/constrcheck/iswrongtype_test.go +++ b/pkg/engine/playmon/internal/constrcheck/iswrongtype_test.go @@ -3,8 +3,8 @@ package constrcheck import ( "testing" - "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" - "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/tokenreader" + "github.com/graph-guard/ggproxy/pkg/gqlparse" "github.com/graph-guard/gqlscan" "github.com/stretchr/testify/require" gqlparser "github.com/vektah/gqlparser/v2" diff --git a/engine/playmon/internal/constrcheck/tests/arithmetic_div.yml b/pkg/engine/playmon/internal/constrcheck/tests/arithmetic_div.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/arithmetic_div.yml rename to pkg/engine/playmon/internal/constrcheck/tests/arithmetic_div.yml diff --git a/engine/playmon/internal/constrcheck/tests/arithmetic_mod.yml b/pkg/engine/playmon/internal/constrcheck/tests/arithmetic_mod.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/arithmetic_mod.yml rename to pkg/engine/playmon/internal/constrcheck/tests/arithmetic_mod.yml diff --git a/engine/playmon/internal/constrcheck/tests/arithmetic_mul.yml b/pkg/engine/playmon/internal/constrcheck/tests/arithmetic_mul.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/arithmetic_mul.yml rename to pkg/engine/playmon/internal/constrcheck/tests/arithmetic_mul.yml diff --git a/engine/playmon/internal/constrcheck/tests/arithmetic_sub.yml b/pkg/engine/playmon/internal/constrcheck/tests/arithmetic_sub.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/arithmetic_sub.yml rename to pkg/engine/playmon/internal/constrcheck/tests/arithmetic_sub.yml diff --git a/engine/playmon/internal/constrcheck/tests/equal_strblock_false.todo.yaml b/pkg/engine/playmon/internal/constrcheck/tests/equal_strblock_false.todo.yaml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/equal_strblock_false.todo.yaml rename to pkg/engine/playmon/internal/constrcheck/tests/equal_strblock_false.todo.yaml diff --git a/engine/playmon/internal/constrcheck/tests/false_arithmetic_add.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_arithmetic_add.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_arithmetic_add.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_arithmetic_add.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_constr_and.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_constr_and.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_constr_and.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_constr_and.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_constr_or.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_constr_or.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_constr_or.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_constr_or.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_equal_arr_int.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_equal_arr_int.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_equal_arr_int.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_equal_arr_int.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_equal_arr_str.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_equal_arr_str.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_equal_arr_str.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_equal_arr_str.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_equal_bool.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_equal_bool.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_equal_bool.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_equal_bool.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_equal_enum.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_equal_enum.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_equal_enum.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_equal_enum.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_equal_float.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_equal_float.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_equal_float.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_equal_float.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_equal_int.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_equal_int.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_equal_int.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_equal_int.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_equal_obj.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_equal_obj.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_equal_obj.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_equal_obj.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_equal_str.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_equal_str.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_equal_str.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_equal_str.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_equal_strblock.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_equal_strblock.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_equal_strblock.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_equal_strblock.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_interface.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_interface.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_interface.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_interface.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_len_array_int.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_len_array_int.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_len_array_int.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_len_array_int.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_len_array_obj.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_len_array_obj.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_len_array_obj.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_len_array_obj.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_len_str_eq.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_len_str_eq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_len_str_eq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_len_str_eq.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_len_str_gr.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_len_str_gr.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_len_str_gr.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_len_str_gr.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_len_str_greq.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_len_str_greq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_len_str_greq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_len_str_greq.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_len_str_leeq.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_len_str_leeq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_len_str_leeq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_len_str_leeq.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_len_str_neq.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_len_str_neq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_len_str_neq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_len_str_neq.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_len_strblock_eq.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_len_strblock_eq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_len_strblock_eq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_len_strblock_eq.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_len_strblock_gr.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_len_strblock_gr.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_len_strblock_gr.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_len_strblock_gr.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_len_strblock_greq.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_len_strblock_greq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_len_strblock_greq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_len_strblock_greq.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_len_strblock_le.yaml b/pkg/engine/playmon/internal/constrcheck/tests/false_len_strblock_le.yaml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_len_strblock_le.yaml rename to pkg/engine/playmon/internal/constrcheck/tests/false_len_strblock_le.yaml diff --git a/engine/playmon/internal/constrcheck/tests/false_len_strblock_le.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_len_strblock_le.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_len_strblock_le.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_len_strblock_le.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_len_strblock_leeq.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_len_strblock_leeq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_len_strblock_leeq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_len_strblock_leeq.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_len_strblock_neq.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_len_strblock_neq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_len_strblock_neq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_len_strblock_neq.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_logical_equal.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_logical_equal.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_logical_equal.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_logical_equal.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_logical_expr_relational.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_logical_expr_relational.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_logical_expr_relational.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_logical_expr_relational.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_logical_expr_relational_float.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_logical_expr_relational_float.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_logical_expr_relational_float.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_logical_expr_relational_float.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_logical_notequal.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_logical_notequal.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_logical_notequal.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_logical_notequal.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_logical_or_eq.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_logical_or_eq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_logical_or_eq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_logical_or_eq.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_logical_or_gr_le.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_logical_or_gr_le.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_logical_or_gr_le.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_logical_or_gr_le.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_map_2d.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_map_2d.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_map_2d.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_map_2d.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_map_array_object.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_map_array_object.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_map_array_object.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_map_array_object.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_map_var_arithmetic.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_map_var_arithmetic.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_map_var_arithmetic.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_map_var_arithmetic.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_not_equal_arr_int.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_not_equal_arr_int.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_not_equal_arr_int.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_not_equal_arr_int.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_not_equal_arr_str.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_not_equal_arr_str.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_not_equal_arr_str.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_not_equal_arr_str.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_not_equal_obj.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_not_equal_obj.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_not_equal_obj.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_not_equal_obj.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_relational_float.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_relational_float.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_relational_float.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_relational_float.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_relational_int.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_relational_int.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_relational_int.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_relational_int.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_union.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_union.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_union.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_union.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_var_bool.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_var_bool.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_var_bool.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_var_bool.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_var_str.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_var_str.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_var_str.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_var_str.yml diff --git a/engine/playmon/internal/constrcheck/tests/false_var_strblock.yml b/pkg/engine/playmon/internal/constrcheck/tests/false_var_strblock.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/false_var_strblock.yml rename to pkg/engine/playmon/internal/constrcheck/tests/false_var_strblock.yml diff --git a/engine/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs.yml b/pkg/engine/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs.yml rename to pkg/engine/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs.yml diff --git a/engine/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs_in_any.yml b/pkg/engine/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs_in_any.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs_in_any.yml rename to pkg/engine/playmon/internal/constrcheck/tests/invalid_obj_duplicate_inputs_in_any.yml diff --git a/engine/playmon/internal/constrcheck/tests/logical_negation.yml b/pkg/engine/playmon/internal/constrcheck/tests/logical_negation.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/logical_negation.yml rename to pkg/engine/playmon/internal/constrcheck/tests/logical_negation.yml diff --git a/engine/playmon/internal/constrcheck/tests/numeric_negation.yml b/pkg/engine/playmon/internal/constrcheck/tests/numeric_negation.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/numeric_negation.yml rename to pkg/engine/playmon/internal/constrcheck/tests/numeric_negation.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_arithmetic_add.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_arithmetic_add.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_arithmetic_add.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_arithmetic_add.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_constr_and.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_constr_and.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_constr_and.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_constr_and.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_constr_or.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_constr_or.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_constr_or.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_constr_or.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_equal_arr_int.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_equal_arr_int.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_equal_arr_int.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_equal_arr_int.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_equal_arr_str.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_equal_arr_str.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_equal_arr_str.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_equal_arr_str.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_equal_bool.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_equal_bool.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_equal_bool.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_equal_bool.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_equal_enum.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_equal_enum.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_equal_enum.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_equal_enum.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_equal_float.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_equal_float.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_equal_float.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_equal_float.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_equal_int.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_equal_int.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_equal_int.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_equal_int.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_equal_obj.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_equal_obj.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_equal_obj.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_equal_obj.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_equal_str.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_equal_str.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_equal_str.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_equal_str.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_equal_strblock.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_equal_strblock.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_equal_strblock.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_equal_strblock.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_interface.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_interface.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_interface.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_interface.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_len_array_int.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_len_array_int.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_len_array_int.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_len_array_int.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_len_array_obj.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_len_array_obj.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_len_array_obj.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_len_array_obj.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_len_str.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_len_str.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_len_str.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_len_str.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_len_str_eq.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_len_str_eq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_len_str_eq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_len_str_eq.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_len_str_gr.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_len_str_gr.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_len_str_gr.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_len_str_gr.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_len_str_greq.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_len_str_greq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_len_str_greq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_len_str_greq.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_len_str_le.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_len_str_le.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_len_str_le.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_len_str_le.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_len_str_leeq.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_len_str_leeq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_len_str_leeq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_len_str_leeq.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_len_str_neq.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_len_str_neq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_len_str_neq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_len_str_neq.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_len_strblock.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_len_strblock.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_len_strblock.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_len_strblock.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_len_strblock_eq.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_len_strblock_eq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_len_strblock_eq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_len_strblock_eq.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_len_strblock_gr.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_len_strblock_gr.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_len_strblock_gr.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_len_strblock_gr.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_len_strblock_greq.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_len_strblock_greq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_len_strblock_greq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_len_strblock_greq.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_len_strblock_le.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_len_strblock_le.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_len_strblock_le.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_len_strblock_le.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_len_strblock_leeq.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_len_strblock_leeq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_len_strblock_leeq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_len_strblock_leeq.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_len_strblock_neq.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_len_strblock_neq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_len_strblock_neq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_len_strblock_neq.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_logical_equal.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_logical_equal.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_logical_equal.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_logical_equal.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_logical_expr_relational.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_logical_expr_relational.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_logical_expr_relational.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_logical_expr_relational.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_logical_expr_relational_float.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_logical_expr_relational_float.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_logical_expr_relational_float.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_logical_expr_relational_float.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_logical_notequal.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_logical_notequal.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_logical_notequal.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_logical_notequal.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_logical_or_eq.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_logical_or_eq.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_logical_or_eq.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_logical_or_eq.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_logical_or_gr_le.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_logical_or_gr_le.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_logical_or_gr_le.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_logical_or_gr_le.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_map_2d.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_map_2d.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_map_2d.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_map_2d.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_map_array_object.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_map_array_object.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_map_array_object.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_map_array_object.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_map_var_arithmetic.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_map_var_arithmetic.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_map_var_arithmetic.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_map_var_arithmetic.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_not_equal_arr_int.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_not_equal_arr_int.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_not_equal_arr_int.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_not_equal_arr_int.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_not_equal_arr_str.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_not_equal_arr_str.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_not_equal_arr_str.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_not_equal_arr_str.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_not_equal_obj.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_not_equal_obj.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_not_equal_obj.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_not_equal_obj.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_relational_float.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_relational_float.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_relational_float.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_relational_float.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_relational_int.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_relational_int.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_relational_int.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_relational_int.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_union.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_union.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_union.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_union.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_var_bool.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_var_bool.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_var_bool.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_var_bool.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_var_str.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_var_str.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_var_str.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_var_str.yml diff --git a/engine/playmon/internal/constrcheck/tests/true_var_strblock.yml b/pkg/engine/playmon/internal/constrcheck/tests/true_var_strblock.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/true_var_strblock.yml rename to pkg/engine/playmon/internal/constrcheck/tests/true_var_strblock.yml diff --git a/engine/playmon/internal/constrcheck/tests/var_equal_nonexistent.yml b/pkg/engine/playmon/internal/constrcheck/tests/var_equal_nonexistent.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/var_equal_nonexistent.yml rename to pkg/engine/playmon/internal/constrcheck/tests/var_equal_nonexistent.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_equal.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_equal.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_equal_arrint.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_arrint.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_equal_arrint.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_arrint.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_equal_bool_f.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_bool_f.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_equal_bool_f.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_bool_f.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_equal_bool_t.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_bool_t.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_equal_bool_t.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_bool_t.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_equal_enum.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_enum.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_equal_enum.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_enum.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_equal_float.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_float.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_equal_float.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_float.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_equal_int.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_int.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_equal_int.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_int.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_equal_missing_field.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_missing_field.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_equal_missing_field.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_missing_field.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_equal_null.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_null.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_equal_null.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_null.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_equal_str.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_str.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_equal_str.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_equal_str.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_false_equal_missing_field.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_false_equal_missing_field.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_false_equal_missing_field.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_false_equal_missing_field.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_false_not_equal_missing_field.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_false_not_equal_missing_field.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_false_not_equal_missing_field.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_false_not_equal_missing_field.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_len_arr_int.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_len_arr_int.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_len_arr_int.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_len_arr_int.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_len_arr_obj.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_len_arr_obj.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_len_arr_obj.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_len_arr_obj.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_len_str.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_len_str.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_len_str.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_len_str.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_map_2d.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_map_2d.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_map_2d.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_map_2d.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_map_obj_too_many_fields.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_map_obj_too_many_fields.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_map_obj_too_many_fields.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_map_obj_too_many_fields.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_not_equal.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_not_equal.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_not_equal.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_not_equal.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_obj_too_many_inputs.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_obj_too_many_inputs.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_obj_too_many_inputs.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_obj_too_many_inputs.yml diff --git a/engine/playmon/internal/constrcheck/tests/wrong_type_relational_float.yml b/pkg/engine/playmon/internal/constrcheck/tests/wrong_type_relational_float.yml similarity index 100% rename from engine/playmon/internal/constrcheck/tests/wrong_type_relational_float.yml rename to pkg/engine/playmon/internal/constrcheck/tests/wrong_type_relational_float.yml diff --git a/engine/playmon/internal/countval/count_values.go b/pkg/engine/playmon/internal/countval/count_values.go similarity index 93% rename from engine/playmon/internal/countval/count_values.go rename to pkg/engine/playmon/internal/countval/count_values.go index 6e4a37c..700fa32 100644 --- a/engine/playmon/internal/countval/count_values.go +++ b/pkg/engine/playmon/internal/countval/count_values.go @@ -1,7 +1,7 @@ package countval import ( - "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/tokenreader" "github.com/graph-guard/gqlscan" ) diff --git a/engine/playmon/internal/countval/count_values_test.go b/pkg/engine/playmon/internal/countval/count_values_test.go similarity index 98% rename from engine/playmon/internal/countval/count_values_test.go rename to pkg/engine/playmon/internal/countval/count_values_test.go index 540c1f8..70e88fb 100644 --- a/engine/playmon/internal/countval/count_values_test.go +++ b/pkg/engine/playmon/internal/countval/count_values_test.go @@ -3,9 +3,9 @@ package countval_test import ( "testing" - "github.com/graph-guard/ggproxy/engine/playmon/internal/countval" - "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" - "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/countval" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/tokenreader" + "github.com/graph-guard/ggproxy/pkg/gqlparse" "github.com/graph-guard/gqlscan" "github.com/stretchr/testify/require" ) diff --git a/engine/playmon/internal/pathmatch/bench_test.go b/pkg/engine/playmon/internal/pathmatch/bench_test.go similarity index 80% rename from engine/playmon/internal/pathmatch/bench_test.go rename to pkg/engine/playmon/internal/pathmatch/bench_test.go index 2dbf66b..55ac226 100644 --- a/engine/playmon/internal/pathmatch/bench_test.go +++ b/pkg/engine/playmon/internal/pathmatch/bench_test.go @@ -3,8 +3,8 @@ package pathmatch_test import ( "testing" - "github.com/graph-guard/ggproxy/config" - "github.com/graph-guard/ggproxy/engine/playmon/internal/pathscan" + "github.com/graph-guard/ggproxy/pkg/config" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/pathscan" ) var GI int diff --git a/engine/playmon/internal/pathmatch/pathmatch.go b/pkg/engine/playmon/internal/pathmatch/pathmatch.go similarity index 93% rename from engine/playmon/internal/pathmatch/pathmatch.go rename to pkg/engine/playmon/internal/pathmatch/pathmatch.go index 7968e15..f610c4e 100644 --- a/engine/playmon/internal/pathmatch/pathmatch.go +++ b/pkg/engine/playmon/internal/pathmatch/pathmatch.go @@ -1,9 +1,9 @@ package pathmatch import ( - "github.com/graph-guard/ggproxy/config" - "github.com/graph-guard/ggproxy/engine/playmon/internal/pathscan" - "github.com/graph-guard/ggproxy/utilities/bitmask" + "github.com/graph-guard/ggproxy/pkg/bitmask" + "github.com/graph-guard/ggproxy/pkg/config" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/pathscan" "github.com/graph-guard/gqt/v4" ) diff --git a/engine/playmon/internal/pathmatch/pathmatch_test.go b/pkg/engine/playmon/internal/pathmatch/pathmatch_test.go similarity index 95% rename from engine/playmon/internal/pathmatch/pathmatch_test.go rename to pkg/engine/playmon/internal/pathmatch/pathmatch_test.go index d393c0e..fef6acd 100644 --- a/engine/playmon/internal/pathmatch/pathmatch_test.go +++ b/pkg/engine/playmon/internal/pathmatch/pathmatch_test.go @@ -4,9 +4,9 @@ import ( "embed" "testing" - "github.com/graph-guard/ggproxy/config" - "github.com/graph-guard/ggproxy/engine/playmon/internal/pathmatch" - "github.com/graph-guard/ggproxy/engine/playmon/internal/pathscan" + "github.com/graph-guard/ggproxy/pkg/config" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/pathmatch" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/pathscan" "github.com/graph-guard/gqt/v4" "github.com/stretchr/testify/require" ) diff --git a/engine/playmon/internal/pathmatch/test_setups/starwars/config.yml b/pkg/engine/playmon/internal/pathmatch/test_setups/starwars/config.yml similarity index 100% rename from engine/playmon/internal/pathmatch/test_setups/starwars/config.yml rename to pkg/engine/playmon/internal/pathmatch/test_setups/starwars/config.yml diff --git a/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars.yml b/pkg/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars.yml similarity index 100% rename from engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars.yml rename to pkg/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars.yml diff --git a/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/a.gqt b/pkg/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/a.gqt similarity index 100% rename from engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/a.gqt rename to pkg/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/a.gqt diff --git a/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/b.gqt b/pkg/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/b.gqt similarity index 100% rename from engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/b.gqt rename to pkg/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/b.gqt diff --git a/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/c.gqt b/pkg/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/c.gqt similarity index 100% rename from engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/c.gqt rename to pkg/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/c.gqt diff --git a/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/d.gqt b/pkg/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/d.gqt similarity index 100% rename from engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/d.gqt rename to pkg/engine/playmon/internal/pathmatch/test_setups/starwars/services_enabled/starwars/templates_enabled/d.gqt diff --git a/engine/playmon/internal/pathscan/pathscan.go b/pkg/engine/playmon/internal/pathscan/pathscan.go similarity index 98% rename from engine/playmon/internal/pathscan/pathscan.go rename to pkg/engine/playmon/internal/pathscan/pathscan.go index faa94e4..3e6d944 100644 --- a/engine/playmon/internal/pathscan/pathscan.go +++ b/pkg/engine/playmon/internal/pathscan/pathscan.go @@ -39,10 +39,10 @@ import ( "fmt" "sort" - "github.com/graph-guard/ggproxy/engine/playmon/internal/travgqt" - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/ggproxy/utilities/stack" - "github.com/graph-guard/ggproxy/utilities/xxhash" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/travgqt" + "github.com/graph-guard/ggproxy/pkg/gqlparse" + "github.com/graph-guard/ggproxy/pkg/stack" + "github.com/graph-guard/ggproxy/pkg/xxhash" gqlscan "github.com/graph-guard/gqlscan" gqt "github.com/graph-guard/gqt/v4" diff --git a/engine/playmon/internal/pathscan/pathscan_test.go b/pkg/engine/playmon/internal/pathscan/pathscan_test.go similarity index 99% rename from engine/playmon/internal/pathscan/pathscan_test.go rename to pkg/engine/playmon/internal/pathscan/pathscan_test.go index 672fc93..dc8700b 100644 --- a/engine/playmon/internal/pathscan/pathscan_test.go +++ b/pkg/engine/playmon/internal/pathscan/pathscan_test.go @@ -4,8 +4,8 @@ import ( "sort" "testing" - "github.com/graph-guard/ggproxy/engine/playmon/internal/pathscan" - "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/pathscan" + "github.com/graph-guard/ggproxy/pkg/gqlparse" "github.com/graph-guard/gqlscan" "github.com/graph-guard/gqt/v4" "github.com/stretchr/testify/require" diff --git a/engine/playmon/internal/scanval/length.go b/pkg/engine/playmon/internal/scanval/length.go similarity index 93% rename from engine/playmon/internal/scanval/length.go rename to pkg/engine/playmon/internal/scanval/length.go index fb885df..d359f4c 100644 --- a/engine/playmon/internal/scanval/length.go +++ b/pkg/engine/playmon/internal/scanval/length.go @@ -1,7 +1,7 @@ package scanval import ( - "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/tokenreader" "github.com/graph-guard/gqlscan" ) diff --git a/engine/playmon/internal/scanval/length_test.go b/pkg/engine/playmon/internal/scanval/length_test.go similarity index 95% rename from engine/playmon/internal/scanval/length_test.go rename to pkg/engine/playmon/internal/scanval/length_test.go index c84d802..1c11906 100644 --- a/engine/playmon/internal/scanval/length_test.go +++ b/pkg/engine/playmon/internal/scanval/length_test.go @@ -3,9 +3,9 @@ package scanval_test import ( "testing" - "github.com/graph-guard/ggproxy/engine/playmon/internal/scanval" - "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" - "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/scanval" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/tokenreader" + "github.com/graph-guard/ggproxy/pkg/gqlparse" "github.com/graph-guard/gqlscan" "github.com/stretchr/testify/require" ) diff --git a/engine/playmon/internal/scanval/scan_values.go b/pkg/engine/playmon/internal/scanval/scan_values.go similarity index 94% rename from engine/playmon/internal/scanval/scan_values.go rename to pkg/engine/playmon/internal/scanval/scan_values.go index 074475f..7b92f1c 100644 --- a/engine/playmon/internal/scanval/scan_values.go +++ b/pkg/engine/playmon/internal/scanval/scan_values.go @@ -1,7 +1,7 @@ package scanval import ( - "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/tokenreader" "github.com/graph-guard/gqlscan" ) diff --git a/engine/playmon/internal/scanval/scan_values_in_arrays.go b/pkg/engine/playmon/internal/scanval/scan_values_in_arrays.go similarity index 95% rename from engine/playmon/internal/scanval/scan_values_in_arrays.go rename to pkg/engine/playmon/internal/scanval/scan_values_in_arrays.go index 1c2cee5..ac4665c 100644 --- a/engine/playmon/internal/scanval/scan_values_in_arrays.go +++ b/pkg/engine/playmon/internal/scanval/scan_values_in_arrays.go @@ -1,7 +1,7 @@ package scanval import ( - "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/tokenreader" "github.com/graph-guard/gqlscan" ) diff --git a/engine/playmon/internal/scanval/scan_values_in_arrays_test.go b/pkg/engine/playmon/internal/scanval/scan_values_in_arrays_test.go similarity index 98% rename from engine/playmon/internal/scanval/scan_values_in_arrays_test.go rename to pkg/engine/playmon/internal/scanval/scan_values_in_arrays_test.go index 8fb6e8f..c717a85 100644 --- a/engine/playmon/internal/scanval/scan_values_in_arrays_test.go +++ b/pkg/engine/playmon/internal/scanval/scan_values_in_arrays_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "github.com/graph-guard/ggproxy/engine/playmon/internal/scanval" - "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" - "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/scanval" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/tokenreader" + "github.com/graph-guard/ggproxy/pkg/gqlparse" "github.com/graph-guard/gqlscan" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/engine/playmon/internal/scanval/scan_values_test.go b/pkg/engine/playmon/internal/scanval/scan_values_test.go similarity index 98% rename from engine/playmon/internal/scanval/scan_values_test.go rename to pkg/engine/playmon/internal/scanval/scan_values_test.go index f0b92d1..0237384 100644 --- a/engine/playmon/internal/scanval/scan_values_test.go +++ b/pkg/engine/playmon/internal/scanval/scan_values_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "github.com/graph-guard/ggproxy/engine/playmon/internal/scanval" - "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" - "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/scanval" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/tokenreader" + "github.com/graph-guard/ggproxy/pkg/gqlparse" "github.com/graph-guard/gqlscan" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/engine/playmon/internal/tokenreader/tokenreader.go b/pkg/engine/playmon/internal/tokenreader/tokenreader.go similarity index 96% rename from engine/playmon/internal/tokenreader/tokenreader.go rename to pkg/engine/playmon/internal/tokenreader/tokenreader.go index 0db1f97..98ff283 100644 --- a/engine/playmon/internal/tokenreader/tokenreader.go +++ b/pkg/engine/playmon/internal/tokenreader/tokenreader.go @@ -3,7 +3,7 @@ package tokenreader import ( - "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/pkg/gqlparse" "github.com/graph-guard/gqlscan" ) diff --git a/engine/playmon/internal/tokenreader/tokenreader_test.go b/pkg/engine/playmon/internal/tokenreader/tokenreader_test.go similarity index 97% rename from engine/playmon/internal/tokenreader/tokenreader_test.go rename to pkg/engine/playmon/internal/tokenreader/tokenreader_test.go index 7b74bec..c840876 100644 --- a/engine/playmon/internal/tokenreader/tokenreader_test.go +++ b/pkg/engine/playmon/internal/tokenreader/tokenreader_test.go @@ -3,8 +3,8 @@ package tokenreader_test import ( "testing" - "github.com/graph-guard/ggproxy/engine/playmon/internal/tokenreader" - "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/tokenreader" + "github.com/graph-guard/ggproxy/pkg/gqlparse" "github.com/graph-guard/gqlscan" "github.com/stretchr/testify/require" ) diff --git a/engine/playmon/internal/travgqt/travgqt.go b/pkg/engine/playmon/internal/travgqt/travgqt.go similarity index 100% rename from engine/playmon/internal/travgqt/travgqt.go rename to pkg/engine/playmon/internal/travgqt/travgqt.go diff --git a/engine/playmon/playmon.go b/pkg/engine/playmon/playmon.go similarity index 92% rename from engine/playmon/playmon.go rename to pkg/engine/playmon/playmon.go index 78281fb..b6bed6d 100644 --- a/engine/playmon/playmon.go +++ b/pkg/engine/playmon/playmon.go @@ -1,11 +1,11 @@ package playmon import ( - "github.com/graph-guard/ggproxy/config" - "github.com/graph-guard/ggproxy/engine/playmon/internal/constrcheck" - "github.com/graph-guard/ggproxy/engine/playmon/internal/pathmatch" - "github.com/graph-guard/ggproxy/engine/playmon/internal/pathscan" - "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/pkg/config" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/constrcheck" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/pathmatch" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/pathscan" + "github.com/graph-guard/ggproxy/pkg/gqlparse" "github.com/graph-guard/gqlscan" "github.com/graph-guard/gqt/v4" ) diff --git a/engine/tests/assets/benchassets/_bench_deep/bench.yml b/pkg/engine/tests/assets/benchassets/_bench_deep/bench.yml similarity index 100% rename from engine/tests/assets/benchassets/_bench_deep/bench.yml rename to pkg/engine/tests/assets/benchassets/_bench_deep/bench.yml diff --git a/engine/tests/assets/benchassets/bench_average/bench.yml b/pkg/engine/tests/assets/benchassets/bench_average/bench.yml similarity index 100% rename from engine/tests/assets/benchassets/bench_average/bench.yml rename to pkg/engine/tests/assets/benchassets/bench_average/bench.yml diff --git a/engine/tests/assets/benchassets/bench_big/bench.yml b/pkg/engine/tests/assets/benchassets/bench_big/bench.yml similarity index 100% rename from engine/tests/assets/benchassets/bench_big/bench.yml rename to pkg/engine/tests/assets/benchassets/bench_big/bench.yml diff --git a/engine/tests/assets/benchassets/templates/0.gqt b/pkg/engine/tests/assets/benchassets/templates/0.gqt similarity index 100% rename from engine/tests/assets/benchassets/templates/0.gqt rename to pkg/engine/tests/assets/benchassets/templates/0.gqt diff --git a/engine/tests/assets/benchassets/templates/1.gqt b/pkg/engine/tests/assets/benchassets/templates/1.gqt similarity index 100% rename from engine/tests/assets/benchassets/templates/1.gqt rename to pkg/engine/tests/assets/benchassets/templates/1.gqt diff --git a/engine/tests/assets/benchassets/templates/2.gqt b/pkg/engine/tests/assets/benchassets/templates/2.gqt similarity index 100% rename from engine/tests/assets/benchassets/templates/2.gqt rename to pkg/engine/tests/assets/benchassets/templates/2.gqt diff --git a/engine/tests/assets/benchassets/templates/3.gqt b/pkg/engine/tests/assets/benchassets/templates/3.gqt similarity index 100% rename from engine/tests/assets/benchassets/templates/3.gqt rename to pkg/engine/tests/assets/benchassets/templates/3.gqt diff --git a/engine/tests/assets/benchassets/templates/4.gqt b/pkg/engine/tests/assets/benchassets/templates/4.gqt similarity index 100% rename from engine/tests/assets/benchassets/templates/4.gqt rename to pkg/engine/tests/assets/benchassets/templates/4.gqt diff --git a/engine/tests/assets/benchassets/templates/5.gqt b/pkg/engine/tests/assets/benchassets/templates/5.gqt similarity index 100% rename from engine/tests/assets/benchassets/templates/5.gqt rename to pkg/engine/tests/assets/benchassets/templates/5.gqt diff --git a/engine/tests/assets/benchassets/templates/6.gqt b/pkg/engine/tests/assets/benchassets/templates/6.gqt similarity index 100% rename from engine/tests/assets/benchassets/templates/6.gqt rename to pkg/engine/tests/assets/benchassets/templates/6.gqt diff --git a/engine/tests/assets/benchassets/templates/7.gqt b/pkg/engine/tests/assets/benchassets/templates/7.gqt similarity index 100% rename from engine/tests/assets/benchassets/templates/7.gqt rename to pkg/engine/tests/assets/benchassets/templates/7.gqt diff --git a/engine/tests/assets/benchassets/templates/8.tgqt b/pkg/engine/tests/assets/benchassets/templates/8.tgqt similarity index 100% rename from engine/tests/assets/benchassets/templates/8.tgqt rename to pkg/engine/tests/assets/benchassets/templates/8.tgqt diff --git a/engine/tests/assets/testassets/test_arguments_merge_00/0.gqt b/pkg/engine/tests/assets/testassets/test_arguments_merge_00/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_arguments_merge_00/0.gqt rename to pkg/engine/tests/assets/testassets/test_arguments_merge_00/0.gqt diff --git a/engine/tests/assets/testassets/test_arguments_merge_00/1.gqt b/pkg/engine/tests/assets/testassets/test_arguments_merge_00/1.gqt similarity index 100% rename from engine/tests/assets/testassets/test_arguments_merge_00/1.gqt rename to pkg/engine/tests/assets/testassets/test_arguments_merge_00/1.gqt diff --git a/engine/tests/assets/testassets/test_arguments_merge_00/2.gqt b/pkg/engine/tests/assets/testassets/test_arguments_merge_00/2.gqt similarity index 100% rename from engine/tests/assets/testassets/test_arguments_merge_00/2.gqt rename to pkg/engine/tests/assets/testassets/test_arguments_merge_00/2.gqt diff --git a/engine/tests/assets/testassets/test_arguments_merge_00/3.gqt b/pkg/engine/tests/assets/testassets/test_arguments_merge_00/3.gqt similarity index 100% rename from engine/tests/assets/testassets/test_arguments_merge_00/3.gqt rename to pkg/engine/tests/assets/testassets/test_arguments_merge_00/3.gqt diff --git a/engine/tests/assets/testassets/test_arguments_merge_00/test.yml b/pkg/engine/tests/assets/testassets/test_arguments_merge_00/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_arguments_merge_00/test.yml rename to pkg/engine/tests/assets/testassets/test_arguments_merge_00/test.yml diff --git a/engine/tests/assets/testassets/test_arguments_merge_01/0.gqt b/pkg/engine/tests/assets/testassets/test_arguments_merge_01/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_arguments_merge_01/0.gqt rename to pkg/engine/tests/assets/testassets/test_arguments_merge_01/0.gqt diff --git a/engine/tests/assets/testassets/test_arguments_merge_01/1.gqt b/pkg/engine/tests/assets/testassets/test_arguments_merge_01/1.gqt similarity index 100% rename from engine/tests/assets/testassets/test_arguments_merge_01/1.gqt rename to pkg/engine/tests/assets/testassets/test_arguments_merge_01/1.gqt diff --git a/engine/tests/assets/testassets/test_arguments_merge_01/2.gqt b/pkg/engine/tests/assets/testassets/test_arguments_merge_01/2.gqt similarity index 100% rename from engine/tests/assets/testassets/test_arguments_merge_01/2.gqt rename to pkg/engine/tests/assets/testassets/test_arguments_merge_01/2.gqt diff --git a/engine/tests/assets/testassets/test_arguments_merge_01/3.gqt b/pkg/engine/tests/assets/testassets/test_arguments_merge_01/3.gqt similarity index 100% rename from engine/tests/assets/testassets/test_arguments_merge_01/3.gqt rename to pkg/engine/tests/assets/testassets/test_arguments_merge_01/3.gqt diff --git a/engine/tests/assets/testassets/test_arguments_merge_01/test.yml b/pkg/engine/tests/assets/testassets/test_arguments_merge_01/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_arguments_merge_01/test.yml rename to pkg/engine/tests/assets/testassets/test_arguments_merge_01/test.yml diff --git a/engine/tests/assets/testassets/test_complex_00/0.gqt b/pkg/engine/tests/assets/testassets/test_complex_00/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_complex_00/0.gqt rename to pkg/engine/tests/assets/testassets/test_complex_00/0.gqt diff --git a/engine/tests/assets/testassets/test_complex_00/1.gqt b/pkg/engine/tests/assets/testassets/test_complex_00/1.gqt similarity index 100% rename from engine/tests/assets/testassets/test_complex_00/1.gqt rename to pkg/engine/tests/assets/testassets/test_complex_00/1.gqt diff --git a/engine/tests/assets/testassets/test_complex_00/2.gqt b/pkg/engine/tests/assets/testassets/test_complex_00/2.gqt similarity index 100% rename from engine/tests/assets/testassets/test_complex_00/2.gqt rename to pkg/engine/tests/assets/testassets/test_complex_00/2.gqt diff --git a/engine/tests/assets/testassets/test_complex_00/3.gqt b/pkg/engine/tests/assets/testassets/test_complex_00/3.gqt similarity index 100% rename from engine/tests/assets/testassets/test_complex_00/3.gqt rename to pkg/engine/tests/assets/testassets/test_complex_00/3.gqt diff --git a/engine/tests/assets/testassets/test_complex_00/4.gqt b/pkg/engine/tests/assets/testassets/test_complex_00/4.gqt similarity index 100% rename from engine/tests/assets/testassets/test_complex_00/4.gqt rename to pkg/engine/tests/assets/testassets/test_complex_00/4.gqt diff --git a/engine/tests/assets/testassets/test_complex_00/5.gqt b/pkg/engine/tests/assets/testassets/test_complex_00/5.gqt similarity index 100% rename from engine/tests/assets/testassets/test_complex_00/5.gqt rename to pkg/engine/tests/assets/testassets/test_complex_00/5.gqt diff --git a/engine/tests/assets/testassets/test_complex_00/test.yml b/pkg/engine/tests/assets/testassets/test_complex_00/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_complex_00/test.yml rename to pkg/engine/tests/assets/testassets/test_complex_00/test.yml diff --git a/engine/tests/assets/testassets/test_conditions_00/0.gqt b/pkg/engine/tests/assets/testassets/test_conditions_00/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_conditions_00/0.gqt rename to pkg/engine/tests/assets/testassets/test_conditions_00/0.gqt diff --git a/engine/tests/assets/testassets/test_conditions_00/test.yml b/pkg/engine/tests/assets/testassets/test_conditions_00/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_conditions_00/test.yml rename to pkg/engine/tests/assets/testassets/test_conditions_00/test.yml diff --git a/engine/tests/assets/testassets/test_conditions_01/0.gqt b/pkg/engine/tests/assets/testassets/test_conditions_01/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_conditions_01/0.gqt rename to pkg/engine/tests/assets/testassets/test_conditions_01/0.gqt diff --git a/engine/tests/assets/testassets/test_conditions_01/1.gqt b/pkg/engine/tests/assets/testassets/test_conditions_01/1.gqt similarity index 100% rename from engine/tests/assets/testassets/test_conditions_01/1.gqt rename to pkg/engine/tests/assets/testassets/test_conditions_01/1.gqt diff --git a/engine/tests/assets/testassets/test_conditions_01/test.yml b/pkg/engine/tests/assets/testassets/test_conditions_01/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_conditions_01/test.yml rename to pkg/engine/tests/assets/testassets/test_conditions_01/test.yml diff --git a/engine/tests/assets/testassets/test_conditions_02/0.gqt b/pkg/engine/tests/assets/testassets/test_conditions_02/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_conditions_02/0.gqt rename to pkg/engine/tests/assets/testassets/test_conditions_02/0.gqt diff --git a/engine/tests/assets/testassets/test_conditions_02/1.gqt b/pkg/engine/tests/assets/testassets/test_conditions_02/1.gqt similarity index 100% rename from engine/tests/assets/testassets/test_conditions_02/1.gqt rename to pkg/engine/tests/assets/testassets/test_conditions_02/1.gqt diff --git a/engine/tests/assets/testassets/test_conditions_02/test.yml b/pkg/engine/tests/assets/testassets/test_conditions_02/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_conditions_02/test.yml rename to pkg/engine/tests/assets/testassets/test_conditions_02/test.yml diff --git a/engine/tests/assets/testassets/test_conditions_03/0.gqt b/pkg/engine/tests/assets/testassets/test_conditions_03/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_conditions_03/0.gqt rename to pkg/engine/tests/assets/testassets/test_conditions_03/0.gqt diff --git a/engine/tests/assets/testassets/test_conditions_03/test.yml b/pkg/engine/tests/assets/testassets/test_conditions_03/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_conditions_03/test.yml rename to pkg/engine/tests/assets/testassets/test_conditions_03/test.yml diff --git a/engine/tests/assets/testassets/test_conditions_04/0.gqt b/pkg/engine/tests/assets/testassets/test_conditions_04/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_conditions_04/0.gqt rename to pkg/engine/tests/assets/testassets/test_conditions_04/0.gqt diff --git a/engine/tests/assets/testassets/test_conditions_04/1.gqt b/pkg/engine/tests/assets/testassets/test_conditions_04/1.gqt similarity index 100% rename from engine/tests/assets/testassets/test_conditions_04/1.gqt rename to pkg/engine/tests/assets/testassets/test_conditions_04/1.gqt diff --git a/engine/tests/assets/testassets/test_conditions_04/test.yml b/pkg/engine/tests/assets/testassets/test_conditions_04/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_conditions_04/test.yml rename to pkg/engine/tests/assets/testassets/test_conditions_04/test.yml diff --git a/engine/tests/assets/testassets/test_enum_00/0.gqt b/pkg/engine/tests/assets/testassets/test_enum_00/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_enum_00/0.gqt rename to pkg/engine/tests/assets/testassets/test_enum_00/0.gqt diff --git a/engine/tests/assets/testassets/test_enum_00/1.gqt b/pkg/engine/tests/assets/testassets/test_enum_00/1.gqt similarity index 100% rename from engine/tests/assets/testassets/test_enum_00/1.gqt rename to pkg/engine/tests/assets/testassets/test_enum_00/1.gqt diff --git a/engine/tests/assets/testassets/test_enum_00/test.yml b/pkg/engine/tests/assets/testassets/test_enum_00/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_enum_00/test.yml rename to pkg/engine/tests/assets/testassets/test_enum_00/test.yml diff --git a/engine/tests/assets/testassets/test_fragments_00/0.gqt b/pkg/engine/tests/assets/testassets/test_fragments_00/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_fragments_00/0.gqt rename to pkg/engine/tests/assets/testassets/test_fragments_00/0.gqt diff --git a/engine/tests/assets/testassets/test_fragments_00/test.yml b/pkg/engine/tests/assets/testassets/test_fragments_00/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_fragments_00/test.yml rename to pkg/engine/tests/assets/testassets/test_fragments_00/test.yml diff --git a/engine/tests/assets/testassets/test_fragments_01/0.gqt b/pkg/engine/tests/assets/testassets/test_fragments_01/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_fragments_01/0.gqt rename to pkg/engine/tests/assets/testassets/test_fragments_01/0.gqt diff --git a/engine/tests/assets/testassets/test_fragments_01/test.yml b/pkg/engine/tests/assets/testassets/test_fragments_01/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_fragments_01/test.yml rename to pkg/engine/tests/assets/testassets/test_fragments_01/test.yml diff --git a/engine/tests/assets/testassets/test_fragments_02/0.gqt b/pkg/engine/tests/assets/testassets/test_fragments_02/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_fragments_02/0.gqt rename to pkg/engine/tests/assets/testassets/test_fragments_02/0.gqt diff --git a/engine/tests/assets/testassets/test_fragments_02/test.yml b/pkg/engine/tests/assets/testassets/test_fragments_02/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_fragments_02/test.yml rename to pkg/engine/tests/assets/testassets/test_fragments_02/test.yml diff --git a/engine/tests/assets/testassets/test_fragments_03/0.gqt b/pkg/engine/tests/assets/testassets/test_fragments_03/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_fragments_03/0.gqt rename to pkg/engine/tests/assets/testassets/test_fragments_03/0.gqt diff --git a/engine/tests/assets/testassets/test_fragments_03/test.yml b/pkg/engine/tests/assets/testassets/test_fragments_03/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_fragments_03/test.yml rename to pkg/engine/tests/assets/testassets/test_fragments_03/test.yml diff --git a/engine/tests/assets/testassets/test_general_logic_00/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_00/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_00/0.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_00/0.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_00/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_00/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_00/test.yml rename to pkg/engine/tests/assets/testassets/test_general_logic_00/test.yml diff --git a/engine/tests/assets/testassets/test_general_logic_01/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_01/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_01/0.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_01/0.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_01/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_01/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_01/test.yml rename to pkg/engine/tests/assets/testassets/test_general_logic_01/test.yml diff --git a/engine/tests/assets/testassets/test_general_logic_02/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_02/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_02/0.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_02/0.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_02/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_02/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_02/test.yml rename to pkg/engine/tests/assets/testassets/test_general_logic_02/test.yml diff --git a/engine/tests/assets/testassets/test_general_logic_03/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_03/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_03/0.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_03/0.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_03/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_03/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_03/test.yml rename to pkg/engine/tests/assets/testassets/test_general_logic_03/test.yml diff --git a/engine/tests/assets/testassets/test_general_logic_04/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_04/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_04/0.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_04/0.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_04/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_04/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_04/test.yml rename to pkg/engine/tests/assets/testassets/test_general_logic_04/test.yml diff --git a/engine/tests/assets/testassets/test_general_logic_05/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_05/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_05/0.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_05/0.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_05/1.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_05/1.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_05/1.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_05/1.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_05/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_05/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_05/test.yml rename to pkg/engine/tests/assets/testassets/test_general_logic_05/test.yml diff --git a/engine/tests/assets/testassets/test_general_logic_06/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_06/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_06/0.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_06/0.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_06/1.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_06/1.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_06/1.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_06/1.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_06/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_06/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_06/test.yml rename to pkg/engine/tests/assets/testassets/test_general_logic_06/test.yml diff --git a/engine/tests/assets/testassets/test_general_logic_07/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_07/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_07/0.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_07/0.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_07/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_07/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_07/test.yml rename to pkg/engine/tests/assets/testassets/test_general_logic_07/test.yml diff --git a/engine/tests/assets/testassets/test_general_logic_08/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_08/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_08/0.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_08/0.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_08/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_08/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_08/test.yml rename to pkg/engine/tests/assets/testassets/test_general_logic_08/test.yml diff --git a/engine/tests/assets/testassets/test_general_logic_09/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_09/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_09/0.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_09/0.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_09/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_09/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_09/test.yml rename to pkg/engine/tests/assets/testassets/test_general_logic_09/test.yml diff --git a/engine/tests/assets/testassets/test_general_logic_10/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_10/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_10/0.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_10/0.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_10/1.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_10/1.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_10/1.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_10/1.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_10/2.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_10/2.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_10/2.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_10/2.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_10/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_10/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_10/test.yml rename to pkg/engine/tests/assets/testassets/test_general_logic_10/test.yml diff --git a/engine/tests/assets/testassets/test_general_logic_11/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_11/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_11/0.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_11/0.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_11/1.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_11/1.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_11/1.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_11/1.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_11/2.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_11/2.gqt similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_11/2.gqt rename to pkg/engine/tests/assets/testassets/test_general_logic_11/2.gqt diff --git a/engine/tests/assets/testassets/test_general_logic_11/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_11/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_general_logic_11/test.yml rename to pkg/engine/tests/assets/testassets/test_general_logic_11/test.yml diff --git a/engine/tests/assets/testassets/test_identical_fields_00/0.gqt b/pkg/engine/tests/assets/testassets/test_identical_fields_00/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_identical_fields_00/0.gqt rename to pkg/engine/tests/assets/testassets/test_identical_fields_00/0.gqt diff --git a/engine/tests/assets/testassets/test_identical_fields_00/test.yml b/pkg/engine/tests/assets/testassets/test_identical_fields_00/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_identical_fields_00/test.yml rename to pkg/engine/tests/assets/testassets/test_identical_fields_00/test.yml diff --git a/engine/tests/assets/testassets/test_map_00/0.gqt b/pkg/engine/tests/assets/testassets/test_map_00/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_map_00/0.gqt rename to pkg/engine/tests/assets/testassets/test_map_00/0.gqt diff --git a/engine/tests/assets/testassets/test_map_00/test.yml b/pkg/engine/tests/assets/testassets/test_map_00/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_map_00/test.yml rename to pkg/engine/tests/assets/testassets/test_map_00/test.yml diff --git a/engine/tests/assets/testassets/test_max_00/0.gqt b/pkg/engine/tests/assets/testassets/test_max_00/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_max_00/0.gqt rename to pkg/engine/tests/assets/testassets/test_max_00/0.gqt diff --git a/engine/tests/assets/testassets/test_max_00/test.yml b/pkg/engine/tests/assets/testassets/test_max_00/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_max_00/test.yml rename to pkg/engine/tests/assets/testassets/test_max_00/test.yml diff --git a/engine/tests/assets/testassets/test_max_02/0.gqt b/pkg/engine/tests/assets/testassets/test_max_02/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_max_02/0.gqt rename to pkg/engine/tests/assets/testassets/test_max_02/0.gqt diff --git a/engine/tests/assets/testassets/test_max_02/1.gqt b/pkg/engine/tests/assets/testassets/test_max_02/1.gqt similarity index 100% rename from engine/tests/assets/testassets/test_max_02/1.gqt rename to pkg/engine/tests/assets/testassets/test_max_02/1.gqt diff --git a/engine/tests/assets/testassets/test_max_02/test.yml b/pkg/engine/tests/assets/testassets/test_max_02/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_max_02/test.yml rename to pkg/engine/tests/assets/testassets/test_max_02/test.yml diff --git a/engine/tests/assets/testassets/test_max_03/0.gqt b/pkg/engine/tests/assets/testassets/test_max_03/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_max_03/0.gqt rename to pkg/engine/tests/assets/testassets/test_max_03/0.gqt diff --git a/engine/tests/assets/testassets/test_max_03/test.yml b/pkg/engine/tests/assets/testassets/test_max_03/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_max_03/test.yml rename to pkg/engine/tests/assets/testassets/test_max_03/test.yml diff --git a/engine/tests/assets/testassets/test_max_complex_00/0.gqt b/pkg/engine/tests/assets/testassets/test_max_complex_00/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_max_complex_00/0.gqt rename to pkg/engine/tests/assets/testassets/test_max_complex_00/0.gqt diff --git a/engine/tests/assets/testassets/test_max_complex_00/test.yml b/pkg/engine/tests/assets/testassets/test_max_complex_00/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_max_complex_00/test.yml rename to pkg/engine/tests/assets/testassets/test_max_complex_00/test.yml diff --git a/engine/tests/assets/testassets/test_negation_00/0.gqt b/pkg/engine/tests/assets/testassets/test_negation_00/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_negation_00/0.gqt rename to pkg/engine/tests/assets/testassets/test_negation_00/0.gqt diff --git a/engine/tests/assets/testassets/test_negation_00/test.yml b/pkg/engine/tests/assets/testassets/test_negation_00/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_negation_00/test.yml rename to pkg/engine/tests/assets/testassets/test_negation_00/test.yml diff --git a/engine/tests/assets/testassets/test_negation_01/0.gqt b/pkg/engine/tests/assets/testassets/test_negation_01/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_negation_01/0.gqt rename to pkg/engine/tests/assets/testassets/test_negation_01/0.gqt diff --git a/engine/tests/assets/testassets/test_negation_01/test.yml b/pkg/engine/tests/assets/testassets/test_negation_01/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_negation_01/test.yml rename to pkg/engine/tests/assets/testassets/test_negation_01/test.yml diff --git a/engine/tests/assets/testassets/test_nesting_00/0.gqt b/pkg/engine/tests/assets/testassets/test_nesting_00/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_nesting_00/0.gqt rename to pkg/engine/tests/assets/testassets/test_nesting_00/0.gqt diff --git a/engine/tests/assets/testassets/test_nesting_00/test.yml b/pkg/engine/tests/assets/testassets/test_nesting_00/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_nesting_00/test.yml rename to pkg/engine/tests/assets/testassets/test_nesting_00/test.yml diff --git a/engine/tests/assets/testassets/test_special_fields_00/0.gqt b/pkg/engine/tests/assets/testassets/test_special_fields_00/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_special_fields_00/0.gqt rename to pkg/engine/tests/assets/testassets/test_special_fields_00/0.gqt diff --git a/engine/tests/assets/testassets/test_special_fields_00/test.yml b/pkg/engine/tests/assets/testassets/test_special_fields_00/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_special_fields_00/test.yml rename to pkg/engine/tests/assets/testassets/test_special_fields_00/test.yml diff --git a/engine/tests/assets/testassets/test_token_null_00/0.gqt b/pkg/engine/tests/assets/testassets/test_token_null_00/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_token_null_00/0.gqt rename to pkg/engine/tests/assets/testassets/test_token_null_00/0.gqt diff --git a/engine/tests/assets/testassets/test_token_null_00/test.yml b/pkg/engine/tests/assets/testassets/test_token_null_00/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_token_null_00/test.yml rename to pkg/engine/tests/assets/testassets/test_token_null_00/test.yml diff --git a/engine/tests/assets/testassets/test_variables_00/0.gqt b/pkg/engine/tests/assets/testassets/test_variables_00/0.gqt similarity index 100% rename from engine/tests/assets/testassets/test_variables_00/0.gqt rename to pkg/engine/tests/assets/testassets/test_variables_00/0.gqt diff --git a/engine/tests/assets/testassets/test_variables_00/test.yml b/pkg/engine/tests/assets/testassets/test_variables_00/test.yml similarity index 100% rename from engine/tests/assets/testassets/test_variables_00/test.yml rename to pkg/engine/tests/assets/testassets/test_variables_00/test.yml diff --git a/engine/tests/benchmark_test.go b/pkg/engine/tests/benchmark_test.go similarity index 96% rename from engine/tests/benchmark_test.go rename to pkg/engine/tests/benchmark_test.go index cab2c07..d866c01 100644 --- a/engine/tests/benchmark_test.go +++ b/pkg/engine/tests/benchmark_test.go @@ -9,7 +9,7 @@ package engine_test // "testing" // "github.com/graph-guard/ggproxy/engines/rmap" -// "github.com/graph-guard/ggproxy/gqlparse" +// "github.com/graph-guard/ggproxy/pkg/gqlparse" // "github.com/graph-guard/gqt" // ) diff --git a/engine/tests/engine_test.go b/pkg/engine/tests/engine_test.go similarity index 98% rename from engine/tests/engine_test.go rename to pkg/engine/tests/engine_test.go index 04b034c..53e6541 100644 --- a/engine/tests/engine_test.go +++ b/pkg/engine/tests/engine_test.go @@ -13,11 +13,11 @@ package engine_test // "strings" // "testing" -// "github.com/graph-guard/ggproxy/config" -// "github.com/graph-guard/ggproxy/config/metadata" -// "github.com/graph-guard/ggproxy/gqlparse" -// "github.com/graph-guard/ggproxy/utilities/container/hamap" -// "github.com/graph-guard/ggproxy/utilities/xxhash" +// "github.com/graph-guard/ggproxy/pkg/config" +// "github.com/graph-guard/ggproxy/pkg/config/metadata" +// "github.com/graph-guard/ggproxy/pkg/gqlparse" +// "github.com/graph-guard/ggproxy/pkg/container/hamap" +// "github.com/graph-guard/ggproxy/pkg/xxhash" // "github.com/graph-guard/gqt" // "github.com/stretchr/testify/require" // "gopkg.in/yaml.v3" diff --git a/gqlparse/bench_test.go b/pkg/gqlparse/bench_test.go similarity index 97% rename from gqlparse/bench_test.go rename to pkg/gqlparse/bench_test.go index b991f0c..7916e6b 100644 --- a/gqlparse/bench_test.go +++ b/pkg/gqlparse/bench_test.go @@ -3,7 +3,7 @@ package gqlparse_test import ( "testing" - "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/pkg/gqlparse" "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" ) diff --git a/gqlparse/gqlparse.go b/pkg/gqlparse/gqlparse.go similarity index 99% rename from gqlparse/gqlparse.go rename to pkg/gqlparse/gqlparse.go index 308ce3d..cf8c02f 100644 --- a/gqlparse/gqlparse.go +++ b/pkg/gqlparse/gqlparse.go @@ -8,11 +8,11 @@ import ( "io" "strings" - "github.com/graph-guard/ggproxy/gqlparse/internal/graph" - "github.com/graph-guard/ggproxy/utilities/container/hamap" - "github.com/graph-guard/ggproxy/utilities/segmented" - "github.com/graph-guard/ggproxy/utilities/stack" - "github.com/graph-guard/ggproxy/utilities/unsafe" + "github.com/graph-guard/ggproxy/pkg/container/hamap" + "github.com/graph-guard/ggproxy/pkg/gqlparse/internal/graph" + "github.com/graph-guard/ggproxy/pkg/segmented" + "github.com/graph-guard/ggproxy/pkg/stack" + "github.com/graph-guard/ggproxy/pkg/unsafe" "github.com/graph-guard/gqlscan" "github.com/tidwall/gjson" "github.com/vektah/gqlparser/v2/ast" diff --git a/gqlparse/gqlparse_test.go b/pkg/gqlparse/gqlparse_test.go similarity index 99% rename from gqlparse/gqlparse_test.go rename to pkg/gqlparse/gqlparse_test.go index 76bd0e2..153f6ee 100644 --- a/gqlparse/gqlparse_test.go +++ b/pkg/gqlparse/gqlparse_test.go @@ -6,9 +6,9 @@ import ( "strings" "testing" - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/ggproxy/utilities/decl" - "github.com/graph-guard/ggproxy/utilities/testeq" + "github.com/graph-guard/ggproxy/pkg/decl" + "github.com/graph-guard/ggproxy/pkg/gqlparse" + "github.com/graph-guard/ggproxy/pkg/testeq" "github.com/graph-guard/gqlscan" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/gqlparse/internal/graph/bench_test.go b/pkg/gqlparse/internal/graph/bench_test.go similarity index 90% rename from gqlparse/internal/graph/bench_test.go rename to pkg/gqlparse/internal/graph/bench_test.go index aa86f7d..59a40f6 100644 --- a/gqlparse/internal/graph/bench_test.go +++ b/pkg/gqlparse/internal/graph/bench_test.go @@ -3,11 +3,13 @@ package graph_test import ( "testing" - "github.com/graph-guard/ggproxy/gqlparse/internal/graph" + "github.com/graph-guard/ggproxy/pkg/gqlparse/internal/graph" ) -var GL bool -var GB []byte +var ( + GL bool + GB []byte +) func BenchmarkIsCyclic(b *testing.B) { for _, td := range testdataCyclic { diff --git a/gqlparse/internal/graph/graph.go b/pkg/gqlparse/internal/graph/graph.go similarity index 95% rename from gqlparse/internal/graph/graph.go rename to pkg/gqlparse/internal/graph/graph.go index 12dd495..496d5dd 100644 --- a/gqlparse/internal/graph/graph.go +++ b/pkg/gqlparse/internal/graph/graph.go @@ -1,8 +1,8 @@ package graph import ( - "github.com/graph-guard/ggproxy/utilities/container/hamap" - "github.com/graph-guard/ggproxy/utilities/stack" + "github.com/graph-guard/ggproxy/pkg/container/hamap" + "github.com/graph-guard/ggproxy/pkg/stack" ) const MaxFragments = 128 diff --git a/gqlparse/internal/graph/graph_test.go b/pkg/gqlparse/internal/graph/graph_test.go similarity index 98% rename from gqlparse/internal/graph/graph_test.go rename to pkg/gqlparse/internal/graph/graph_test.go index eed2722..7f06557 100644 --- a/gqlparse/internal/graph/graph_test.go +++ b/pkg/gqlparse/internal/graph/graph_test.go @@ -4,8 +4,8 @@ import ( "strconv" "testing" - "github.com/graph-guard/ggproxy/gqlparse/internal/graph" - "github.com/graph-guard/ggproxy/utilities/decl" + "github.com/graph-guard/ggproxy/pkg/decl" + "github.com/graph-guard/ggproxy/pkg/gqlparse/internal/graph" "github.com/stretchr/testify/require" ) diff --git a/utilities/math/math.go b/pkg/math/math.go similarity index 100% rename from utilities/math/math.go rename to pkg/math/math.go diff --git a/utilities/math/math_test.go b/pkg/math/math_test.go similarity index 86% rename from utilities/math/math_test.go rename to pkg/math/math_test.go index 834e5ef..0c68ad4 100644 --- a/utilities/math/math_test.go +++ b/pkg/math/math_test.go @@ -3,7 +3,7 @@ package math_test import ( "testing" - "github.com/graph-guard/ggproxy/utilities/math" + "github.com/graph-guard/ggproxy/pkg/math" "github.com/stretchr/testify/require" ) diff --git a/utilities/mhstore/benchmark_test.go b/pkg/mhstore/benchmark_test.go similarity index 92% rename from utilities/mhstore/benchmark_test.go rename to pkg/mhstore/benchmark_test.go index a67eebb..123bba7 100644 --- a/utilities/mhstore/benchmark_test.go +++ b/pkg/mhstore/benchmark_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/graph-guard/ggproxy/utilities/mhstore" + "github.com/graph-guard/ggproxy/pkg/mhstore" ) func BenchmarkAdd(b *testing.B) { diff --git a/utilities/mhstore/mhstore.go b/pkg/mhstore/mhstore.go similarity index 96% rename from utilities/mhstore/mhstore.go rename to pkg/mhstore/mhstore.go index b7c81f4..b2869f3 100644 --- a/utilities/mhstore/mhstore.go +++ b/pkg/mhstore/mhstore.go @@ -1,6 +1,6 @@ package mhstore -import "github.com/graph-guard/ggproxy/utilities/aset" +import "github.com/graph-guard/ggproxy/pkg/aset" // MHStore stands for Mask-Hash-Store. // Storage for faster calculation of matching hashes per mask. diff --git a/utilities/mhstore/mhstore_test.go b/pkg/mhstore/mhstore_test.go similarity index 94% rename from utilities/mhstore/mhstore_test.go rename to pkg/mhstore/mhstore_test.go index a399fce..d16b119 100644 --- a/utilities/mhstore/mhstore_test.go +++ b/pkg/mhstore/mhstore_test.go @@ -5,7 +5,7 @@ import ( "math/rand" "testing" - "github.com/graph-guard/ggproxy/utilities/mhstore" + "github.com/graph-guard/ggproxy/pkg/mhstore" "github.com/stretchr/testify/require" ) diff --git a/utilities/segmented/segmented.go b/pkg/segmented/segmented.go similarity index 97% rename from utilities/segmented/segmented.go rename to pkg/segmented/segmented.go index 2d0eeef..f8b951a 100644 --- a/utilities/segmented/segmented.go +++ b/pkg/segmented/segmented.go @@ -2,7 +2,7 @@ package segmented import ( - "github.com/graph-guard/ggproxy/utilities/container/hamap" + "github.com/graph-guard/ggproxy/pkg/container/hamap" ) // Segment defines the logical index and diff --git a/utilities/segmented/segmented_test.go b/pkg/segmented/segmented_test.go similarity index 98% rename from utilities/segmented/segmented_test.go rename to pkg/segmented/segmented_test.go index 600aaeb..68c6ba4 100644 --- a/utilities/segmented/segmented_test.go +++ b/pkg/segmented/segmented_test.go @@ -3,7 +3,7 @@ package segmented_test import ( "testing" - "github.com/graph-guard/ggproxy/utilities/segmented" + "github.com/graph-guard/ggproxy/pkg/segmented" "github.com/stretchr/testify/require" ) diff --git a/server/api.go b/pkg/server/api.go similarity index 96% rename from server/api.go rename to pkg/server/api.go index f1f0837..41b6225 100644 --- a/server/api.go +++ b/pkg/server/api.go @@ -15,11 +15,11 @@ import ( "github.com/99designs/gqlgen/graphql/handler/extension" "github.com/99designs/gqlgen/graphql/handler/lru" "github.com/99designs/gqlgen/graphql/handler/transport" - "github.com/graph-guard/ggproxy/api/graph" - "github.com/graph-guard/ggproxy/api/graph/generated" - "github.com/graph-guard/ggproxy/api/graph/model" - "github.com/graph-guard/ggproxy/config" - "github.com/graph-guard/ggproxy/engine/playmon" + "github.com/graph-guard/ggproxy/pkg/api/graph" + "github.com/graph-guard/ggproxy/pkg/api/graph/generated" + "github.com/graph-guard/ggproxy/pkg/api/graph/model" + "github.com/graph-guard/ggproxy/pkg/config" + "github.com/graph-guard/ggproxy/pkg/engine/playmon" gqt "github.com/graph-guard/gqt/v4" plog "github.com/phuslu/log" "github.com/valyala/fasthttp" diff --git a/server/proxy.go b/pkg/server/proxy.go similarity index 97% rename from server/proxy.go rename to pkg/server/proxy.go index 65db17a..4220a32 100644 --- a/server/proxy.go +++ b/pkg/server/proxy.go @@ -6,11 +6,11 @@ import ( "sync" "time" - "github.com/graph-guard/ggproxy/config" - "github.com/graph-guard/ggproxy/engine/playmon" - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/ggproxy/statistics" - "github.com/graph-guard/ggproxy/utilities/tokenwriter" + "github.com/graph-guard/ggproxy/pkg/config" + "github.com/graph-guard/ggproxy/pkg/engine/playmon" + "github.com/graph-guard/ggproxy/pkg/gqlparse" + "github.com/graph-guard/ggproxy/pkg/statistics" + "github.com/graph-guard/ggproxy/pkg/tokenwriter" gqt "github.com/graph-guard/gqt/v4" plog "github.com/phuslu/log" "github.com/tidwall/gjson" diff --git a/server/server_test.go b/pkg/server/server_test.go similarity index 98% rename from server/server_test.go rename to pkg/server/server_test.go index 3349a82..87340a3 100644 --- a/server/server_test.go +++ b/pkg/server/server_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - "github.com/graph-guard/ggproxy/server" - "github.com/graph-guard/ggproxy/testsetup" + "github.com/graph-guard/ggproxy/pkg/server" + "github.com/graph-guard/ggproxy/pkg/testsetup" plog "github.com/phuslu/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/utilities/set/bench_test.go b/pkg/set/bench_test.go similarity index 96% rename from utilities/set/bench_test.go rename to pkg/set/bench_test.go index 17f46d4..6a97dee 100644 --- a/utilities/set/bench_test.go +++ b/pkg/set/bench_test.go @@ -6,7 +6,7 @@ import ( "math/rand" "testing" - "github.com/graph-guard/ggproxy/utilities/set" + "github.com/graph-guard/ggproxy/pkg/set" ) var GB bool diff --git a/utilities/set/set.go b/pkg/set/set.go similarity index 100% rename from utilities/set/set.go rename to pkg/set/set.go diff --git a/utilities/set/set_test.go b/pkg/set/set_test.go similarity index 97% rename from utilities/set/set_test.go rename to pkg/set/set_test.go index cd2fa62..e275639 100644 --- a/utilities/set/set_test.go +++ b/pkg/set/set_test.go @@ -3,7 +3,7 @@ package set_test import ( "testing" - "github.com/graph-guard/ggproxy/utilities/set" + "github.com/graph-guard/ggproxy/pkg/set" "github.com/stretchr/testify/require" ) diff --git a/utilities/stack/stack.go b/pkg/stack/stack.go similarity index 100% rename from utilities/stack/stack.go rename to pkg/stack/stack.go diff --git a/utilities/stack/stack_test.go b/pkg/stack/stack_test.go similarity index 97% rename from utilities/stack/stack_test.go rename to pkg/stack/stack_test.go index 3831e38..604ae7d 100644 --- a/utilities/stack/stack_test.go +++ b/pkg/stack/stack_test.go @@ -3,7 +3,7 @@ package stack_test import ( "testing" - "github.com/graph-guard/ggproxy/utilities/stack" + "github.com/graph-guard/ggproxy/pkg/stack" "github.com/stretchr/testify/require" ) diff --git a/statistics/statistics.go b/pkg/statistics/statistics.go similarity index 100% rename from statistics/statistics.go rename to pkg/statistics/statistics.go diff --git a/statistics/statistics_test.go b/pkg/statistics/statistics_test.go similarity index 98% rename from statistics/statistics_test.go rename to pkg/statistics/statistics_test.go index 67c927e..5aa6f53 100644 --- a/statistics/statistics_test.go +++ b/pkg/statistics/statistics_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/graph-guard/ggproxy/statistics" + "github.com/graph-guard/ggproxy/pkg/statistics" "github.com/stretchr/testify/require" ) diff --git a/utilities/testeq/testeq.go b/pkg/testeq/testeq.go similarity index 100% rename from utilities/testeq/testeq.go rename to pkg/testeq/testeq.go diff --git a/utilities/testeq/testeq_test.go b/pkg/testeq/testeq_test.go similarity index 98% rename from utilities/testeq/testeq_test.go rename to pkg/testeq/testeq_test.go index 1ea0c30..872c5e8 100644 --- a/utilities/testeq/testeq_test.go +++ b/pkg/testeq/testeq_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/graph-guard/ggproxy/utilities/testeq" + "github.com/graph-guard/ggproxy/pkg/testeq" "github.com/stretchr/testify/require" ) diff --git a/testsetup/inputs_schema/all-services/service_a.yaml b/pkg/testsetup/inputs_schema/all-services/service_a.yaml similarity index 100% rename from testsetup/inputs_schema/all-services/service_a.yaml rename to pkg/testsetup/inputs_schema/all-services/service_a.yaml diff --git a/testsetup/inputs_schema/all-templates/service_a/mut_arr_str.gqt b/pkg/testsetup/inputs_schema/all-templates/service_a/mut_arr_str.gqt similarity index 100% rename from testsetup/inputs_schema/all-templates/service_a/mut_arr_str.gqt rename to pkg/testsetup/inputs_schema/all-templates/service_a/mut_arr_str.gqt diff --git a/testsetup/inputs_schema/config.yml b/pkg/testsetup/inputs_schema/config.yml similarity index 100% rename from testsetup/inputs_schema/config.yml rename to pkg/testsetup/inputs_schema/config.yml diff --git a/testsetup/inputs_schema/enabled-services/service_a.yaml b/pkg/testsetup/inputs_schema/enabled-services/service_a.yaml similarity index 100% rename from testsetup/inputs_schema/enabled-services/service_a.yaml rename to pkg/testsetup/inputs_schema/enabled-services/service_a.yaml diff --git a/testsetup/inputs_schema/enabled-templates/service_a/mut_arr_str.gqt b/pkg/testsetup/inputs_schema/enabled-templates/service_a/mut_arr_str.gqt similarity index 100% rename from testsetup/inputs_schema/enabled-templates/service_a/mut_arr_str.gqt rename to pkg/testsetup/inputs_schema/enabled-templates/service_a/mut_arr_str.gqt diff --git a/testsetup/inputs_schema/service_a.graphqls b/pkg/testsetup/inputs_schema/service_a.graphqls similarity index 100% rename from testsetup/inputs_schema/service_a.graphqls rename to pkg/testsetup/inputs_schema/service_a.graphqls diff --git a/testsetup/inputs_schema/test_arr_str_eq.yaml b/pkg/testsetup/inputs_schema/test_arr_str_eq.yaml similarity index 100% rename from testsetup/inputs_schema/test_arr_str_eq.yaml rename to pkg/testsetup/inputs_schema/test_arr_str_eq.yaml diff --git a/testsetup/starwars/config.yml b/pkg/testsetup/starwars/config.yml similarity index 100% rename from testsetup/starwars/config.yml rename to pkg/testsetup/starwars/config.yml diff --git a/testsetup/starwars/services_enabled/starwars.graphqls b/pkg/testsetup/starwars/services_enabled/starwars.graphqls similarity index 100% rename from testsetup/starwars/services_enabled/starwars.graphqls rename to pkg/testsetup/starwars/services_enabled/starwars.graphqls diff --git a/testsetup/starwars/services_enabled/starwars.yml b/pkg/testsetup/starwars/services_enabled/starwars.yml similarity index 100% rename from testsetup/starwars/services_enabled/starwars.yml rename to pkg/testsetup/starwars/services_enabled/starwars.yml diff --git a/testsetup/starwars/services_enabled/starwars/templates_enabled/a.gqt b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/a.gqt similarity index 100% rename from testsetup/starwars/services_enabled/starwars/templates_enabled/a.gqt rename to pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/a.gqt diff --git a/testsetup/starwars/services_enabled/starwars/templates_enabled/b.gqt b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/b.gqt similarity index 100% rename from testsetup/starwars/services_enabled/starwars/templates_enabled/b.gqt rename to pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/b.gqt diff --git a/testsetup/starwars/services_enabled/starwars/templates_enabled/c.gqt b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/c.gqt similarity index 100% rename from testsetup/starwars/services_enabled/starwars/templates_enabled/c.gqt rename to pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/c.gqt diff --git a/testsetup/starwars/services_enabled/starwars/templates_enabled/d.gqt b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/d.gqt similarity index 100% rename from testsetup/starwars/services_enabled/starwars/templates_enabled/d.gqt rename to pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/d.gqt diff --git a/testsetup/starwars/services_enabled/starwars/templates_enabled/e.gqt b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/e.gqt similarity index 100% rename from testsetup/starwars/services_enabled/starwars/templates_enabled/e.gqt rename to pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/e.gqt diff --git a/testsetup/starwars/services_enabled/starwars/templates_enabled/f.gqt b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/f.gqt similarity index 100% rename from testsetup/starwars/services_enabled/starwars/templates_enabled/f.gqt rename to pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/f.gqt diff --git a/testsetup/starwars/services_enabled/starwars/templates_enabled/max_1.gqt b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/max_1.gqt similarity index 100% rename from testsetup/starwars/services_enabled/starwars/templates_enabled/max_1.gqt rename to pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/max_1.gqt diff --git a/testsetup/starwars/test_qry2_ok.yaml b/pkg/testsetup/starwars/test_qry2_ok.yaml similarity index 100% rename from testsetup/starwars/test_qry2_ok.yaml rename to pkg/testsetup/starwars/test_qry2_ok.yaml diff --git a/testsetup/starwars/test_qry_ok.yaml b/pkg/testsetup/starwars/test_qry_ok.yaml similarity index 100% rename from testsetup/starwars/test_qry_ok.yaml rename to pkg/testsetup/starwars/test_qry_ok.yaml diff --git a/testsetup/test1/all_services/testservice.yaml b/pkg/testsetup/test1/all_services/testservice.yaml similarity index 100% rename from testsetup/test1/all_services/testservice.yaml rename to pkg/testsetup/test1/all_services/testservice.yaml diff --git a/testsetup/test1/all_templates/template_mut.gqt b/pkg/testsetup/test1/all_templates/template_mut.gqt similarity index 100% rename from testsetup/test1/all_templates/template_mut.gqt rename to pkg/testsetup/test1/all_templates/template_mut.gqt diff --git a/testsetup/test1/all_templates/template_qry.gqt b/pkg/testsetup/test1/all_templates/template_qry.gqt similarity index 100% rename from testsetup/test1/all_templates/template_qry.gqt rename to pkg/testsetup/test1/all_templates/template_qry.gqt diff --git a/testsetup/test1/config.yml b/pkg/testsetup/test1/config.yml similarity index 100% rename from testsetup/test1/config.yml rename to pkg/testsetup/test1/config.yml diff --git a/testsetup/test1/enabled_services/testservice.yaml b/pkg/testsetup/test1/enabled_services/testservice.yaml similarity index 100% rename from testsetup/test1/enabled_services/testservice.yaml rename to pkg/testsetup/test1/enabled_services/testservice.yaml diff --git a/testsetup/test1/enabled_templates/template_mut.gqt b/pkg/testsetup/test1/enabled_templates/template_mut.gqt similarity index 100% rename from testsetup/test1/enabled_templates/template_mut.gqt rename to pkg/testsetup/test1/enabled_templates/template_mut.gqt diff --git a/testsetup/test1/enabled_templates/template_qry.gqt b/pkg/testsetup/test1/enabled_templates/template_qry.gqt similarity index 100% rename from testsetup/test1/enabled_templates/template_qry.gqt rename to pkg/testsetup/test1/enabled_templates/template_qry.gqt diff --git a/testsetup/test1/test_qry_ok.yaml b/pkg/testsetup/test1/test_qry_ok.yaml similarity index 100% rename from testsetup/test1/test_qry_ok.yaml rename to pkg/testsetup/test1/test_qry_ok.yaml diff --git a/testsetup/test1/x_test_mut_ok.yaml b/pkg/testsetup/test1/x_test_mut_ok.yaml similarity index 100% rename from testsetup/test1/x_test_mut_ok.yaml rename to pkg/testsetup/test1/x_test_mut_ok.yaml diff --git a/testsetup/testsetup.go b/pkg/testsetup/testsetup.go similarity index 98% rename from testsetup/testsetup.go rename to pkg/testsetup/testsetup.go index c8f8796..8b65947 100644 --- a/testsetup/testsetup.go +++ b/pkg/testsetup/testsetup.go @@ -7,7 +7,7 @@ import ( "path/filepath" "strings" - "github.com/graph-guard/ggproxy/config" + "github.com/graph-guard/ggproxy/pkg/config" "gopkg.in/yaml.v3" ) diff --git a/testsetup/testsetup_test.go b/pkg/testsetup/testsetup_test.go similarity index 90% rename from testsetup/testsetup_test.go rename to pkg/testsetup/testsetup_test.go index c115ff0..57d7e8c 100644 --- a/testsetup/testsetup_test.go +++ b/pkg/testsetup/testsetup_test.go @@ -3,7 +3,7 @@ package testsetup_test import ( "testing" - "github.com/graph-guard/ggproxy/testsetup" + "github.com/graph-guard/ggproxy/pkg/testsetup" "github.com/stretchr/testify/require" ) diff --git a/utilities/tokenwriter/bench_test.go b/pkg/tokenwriter/bench_test.go similarity index 88% rename from utilities/tokenwriter/bench_test.go rename to pkg/tokenwriter/bench_test.go index c1a09cc..033a778 100644 --- a/utilities/tokenwriter/bench_test.go +++ b/pkg/tokenwriter/bench_test.go @@ -4,8 +4,8 @@ import ( "io" "testing" - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/ggproxy/utilities/tokenwriter" + "github.com/graph-guard/ggproxy/pkg/gqlparse" + "github.com/graph-guard/ggproxy/pkg/tokenwriter" ) func BenchmarkWrite(b *testing.B) { diff --git a/utilities/tokenwriter/tokenwriter.go b/pkg/tokenwriter/tokenwriter.go similarity index 89% rename from utilities/tokenwriter/tokenwriter.go rename to pkg/tokenwriter/tokenwriter.go index 1356b53..536321d 100644 --- a/utilities/tokenwriter/tokenwriter.go +++ b/pkg/tokenwriter/tokenwriter.go @@ -4,7 +4,7 @@ import ( "fmt" "io" - "github.com/graph-guard/ggproxy/gqlparse" + "github.com/graph-guard/ggproxy/pkg/gqlparse" "github.com/graph-guard/gqlscan" ) @@ -319,27 +319,29 @@ func Write(w io.Writer, tokens []gqlparse.Token) (err error) { return nil } -var partSpace = []byte(" ") -var partDoubleQuotes = []byte("\"") -var part3DoubleQuotes = []byte("\"\"\"") -var partSquareBracketL = []byte("[") -var partSquareBracketR = []byte("]") -var partCurlyBracketL = []byte("{") -var partCurlyBracketR = []byte("}") -var partColumn = []byte(":") -var partExlMark = []byte("!") -var partEq = []byte("=") -var partDefQry = []byte("query") -var partDefMut = []byte("mutation ") -var partDefSub = []byte("subscription ") -var partDirName = []byte(" @") -var partParenthesisL = []byte("(") -var partParenthesisR = []byte(")") -var partFragInline = []byte("...on ") -var partTrue = []byte("true") -var partFalse = []byte("false") -var partNull = []byte("null") -var partVarDollar = []byte("$") +var ( + partSpace = []byte(" ") + partDoubleQuotes = []byte("\"") + part3DoubleQuotes = []byte("\"\"\"") + partSquareBracketL = []byte("[") + partSquareBracketR = []byte("]") + partCurlyBracketL = []byte("{") + partCurlyBracketR = []byte("}") + partColumn = []byte(":") + partExlMark = []byte("!") + partEq = []byte("=") + partDefQry = []byte("query") + partDefMut = []byte("mutation ") + partDefSub = []byte("subscription ") + partDirName = []byte(" @") + partParenthesisL = []byte("(") + partParenthesisR = []byte(")") + partFragInline = []byte("...on ") + partTrue = []byte("true") + partFalse = []byte("false") + partNull = []byte("null") + partVarDollar = []byte("$") +) func isTokenEndOfVal(t gqlscan.Token) bool { switch t { diff --git a/utilities/tokenwriter/tokenwriter_test.go b/pkg/tokenwriter/tokenwriter_test.go similarity index 98% rename from utilities/tokenwriter/tokenwriter_test.go rename to pkg/tokenwriter/tokenwriter_test.go index 16525e5..808b5a6 100644 --- a/utilities/tokenwriter/tokenwriter_test.go +++ b/pkg/tokenwriter/tokenwriter_test.go @@ -5,8 +5,8 @@ import ( "errors" "testing" - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/ggproxy/utilities/tokenwriter" + "github.com/graph-guard/ggproxy/pkg/gqlparse" + "github.com/graph-guard/ggproxy/pkg/tokenwriter" "github.com/graph-guard/gqlscan" "github.com/stretchr/testify/require" ) diff --git a/utilities/unsafe/unsafe.go b/pkg/unsafe/unsafe.go similarity index 100% rename from utilities/unsafe/unsafe.go rename to pkg/unsafe/unsafe.go diff --git a/utilities/unsafe/unsafe_test.go b/pkg/unsafe/unsafe_test.go similarity index 85% rename from utilities/unsafe/unsafe_test.go rename to pkg/unsafe/unsafe_test.go index deffd88..f689e44 100644 --- a/utilities/unsafe/unsafe_test.go +++ b/pkg/unsafe/unsafe_test.go @@ -3,7 +3,7 @@ package unsafe_test import ( "testing" - "github.com/graph-guard/ggproxy/utilities/unsafe" + "github.com/graph-guard/ggproxy/pkg/unsafe" "github.com/stretchr/testify/require" ) diff --git a/utilities/xxhash/bench_test.go b/pkg/xxhash/bench_test.go similarity index 91% rename from utilities/xxhash/bench_test.go rename to pkg/xxhash/bench_test.go index 6894026..a70811a 100644 --- a/utilities/xxhash/bench_test.go +++ b/pkg/xxhash/bench_test.go @@ -3,7 +3,7 @@ package xxhash_test import ( "testing" - "github.com/graph-guard/ggproxy/utilities/xxhash" + "github.com/graph-guard/ggproxy/pkg/xxhash" "github.com/pierrec/xxHash/xxHash64" ) @@ -22,6 +22,7 @@ func BenchmarkOriginal(b *testing.B) { h.Reset() } } + func BenchmarkCustom(b *testing.B) { s1 := []byte("foobar") s2 := []byte("bazzfuzz") diff --git a/utilities/xxhash/xxhash.go b/pkg/xxhash/xxhash.go similarity index 100% rename from utilities/xxhash/xxhash.go rename to pkg/xxhash/xxhash.go diff --git a/utilities/xxhash/xxhash_test.go b/pkg/xxhash/xxhash_test.go similarity index 96% rename from utilities/xxhash/xxhash_test.go rename to pkg/xxhash/xxhash_test.go index 3b0a193..95d6ef0 100644 --- a/utilities/xxhash/xxhash_test.go +++ b/pkg/xxhash/xxhash_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/graph-guard/ggproxy/utilities/xxhash" + "github.com/graph-guard/ggproxy/pkg/xxhash" "github.com/pierrec/xxHash/xxHash64" "github.com/stretchr/testify/require" From e5e923727adace70ee9bafe8ff4aebd2c0a5d8aa Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Tue, 7 Mar 2023 00:03:18 +0100 Subject: [PATCH 27/35] feat: Add support for `max` set --- cmd/engineinit/conf/config.yaml | 13 ++ cmd/engineinit/conf/services/templates/a.gqt | 11 ++ cmd/engineinit/conf/services/templates/b.gqt | 13 ++ cmd/engineinit/conf/services/test.graphqls | 10 ++ cmd/engineinit/conf/services/test.yaml | 15 ++ cmd/engineinit/main.go | 41 +++++ cmd/engineinit/request.graphql | 7 + go.mod | 2 +- go.sum | 4 +- .../internal/constrcheck/constrcheck.go | 23 ++- .../constrcheck/internal/tstcomb/tstcomb.go | 133 ++++++++++++++ .../playmon/internal/pathinfo/pathinfo.go | 17 ++ .../internal/pathinfo/pathinfo_test.go | 92 ++++++++++ .../playmon/internal/pathmatch/pathmatch.go | 168 +++++++++++++++--- .../playmon/internal/pathscan/pathscan.go | 26 ++- .../internal/pathscan/pathscan_test.go | 45 +++-- pkg/engine/playmon/playmon.go | 24 ++- 17 files changed, 582 insertions(+), 62 deletions(-) create mode 100644 cmd/engineinit/conf/config.yaml create mode 100644 cmd/engineinit/conf/services/templates/a.gqt create mode 100644 cmd/engineinit/conf/services/templates/b.gqt create mode 100644 cmd/engineinit/conf/services/test.graphqls create mode 100644 cmd/engineinit/conf/services/test.yaml create mode 100644 cmd/engineinit/main.go create mode 100644 cmd/engineinit/request.graphql create mode 100644 pkg/engine/playmon/internal/constrcheck/internal/tstcomb/tstcomb.go create mode 100644 pkg/engine/playmon/internal/pathinfo/pathinfo.go create mode 100644 pkg/engine/playmon/internal/pathinfo/pathinfo_test.go diff --git a/cmd/engineinit/conf/config.yaml b/cmd/engineinit/conf/config.yaml new file mode 100644 index 0000000..5948c36 --- /dev/null +++ b/cmd/engineinit/conf/config.yaml @@ -0,0 +1,13 @@ +proxy: + # Address and port of the proxy server. + host: localhost:8000 + # Optional, in bytes, default: 4MiB. + max-request-body-size: 4096 + +# Optional, enables API server. +api: + # Address and port of the API server. + host: localhost:3000 + +all-services: services +enabled-services: services diff --git a/cmd/engineinit/conf/services/templates/a.gqt b/cmd/engineinit/conf/services/templates/a.gqt new file mode 100644 index 0000000..983ebff --- /dev/null +++ b/cmd/engineinit/conf/services/templates/a.gqt @@ -0,0 +1,11 @@ +query { + max 1 { + a { + max 1 { + a_0 + a_1 + } + } + b + } +} diff --git a/cmd/engineinit/conf/services/templates/b.gqt b/cmd/engineinit/conf/services/templates/b.gqt new file mode 100644 index 0000000..89c6a9a --- /dev/null +++ b/cmd/engineinit/conf/services/templates/b.gqt @@ -0,0 +1,13 @@ +query { + max 2 { + a { + max 2 { + a_0 + a_1 + a_2 + } + } + b + c + } +} \ No newline at end of file diff --git a/cmd/engineinit/conf/services/test.graphqls b/cmd/engineinit/conf/services/test.graphqls new file mode 100644 index 0000000..1d79c6e --- /dev/null +++ b/cmd/engineinit/conf/services/test.graphqls @@ -0,0 +1,10 @@ +type Query { + a: A! + b: Int! + c: Int! +} +type A { + a_0: Int! + a_1: Int! + a_2: Int! +} diff --git a/cmd/engineinit/conf/services/test.yaml b/cmd/engineinit/conf/services/test.yaml new file mode 100644 index 0000000..af597ad --- /dev/null +++ b/cmd/engineinit/conf/services/test.yaml @@ -0,0 +1,15 @@ +# The service's display name +name: "Test" +# Source URL path +path: "/test" +# Destination URL (where to proxy requests to) +forward-url: "http://localhost:8080/test" + +# false for forwarding the original request, +# true for the reduced version. +forward-reduced: true + +schema: test.graphqls + +all-templates: templates +enabled-templates: templates diff --git a/cmd/engineinit/main.go b/cmd/engineinit/main.go new file mode 100644 index 0000000..4c0e27b --- /dev/null +++ b/cmd/engineinit/main.go @@ -0,0 +1,41 @@ +package main + +import ( + _ "embed" + "fmt" + "log" + "os" + + "github.com/graph-guard/ggproxy/pkg/config" + "github.com/graph-guard/ggproxy/pkg/engine/playmon" + "github.com/graph-guard/ggproxy/pkg/gqlparse" +) + +//go:embed request.graphql +var request string + +func main() { + c, err := config.Read(os.DirFS("conf"), "conf", "config.yaml") + if err != nil { + log.Fatal("config: ", err) + } + e := playmon.New(c.ServicesEnabled[0]) + if e == nil { + log.Fatal(e) + } + + fmt.Println("\n\nMATCH:") + e.Match( + []byte(request), nil, nil, + func(operation, selectionSet []gqlparse.Token) (stop bool) { + return false + }, + func(t *config.Template) (stop bool) { + fmt.Println("MATCHED ", t.ID) + return false + }, + func(err error) { + log.Fatal("ERR:", err) + }, + ) +} diff --git a/cmd/engineinit/request.graphql b/cmd/engineinit/request.graphql new file mode 100644 index 0000000..ca3119f --- /dev/null +++ b/cmd/engineinit/request.graphql @@ -0,0 +1,7 @@ +query { + c + a { + a_2 + a_0 + } +} \ No newline at end of file diff --git a/go.mod b/go.mod index 307c694..e8b58d2 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/dustin/go-humanize v1.0.0 github.com/google/go-cmp v0.5.8 github.com/graph-guard/gqlscan v1.0.0 - github.com/graph-guard/gqt/v4 v4.0.9 + github.com/graph-guard/gqt/v4 v4.0.10 github.com/phuslu/log v1.0.81 github.com/pierrec/xxHash v0.1.5 github.com/stretchr/testify v1.7.2 diff --git a/go.sum b/go.sum index 079b5b1..8f044a0 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-guard/gqlscan v1.0.0 h1:hUEDqnJQJ7nV9S8hCPJuYI/H97KP3unu0Gu7wd0wV8A= github.com/graph-guard/gqlscan v1.0.0/go.mod h1:A7TeIFxoiP39gw/r4sWjDnTZJnNy+4kaytP9Kh7Luuk= -github.com/graph-guard/gqt/v4 v4.0.9 h1:rqCwhrns1u65a3GVutFQEPXWxO8Y6R3fv+t8oPsFhVU= -github.com/graph-guard/gqt/v4 v4.0.9/go.mod h1:tXBLe4Gr3S/y8pM6ABkcIWFcspyU1c1YfrsXdBf0hKE= +github.com/graph-guard/gqt/v4 v4.0.10 h1:keAxTwkopb44eR1CwRYL4/u0PGUHHEd6d8RjR85b3c0= +github.com/graph-guard/gqt/v4 v4.0.10/go.mod h1:tXBLe4Gr3S/y8pM6ABkcIWFcspyU1c1YfrsXdBf0hKE= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM= diff --git a/pkg/engine/playmon/internal/constrcheck/constrcheck.go b/pkg/engine/playmon/internal/constrcheck/constrcheck.go index 7ec74e6..4e1b329 100644 --- a/pkg/engine/playmon/internal/constrcheck/constrcheck.go +++ b/pkg/engine/playmon/internal/constrcheck/constrcheck.go @@ -438,10 +438,19 @@ func New(o *gqt.Operation, s *gqlast.Schema) *Checker { } if errs := pathscan.InAST( o, - func(path uint64, e gqt.Expression) (stop bool) { + func( + path string, + pathHash uint64, + e gqt.Expression, + ) (stop bool) { // On structural return false - }, func(path uint64, e gqt.Expression) (stop bool) { + }, + func( + path string, + pathHash uint64, + e gqt.Expression, + ) (stop bool) { // On argument a := e.(*gqt.Argument) var expect *gqlast.Type @@ -449,13 +458,17 @@ func New(o *gqt.Operation, s *gqlast.Schema) *Checker { expect = a.Def.Type } if fn := makeCheck(a.Constraint, expect, s); fn != nil { - c.checkers[path] = fn + c.checkers[pathHash] = fn } return false }, - func(path uint64, e *gqt.VariableDeclaration) (stop bool) { + func( + path string, + pathHash uint64, + e *gqt.VariableDeclaration, + ) (stop bool) { // On variable - c.pathByVarDecl[e] = path + c.pathByVarDecl[e] = pathHash return false }, ); errs != nil { diff --git a/pkg/engine/playmon/internal/constrcheck/internal/tstcomb/tstcomb.go b/pkg/engine/playmon/internal/constrcheck/internal/tstcomb/tstcomb.go new file mode 100644 index 0000000..3abafaf --- /dev/null +++ b/pkg/engine/playmon/internal/constrcheck/internal/tstcomb/tstcomb.go @@ -0,0 +1,133 @@ +package main + +import "github.com/graph-guard/ggproxy/pkg/bitmask" + +type ( + Variant struct { + Mask *bitmask.Set + Combinations []Combination + } + Combination struct{ CombinationIndex, Depth, TemplateIndex int } +) + +/* + A: query { + max 1 { + a { + max 1 { + a_0 + a_1 + } + } + b + } + } + B: query { + max 2 { + a { + max 2 { + a_0 + a_1 + a_2 + } + } + b + c + } + } +*/ + +// combinations is sorted by the order of encountered paths while +// parsing the templates. +var combinations = []Combination{ + {CombinationIndex: 0, Depth: 1, TemplateIndex: 0}, // A: Q.a.a_0 + {CombinationIndex: 1, Depth: 1, TemplateIndex: 0}, // A: Q.a.a_1 + {CombinationIndex: 2, Depth: 0, TemplateIndex: 0}, // A: Q.b + {CombinationIndex: 3, Depth: 1, TemplateIndex: 1}, // B: Q.a.a_0 + {CombinationIndex: 4, Depth: 1, TemplateIndex: 1}, // B: Q.a.a_1 + {CombinationIndex: 5, Depth: 1, TemplateIndex: 1}, // B: Q.a.a_2 + {CombinationIndex: 6, Depth: 0, TemplateIndex: 1}, // B: Q.b + {CombinationIndex: 7, Depth: 0, TemplateIndex: 1}, // B: Q.c +} + +// limits define the limit of the max combinator across all combinations. +var limits = []int{ + 1, // A: Q.a.a_0 + 1, // A: Q.a.a_1 + 1, // A: Q.b + 2, // B: Q.a.a_0 + 2, // B: Q.a.a_1 + 2, // B: Q.a.a_2 + 2, // B: Q.b + 2, // B: Q.c +} + +// counters is reset on every matching attempt. +var counters = []int{ + 2, // A: Q.a.a_0 + 1, // A: Q.a.a_1 + 1, // A: Q.b + 1, // B: Q.a.a_0 + 1, // B: Q.a.a_1 + 0, // B: Q.a.a_2 + 1, // B: Q.b + 0, // B: Q.c +} + +// query { b a { a_1 a_0 } } + +var structural = map[string][]Variant{ + "Q.a.a_0": {{ + Mask: bitmask.New(0, 1), + Combinations: combinations[0:1], + }, { + Mask: bitmask.New(0, 1), + Combinations: combinations[3:4], + }}, + "Q.a.a_1": {{ + Mask: bitmask.New(0, 1), + Combinations: combinations[1:2], + }, { + Mask: bitmask.New(0, 1), + Combinations: combinations[4:5], + }}, + "Q.a.a_2": {{ + Mask: bitmask.New(1), + Combinations: combinations[5:6], + }}, + "Q.b": {{ + Mask: bitmask.New(0, 1), + Combinations: combinations[2:3], + }, { + Mask: bitmask.New(0, 1), + Combinations: combinations[6:7], + }}, + "Q.c": {{ + Mask: bitmask.New(1), + Combinations: combinations[7:8], + }}, +} + +func main() { + for i := range counters { + counters[i] = 0 + } + + for _, v := range structural["Q.a"] { + for _, c := range v.Combinations { + for i := maxInt(0, c.CombinationIndex-c.Depth); i <= c.CombinationIndex; i++ { + counters[i]++ + if limits[i] < counters[i] { + // reject c.TemplateIndex + } + } + } + } +} + +func maxInt(a, b int) int { + if a > b { + return a + } + return b +} diff --git a/pkg/engine/playmon/internal/pathinfo/pathinfo.go b/pkg/engine/playmon/internal/pathinfo/pathinfo.go new file mode 100644 index 0000000..677b15f --- /dev/null +++ b/pkg/engine/playmon/internal/pathinfo/pathinfo.go @@ -0,0 +1,17 @@ +package pathinfo + +import "github.com/graph-guard/gqt/v4" + +// Info returns the number of `max` sets expression e is inside +// and the most relevant (parent) `max` set, if any. +func Info(e gqt.Expression) (depth int, parent *gqt.SelectionMax) { + for ; e != nil; e = e.GetParent() { + if s, ok := e.(*gqt.SelectionMax); ok { + if parent == nil { + parent = s + } + depth++ + } + } + return depth, parent +} diff --git a/pkg/engine/playmon/internal/pathinfo/pathinfo_test.go b/pkg/engine/playmon/internal/pathinfo/pathinfo_test.go new file mode 100644 index 0000000..40429d6 --- /dev/null +++ b/pkg/engine/playmon/internal/pathinfo/pathinfo_test.go @@ -0,0 +1,92 @@ +package pathinfo_test + +import ( + "testing" + + maxsets "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/pathinfo" + "github.com/graph-guard/gqt/v4" + "github.com/stretchr/testify/require" +) + +func TestInfo(t *testing.T) { + opr, _, errs := gqt.Parse([]byte(`query { + maz { + muzz + } + max 1 { + foo(x:>1,y:!="ok") { + max 2 { + bar { + ... on Baz { + baz + } + } + bar2 + bar3 + } + } + foo2 + } + }`)) + require.Nil(t, errs) + { + depth, parent := maxsets.Info(nil) + require.Equal(t, 0, depth) + require.Nil(t, parent) + } + { + depth, parent := maxsets.Info(opr) + require.Equal(t, 0, depth) + require.Nil(t, parent) + } + { + maz := opr.Selections[0].(*gqt.SelectionField) + require.Equal(t, "maz", maz.Name.Name) + + iDepth, iParent := maxsets.Info(maz) + require.Equal(t, 0, iDepth) + require.Nil(t, iParent) + } + { + maz := opr.Selections[0].(*gqt.SelectionField) + muzz := maz.Selections[0].(*gqt.SelectionField) + require.Equal(t, "muzz", muzz.Name.Name) + + iDepth, iParent := maxsets.Info(muzz) + require.Equal(t, 0, iDepth) + require.Nil(t, iParent) + } + { + firstMax := opr.Selections[1].(*gqt.SelectionMax) + foo := firstMax.Options.Selections[0].(*gqt.SelectionField) + require.Equal(t, "foo", foo.Name.Name) + + iDepth, iParent := maxsets.Info(foo) + require.Equal(t, 1, iDepth) + require.Equal(t, 1, iParent.Limit) + } + { + firstMax := opr.Selections[1].(*gqt.SelectionMax) + foo := firstMax.Options.Selections[0].(*gqt.SelectionField) + secondMax := foo.Selections[0].(*gqt.SelectionMax) + bar := secondMax.Options.Selections[0].(*gqt.SelectionField) + require.Equal(t, "bar", bar.Name.Name) + + iDepth, iParent := maxsets.Info(bar) + require.Equal(t, 2, iDepth) + require.Equal(t, 2, iParent.Limit) + } + { + firstMax := opr.Selections[1].(*gqt.SelectionMax) + foo := firstMax.Options.Selections[0].(*gqt.SelectionField) + secondMax := foo.Selections[0].(*gqt.SelectionMax) + bar := secondMax.Options.Selections[0].(*gqt.SelectionField) + onBaz := bar.Selections[0].(*gqt.SelectionInlineFrag) + baz := onBaz.Selections[0].(*gqt.SelectionField) + require.Equal(t, "baz", baz.Name.Name) + + iDepth, iParent := maxsets.Info(baz) + require.Equal(t, 2, iDepth) + require.Equal(t, 2, iParent.Limit) + } +} diff --git a/pkg/engine/playmon/internal/pathmatch/pathmatch.go b/pkg/engine/playmon/internal/pathmatch/pathmatch.go index f610c4e..d1a9b62 100644 --- a/pkg/engine/playmon/internal/pathmatch/pathmatch.go +++ b/pkg/engine/playmon/internal/pathmatch/pathmatch.go @@ -1,41 +1,87 @@ package pathmatch import ( + "fmt" + "github.com/graph-guard/ggproxy/pkg/bitmask" "github.com/graph-guard/ggproxy/pkg/config" + "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/pathinfo" "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/pathscan" + "github.com/graph-guard/ggproxy/pkg/math" "github.com/graph-guard/gqt/v4" ) +type structuralPath struct { + Name string + Mask *bitmask.Set + Combinations []combination +} + +type combination struct { + Index int + Depth int + TemplateIndex int +} + +type template struct { + *config.Template + paths []uint64 +} + type Matcher struct { - conf *config.Service - templatePaths [][]uint64 - paths map[uint64]*bitmask.Set - lengths []int + conf *config.Service + templates []template + paths map[uint64]structuralPath + + // maxSetLimits defines the limit of the `max` set of every combination. + maxSetLimits []int + + lengths []int + + /* Operational data, reset for every match call */ + + // maxCounters keeps the counters for every combination. + maxCounters []int - // Operational data, reset for every match call matchMask, rejectedMask *bitmask.Set matchesPerTemplate []int } -// Match calls onMatch for every template matching paths -func (m *Matcher) Match( - paths []uint64, - onMatch func(*config.Template) (stop bool), -) { +func (m *Matcher) reset() { m.matchMask.Reset() m.rejectedMask.Reset() for i := range m.matchesPerTemplate { m.matchesPerTemplate[i] = 0 } + for i := range m.maxCounters { + m.maxCounters[i] = 0 + } +} + +// Match calls onMatch for every template matching paths. +func (m *Matcher) Match( + paths []uint64, + onMatch func(*config.Template) (stop bool), +) { + m.reset() for i := range paths { b, ok := m.paths[paths[i]] if !ok { return // Unknown path, can't match any template } - b.VisitAll(func(n int) { m.matchesPerTemplate[n]++ }) - m.matchMask.SetOr(m.matchMask, b) + + for _, c := range b.Combinations { + for i := math.Max(0, c.Index-c.Depth); i <= c.Index; i++ { + m.maxCounters[i]++ + if m.maxSetLimits[i] < m.maxCounters[i] { + m.rejectedMask.Add(i) + } + } + } + + b.Mask.VisitAll(func(n int) { m.matchesPerTemplate[n]++ }) + m.matchMask.SetOr(m.matchMask, b.Mask) } for i := range m.matchesPerTemplate { if m.matchesPerTemplate[i] < len(paths) { @@ -50,30 +96,71 @@ func (m *Matcher) Match( func New(conf *config.Service) *Matcher { m := &Matcher{ - conf: conf, - // There will at least be 1 path per template - paths: make(map[uint64]*bitmask.Set, len(conf.TemplatesEnabled)), - templatePaths: make([][]uint64, len(conf.TemplatesEnabled)), - lengths: make([]int, len(conf.TemplatesEnabled)), + conf: conf, + paths: make(map[uint64]structuralPath, len(conf.TemplatesEnabled)), + templates: make([]template, len(conf.TemplatesEnabled)), + lengths: make([]int, len(conf.TemplatesEnabled)), matchMask: bitmask.New(), rejectedMask: bitmask.New(), matchesPerTemplate: make([]int, len(conf.TemplatesEnabled)), } + var paths, templateIDs []string + var combinations []combination for i := range conf.TemplatesEnabled { { // Associate paths with templates by index if errs := pathscan.InAST( conf.TemplatesEnabled[i].GQTTemplate, - func(pathHash uint64, e gqt.Expression) (stop bool) { + func( + path string, + pathHash uint64, + e gqt.Expression, + ) (stop bool) { // On structural - m.templatePaths[i] = append(m.templatePaths[i], pathHash) + m.templates[i].paths = append(m.templates[i].paths, pathHash) + + var v structuralPath + var ok bool + if v, ok = m.paths[pathHash]; !ok { + v.Mask = bitmask.New() + v.Name = path + m.paths[pathHash] = v + } + + if depth, parentMax := pathinfo.Info(e); depth > 0 { + // Register combinations for paths inside `max` sets + paths = append(paths, path) + templateIDs = append(templateIDs, conf.TemplatesEnabled[i].ID) + v.Combinations = append(v.Combinations, combination{ + Index: len(m.maxSetLimits), + Depth: depth - 1, + TemplateIndex: i, + }) + combinations = append(combinations, combination{ + Index: len(m.maxSetLimits), + Depth: depth - 1, + TemplateIndex: i, + }) + m.paths[pathHash] = v + + m.maxSetLimits = append(m.maxSetLimits, parentMax.Limit) + m.maxCounters = append(m.maxCounters, 0) + } return false }, - func(pathHash uint64, e gqt.Expression) (stop bool) { + func( + path string, + pathHash uint64, + e gqt.Expression, + ) (stop bool) { // On argument return false }, - func(pathHash uint64, e *gqt.VariableDeclaration) (stop bool) { + func( + path string, + pathHash uint64, + e *gqt.VariableDeclaration, + ) (stop bool) { // On variable return false }, @@ -83,13 +170,40 @@ func New(conf *config.Service) *Matcher { } // Initialize path bitmasks - for _, p := range m.templatePaths[i] { - if _, ok := m.paths[p]; !ok { - m.paths[p] = bitmask.New() - } - m.paths[p].Add(i) + for _, p := range m.templates[i].paths { + m.paths[p].Mask.Add(i) + } + m.lengths[i] = len(m.templates[i].paths) + } + + /*DUMP*/ + fmt.Println("\n<################# ENGINE DUMP>") + fmt.Println("paths: ", len(m.paths)) + for pathHash, p := range m.paths { + fmt.Printf(" %s: %d\n", p.Name, pathHash) + for _, c := range p.Combinations { + fmt.Printf( + " - CombinationIndex: %d; Depth: %d; Template: %d\n", + c.Index, c.Depth, c.TemplateIndex, + ) } - m.lengths[i] = len(m.templatePaths[i]) } + fmt.Println("combinations: ", len(combinations)) + for i, c := range combinations { + fmt.Printf( + " - CombinationIndex: %d; Depth: %d; Template: %d // %s: %s\n", + c.Index, c.Depth, c.TemplateIndex, templateIDs[i], paths[i], + ) + } + fmt.Println("maxSetLimits: ", len(m.maxSetLimits)) + for i := range m.maxSetLimits { + fmt.Printf(" %d: %d\n", i, m.maxSetLimits[i]) + } + fmt.Println("maxCounters: ", len(m.maxCounters)) + for i := range m.maxCounters { + fmt.Printf(" %d: %d\n", i, m.maxCounters[i]) + } + fmt.Println("<################# ENGINE DUMP/>") + return m } diff --git a/pkg/engine/playmon/internal/pathscan/pathscan.go b/pkg/engine/playmon/internal/pathscan/pathscan.go index 3e6d944..6763bca 100644 --- a/pkg/engine/playmon/internal/pathscan/pathscan.go +++ b/pkg/engine/playmon/internal/pathscan/pathscan.go @@ -251,9 +251,21 @@ func (s *PathScanner) structuralPop() { // argument. func InAST( o *gqt.Operation, - onStructural func(pathHash uint64, e gqt.Expression) (stop bool), - onArg func(pathHash uint64, e gqt.Expression) (stop bool), - onVariable func(pathHash uint64, e *gqt.VariableDeclaration) (stop bool), + onStructural func( + path string, + pathHash uint64, + e gqt.Expression, + ) (stop bool), + onArg func( + path string, + pathHash uint64, + e gqt.Expression, + ) (stop bool), + onVariable func( + path string, + pathHash uint64, + e *gqt.VariableDeclaration, + ) (stop bool), ) (errs []error) { hashes := map[uint64]string{} hash := func(path []byte) uint64 { @@ -276,12 +288,12 @@ func InAST( case *gqt.SelectionField: for _, a := range e.Arguments { p := makePathVar(a) - if onArg(hash(p), a) { + if onArg(string(p), hash(p), a) { return true, true } if a.AssociatedVariable != nil { p := makePathVar(a) - if onVariable(hash(p), a.AssociatedVariable) { + if onVariable(string(p), hash(p), a.AssociatedVariable) { return true, true } } @@ -291,13 +303,13 @@ func InAST( } p := makePathStructural(e) hashes[hash(p)] = string(p) - if onStructural(hash(p), e) { + if onStructural(string(p), hash(p), e) { return true, true } case *gqt.ObjectField: if e.AssociatedVariable != nil { p := makePathVar(e) - if onVariable(hash(p), e.AssociatedVariable) { + if onVariable(string(p), hash(p), e.AssociatedVariable) { return true, true } } diff --git a/pkg/engine/playmon/internal/pathscan/pathscan_test.go b/pkg/engine/playmon/internal/pathscan/pathscan_test.go index dc8700b..0f6d404 100644 --- a/pkg/engine/playmon/internal/pathscan/pathscan_test.go +++ b/pkg/engine/playmon/internal/pathscan/pathscan_test.go @@ -362,16 +362,16 @@ var testsInAST = []struct { Name: "subscription_multiple", GQTTemplateSrc: `subscription{foo bar}`, ExpectStructural: []string{ - "S.foo", "S.bar", + "S.foo", }, }, { Name: "args", GQTTemplateSrc: `query{foo(b:*,a:"t") bar(c:42)}`, ExpectStructural: []string{ - "Q.foo|a,b,", "Q.bar|c,", + "Q.foo|a,b,", }, ExpectedArgPaths: []string{ "Q.foo|b", @@ -383,8 +383,8 @@ var testsInAST = []struct { Name: "args_with_subselections", GQTTemplateSrc: `query{foo(b:*,a:"t") { fraz kraz(c:*) }}`, ExpectStructural: []string{ - "Q.foo|a,b,.fraz", "Q.foo|a,b,.kraz|c,", + "Q.foo|a,b,.fraz", }, ExpectedArgPaths: []string{ "Q.foo|b", @@ -418,10 +418,10 @@ var testsInAST = []struct { bazz(x:*) }`, ExpectStructural: []string{ + "M.bazz|x,", "M.foo|bar,.fa2", - "M.foo|bar,.fo2|a,b,.fo3|c,", "M.foo|bar,.fo2|a,b,.fa3", - "M.bazz|x,", + "M.foo|bar,.fo2|a,b,.fo3|c,", }, ExpectedArgPaths: []string{ "M.foo|bar", @@ -489,10 +489,10 @@ var testsInAST = []struct { } }`, ExpectStructural: []string{ - "Q.u|u1,u2,&Foo.foo1|x,y,", - "Q.u|u1,u2,&Foo.foo2|x,", - "Q.u|u1,u2,&Bar.bar1|x,", "Q.u|u1,u2,&Bar.bar2|x,y,", + "Q.u|u1,u2,&Bar.bar1|x,", + "Q.u|u1,u2,&Foo.foo2|x,", + "Q.u|u1,u2,&Foo.foo1|x,y,", }, ExpectedArgPaths: []string{ "Q.u|u2", @@ -532,12 +532,12 @@ var testsInAST = []struct { mazz }`, ExpectStructural: []string{ - "Q.foo.bar.burr|x,", - "Q.foo.baz&Kraz.fraz", - "Q.foo.baz&Kraz.graz|argument,.lum", + "Q.mazz", "Q.foo.baz&Guz.guz", "Q.foo.baz.buzz|a,b,c,", - "Q.mazz", + "Q.foo.baz&Kraz.graz|argument,.lum", + "Q.foo.baz&Kraz.fraz", + "Q.foo.bar.burr|x,", }, ExpectedArgPaths: []string{ "Q.foo.bar.burr|x", @@ -555,20 +555,34 @@ func TestInAST(t *testing.T) { o, _, errs := gqt.Parse([]byte(tt.GQTTemplateSrc)) require.Nil(t, errs) + var actualPathNames []string var actualPaths, actualArgPaths []uint64 actualVarPaths := map[uint64]string{} // Hash -> variable name errsP := pathscan.InAST( o, - func(pathHash uint64, e gqt.Expression) (stop bool) { + func( + path string, + pathHash uint64, + e gqt.Expression, + ) (stop bool) { // On structural + actualPathNames = append(actualPathNames, path) actualPaths = append(actualPaths, pathHash) require.NotNil(t, e) return false - }, func(pathHash uint64, e gqt.Expression) (stop bool) { + }, func( + path string, + pathHash uint64, + e gqt.Expression, + ) (stop bool) { // On argument actualArgPaths = append(actualArgPaths, pathHash) return false - }, func(pathHash uint64, e *gqt.VariableDeclaration) (stop bool) { + }, func( + path string, + pathHash uint64, + e *gqt.VariableDeclaration, + ) (stop bool) { // On variable actualVarPaths[pathHash] = e.Name return false @@ -576,6 +590,7 @@ func TestInAST(t *testing.T) { ) require.Nil(t, errsP) + require.Equal(t, tt.ExpectStructural, actualPathNames) compareStringsByHash( t, tt.ExpectStructural, actualPaths, "structural paths", ) diff --git a/pkg/engine/playmon/playmon.go b/pkg/engine/playmon/playmon.go index b6bed6d..a39b304 100644 --- a/pkg/engine/playmon/playmon.go +++ b/pkg/engine/playmon/playmon.go @@ -58,16 +58,30 @@ func New(s *config.Service) *Engine { e.templates[tmpl.ID] = tmpl if errs := pathscan.InAST( tmpl.GQTOpr, - func(path uint64, e gqt.Expression) (stop bool) { + func( + path string, + pathHash uint64, + e gqt.Expression, + ) (stop bool) { // Structural return false - }, func(path uint64, _ gqt.Expression) (stop bool) { + }, + func( + path string, + pathHash uint64, + _ gqt.Expression, + ) (stop bool) { // Argument - e.argumentPaths[path] = struct{}{} + e.argumentPaths[pathHash] = struct{}{} return false - }, func(path uint64, _ *gqt.VariableDeclaration) (stop bool) { + }, + func( + path string, + pathHash uint64, + _ *gqt.VariableDeclaration, + ) (stop bool) { // Variable - e.varValues[path] = nil + e.varValues[pathHash] = nil return false }, ); errs != nil { From 5e97546e745748f5ec6c71e68e85f8ba296993f5 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Thu, 9 Mar 2023 01:48:27 +0100 Subject: [PATCH 28/35] fix: Fix max combinator --- .../playmon/internal/pathmatch/bench_test.go | 22 +- .../playmon/internal/pathmatch/pathmatch.go | 106 ++++----- .../internal/pathmatch/pathmatch_test.go | 224 ++++++++++++++---- 3 files changed, 241 insertions(+), 111 deletions(-) diff --git a/pkg/engine/playmon/internal/pathmatch/bench_test.go b/pkg/engine/playmon/internal/pathmatch/bench_test.go index 55ac226..a9171f5 100644 --- a/pkg/engine/playmon/internal/pathmatch/bench_test.go +++ b/pkg/engine/playmon/internal/pathmatch/bench_test.go @@ -13,15 +13,19 @@ func BenchmarkPathmatch(b *testing.B) { for _, bb := range tests { b.Run(bb.name, func(b *testing.B) { m := prepareTestSetup(b, bb.conf) - paths := make([]uint64, len(bb.paths)) - for i := range bb.paths { - paths[i] = pathscan.Hash(bb.paths[i]) - } - b.ResetTimer() - for n := 0; n < b.N; n++ { - m.Match(paths, func(tm *config.Template) (stop bool) { - GI++ - return false + for _, bb := range bb.tests { + b.Run(bb.name, func(b *testing.B) { + paths := make([]uint64, len(bb.paths)) + for i := range bb.paths { + paths[i] = pathscan.Hash(bb.paths[i]) + } + b.ResetTimer() + for n := 0; n < b.N; n++ { + m.Match(paths, func(tm *config.Template) (stop bool) { + GI++ + return false + }) + } }) } }) diff --git a/pkg/engine/playmon/internal/pathmatch/pathmatch.go b/pkg/engine/playmon/internal/pathmatch/pathmatch.go index d1a9b62..7368963 100644 --- a/pkg/engine/playmon/internal/pathmatch/pathmatch.go +++ b/pkg/engine/playmon/internal/pathmatch/pathmatch.go @@ -1,20 +1,17 @@ package pathmatch import ( - "fmt" - "github.com/graph-guard/ggproxy/pkg/bitmask" "github.com/graph-guard/ggproxy/pkg/config" "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/pathinfo" "github.com/graph-guard/ggproxy/pkg/engine/playmon/internal/pathscan" - "github.com/graph-guard/ggproxy/pkg/math" "github.com/graph-guard/gqt/v4" ) type structuralPath struct { - Name string - Mask *bitmask.Set - Combinations []combination + Name string + Mask *bitmask.Set + Combinators []combination } type combination struct { @@ -33,15 +30,15 @@ type Matcher struct { templates []template paths map[uint64]structuralPath - // maxSetLimits defines the limit of the `max` set of every combination. - maxSetLimits []int + // combinatorsLimits defines the limit of the `max` set of every combination. + combinatorsLimits []int lengths []int /* Operational data, reset for every match call */ - // maxCounters keeps the counters for every combination. - maxCounters []int + // combinatorCounters keeps the counters for every combination. + combinatorCounters []int matchMask, rejectedMask *bitmask.Set matchesPerTemplate []int @@ -53,8 +50,8 @@ func (m *Matcher) reset() { for i := range m.matchesPerTemplate { m.matchesPerTemplate[i] = 0 } - for i := range m.maxCounters { - m.maxCounters[i] = 0 + for i := range m.combinatorCounters { + m.combinatorCounters[i] = 0 } } @@ -71,11 +68,15 @@ func (m *Matcher) Match( return // Unknown path, can't match any template } - for _, c := range b.Combinations { - for i := math.Max(0, c.Index-c.Depth); i <= c.Index; i++ { - m.maxCounters[i]++ - if m.maxSetLimits[i] < m.maxCounters[i] { - m.rejectedMask.Add(i) + for _, c := range b.Combinators { + depth := 0 + if m.combinatorCounters[c.Index] < 1 { + depth = c.Depth + } + for i := c.Index - depth; i <= c.Index; i++ { + m.combinatorCounters[i]++ + if m.combinatorsLimits[i] < m.combinatorCounters[i] { + m.rejectedMask.Add(c.TemplateIndex) } } } @@ -105,8 +106,7 @@ func New(conf *config.Service) *Matcher { rejectedMask: bitmask.New(), matchesPerTemplate: make([]int, len(conf.TemplatesEnabled)), } - var paths, templateIDs []string - var combinations []combination + var maxCombinators []*gqt.SelectionMax for i := range conf.TemplatesEnabled { { // Associate paths with templates by index if errs := pathscan.InAST( @@ -128,23 +128,23 @@ func New(conf *config.Service) *Matcher { } if depth, parentMax := pathinfo.Info(e); depth > 0 { - // Register combinations for paths inside `max` sets - paths = append(paths, path) - templateIDs = append(templateIDs, conf.TemplatesEnabled[i].ID) - v.Combinations = append(v.Combinations, combination{ - Index: len(m.maxSetLimits), - Depth: depth - 1, - TemplateIndex: i, - }) - combinations = append(combinations, combination{ - Index: len(m.maxSetLimits), + index := indexOf(maxCombinators, parentMax) + if index < 0 { + index = len(maxCombinators) + maxCombinators = append(maxCombinators, parentMax) + m.combinatorsLimits = append( + m.combinatorsLimits, parentMax.Limit, + ) + m.combinatorCounters = append(m.combinatorCounters, 0) + } + + // Register combinator for paths inside `max` sets + v.Combinators = append(v.Combinators, combination{ + Index: index, Depth: depth - 1, TemplateIndex: i, }) m.paths[pathHash] = v - - m.maxSetLimits = append(m.maxSetLimits, parentMax.Limit) - m.maxCounters = append(m.maxCounters, 0) } return false }, @@ -176,34 +176,20 @@ func New(conf *config.Service) *Matcher { m.lengths[i] = len(m.templates[i].paths) } - /*DUMP*/ - fmt.Println("\n<################# ENGINE DUMP>") - fmt.Println("paths: ", len(m.paths)) - for pathHash, p := range m.paths { - fmt.Printf(" %s: %d\n", p.Name, pathHash) - for _, c := range p.Combinations { - fmt.Printf( - " - CombinationIndex: %d; Depth: %d; Template: %d\n", - c.Index, c.Depth, c.TemplateIndex, - ) - } - } - fmt.Println("combinations: ", len(combinations)) - for i, c := range combinations { - fmt.Printf( - " - CombinationIndex: %d; Depth: %d; Template: %d // %s: %s\n", - c.Index, c.Depth, c.TemplateIndex, templateIDs[i], paths[i], - ) - } - fmt.Println("maxSetLimits: ", len(m.maxSetLimits)) - for i := range m.maxSetLimits { - fmt.Printf(" %d: %d\n", i, m.maxSetLimits[i]) - } - fmt.Println("maxCounters: ", len(m.maxCounters)) - for i := range m.maxCounters { - fmt.Printf(" %d: %d\n", i, m.maxCounters[i]) + return m +} + +func reverseSlice[T any](s []T) { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] } - fmt.Println("<################# ENGINE DUMP/>") +} - return m +func indexOf[T comparable](s []T, x T) (index int) { + for i := range s { + if s[i] == x { + return i + } + } + return -1 } diff --git a/pkg/engine/playmon/internal/pathmatch/pathmatch_test.go b/pkg/engine/playmon/internal/pathmatch/pathmatch_test.go index fef6acd..a4e2e6d 100644 --- a/pkg/engine/playmon/internal/pathmatch/pathmatch_test.go +++ b/pkg/engine/playmon/internal/pathmatch/pathmatch_test.go @@ -15,16 +15,20 @@ func TestMatch(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m := prepareTestSetup(t, tt.conf) - var actualMatches []string - paths := make([]uint64, len(tt.paths)) - for i := range tt.paths { - paths[i] = pathscan.Hash(tt.paths[i]) + for _, tt := range tt.tests { + t.Run(tt.name, func(t *testing.T) { + var actualMatches []string + paths := make([]uint64, len(tt.paths)) + for i := range tt.paths { + paths[i] = pathscan.Hash(tt.paths[i]) + } + m.Match(paths, func(tm *config.Template) (stop bool) { + actualMatches = append(actualMatches, tm.ID) + return false + }) + require.Equal(t, tt.expectIDs, actualMatches) + }) } - m.Match(paths, func(tm *config.Template) (stop bool) { - actualMatches = append(actualMatches, tm.ID) - return false - }) - require.Equal(t, tt.expectIDs, actualMatches) }) } } @@ -32,11 +36,16 @@ func TestMatch(t *testing.T) { //go:embed test_setups var embeddedTestSetups embed.FS -var tests = []struct { +type test struct { name string - conf *config.Service paths []string expectIDs []string +} + +var tests = []struct { + name string + conf *config.Service + tests []test }{ { name: "no paths", @@ -45,8 +54,10 @@ var tests = []struct { ID: "A", Source: []byte(`query { foo }`), }}, }, - paths: nil, - expectIDs: nil, + tests: []test{{ + paths: nil, + expectIDs: nil, + }}, }, { name: "unknown path", @@ -55,8 +66,10 @@ var tests = []struct { ID: "A", Source: []byte(`query { foo }`), }}, }, - paths: []string{"Q.bar"}, - expectIDs: nil, + tests: []test{{ + paths: []string{"Q.bar"}, + expectIDs: nil, + }}, }, { name: "1_of_1", @@ -65,8 +78,10 @@ var tests = []struct { ID: "A", Source: []byte(`query { foo }`), }}, }, - paths: []string{"Q.foo"}, - expectIDs: []string{"A"}, + tests: []test{{ + paths: []string{"Q.foo"}, + expectIDs: []string{"A"}, + }}, }, { name: "first_of_2", @@ -76,8 +91,10 @@ var tests = []struct { {ID: "B", Source: []byte(`query { bar }`)}, }, }, - paths: []string{"Q.foo"}, - expectIDs: []string{"A"}, + tests: []test{{ + paths: []string{"Q.foo"}, + expectIDs: []string{"A"}, + }}, }, { name: "second_of_2", @@ -87,8 +104,10 @@ var tests = []struct { {ID: "B", Source: []byte(`query { bar }`)}, }, }, - paths: []string{"Q.bar"}, - expectIDs: []string{"B"}, + tests: []test{{ + paths: []string{"Q.bar"}, + expectIDs: []string{"B"}, + }}, }, { name: "not_both", @@ -98,8 +117,10 @@ var tests = []struct { {ID: "B", Source: []byte(`query { bar }`)}, }, }, - paths: []string{"Q.foo", "Q.bar"}, - expectIDs: nil, + tests: []test{{ + paths: []string{"Q.foo", "Q.bar"}, + expectIDs: nil, + }}, }, { name: "1_of_3", @@ -109,8 +130,10 @@ var tests = []struct { {ID: "B", Source: []byte(`query { bar bazz }`)}, }, }, - paths: []string{"Q.bazz", "Q.bar"}, - expectIDs: []string{"B"}, + tests: []test{{ + paths: []string{"Q.bazz", "Q.bar"}, + expectIDs: []string{"B"}, + }}, }, { name: "3_of_3", @@ -121,8 +144,10 @@ var tests = []struct { {ID: "C", Source: []byte(`query { bazz }`)}, }, }, - paths: []string{"Q.bazz"}, - expectIDs: []string{"A", "B", "C"}, + tests: []test{{ + paths: []string{"Q.bazz"}, + expectIDs: []string{"A", "B", "C"}, + }}, }, { name: "starwars", @@ -148,22 +173,137 @@ var tests = []struct { friends { id, name, appearsIn } } } } */ - paths: []string{ - "Q.hero|episode,.id", - "Q.hero|episode,.name", - "Q.hero|episode,.appearsIn", - "Q.hero|episode,.friends.id", - "Q.hero|episode,.friends.name", - "Q.hero|episode,.friends.appearsIn", - "Q.hero|episode,.friends.friends.id", - "Q.hero|episode,.friends.friends.name", - "Q.hero|episode,.friends.friends.appearsIn", - "Q.hero|episode,.friendsConnection|after,first,.totalCount", - "Q.hero|episode,.friendsConnection|after,first,.friends.id", - "Q.hero|episode,.friendsConnection|after,first,.friends.name", - "Q.hero|episode,.friendsConnection|after,first,.friends.appearsIn", + tests: []test{{ + paths: []string{ + "Q.hero|episode,.id", + "Q.hero|episode,.name", + "Q.hero|episode,.appearsIn", + "Q.hero|episode,.friends.id", + "Q.hero|episode,.friends.name", + "Q.hero|episode,.friends.appearsIn", + "Q.hero|episode,.friends.friends.id", + "Q.hero|episode,.friends.friends.name", + "Q.hero|episode,.friends.friends.appearsIn", + "Q.hero|episode,.friendsConnection|after,first,.totalCount", + "Q.hero|episode,.friendsConnection|after,first,.friends.id", + "Q.hero|episode,.friendsConnection|after,first,.friends.name", + "Q.hero|episode,.friendsConnection|after,first,.friends.appearsIn", + }, + expectIDs: []string{"c"}, + }}, + }, + { + name: "max1_1_template", + conf: &config.Service{ + TemplatesEnabled: []*config.Template{ + {ID: "A", Source: []byte(`query { + max 1 { a b c } + } + `)}, + }, }, - expectIDs: []string{"c"}, + tests: []test{{ + name: "field_a", + paths: []string{"Q.a"}, + expectIDs: []string{"A"}, + }, { + name: "field_b", + paths: []string{"Q.b"}, + expectIDs: []string{"A"}, + }, { + name: "field_c", + paths: []string{"Q.c"}, + expectIDs: []string{"A"}, + }, { + name: "violate_c_a", + paths: []string{"Q.c", "Q.a"}, + expectIDs: nil, + }, { + name: "violate_b_a", + paths: []string{"Q.b", "Q.a"}, + expectIDs: nil, + }, { + name: "violate_a_c", + paths: []string{"Q.a", "Q.c"}, + expectIDs: nil, + }, { + name: "violate_a_b_c", + paths: []string{"Q.a", "Q.b", "Q.c"}, + expectIDs: nil, + }}, + }, + { + name: "max_2_multitemplates", + conf: &config.Service{ + TemplatesEnabled: []*config.Template{ + {ID: "A", Source: []byte(`query { + max 1 { + a { + max 1 { a0 a1 } + } + b + } + } + `)}, + {ID: "B", Source: []byte(`query { + max 2 { + b + a { + max 2 { a0 a1 a2 } + } + c + } + } + `)}, + }, + }, + tests: []test{{ + name: "field_b", + paths: []string{"Q.b"}, + expectIDs: []string{"A", "B"}, + }, { + name: "field_c", + paths: []string{"Q.c"}, + expectIDs: []string{"B"}, + }, { + name: "inexistent_path", + paths: []string{"Q.d"}, + expectIDs: nil, + }, { + name: "violate_A_max", + paths: []string{ + "Q.a.a0", + "Q.b", + }, + expectIDs: []string{"B"}, + }, { + name: "violate_A", + paths: []string{ + "Q.a.a1", + "Q.a.a0", + }, + expectIDs: []string{"B"}, + }, { + name: "violate_A_all_levels", + paths: []string{ + "Q.b", + "Q.a.a1", + "Q.a.a0", + }, + expectIDs: []string{"B"}, + }, { + name: "a1", + paths: []string{ + "Q.a.a1", + }, + expectIDs: []string{"A", "B"}, + }, { + name: "a2", + paths: []string{ + "Q.a.a2", + }, + expectIDs: []string{"B"}, + }}, }, } From c87bef27948742cf4da62e98d9d61868f60cb1d4 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Thu, 9 Mar 2023 10:40:41 +0100 Subject: [PATCH 29/35] refactor: Remove obsolete code --- cmd/engineinit/conf/config.yaml | 13 -- cmd/engineinit/conf/services/templates/a.gqt | 11 -- cmd/engineinit/conf/services/templates/b.gqt | 13 -- cmd/engineinit/conf/services/test.graphqls | 10 -- cmd/engineinit/conf/services/test.yaml | 15 -- cmd/engineinit/main.go | 41 ------ cmd/engineinit/request.graphql | 7 - cmd/tst/main.go | 102 -------------- .../constrcheck/internal/tstcomb/tstcomb.go | 133 ------------------ .../playmon/internal/pathmatch/pathmatch.go | 118 ++++++++-------- 10 files changed, 55 insertions(+), 408 deletions(-) delete mode 100644 cmd/engineinit/conf/config.yaml delete mode 100644 cmd/engineinit/conf/services/templates/a.gqt delete mode 100644 cmd/engineinit/conf/services/templates/b.gqt delete mode 100644 cmd/engineinit/conf/services/test.graphqls delete mode 100644 cmd/engineinit/conf/services/test.yaml delete mode 100644 cmd/engineinit/main.go delete mode 100644 cmd/engineinit/request.graphql delete mode 100644 cmd/tst/main.go delete mode 100644 pkg/engine/playmon/internal/constrcheck/internal/tstcomb/tstcomb.go diff --git a/cmd/engineinit/conf/config.yaml b/cmd/engineinit/conf/config.yaml deleted file mode 100644 index 5948c36..0000000 --- a/cmd/engineinit/conf/config.yaml +++ /dev/null @@ -1,13 +0,0 @@ -proxy: - # Address and port of the proxy server. - host: localhost:8000 - # Optional, in bytes, default: 4MiB. - max-request-body-size: 4096 - -# Optional, enables API server. -api: - # Address and port of the API server. - host: localhost:3000 - -all-services: services -enabled-services: services diff --git a/cmd/engineinit/conf/services/templates/a.gqt b/cmd/engineinit/conf/services/templates/a.gqt deleted file mode 100644 index 983ebff..0000000 --- a/cmd/engineinit/conf/services/templates/a.gqt +++ /dev/null @@ -1,11 +0,0 @@ -query { - max 1 { - a { - max 1 { - a_0 - a_1 - } - } - b - } -} diff --git a/cmd/engineinit/conf/services/templates/b.gqt b/cmd/engineinit/conf/services/templates/b.gqt deleted file mode 100644 index 89c6a9a..0000000 --- a/cmd/engineinit/conf/services/templates/b.gqt +++ /dev/null @@ -1,13 +0,0 @@ -query { - max 2 { - a { - max 2 { - a_0 - a_1 - a_2 - } - } - b - c - } -} \ No newline at end of file diff --git a/cmd/engineinit/conf/services/test.graphqls b/cmd/engineinit/conf/services/test.graphqls deleted file mode 100644 index 1d79c6e..0000000 --- a/cmd/engineinit/conf/services/test.graphqls +++ /dev/null @@ -1,10 +0,0 @@ -type Query { - a: A! - b: Int! - c: Int! -} -type A { - a_0: Int! - a_1: Int! - a_2: Int! -} diff --git a/cmd/engineinit/conf/services/test.yaml b/cmd/engineinit/conf/services/test.yaml deleted file mode 100644 index af597ad..0000000 --- a/cmd/engineinit/conf/services/test.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# The service's display name -name: "Test" -# Source URL path -path: "/test" -# Destination URL (where to proxy requests to) -forward-url: "http://localhost:8080/test" - -# false for forwarding the original request, -# true for the reduced version. -forward-reduced: true - -schema: test.graphqls - -all-templates: templates -enabled-templates: templates diff --git a/cmd/engineinit/main.go b/cmd/engineinit/main.go deleted file mode 100644 index 4c0e27b..0000000 --- a/cmd/engineinit/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - _ "embed" - "fmt" - "log" - "os" - - "github.com/graph-guard/ggproxy/pkg/config" - "github.com/graph-guard/ggproxy/pkg/engine/playmon" - "github.com/graph-guard/ggproxy/pkg/gqlparse" -) - -//go:embed request.graphql -var request string - -func main() { - c, err := config.Read(os.DirFS("conf"), "conf", "config.yaml") - if err != nil { - log.Fatal("config: ", err) - } - e := playmon.New(c.ServicesEnabled[0]) - if e == nil { - log.Fatal(e) - } - - fmt.Println("\n\nMATCH:") - e.Match( - []byte(request), nil, nil, - func(operation, selectionSet []gqlparse.Token) (stop bool) { - return false - }, - func(t *config.Template) (stop bool) { - fmt.Println("MATCHED ", t.ID) - return false - }, - func(err error) { - log.Fatal("ERR:", err) - }, - ) -} diff --git a/cmd/engineinit/request.graphql b/cmd/engineinit/request.graphql deleted file mode 100644 index ca3119f..0000000 --- a/cmd/engineinit/request.graphql +++ /dev/null @@ -1,7 +0,0 @@ -query { - c - a { - a_2 - a_0 - } -} \ No newline at end of file diff --git a/cmd/tst/main.go b/cmd/tst/main.go deleted file mode 100644 index ef07dc1..0000000 --- a/cmd/tst/main.go +++ /dev/null @@ -1,102 +0,0 @@ -package main - -import ( - "fmt" - "log" - - "github.com/graph-guard/ggproxy/config" - "github.com/graph-guard/ggproxy/engine/playmon" - "github.com/graph-guard/ggproxy/gqlparse" - "github.com/graph-guard/gqt/v4" - "github.com/vektah/gqlparser/v2" - "github.com/vektah/gqlparser/v2/ast" -) - -const ( - schemaSrc = `type Query {f(a:Int):Int}` - requestSrc = `query { f(a:42) }` - operationName = `` - variablesJSON = `` -) - -var templateSrc = map[string]string{ - "testtemplate": `query { f(a:*) }`, -} - -func main() { - gqlParser := gqlparse.NewParser() - var schema *ast.Schema - var gqtParser *gqt.Parser - if schemaSrc != "" { - var err error - if schema, err = gqlparser.LoadSchema(&ast.Source{ - Input: schemaSrc, - Name: "schema.graphqls", - }); err != nil { - log.Fatalf("parsing schema: %v", err) - } - - if gqtParser, err = gqt.NewParser([]gqt.Source{ - { - Name: "schema.graphqls", - Content: schemaSrc, - }, - }); err != nil { - log.Fatalf("initializing GQT parser: %v", err) - } - } else { - var err error - if gqtParser, err = gqt.NewParser(nil); err != nil { - log.Fatalf("initializing GQT parser: %v", err) - } - } - - templates := make(map[string]*config.Template, len(templateSrc)) - for id, src := range templateSrc { - tmpl, _, errs := gqtParser.Parse([]byte(src)) - if errs != nil { - log.Fatalf("parsing template %q: %v", id, errs) - } - templates[id] = &config.Template{ - Enabled: true, - ID: id, - Name: "name of " + id, - Source: []byte(src), - GQTTemplate: tmpl, - FilePath: "/path/to/" + id + ".gqt", - } - } - - s := &config.Service{ - Enabled: true, - ID: "testservice", - Path: "/testservice", - ForwardURL: "https://forward.here", - Schema: schema, - FilePath: "/path/to/testservice", - Templates: templates, - } - e := playmon.New(s) - fmt.Println(e) - - var oprName, varsJSON []byte - if operationName != "" { - oprName = []byte(operationName) - } - if variablesJSON != "" { - varsJSON = []byte(variablesJSON) - } - gqlParser.Parse( - []byte(requestSrc), oprName, varsJSON, - func(varValues [][]gqlparse.Token, operation, selectionSet []gqlparse.Token) { - tid := e.Match(varValues, operation[0].ID, selectionSet) - if tid == "" { - log.Print("no match") - } - fmt.Println("TID:", tid) - }, - func(err error) { - log.Fatalf("parsing request: %v", err) - }, - ) -} diff --git a/pkg/engine/playmon/internal/constrcheck/internal/tstcomb/tstcomb.go b/pkg/engine/playmon/internal/constrcheck/internal/tstcomb/tstcomb.go deleted file mode 100644 index 3abafaf..0000000 --- a/pkg/engine/playmon/internal/constrcheck/internal/tstcomb/tstcomb.go +++ /dev/null @@ -1,133 +0,0 @@ -package main - -import "github.com/graph-guard/ggproxy/pkg/bitmask" - -type ( - Variant struct { - Mask *bitmask.Set - Combinations []Combination - } - Combination struct{ CombinationIndex, Depth, TemplateIndex int } -) - -/* - A: query { - max 1 { - a { - max 1 { - a_0 - a_1 - } - } - b - } - } - B: query { - max 2 { - a { - max 2 { - a_0 - a_1 - a_2 - } - } - b - c - } - } -*/ - -// combinations is sorted by the order of encountered paths while -// parsing the templates. -var combinations = []Combination{ - {CombinationIndex: 0, Depth: 1, TemplateIndex: 0}, // A: Q.a.a_0 - {CombinationIndex: 1, Depth: 1, TemplateIndex: 0}, // A: Q.a.a_1 - {CombinationIndex: 2, Depth: 0, TemplateIndex: 0}, // A: Q.b - {CombinationIndex: 3, Depth: 1, TemplateIndex: 1}, // B: Q.a.a_0 - {CombinationIndex: 4, Depth: 1, TemplateIndex: 1}, // B: Q.a.a_1 - {CombinationIndex: 5, Depth: 1, TemplateIndex: 1}, // B: Q.a.a_2 - {CombinationIndex: 6, Depth: 0, TemplateIndex: 1}, // B: Q.b - {CombinationIndex: 7, Depth: 0, TemplateIndex: 1}, // B: Q.c -} - -// limits define the limit of the max combinator across all combinations. -var limits = []int{ - 1, // A: Q.a.a_0 - 1, // A: Q.a.a_1 - 1, // A: Q.b - 2, // B: Q.a.a_0 - 2, // B: Q.a.a_1 - 2, // B: Q.a.a_2 - 2, // B: Q.b - 2, // B: Q.c -} - -// counters is reset on every matching attempt. -var counters = []int{ - 2, // A: Q.a.a_0 - 1, // A: Q.a.a_1 - 1, // A: Q.b - 1, // B: Q.a.a_0 - 1, // B: Q.a.a_1 - 0, // B: Q.a.a_2 - 1, // B: Q.b - 0, // B: Q.c -} - -// query { b a { a_1 a_0 } } - -var structural = map[string][]Variant{ - "Q.a.a_0": {{ - Mask: bitmask.New(0, 1), - Combinations: combinations[0:1], - }, { - Mask: bitmask.New(0, 1), - Combinations: combinations[3:4], - }}, - "Q.a.a_1": {{ - Mask: bitmask.New(0, 1), - Combinations: combinations[1:2], - }, { - Mask: bitmask.New(0, 1), - Combinations: combinations[4:5], - }}, - "Q.a.a_2": {{ - Mask: bitmask.New(1), - Combinations: combinations[5:6], - }}, - "Q.b": {{ - Mask: bitmask.New(0, 1), - Combinations: combinations[2:3], - }, { - Mask: bitmask.New(0, 1), - Combinations: combinations[6:7], - }}, - "Q.c": {{ - Mask: bitmask.New(1), - Combinations: combinations[7:8], - }}, -} - -func main() { - for i := range counters { - counters[i] = 0 - } - - for _, v := range structural["Q.a"] { - for _, c := range v.Combinations { - for i := maxInt(0, c.CombinationIndex-c.Depth); i <= c.CombinationIndex; i++ { - counters[i]++ - if limits[i] < counters[i] { - // reject c.TemplateIndex - } - } - } - } -} - -func maxInt(a, b int) int { - if a > b { - return a - } - return b -} diff --git a/pkg/engine/playmon/internal/pathmatch/pathmatch.go b/pkg/engine/playmon/internal/pathmatch/pathmatch.go index 7368963..12138ce 100644 --- a/pkg/engine/playmon/internal/pathmatch/pathmatch.go +++ b/pkg/engine/playmon/internal/pathmatch/pathmatch.go @@ -108,65 +108,63 @@ func New(conf *config.Service) *Matcher { } var maxCombinators []*gqt.SelectionMax for i := range conf.TemplatesEnabled { - { // Associate paths with templates by index - if errs := pathscan.InAST( - conf.TemplatesEnabled[i].GQTTemplate, - func( - path string, - pathHash uint64, - e gqt.Expression, - ) (stop bool) { - // On structural - m.templates[i].paths = append(m.templates[i].paths, pathHash) - - var v structuralPath - var ok bool - if v, ok = m.paths[pathHash]; !ok { - v.Mask = bitmask.New() - v.Name = path - m.paths[pathHash] = v - } + if errs := pathscan.InAST( + conf.TemplatesEnabled[i].GQTTemplate, + func( + path string, + pathHash uint64, + e gqt.Expression, + ) (stop bool) { + // On structural + m.templates[i].paths = append(m.templates[i].paths, pathHash) + + var v structuralPath + var ok bool + if v, ok = m.paths[pathHash]; !ok { + v.Mask = bitmask.New() + v.Name = path + m.paths[pathHash] = v + } - if depth, parentMax := pathinfo.Info(e); depth > 0 { - index := indexOf(maxCombinators, parentMax) - if index < 0 { - index = len(maxCombinators) - maxCombinators = append(maxCombinators, parentMax) - m.combinatorsLimits = append( - m.combinatorsLimits, parentMax.Limit, - ) - m.combinatorCounters = append(m.combinatorCounters, 0) - } - - // Register combinator for paths inside `max` sets - v.Combinators = append(v.Combinators, combination{ - Index: index, - Depth: depth - 1, - TemplateIndex: i, - }) - m.paths[pathHash] = v + if depth, parentMax := pathinfo.Info(e); depth > 0 { + index := indexOf(maxCombinators, parentMax) + if index < 0 { + index = len(maxCombinators) + maxCombinators = append(maxCombinators, parentMax) + m.combinatorsLimits = append( + m.combinatorsLimits, parentMax.Limit, + ) + m.combinatorCounters = append(m.combinatorCounters, 0) } - return false - }, - func( - path string, - pathHash uint64, - e gqt.Expression, - ) (stop bool) { - // On argument - return false - }, - func( - path string, - pathHash uint64, - e *gqt.VariableDeclaration, - ) (stop bool) { - // On variable - return false - }, - ); errs != nil { - panic(errs) - } + + // Register combinator for paths inside `max` sets + v.Combinators = append(v.Combinators, combination{ + Index: index, + Depth: depth - 1, + TemplateIndex: i, + }) + m.paths[pathHash] = v + } + return false + }, + func( + path string, + pathHash uint64, + e gqt.Expression, + ) (stop bool) { + // On argument + return false + }, + func( + path string, + pathHash uint64, + e *gqt.VariableDeclaration, + ) (stop bool) { + // On variable + return false + }, + ); errs != nil { + panic(errs) } // Initialize path bitmasks @@ -179,12 +177,6 @@ func New(conf *config.Service) *Matcher { return m } -func reverseSlice[T any](s []T) { - for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { - s[i], s[j] = s[j], s[i] - } -} - func indexOf[T comparable](s []T, x T) (index int) { for i := range s { if s[i] == x { From c9392fd482e313b0fd406105b6f70b3f9a12d2f9 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Fri, 10 Mar 2023 12:25:13 +0100 Subject: [PATCH 30/35] refactor: Restructure engine tests --- pkg/engine/playmon/bench_test.go | 60 +- .../assets/benchassets/_bench_deep/bench.yml | 120 --- .../benchassets/bench_average/bench.yml | 17 - .../assets/benchassets/bench_big/bench.yml | 145 ---- .../tests/assets/benchassets/templates/0.gqt | 48 -- .../tests/assets/benchassets/templates/1.gqt | 47 -- .../tests/assets/benchassets/templates/2.gqt | 25 - .../tests/assets/benchassets/templates/3.gqt | 55 -- .../tests/assets/benchassets/templates/4.gqt | 46 -- .../tests/assets/benchassets/templates/5.gqt | 45 -- .../tests/assets/benchassets/templates/6.gqt | 118 --- .../tests/assets/benchassets/templates/7.gqt | 118 --- .../tests/assets/benchassets/templates/8.tgqt | 43 -- .../testassets/test_arguments_merge_00/0.gqt | 12 - .../testassets/test_arguments_merge_00/1.gqt | 12 - .../testassets/test_arguments_merge_00/2.gqt | 13 - .../testassets/test_arguments_merge_00/3.gqt | 12 - .../test_arguments_merge_00/test.yml | 16 - .../testassets/test_arguments_merge_01/0.gqt | 8 - .../testassets/test_arguments_merge_01/1.gqt | 8 - .../testassets/test_arguments_merge_01/2.gqt | 9 - .../testassets/test_arguments_merge_01/3.gqt | 8 - .../test_arguments_merge_01/test.yml | 15 - .../assets/testassets/test_complex_00/0.gqt | 48 -- .../assets/testassets/test_complex_00/1.gqt | 47 -- .../assets/testassets/test_complex_00/2.gqt | 23 - .../assets/testassets/test_complex_00/3.gqt | 55 -- .../assets/testassets/test_complex_00/4.gqt | 46 -- .../assets/testassets/test_complex_00/5.gqt | 45 -- .../testassets/test_complex_00/test.yml | 148 ---- .../testassets/test_conditions_00/0.gqt | 34 - .../testassets/test_conditions_00/test.yml | 39 - .../testassets/test_conditions_01/0.gqt | 77 -- .../testassets/test_conditions_01/1.gqt | 77 -- .../testassets/test_conditions_01/test.yml | 82 --- .../testassets/test_conditions_02/0.gqt | 20 - .../testassets/test_conditions_02/1.gqt | 20 - .../testassets/test_conditions_02/test.yml | 25 - .../testassets/test_conditions_03/0.gqt | 20 - .../testassets/test_conditions_03/test.yml | 36 - .../testassets/test_conditions_04/0.gqt | 8 - .../testassets/test_conditions_04/1.gqt | 8 - .../testassets/test_conditions_04/test.yml | 13 - .../assets/testassets/test_enum_00/0.gqt | 6 - .../assets/testassets/test_enum_00/1.gqt | 6 - .../assets/testassets/test_enum_00/test.yml | 12 - .../assets/testassets/test_fragments_00/0.gqt | 6 - .../testassets/test_fragments_00/test.yml | 11 - .../assets/testassets/test_fragments_01/0.gqt | 9 - .../testassets/test_fragments_01/test.yml | 14 - .../assets/testassets/test_fragments_02/0.gqt | 11 - .../testassets/test_fragments_02/test.yml | 31 - .../assets/testassets/test_fragments_03/0.gqt | 17 - .../testassets/test_fragments_03/test.yml | 29 - .../testassets/test_general_logic_00/0.gqt | 3 - .../testassets/test_general_logic_00/test.yml | 8 - .../testassets/test_general_logic_01/0.gqt | 8 - .../testassets/test_general_logic_01/test.yml | 13 - .../testassets/test_general_logic_02/0.gqt | 10 - .../testassets/test_general_logic_02/test.yml | 15 - .../testassets/test_general_logic_03/0.gqt | 12 - .../testassets/test_general_logic_03/test.yml | 7 - .../testassets/test_general_logic_04/0.gqt | 12 - .../testassets/test_general_logic_04/test.yml | 12 - .../testassets/test_general_logic_05/0.gqt | 12 - .../testassets/test_general_logic_05/1.gqt | 5 - .../testassets/test_general_logic_05/test.yml | 10 - .../testassets/test_general_logic_06/0.gqt | 10 - .../testassets/test_general_logic_06/1.gqt | 5 - .../testassets/test_general_logic_06/test.yml | 10 - .../testassets/test_general_logic_07/0.gqt | 13 - .../testassets/test_general_logic_07/test.yml | 16 - .../testassets/test_general_logic_08/0.gqt | 15 - .../testassets/test_general_logic_08/test.yml | 18 - .../testassets/test_general_logic_09/0.gqt | 6 - .../testassets/test_general_logic_09/test.yml | 10 - .../testassets/test_general_logic_10/0.gqt | 12 - .../testassets/test_general_logic_10/1.gqt | 14 - .../testassets/test_general_logic_10/2.gqt | 15 - .../testassets/test_general_logic_10/test.yml | 14 - .../testassets/test_general_logic_11/0.gqt | 10 - .../testassets/test_general_logic_11/1.gqt | 10 - .../testassets/test_general_logic_11/2.gqt | 10 - .../testassets/test_general_logic_11/test.yml | 15 - .../testassets/test_identical_fields_00/0.gqt | 3 - .../test_identical_fields_00/test.yml | 9 - .../tests/assets/testassets/test_map_00/0.gqt | 24 - .../assets/testassets/test_map_00/test.yml | 27 - .../tests/assets/testassets/test_max_00/0.gqt | 6 - .../assets/testassets/test_max_00/test.yml | 8 - .../tests/assets/testassets/test_max_02/0.gqt | 7 - .../tests/assets/testassets/test_max_02/1.gqt | 7 - .../assets/testassets/test_max_02/test.yml | 9 - .../tests/assets/testassets/test_max_03/0.gqt | 10 - .../assets/testassets/test_max_03/test.yml | 9 - .../testassets/test_max_complex_00/0.gqt | 38 - .../testassets/test_max_complex_00/test.yml | 19 - .../assets/testassets/test_negation_00/0.gqt | 7 - .../testassets/test_negation_00/test.yml | 11 - .../assets/testassets/test_negation_01/0.gqt | 7 - .../testassets/test_negation_01/test.yml | 12 - .../assets/testassets/test_nesting_00/0.gqt | 18 - .../testassets/test_nesting_00/test.yml | 23 - .../testassets/test_special_fields_00/0.gqt | 4 - .../test_special_fields_00/test.yml | 8 - .../testassets/test_token_null_00/0.gqt | 4 - .../testassets/test_token_null_00/test.yml | 9 - .../assets/testassets/test_variables_00/0.gqt | 42 -- .../testassets/test_variables_00/test.yml | 83 --- pkg/engine/tests/engine_test.go | 278 +++---- .../tests/tests/starwars/hero_minimal.yaml | 12 + pkg/server/server_test.go | 695 +++++++++--------- .../inputs_schema/test_arr_str_eq.yaml | 72 -- .../{f.gqt => query_hero_and_reviews.gqt} | 7 +- .../{c.gqt => query_hero_expanded.gqt} | 12 +- .../{b.gqt => query_hero_friends.gqt} | 8 +- ..._hero_friends_connection_and_starship.gqt} | 14 +- .../{a.gqt => query_hero_minimal.gqt} | 6 +- ...> query_hero_with_friends_and_reviews.gqt} | 8 +- .../{max_1.gqt => query_human_max2.gqt} | 6 +- .../test1/all_services/testservice.yaml | 6 - .../test1/all_templates/template_mut.gqt | 16 - .../test1/all_templates/template_qry.gqt | 12 - pkg/testsetup/test1/config.yml | 5 - .../test1/enabled_services/testservice.yaml | 6 - .../test1/enabled_templates/template_mut.gqt | 16 - .../test1/enabled_templates/template_qry.gqt | 12 - pkg/testsetup/test1/test_qry_ok.yaml | 73 -- pkg/testsetup/test1/x_test_mut_ok.yaml | 65 -- pkg/testsetup/testsetup.go | 154 +--- pkg/testsetup/testsetup_test.go | 15 +- 131 files changed, 543 insertions(+), 3712 deletions(-) delete mode 100644 pkg/engine/tests/assets/benchassets/_bench_deep/bench.yml delete mode 100644 pkg/engine/tests/assets/benchassets/bench_average/bench.yml delete mode 100644 pkg/engine/tests/assets/benchassets/bench_big/bench.yml delete mode 100644 pkg/engine/tests/assets/benchassets/templates/0.gqt delete mode 100644 pkg/engine/tests/assets/benchassets/templates/1.gqt delete mode 100644 pkg/engine/tests/assets/benchassets/templates/2.gqt delete mode 100644 pkg/engine/tests/assets/benchassets/templates/3.gqt delete mode 100644 pkg/engine/tests/assets/benchassets/templates/4.gqt delete mode 100644 pkg/engine/tests/assets/benchassets/templates/5.gqt delete mode 100644 pkg/engine/tests/assets/benchassets/templates/6.gqt delete mode 100644 pkg/engine/tests/assets/benchassets/templates/7.gqt delete mode 100644 pkg/engine/tests/assets/benchassets/templates/8.tgqt delete mode 100644 pkg/engine/tests/assets/testassets/test_arguments_merge_00/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_arguments_merge_00/1.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_arguments_merge_00/2.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_arguments_merge_00/3.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_arguments_merge_00/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_arguments_merge_01/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_arguments_merge_01/1.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_arguments_merge_01/2.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_arguments_merge_01/3.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_arguments_merge_01/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_complex_00/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_complex_00/1.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_complex_00/2.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_complex_00/3.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_complex_00/4.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_complex_00/5.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_complex_00/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_conditions_00/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_conditions_00/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_conditions_01/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_conditions_01/1.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_conditions_01/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_conditions_02/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_conditions_02/1.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_conditions_02/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_conditions_03/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_conditions_03/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_conditions_04/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_conditions_04/1.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_conditions_04/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_enum_00/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_enum_00/1.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_enum_00/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_fragments_00/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_fragments_00/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_fragments_01/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_fragments_01/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_fragments_02/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_fragments_02/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_fragments_03/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_fragments_03/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_00/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_00/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_01/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_01/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_02/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_02/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_03/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_03/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_04/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_04/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_05/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_05/1.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_05/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_06/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_06/1.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_06/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_07/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_07/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_08/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_08/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_09/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_09/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_10/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_10/1.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_10/2.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_10/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_11/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_11/1.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_11/2.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_general_logic_11/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_identical_fields_00/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_identical_fields_00/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_map_00/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_map_00/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_max_00/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_max_00/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_max_02/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_max_02/1.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_max_02/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_max_03/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_max_03/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_max_complex_00/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_max_complex_00/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_negation_00/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_negation_00/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_negation_01/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_negation_01/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_nesting_00/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_nesting_00/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_special_fields_00/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_special_fields_00/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_token_null_00/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_token_null_00/test.yml delete mode 100644 pkg/engine/tests/assets/testassets/test_variables_00/0.gqt delete mode 100644 pkg/engine/tests/assets/testassets/test_variables_00/test.yml create mode 100644 pkg/engine/tests/tests/starwars/hero_minimal.yaml delete mode 100644 pkg/testsetup/inputs_schema/test_arr_str_eq.yaml rename pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/{f.gqt => query_hero_and_reviews.gqt} (65%) rename pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/{c.gqt => query_hero_expanded.gqt} (69%) rename pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/{b.gqt => query_hero_friends.gqt} (72%) rename pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/{e.gqt => query_hero_friends_connection_and_starship.gqt} (66%) rename pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/{a.gqt => query_hero_minimal.gqt} (64%) rename pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/{d.gqt => query_hero_with_friends_and_reviews.gqt} (70%) rename pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/{max_1.gqt => query_human_max2.gqt} (66%) delete mode 100644 pkg/testsetup/test1/all_services/testservice.yaml delete mode 100644 pkg/testsetup/test1/all_templates/template_mut.gqt delete mode 100644 pkg/testsetup/test1/all_templates/template_qry.gqt delete mode 100644 pkg/testsetup/test1/config.yml delete mode 100644 pkg/testsetup/test1/enabled_services/testservice.yaml delete mode 100644 pkg/testsetup/test1/enabled_templates/template_mut.gqt delete mode 100644 pkg/testsetup/test1/enabled_templates/template_qry.gqt delete mode 100644 pkg/testsetup/test1/test_qry_ok.yaml delete mode 100644 pkg/testsetup/test1/x_test_mut_ok.yaml diff --git a/pkg/engine/playmon/bench_test.go b/pkg/engine/playmon/bench_test.go index 74acff8..654929a 100644 --- a/pkg/engine/playmon/bench_test.go +++ b/pkg/engine/playmon/bench_test.go @@ -1,35 +1,35 @@ package playmon_test -import ( - "testing" +// import ( +// "testing" - "github.com/graph-guard/ggproxy/pkg/config" - "github.com/graph-guard/ggproxy/pkg/engine/playmon" - "github.com/graph-guard/ggproxy/pkg/gqlparse" - "github.com/graph-guard/ggproxy/pkg/testsetup" -) +// "github.com/graph-guard/ggproxy/pkg/config" +// "github.com/graph-guard/ggproxy/pkg/engine/playmon" +// "github.com/graph-guard/ggproxy/pkg/gqlparse" +// "github.com/graph-guard/ggproxy/pkg/testsetup" +// ) -var GS string +// // var GS string -func BenchmarkMatchStarwars(b *testing.B) { - s := testsetup.Starwars() - service := s.Config.ServicesEnabled[0] - e := playmon.New(service) - b.ResetTimer() - for n := 0; n < b.N; n++ { - e.Match( - []byte(s.Tests[1].Client.Input.BodyJSON["query"].(string)), - nil, nil, - func(operation, selectionSet []gqlparse.Token) (stop bool) { - return false - }, - func(t *config.Template) (stop bool) { - GS = t.ID - return false - }, - func(err error) { - b.Fatal("unexpected error:", err) - }, - ) - } -} +// // func BenchmarkMatchStarwars(b *testing.B) { +// // s := testsetup.Starwars() +// // service := s.Config.ServicesEnabled[0] +// // e := playmon.New(service) +// // b.ResetTimer() +// // for n := 0; n < b.N; n++ { +// // e.Match( +// // []byte(s.Tests[1].Client.Input.BodyJSON["query"].(string)), +// // nil, nil, +// // func(operation, selectionSet []gqlparse.Token) (stop bool) { +// // return false +// // }, +// // func(t *config.Template) (stop bool) { +// // GS = t.ID +// // return false +// // }, +// // func(err error) { +// // b.Fatal("unexpected error:", err) +// // }, +// // ) +// // } +// // } diff --git a/pkg/engine/tests/assets/benchassets/_bench_deep/bench.yml b/pkg/engine/tests/assets/benchassets/_bench_deep/bench.yml deleted file mode 100644 index b24538a..0000000 --- a/pkg/engine/tests/assets/benchassets/_bench_deep/bench.yml +++ /dev/null @@ -1,120 +0,0 @@ -query: | - query X { - a { - a0 { - a00( - a00_0: { - a00_00: { - a00_000: -273 - } - a00_01: { - a00_010: "eagle" - a00_011: "hawk" - a00_012: "falcon" - } - a00_02: { - a00_020: [ "eagle", "hawk", "falcon" ] - a00_031: [ -273, -273, -273 ] - } - a00_03: { - a00_030: [ - { - a00_030x0: [0, 1] - a00_030x1: "x" - }, - { - a00_030x0: [1, 0] - a00_030x1: "y" - } - ] - } - } - a00_1: { - a00_10: { - a00_100: { - a00_1000: { - a00_10000: [ -13, -88 ] - a00_10001: "deep" - a00_10002: "not shallow" - } - } - a00_101: { - a00_1010: "hohoho" - a00_1011: -1 - } - } - } - ) { - a000( - a000_0: { - a000_00: { - a000_000: "coffe?" - a000_001: [ "yes", "please" ] - } - } - a000_1: { - a000_10: 0 - } - ) { - a0000( - a0000_0: { - a0000_00: 65535 - a0000_01: { - a0000_010: [ - { - a0000_01000: "I need your clothes, boots and motorcycle" - } - ] - } - } - ) - a0001 { - a00000( - a00000_0: { - a00000_00: { - a00000_000: { - a00000_0000: "Not enought wood" - a00000_0001: "Milord" - } - } - } - ) - } - } - a001( - a001_0: "surprise!" - a001_1: { - a001_10: { - a001_100: { - a001_1001: true - a001_1002: [false, true] - } - a001_101: { - a001_1010: { - a001_10100: "enough" - } - } - } - } - ) - } - } - a1 { - a10 { - a100( - a100_0: { - a100_00: [0, 1, 2, 3] - } - ) - a101( - a101_0: { - a101_00: { - a101_000: "putin huilo" - } - } - ) - } - } - } - } -operationName: X diff --git a/pkg/engine/tests/assets/benchassets/bench_average/bench.yml b/pkg/engine/tests/assets/benchassets/bench_average/bench.yml deleted file mode 100644 index 54396f4..0000000 --- a/pkg/engine/tests/assets/benchassets/bench_average/bench.yml +++ /dev/null @@ -1,17 +0,0 @@ -query: | - query X { - a { - a0( - a0_0: { - a0_01: { - a0_010: JEDI - } - a0_00: [[0, 1]] - } - a0_1: 0 - ) { - a01 - } - } - } -operationName: X diff --git a/pkg/engine/tests/assets/benchassets/bench_big/bench.yml b/pkg/engine/tests/assets/benchassets/bench_big/bench.yml deleted file mode 100644 index 1273028..0000000 --- a/pkg/engine/tests/assets/benchassets/bench_big/bench.yml +++ /dev/null @@ -1,145 +0,0 @@ -query: | - query X ( - $b01_0: String! = "alive" - $b00_0: [Input!]! = [ - {b00_0x0: -273} - {b00_0x0: -273} - ] - ) { - ... on Query { - ... f1 - } - a { - a0( - a0_0: { - a0_00: [ - [ - { - a0_00xx0: [1, 0, 1] - a0_00xx1: [0, 1, 2, 3] - } - { - a0_00xx0: [69, -69] - a0_00xx1: [0, -1] - } - ] - [ - { - a0_00xx0: [-1, 0, 1] - a0_00xx1: [0, 1] - } - { - a0_00xx0: [1, 2] - a0_00xx1: [-1, -2] - } - ] - ] - a0_01: { - a0_010: JEDI - } - } - a0_1: -1 - ) { - a00( - a00_0: ["foo", "bar"] - a00_1: { - a00_10: 0 - a00_11: "lol" - } - ) - a01 - } - } - c( - c_0: [ - [ - [ - { - c_0xxx0: "too" - } - { - c_0xxx0: "deep" - } - ] - [ - { - c_0xxx0: "deep" - } - { - c_0xxx0: "again" - } - ] - [ - { - c_0xxx0: "1" - } - { - c_0xxx0: "2" - } - ] - ] - [ - [ - { - c_0xxx0: "what is this!?" - } - { - c_0xxx0: "what is that?!" - } - ] - [ - { - c_0xxx0: "nothing" - } - { - c_0xxx0: "special" - } - ] - [ - { - c_0xxx0: "just the wind" - } - { - c_0xxx0: "..." - } - ] - ] - ] - ) - } - - fragment f1 on Query { - ... f2 - } - - fragment f2 on Query { - b { - ... { - ... f3 - ... f3 - } - } - } - - fragment f3 on Something { - b0 { - b00( - b00_0: $b00_0 - b00_1: [ -13, -88 ] - ) - b01( - b01_0: $b01_0 - ) { - b010 - } - } - } -operationName: X -variables: | - { - "b01_0": "alive", - "b00_0": [ - {"b00_0x0": -273}, - {"b00_0x0": -273} - ] - } diff --git a/pkg/engine/tests/assets/benchassets/templates/0.gqt b/pkg/engine/tests/assets/benchassets/templates/0.gqt deleted file mode 100644 index 522f972..0000000 --- a/pkg/engine/tests/assets/benchassets/templates/0.gqt +++ /dev/null @@ -1,48 +0,0 @@ -query { - a { - combine 1 { - a0( - a0_0: val = { - a0_01: val = { - a0_010: val = JEDI - } - a0_00: val = [ ... len <= 2 ] - } - a0_1: val <= 0 && val >= -9 - ) { - combine 2 { - a00( - a00_0: val = [ ... bytelen = 3 ] - a00_1: val = { - a00_11: val != "kek" && bytelen = 3 - a00_10: val < 1 || val > 9 - } - ) - a01 - a02 - } - } - a1 - } - } - c(c_0: val = [ ... val = [ ... len = 2 ] ]) - ... on Query { - b { - b0 { - b00( - b00_0: val = [ - ... val = { - b00_0x0: val = -273 - } - ] - b00_1: val = [ ... val <= 0 && val > -99 ] - ) - b01( - b01_0: bytelen > 1 - ) { - b010 - } - } - } - } -} diff --git a/pkg/engine/tests/assets/benchassets/templates/1.gqt b/pkg/engine/tests/assets/benchassets/templates/1.gqt deleted file mode 100644 index 6b786c9..0000000 --- a/pkg/engine/tests/assets/benchassets/templates/1.gqt +++ /dev/null @@ -1,47 +0,0 @@ -query { - a { - combine 2 { - a0( - a0_0: val = { - a0_01: val = { - a0_010: val = "JEDI" - } - a0_00: val = [ ... len <= 2 ] - } - a0_1: val <= 0 - ) { - a01 - a00( - a00_0: val = [ ... bytelen = 3 ] - a00_1: val = { - a00_11: val != "kek" - a00_10: val < 1 - } - ) - } - a1 - a2 - } - } - b { - b0 { - b00( - b00_0: val = [ - ... val = { - b00_0x0: val = -273 - } - ] - b00_1: val = [ ... val <= 0 ] - ) - b01( - b01_0: bytelen > 1 - ) { - b010 - } - } - } - c(c_0: val = [ ... val = [ ... len = 2 ] ]) - d { - d0(d0_0: val > 0) - } -} diff --git a/pkg/engine/tests/assets/benchassets/templates/2.gqt b/pkg/engine/tests/assets/benchassets/templates/2.gqt deleted file mode 100644 index 239b147..0000000 --- a/pkg/engine/tests/assets/benchassets/templates/2.gqt +++ /dev/null @@ -1,25 +0,0 @@ -query { - a { - a0( - a0_0: val != { - a0_01: val != { - a0_010: val = JEDI - } - a0_00: val = [ ... len <= 2 ] - } - a0_1: val <= 0 - ) { - combine 1 { - a01 - a00( - a00_0: val = [ ... bytelen > 0 ] - a00_1: val = { - a00_10: val = 0 - a00_11: val = "lol" - } - ) - } - } - } - c(c_0: val = [ ... len = 3 ]) -} diff --git a/pkg/engine/tests/assets/benchassets/templates/3.gqt b/pkg/engine/tests/assets/benchassets/templates/3.gqt deleted file mode 100644 index 7e10db0..0000000 --- a/pkg/engine/tests/assets/benchassets/templates/3.gqt +++ /dev/null @@ -1,55 +0,0 @@ -query { - ... on Query { - b { - b0 { - b00( - b00_0: val = [ - val != { - b00_0x0: val > 0 - } - val = { - b00_0x0: val >= -999 - } - ] - ) - b01( - b01_0: bytelen > 1 - ) { - b010 - } - } - } - } - a { - a0( - a0_0: val = { - a0_01: val = { - a0_010: bytelen < 10 - } - a0_00: val = [ - val = [ - val = { - a0_00xx0: len < 5 - a0_00xx1: len >= 1 - } - val = { - a0_00xx0: val = [val >= 69, val <= -69] - a0_00xx1: val = [val = 0, val != 0] - } - ] - val = [ val = 2, val = 42] - ] - } - a0_1: val <= 0 - ) { - a01 - a00( - a00_0: val = [ ... bytelen > 0 ] - a00_1: val = { - a00_10: val = 0 - a00_11: val = "lol" - } - ) - } - } -} diff --git a/pkg/engine/tests/assets/benchassets/templates/4.gqt b/pkg/engine/tests/assets/benchassets/templates/4.gqt deleted file mode 100644 index 04f4dbe..0000000 --- a/pkg/engine/tests/assets/benchassets/templates/4.gqt +++ /dev/null @@ -1,46 +0,0 @@ -query { - c(c_0: val = [ ... len = 3 ]) - ... on Query { - b { - b0 { - b00( - b00_0: val = [ - ... val = { - b00_0x0: val < 0 - } - ] - b00_1: val = [ ... val <= 0 && val > -99 ] - ) - b01( - b01_0: bytelen > 1 - ) { - b010 - } - } - } - } - a { - a0( - a0_0: val = { - a0_00: val = [ ... len <= 2 ] - a0_01: val = { - a0_010: val = JEDI - } - } - a0_1: val <= 0 - ) { - combine 2 { - a01 - a02 - a00( - a00_0: val = [ ... bytelen > 0 ] - a00_1: val = { - a00_10: val = 0 - a00_11: val = "lol" - } - ) - } - } - a1 - } -} diff --git a/pkg/engine/tests/assets/benchassets/templates/5.gqt b/pkg/engine/tests/assets/benchassets/templates/5.gqt deleted file mode 100644 index d029877..0000000 --- a/pkg/engine/tests/assets/benchassets/templates/5.gqt +++ /dev/null @@ -1,45 +0,0 @@ -query { - a { - a0( - a0_0: val != { - a0_01: val != { - a0_010: val != JEDI - } - a0_00: val = [ ... len <= 2 ] - } - a0_1: val <= 0 - ) { - combine 1 { - a01 - a00( - a00_0: val = [ ... bytelen > 0 ] - a00_1: val = { - a00_10: val = 0 - a00_11: val = "lol" - } - ) - } - } - } - b { - b0 { - b00( - b00_0: val = [ - val != { - b00_0x0: val > 0 - } - val = { - b00_0x0: val >= -999 - } - ] - b00_1: val = [ ... val < 0 ] - ) - b01( - b01_0: bytelen > 1 - ) { - b010 - } - } - } - c(c_0: val = [ ... len = 3 ]) -} diff --git a/pkg/engine/tests/assets/benchassets/templates/6.gqt b/pkg/engine/tests/assets/benchassets/templates/6.gqt deleted file mode 100644 index d08132f..0000000 --- a/pkg/engine/tests/assets/benchassets/templates/6.gqt +++ /dev/null @@ -1,118 +0,0 @@ -query { - a { - a0 { - a00( - a00_0: val = { - a00_00: val = { - a00_000: val = -273 - } - a00_01: val = { - a00_010: val = "eagle" - a00_011: val = "hawk" - a00_012: val = "falcon" - } - a00_02: val = { - a00_020: val = [val = "eagle", val = "hawk", val = "falcon"] - a00_031: val = [val = -273, val = -273, val = -273] - } - a00_03: val = { - a00_030: val = [ - val = { - a00_030x0: val = [val = 0, val = 1] - a00_030x1: val = "x" - }, - val = { - a00_030x0: val = [val = 1, val = 0] - a00_030x1: val = "y" - } - ] - } - } - a00_1: val = { - a00_10: val = { - a00_100: val = { - a00_1000: val = { - a00_10000: val = [val = -13, val = -88] - a00_10001: val = "deep" - a00_10002: val = "not shallow" - } - } - a00_101: val = { - a00_1010: val = "hohoho" - a00_1011: val = -1 - } - } - } - ) { - a000( - a000_0: val = { - a000_00: val = { - a000_000: val = "coffe?" - a000_001: val = [val = "yes", val = "please"] - } - } - a000_1: val = { - a000_10: val = 0 - } - ) { - a0000( - a0000_0: val = { - a0000_00: val = 65535 - a0000_01: val = { - a0000_010: val = [ - val = { - a0000_01000: val = "I need your clothes, boots and motorcycle" - } - ] - } - } - ) - a0001 { - a00000( - a00000_0: val = { - a00000_00: val = { - a00000_000: val = { - a00000_0000: val = "Not enought wood" - a00000_0001: val = "Milord" - } - } - } - ) - } - } - a001( - a001_0: val = "surprise!" - a001_1: val = { - a001_10: val = { - a001_100: val = { - a001_1001: val = true - a001_1002: val = [val = false, val = true] - } - a001_101: val = { - a001_1010: val = { - a001_10100: val = "enough" - } - } - } - } - ) - } - } - a1 { - a10 { - a100( - a100_0: val = { - a100_00: val = [val = 0, val = 1, val = 2, val = 3] - } - ) - a101( - a101_0: val = { - a101_00: val = { - a101_000: val = "putin huilo" - } - } - ) - } - } - } -} diff --git a/pkg/engine/tests/assets/benchassets/templates/7.gqt b/pkg/engine/tests/assets/benchassets/templates/7.gqt deleted file mode 100644 index 9ea67cf..0000000 --- a/pkg/engine/tests/assets/benchassets/templates/7.gqt +++ /dev/null @@ -1,118 +0,0 @@ -query { - a { - a0 { - a00( - a00_0: val = { - a00_00: val = { - a00_000: val < 0 - } - a00_01: val = { - a00_010: val != "hawk" - a00_011: val != "falcon" - a00_012: val != "eagle" - } - a00_02: val = { - a00_020: val = [ ... val != "cock" ] - a00_031: val = [ ... val = -273 ] - } - a00_03: val = { - a00_030: val = [ - val = { - a00_030x0: len > 0 - a00_030x1: val != "y" - }, - val != { - a00_030x0: len = 0 - a00_030x1: val != "x" - } - ] - } - } - a00_1: val = { - a00_10: val = { - a00_100: val != { - a00_1000: val != { - a00_10000: val = [val = -13, val = -88] - a00_10001: val = "deep" - a00_10002: val = "not shallow" - } - } - a00_101: val = { - a00_1010: bytelen > 0 - a00_1011: val < 0 - } - } - } - ) { - a000( - a000_0: val = { - a000_00: val = { - a000_000: bytelen > 0 - a000_001: val = [ ... bytelen > 0 ] - } - } - a000_1: val != { - a000_10: val != 0 - } - ) { - a0000( - a0000_0: val = { - a0000_00: val < 65536 - a0000_01: val != { - a0000_010: val != [ - val != { - a0000_01000: val != "I need your clothes, boots and motorcycle" - } - ] - } - } - ) - a0001 { - a00000( - a00000_0: val = { - a00000_00: val != { - a00000_000: val != { - a00000_0000: val = "Not enought wood" - a00000_0001: val = "Milord" - } - } - } - ) - } - } - a001( - a001_0: val != "no prise" - a001_1: val = { - a001_10: val = { - a001_100: val = { - a001_1001: val = true - a001_1002: val = [val = false, val = true] - } - a001_101: val = { - a001_1010: val = { - a001_10100: val = "enough" - } - } - } - } - ) - } - } - a1 { - a10 { - a100( - a100_0: val = { - a100_00: val = [ ... val >= 0 ] - } - ) - a101( - a101_0: val = { - a101_00: val = { - a101_000: val != "putin ne huilo" - } - } - ) - } - } - } -} diff --git a/pkg/engine/tests/assets/benchassets/templates/8.tgqt b/pkg/engine/tests/assets/benchassets/templates/8.tgqt deleted file mode 100644 index 452d316..0000000 --- a/pkg/engine/tests/assets/benchassets/templates/8.tgqt +++ /dev/null @@ -1,43 +0,0 @@ -query { - a { - a0( - a0_0: val = { - a0_01: val = { - a0_010: val = "yo" - } - a0_00: val = [ ... len <= 2 ] - } - a0_1: val <= 0 - ) { - a01 - a00( - a00_0: val = [ ... bytelen = 3 ] - a00_1: val = { - a00_11: val != "kek" - a00_10: val < 1 - } - ) - } - } - b { - b0 { - b00( - b00_0: val = [ - ... val = { - b00_0x0: val = -273 - } - ] - b00_1: val = [ ... val <= 0 ] - ) - b01( - b01_0: bytelen > 1 - ) { - b010 - } - } - } - c(c_0: val = [ ... val = [ ... len = 2 ] ]) - d { - d0(d0_0: val > 0) - } -} diff --git a/pkg/engine/tests/assets/testassets/test_arguments_merge_00/0.gqt b/pkg/engine/tests/assets/testassets/test_arguments_merge_00/0.gqt deleted file mode 100644 index 490e9f7..0000000 --- a/pkg/engine/tests/assets/testassets/test_arguments_merge_00/0.gqt +++ /dev/null @@ -1,12 +0,0 @@ -query { - a( - a_0: val = [val = 0] - a_1: val = [val = [ val = 0 ]] - a_2: val = [ - val = { - a_20: val = 0 - } - ] - a_3: val = [ ... bytelen > 0 ] - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_arguments_merge_00/1.gqt b/pkg/engine/tests/assets/testassets/test_arguments_merge_00/1.gqt deleted file mode 100644 index 490e9f7..0000000 --- a/pkg/engine/tests/assets/testassets/test_arguments_merge_00/1.gqt +++ /dev/null @@ -1,12 +0,0 @@ -query { - a( - a_0: val = [val = 0] - a_1: val = [val = [ val = 0 ]] - a_2: val = [ - val = { - a_20: val = 0 - } - ] - a_3: val = [ ... bytelen > 0 ] - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_arguments_merge_00/2.gqt b/pkg/engine/tests/assets/testassets/test_arguments_merge_00/2.gqt deleted file mode 100644 index 2be40fd..0000000 --- a/pkg/engine/tests/assets/testassets/test_arguments_merge_00/2.gqt +++ /dev/null @@ -1,13 +0,0 @@ -query { - a( - a_0: val = [val = 0, val = 1] - a_1: val = [val = [ val = 0 ], val = [ val = 1 ]] - a_2: val = [ - val = { - a_20: val = 0 - a_21: val = 1 - } - ] - a_3: val = [ bytelen > 0 ] - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_arguments_merge_00/3.gqt b/pkg/engine/tests/assets/testassets/test_arguments_merge_00/3.gqt deleted file mode 100644 index d8e2c70..0000000 --- a/pkg/engine/tests/assets/testassets/test_arguments_merge_00/3.gqt +++ /dev/null @@ -1,12 +0,0 @@ -query { - a( - a_0: val = [val = 1] - a_1: val = [val = [ val = 1 ]] - a_2: val = [ - val = { - a_20: val = 1 - } - ] - a_3: val = [ ... bytelen = 0 ] - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_arguments_merge_00/test.yml b/pkg/engine/tests/assets/testassets/test_arguments_merge_00/test.yml deleted file mode 100644 index 60373b3..0000000 --- a/pkg/engine/tests/assets/testassets/test_arguments_merge_00/test.yml +++ /dev/null @@ -1,16 +0,0 @@ -query: | - query X { - a( - a_0: [0] - a_1: [[0]] - a_2: [{ - a_20: 0 - }] - a_3: [ "0", "00" ] - ) - } -operationName: X -variables: -expect: - - 0 - - 1 diff --git a/pkg/engine/tests/assets/testassets/test_arguments_merge_01/0.gqt b/pkg/engine/tests/assets/testassets/test_arguments_merge_01/0.gqt deleted file mode 100644 index e122030..0000000 --- a/pkg/engine/tests/assets/testassets/test_arguments_merge_01/0.gqt +++ /dev/null @@ -1,8 +0,0 @@ -query { - a( - a_0: val = 0 - a_1: val = { - a_10: val = 0 - } - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_arguments_merge_01/1.gqt b/pkg/engine/tests/assets/testassets/test_arguments_merge_01/1.gqt deleted file mode 100644 index e122030..0000000 --- a/pkg/engine/tests/assets/testassets/test_arguments_merge_01/1.gqt +++ /dev/null @@ -1,8 +0,0 @@ -query { - a( - a_0: val = 0 - a_1: val = { - a_10: val = 0 - } - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_arguments_merge_01/2.gqt b/pkg/engine/tests/assets/testassets/test_arguments_merge_01/2.gqt deleted file mode 100644 index 4aabadd..0000000 --- a/pkg/engine/tests/assets/testassets/test_arguments_merge_01/2.gqt +++ /dev/null @@ -1,9 +0,0 @@ -query { - a( - a_0: val = 0 - a_1: val = { - a_10: val = 0 - a_11: val = 0 - } - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_arguments_merge_01/3.gqt b/pkg/engine/tests/assets/testassets/test_arguments_merge_01/3.gqt deleted file mode 100644 index 4de37d5..0000000 --- a/pkg/engine/tests/assets/testassets/test_arguments_merge_01/3.gqt +++ /dev/null @@ -1,8 +0,0 @@ -query { - a( - a_0: val != 0 - a_1: val != { - a_10: val = 0 - } - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_arguments_merge_01/test.yml b/pkg/engine/tests/assets/testassets/test_arguments_merge_01/test.yml deleted file mode 100644 index 001863c..0000000 --- a/pkg/engine/tests/assets/testassets/test_arguments_merge_01/test.yml +++ /dev/null @@ -1,15 +0,0 @@ -query: | - query X { - a( - a_0: 0 - a_1: { - a_10: 0 - } - ) - } -operationName: X -variables: -expect: - - 0 - - 1 - - 2 diff --git a/pkg/engine/tests/assets/testassets/test_complex_00/0.gqt b/pkg/engine/tests/assets/testassets/test_complex_00/0.gqt deleted file mode 100644 index 522f972..0000000 --- a/pkg/engine/tests/assets/testassets/test_complex_00/0.gqt +++ /dev/null @@ -1,48 +0,0 @@ -query { - a { - combine 1 { - a0( - a0_0: val = { - a0_01: val = { - a0_010: val = JEDI - } - a0_00: val = [ ... len <= 2 ] - } - a0_1: val <= 0 && val >= -9 - ) { - combine 2 { - a00( - a00_0: val = [ ... bytelen = 3 ] - a00_1: val = { - a00_11: val != "kek" && bytelen = 3 - a00_10: val < 1 || val > 9 - } - ) - a01 - a02 - } - } - a1 - } - } - c(c_0: val = [ ... val = [ ... len = 2 ] ]) - ... on Query { - b { - b0 { - b00( - b00_0: val = [ - ... val = { - b00_0x0: val = -273 - } - ] - b00_1: val = [ ... val <= 0 && val > -99 ] - ) - b01( - b01_0: bytelen > 1 - ) { - b010 - } - } - } - } -} diff --git a/pkg/engine/tests/assets/testassets/test_complex_00/1.gqt b/pkg/engine/tests/assets/testassets/test_complex_00/1.gqt deleted file mode 100644 index 6b786c9..0000000 --- a/pkg/engine/tests/assets/testassets/test_complex_00/1.gqt +++ /dev/null @@ -1,47 +0,0 @@ -query { - a { - combine 2 { - a0( - a0_0: val = { - a0_01: val = { - a0_010: val = "JEDI" - } - a0_00: val = [ ... len <= 2 ] - } - a0_1: val <= 0 - ) { - a01 - a00( - a00_0: val = [ ... bytelen = 3 ] - a00_1: val = { - a00_11: val != "kek" - a00_10: val < 1 - } - ) - } - a1 - a2 - } - } - b { - b0 { - b00( - b00_0: val = [ - ... val = { - b00_0x0: val = -273 - } - ] - b00_1: val = [ ... val <= 0 ] - ) - b01( - b01_0: bytelen > 1 - ) { - b010 - } - } - } - c(c_0: val = [ ... val = [ ... len = 2 ] ]) - d { - d0(d0_0: val > 0) - } -} diff --git a/pkg/engine/tests/assets/testassets/test_complex_00/2.gqt b/pkg/engine/tests/assets/testassets/test_complex_00/2.gqt deleted file mode 100644 index 97b8cc2..0000000 --- a/pkg/engine/tests/assets/testassets/test_complex_00/2.gqt +++ /dev/null @@ -1,23 +0,0 @@ -query { - a { - a0( - a0_0: val != { - a0_01: val != { - a0_010: val = "yo" - } - a0_00: val = [ ... len <= 2 ] - } - a0_1: val <= 0 - ) { - a01 - a00( - a00_0: val = [ ... bytelen > 0 ] - a00_1: val = { - a00_10: val = 0 - a00_11: val = "lol" - } - ) - } - } - c(c_0: val = [ ... len = 3 ]) -} diff --git a/pkg/engine/tests/assets/testassets/test_complex_00/3.gqt b/pkg/engine/tests/assets/testassets/test_complex_00/3.gqt deleted file mode 100644 index 7e10db0..0000000 --- a/pkg/engine/tests/assets/testassets/test_complex_00/3.gqt +++ /dev/null @@ -1,55 +0,0 @@ -query { - ... on Query { - b { - b0 { - b00( - b00_0: val = [ - val != { - b00_0x0: val > 0 - } - val = { - b00_0x0: val >= -999 - } - ] - ) - b01( - b01_0: bytelen > 1 - ) { - b010 - } - } - } - } - a { - a0( - a0_0: val = { - a0_01: val = { - a0_010: bytelen < 10 - } - a0_00: val = [ - val = [ - val = { - a0_00xx0: len < 5 - a0_00xx1: len >= 1 - } - val = { - a0_00xx0: val = [val >= 69, val <= -69] - a0_00xx1: val = [val = 0, val != 0] - } - ] - val = [ val = 2, val = 42] - ] - } - a0_1: val <= 0 - ) { - a01 - a00( - a00_0: val = [ ... bytelen > 0 ] - a00_1: val = { - a00_10: val = 0 - a00_11: val = "lol" - } - ) - } - } -} diff --git a/pkg/engine/tests/assets/testassets/test_complex_00/4.gqt b/pkg/engine/tests/assets/testassets/test_complex_00/4.gqt deleted file mode 100644 index 04f4dbe..0000000 --- a/pkg/engine/tests/assets/testassets/test_complex_00/4.gqt +++ /dev/null @@ -1,46 +0,0 @@ -query { - c(c_0: val = [ ... len = 3 ]) - ... on Query { - b { - b0 { - b00( - b00_0: val = [ - ... val = { - b00_0x0: val < 0 - } - ] - b00_1: val = [ ... val <= 0 && val > -99 ] - ) - b01( - b01_0: bytelen > 1 - ) { - b010 - } - } - } - } - a { - a0( - a0_0: val = { - a0_00: val = [ ... len <= 2 ] - a0_01: val = { - a0_010: val = JEDI - } - } - a0_1: val <= 0 - ) { - combine 2 { - a01 - a02 - a00( - a00_0: val = [ ... bytelen > 0 ] - a00_1: val = { - a00_10: val = 0 - a00_11: val = "lol" - } - ) - } - } - a1 - } -} diff --git a/pkg/engine/tests/assets/testassets/test_complex_00/5.gqt b/pkg/engine/tests/assets/testassets/test_complex_00/5.gqt deleted file mode 100644 index d029877..0000000 --- a/pkg/engine/tests/assets/testassets/test_complex_00/5.gqt +++ /dev/null @@ -1,45 +0,0 @@ -query { - a { - a0( - a0_0: val != { - a0_01: val != { - a0_010: val != JEDI - } - a0_00: val = [ ... len <= 2 ] - } - a0_1: val <= 0 - ) { - combine 1 { - a01 - a00( - a00_0: val = [ ... bytelen > 0 ] - a00_1: val = { - a00_10: val = 0 - a00_11: val = "lol" - } - ) - } - } - } - b { - b0 { - b00( - b00_0: val = [ - val != { - b00_0x0: val > 0 - } - val = { - b00_0x0: val >= -999 - } - ] - b00_1: val = [ ... val < 0 ] - ) - b01( - b01_0: bytelen > 1 - ) { - b010 - } - } - } - c(c_0: val = [ ... len = 3 ]) -} diff --git a/pkg/engine/tests/assets/testassets/test_complex_00/test.yml b/pkg/engine/tests/assets/testassets/test_complex_00/test.yml deleted file mode 100644 index c4eaac7..0000000 --- a/pkg/engine/tests/assets/testassets/test_complex_00/test.yml +++ /dev/null @@ -1,148 +0,0 @@ -query: | - query X ( - $b01_0: String! = "alive" - $b00_0: [Input!]! = [ - {b00_0x0: -273} - {b00_0x0: -273} - ] - ) { - ... on Query { - ... f1 - } - a { - a0( - a0_0: { - a0_00: [ - [ - { - a0_00xx0: [1, 0, 1] - a0_00xx1: [0, 1, 2, 3] - } - { - a0_00xx0: [69, -69] - a0_00xx1: [0, -1] - } - ] - [ - { - a0_00xx0: [-1, 0, 1] - a0_00xx1: [0, 1] - } - { - a0_00xx0: [1, 2] - a0_00xx1: [-1, -2] - } - ] - ] - a0_01: { - a0_010: JEDI - } - } - a0_1: -1 - ) { - a00( - a00_0: ["foo", "bar"] - a00_1: { - a00_10: 0 - a00_11: "lol" - } - ) - a01 - } - } - c( - c_0: [ - [ - [ - { - c_0xxx0: "too" - } - { - c_0xxx0: "deep" - } - ] - [ - { - c_0xxx0: "deep" - } - { - c_0xxx0: "again" - } - ] - [ - { - c_0xxx0: "1" - } - { - c_0xxx0: "2" - } - ] - ] - [ - [ - { - c_0xxx0: "what is this!?" - } - { - c_0xxx0: "what is that?!" - } - ] - [ - { - c_0xxx0: "nothing" - } - { - c_0xxx0: "special" - } - ] - [ - { - c_0xxx0: "just the wind" - } - { - c_0xxx0: "..." - } - ] - ] - ] - ) - } - - fragment f1 on Query { - ... f2 - } - - fragment f2 on Query { - b { - ... { - ... f3 - ... f3 - } - } - } - - fragment f3 on Something { - b0 { - b00( - b00_0: $b00_0 - b00_1: [ -13, -88 ] - ) - b01( - b01_0: $b01_0 - ) { - b010 - } - } - } -operationName: X -variables: | - { - "b01_0": "alive", - "b00_0": [ - {"b00_0x0": -273}, - {"b00_0x0": -273} - ] - } -expect: - - 0 - - 4 diff --git a/pkg/engine/tests/assets/testassets/test_conditions_00/0.gqt b/pkg/engine/tests/assets/testassets/test_conditions_00/0.gqt deleted file mode 100644 index 103cb41..0000000 --- a/pkg/engine/tests/assets/testassets/test_conditions_00/0.gqt +++ /dev/null @@ -1,34 +0,0 @@ -query { - a( - a_0: val = 0 - a_1: val != 0 - a_2: val < 0 - a_3: val <= 0 - a_4: val > 0 - a_5: val >= 0 - ) - b( - b_0: val = 0.0 - b_1: val != 0.0 - b_2: val < 0.0 - b_3: val <= 0.0 - b_4: val > 0.0 - b_5: val >= 0.0 - ) - c( - c_0: bytelen = 1 - c_1: bytelen != 1 - c_2: bytelen < 1 - c_3: bytelen <= 1 - c_4: bytelen > 1 - c_5: bytelen >= 1 - ) - d( - d_0: len = 1 - d_1: len != 1 - d_2: len < 1 - d_3: len <= 1 - d_4: len > 1 - d_5: len >= 1 - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_conditions_00/test.yml b/pkg/engine/tests/assets/testassets/test_conditions_00/test.yml deleted file mode 100644 index 2d40f1d..0000000 --- a/pkg/engine/tests/assets/testassets/test_conditions_00/test.yml +++ /dev/null @@ -1,39 +0,0 @@ -query: | - query X { - a( - a_0: 0 - a_1: 1 - a_2: -1 - a_3: 0 - a_4: 1 - a_5: 0 - ) - b( - b_0: 0.0 - b_1: 1.0 - b_2: -1.0 - b_3: 0.0 - b_4: 1.0 - b_5: 0.0 - ) - c( - c_0: "a" - c_1: "aa" - c_2: "" - c_3: "a" - c_4: "aa" - c_5: "a" - ) - d( - d_0: [0] - d_1: [0 0] - d_2: [] - d_3: [0] - d_4: [0 0] - d_5: [0] - ) - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_conditions_01/0.gqt b/pkg/engine/tests/assets/testassets/test_conditions_01/0.gqt deleted file mode 100644 index 8459074..0000000 --- a/pkg/engine/tests/assets/testassets/test_conditions_01/0.gqt +++ /dev/null @@ -1,77 +0,0 @@ -query { - a0( - a0_0: val = 0 - ) - a1( - a1_0: val != 0 - ) - a2( - a2_0: val < 0 - ) - a3( - a3_0: val <= 0 - ) - a4( - a4_0: val > 0 - ) - a5( - a5_0: val >= 0 - ) - - b0( - b0_0: val = 0.0 - ) - b1( - b1_0: val != 0.0 - ) - b2( - b2_0: val < 0.0 - ) - b3( - b3_0: val <= 0.0 - ) - b4( - b4_0: val > 0.0 - ) - b5( - b6_0: val >= 0.0 - ) - - c0( - c0_0: bytelen = 1 - ) - c1( - c1_0: bytelen != 1 - ) - c2( - c2_0: bytelen < 1 - ) - c3( - c3_0: bytelen <= 1 - ) - c4( - c4_0: bytelen > 1 - ) - c5( - c5_0: bytelen >= 1 - ) - - d0( - d0_0: len = 1 - ) - d1( - d1_0: len != 1 - ) - d2( - d2_0: len < 1 - ) - d3( - d3_0: len <= 1 - ) - d4( - d4_0: len > 1 - ) - d5( - d5_0: len >= 1 - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_conditions_01/1.gqt b/pkg/engine/tests/assets/testassets/test_conditions_01/1.gqt deleted file mode 100644 index cc76074..0000000 --- a/pkg/engine/tests/assets/testassets/test_conditions_01/1.gqt +++ /dev/null @@ -1,77 +0,0 @@ -query { - a0( - a0_0: val = 1 - ) - a1( - a1_0: val != 1 - ) - a2( - a2_0: val < 1 - ) - a3( - a3_0: val <= 1 - ) - a4( - a4_0: val > -1 - ) - a5( - a5_0: val >= -1 - ) - - b0( - b0_0: val = 1.0 - ) - b1( - b1_0: val != 1.0 - ) - b2( - b2_0: val < 1.0 - ) - b3( - b3_0: val <= 1.0 - ) - b4( - b4_0: val > -1.0 - ) - b5( - b5_0: val >= -1.0 - ) - - c0( - c0_0: bytelen = 0 - ) - c1( - c1_0: bytelen != 0 - ) - c2( - c2_0: bytelen < 2 - ) - c3( - c3_0: bytelen <= 2 - ) - c4( - c4_0: bytelen > 0 - ) - c5( - c5_0: bytelen >= 0 - ) - - d0( - d0_0: len = 0 - ) - d1( - d1_0: len != 0 - ) - d2( - d2_0: len < 2 - ) - d3( - d3_0: len <= 2 - ) - d4( - d4_0: len > 0 - ) - d5( - d5_0: len >= 0 - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_conditions_01/test.yml b/pkg/engine/tests/assets/testassets/test_conditions_01/test.yml deleted file mode 100644 index 546ddf2..0000000 --- a/pkg/engine/tests/assets/testassets/test_conditions_01/test.yml +++ /dev/null @@ -1,82 +0,0 @@ -query: | - query X { - a0( - a0_0: 1 - ) - a1( - a1_0: 0 - ) - a2( - a2_0: 0 - ) - a3( - a3_0: 1 - ) - a4( - a4_0: 0 - ) - a5( - a5_0: -1 - ) - - b0( - b0_0: 1.0 - ) - b1( - b1_0: 0.0 - ) - b2( - b2_0: 0.0 - ) - b3( - b3_0: 1.0 - ) - b4( - b4_0: 0.0 - ) - b5( - b5_0: -1.0 - ) - - c0( - c0_0: "" - ) - c1( - c1_0: "a" - ) - c2( - c2_0: "a" - ) - c3( - c3_0: "aa" - ) - c4( - c4_0: "a" - ) - c5( - c5_0: "" - ) - - d0( - d0_0: [] - ) - d1( - d1_0: [0] - ) - d2( - d2_0: [0] - ) - d3( - d3_0: [0 0] - ) - d4( - d4_0: [0] - ) - d5( - d5_0: [] - ) - } -operationName: X -variables: -expect: - - 1 diff --git a/pkg/engine/tests/assets/testassets/test_conditions_02/0.gqt b/pkg/engine/tests/assets/testassets/test_conditions_02/0.gqt deleted file mode 100644 index aa5c15f..0000000 --- a/pkg/engine/tests/assets/testassets/test_conditions_02/0.gqt +++ /dev/null @@ -1,20 +0,0 @@ -query { - a0( - a0_0: val < 0 - ) - - b0( - b0_0: val < 0.0 - ) - - c0( - c0_0: bytelen = 1 - ) - - d0( - d0_0: len = 1.0 - ) - d1( - d1_0: len = 1 - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_conditions_02/1.gqt b/pkg/engine/tests/assets/testassets/test_conditions_02/1.gqt deleted file mode 100644 index 6a8145a..0000000 --- a/pkg/engine/tests/assets/testassets/test_conditions_02/1.gqt +++ /dev/null @@ -1,20 +0,0 @@ -query { - a0( - a0_0: val < 0.0 - ) - - b0( - b0_0: val < 0 - ) - - c0( - c0_0: val = 0 - ) - - d0( - d0_0: len = 1 - ) - d1( - d1_0: val = 0 - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_conditions_02/test.yml b/pkg/engine/tests/assets/testassets/test_conditions_02/test.yml deleted file mode 100644 index 404ba43..0000000 --- a/pkg/engine/tests/assets/testassets/test_conditions_02/test.yml +++ /dev/null @@ -1,25 +0,0 @@ -query: | - query X { - a0( - a0_0: -1.0 - ) - - b0( - b0_0: -1 - ) - - c0( - c0_0: 0 - ) - - d0( - d0_0: [0] - ) - d1( - d1_0: 0 - ) - } -operationName: X -variables: -expect: - - 1 diff --git a/pkg/engine/tests/assets/testassets/test_conditions_03/0.gqt b/pkg/engine/tests/assets/testassets/test_conditions_03/0.gqt deleted file mode 100644 index ebf2c31..0000000 --- a/pkg/engine/tests/assets/testassets/test_conditions_03/0.gqt +++ /dev/null @@ -1,20 +0,0 @@ -query { - a( - a_0: val > 0 && val <= 9 || val < 0 && val >= -9 - a_1: val = NEWHOPE || val = EMPIRE - a_2: val = [ val = { - a_xx0: val = 0 - } - ] - a_3: val = [ val < 0 && val > -9, val > 0 ] - a_4: val = [ val <= 0 ] && val != [ val = -1 ] - a_5: val = [ ... bytelen > 0 && bytelen != 3 ] - a_6: val = [ ... val < 0 || val > 0 ] - a_7: val = [ ... val = [ val != 0 ] ] - a_8: val = [ ... val = { - a_xx0: val = 0 - } - ] - a_9: val = [ ... val = [ ... len > 0 ] ] - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_conditions_03/test.yml b/pkg/engine/tests/assets/testassets/test_conditions_03/test.yml deleted file mode 100644 index 780791f..0000000 --- a/pkg/engine/tests/assets/testassets/test_conditions_03/test.yml +++ /dev/null @@ -1,36 +0,0 @@ -query: | - query X { - a( - a_0: 9 - a_1: EMPIRE - a_2: [ - { - a_xx0: 0 - } - ] - a_3: [ -1, 1 ] - a_4: [ -2 ] - a_5: [ "test", "object" ] - a_6: [ -1 ] - a_7: [ [ 1 ] ] - a_8: [ - { - a_xx0: 0 - }, - { - a_xx0: 0 - } - ] - a_9: [ - [ - [ - 0 - ] - ] - ] - ) - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_conditions_04/0.gqt b/pkg/engine/tests/assets/testassets/test_conditions_04/0.gqt deleted file mode 100644 index cbff7df..0000000 --- a/pkg/engine/tests/assets/testassets/test_conditions_04/0.gqt +++ /dev/null @@ -1,8 +0,0 @@ -query { - a0( - a0_0: val > 0 && val <= 9 || val < 0 && val >= -9 - ) - a1( - a1_0: val > 0 && val < 9 - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_conditions_04/1.gqt b/pkg/engine/tests/assets/testassets/test_conditions_04/1.gqt deleted file mode 100644 index 2746cb5..0000000 --- a/pkg/engine/tests/assets/testassets/test_conditions_04/1.gqt +++ /dev/null @@ -1,8 +0,0 @@ -query { - a0( - a0_0: val >= 0 && val <= 9 || val < 0 && val >= -9 - ) - a1( - a1_0: val >= 0 && val < 9 - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_conditions_04/test.yml b/pkg/engine/tests/assets/testassets/test_conditions_04/test.yml deleted file mode 100644 index 72dc9a0..0000000 --- a/pkg/engine/tests/assets/testassets/test_conditions_04/test.yml +++ /dev/null @@ -1,13 +0,0 @@ -query: | - query X { - a0( - a0_0: 0 - ) - a1( - a1_0: 0 - ) - } -operationName: X -variables: -expect: - - 1 diff --git a/pkg/engine/tests/assets/testassets/test_enum_00/0.gqt b/pkg/engine/tests/assets/testassets/test_enum_00/0.gqt deleted file mode 100644 index ea0d478..0000000 --- a/pkg/engine/tests/assets/testassets/test_enum_00/0.gqt +++ /dev/null @@ -1,6 +0,0 @@ -query { - a( - a_0: val = [ ... val = NEWHOPE || val = EMPIRE || val = JEDI] - a_1: val = NEWHOPE || val = EMPIRE || val = JEDI - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_enum_00/1.gqt b/pkg/engine/tests/assets/testassets/test_enum_00/1.gqt deleted file mode 100644 index 6704f70..0000000 --- a/pkg/engine/tests/assets/testassets/test_enum_00/1.gqt +++ /dev/null @@ -1,6 +0,0 @@ -query { - a( - a_0: val = [ ... val = JEDI || val = EMPIRE || val = NEWHOPE] - a_1: val = JEDI || val = EMPIRE || val = NEWHOPE - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_enum_00/test.yml b/pkg/engine/tests/assets/testassets/test_enum_00/test.yml deleted file mode 100644 index c24f7c5..0000000 --- a/pkg/engine/tests/assets/testassets/test_enum_00/test.yml +++ /dev/null @@ -1,12 +0,0 @@ -query: | - query X { - a( - a_0: [NEWHOPE, JEDI] - a_1: EMPIRE - ) - } -operationName: X -variables: -expect: - - 0 - - 1 diff --git a/pkg/engine/tests/assets/testassets/test_fragments_00/0.gqt b/pkg/engine/tests/assets/testassets/test_fragments_00/0.gqt deleted file mode 100644 index e726470..0000000 --- a/pkg/engine/tests/assets/testassets/test_fragments_00/0.gqt +++ /dev/null @@ -1,6 +0,0 @@ -query { - a - ... on Something { - b - } -} diff --git a/pkg/engine/tests/assets/testassets/test_fragments_00/test.yml b/pkg/engine/tests/assets/testassets/test_fragments_00/test.yml deleted file mode 100644 index 65c8c55..0000000 --- a/pkg/engine/tests/assets/testassets/test_fragments_00/test.yml +++ /dev/null @@ -1,11 +0,0 @@ -query: | - query X { - a - ... on Something { - b - } - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_fragments_01/0.gqt b/pkg/engine/tests/assets/testassets/test_fragments_01/0.gqt deleted file mode 100644 index 815cd78..0000000 --- a/pkg/engine/tests/assets/testassets/test_fragments_01/0.gqt +++ /dev/null @@ -1,9 +0,0 @@ -query { - a - ... on Something { - b - } - Something { - b - } -} diff --git a/pkg/engine/tests/assets/testassets/test_fragments_01/test.yml b/pkg/engine/tests/assets/testassets/test_fragments_01/test.yml deleted file mode 100644 index 86477d6..0000000 --- a/pkg/engine/tests/assets/testassets/test_fragments_01/test.yml +++ /dev/null @@ -1,14 +0,0 @@ -query: | - query X { - a - ... on Something { - b - } - Something { - b - } - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_fragments_02/0.gqt b/pkg/engine/tests/assets/testassets/test_fragments_02/0.gqt deleted file mode 100644 index 86e9191..0000000 --- a/pkg/engine/tests/assets/testassets/test_fragments_02/0.gqt +++ /dev/null @@ -1,11 +0,0 @@ -query { - a - ... on Query { - b { - b0 { - b00 - b01 - } - } - } -} diff --git a/pkg/engine/tests/assets/testassets/test_fragments_02/test.yml b/pkg/engine/tests/assets/testassets/test_fragments_02/test.yml deleted file mode 100644 index dddf41a..0000000 --- a/pkg/engine/tests/assets/testassets/test_fragments_02/test.yml +++ /dev/null @@ -1,31 +0,0 @@ -query: | - query X { - a - ... on Query { - ... f1 - } - } - - fragment f1 on Query { - ... f2 - } - - fragment f2 on Query { - b { - ... { - ... f3 - ... f3 - } - } - } - - fragment f3 on Something { - b0 { - b00 - b01 - } - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_fragments_03/0.gqt b/pkg/engine/tests/assets/testassets/test_fragments_03/0.gqt deleted file mode 100644 index ff13301..0000000 --- a/pkg/engine/tests/assets/testassets/test_fragments_03/0.gqt +++ /dev/null @@ -1,17 +0,0 @@ -query { - a - b { - b0 { - b00 - b01 - } - } - ... on Query { - b { - b0 { - b00 - b01 - } - } - } -} diff --git a/pkg/engine/tests/assets/testassets/test_fragments_03/test.yml b/pkg/engine/tests/assets/testassets/test_fragments_03/test.yml deleted file mode 100644 index c307955..0000000 --- a/pkg/engine/tests/assets/testassets/test_fragments_03/test.yml +++ /dev/null @@ -1,29 +0,0 @@ -query: | - query X { - a - ... f1 - } - - fragment f1 on Query { - ... f2 - } - - fragment f2 on Query { - b { - ... { - ... f3 - ... f3 - } - } - } - - fragment f3 on Something { - b0 { - b00 - b01 - } - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_00/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_00/0.gqt deleted file mode 100644 index 8d4dd10..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_00/0.gqt +++ /dev/null @@ -1,3 +0,0 @@ -query { - b -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_00/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_00/test.yml deleted file mode 100644 index 3e5ab8c..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_00/test.yml +++ /dev/null @@ -1,8 +0,0 @@ -query: | - query X { - a - b - } -operationName: X -variables: -expect: diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_01/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_01/0.gqt deleted file mode 100644 index e2cac4a..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_01/0.gqt +++ /dev/null @@ -1,8 +0,0 @@ -query { - a { - a0 { - a00 - } - a1 - } -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_01/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_01/test.yml deleted file mode 100644 index fb02813..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_01/test.yml +++ /dev/null @@ -1,13 +0,0 @@ -query: | - query X { - a { - a0 { - a00 - } - a1 - } - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_02/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_02/0.gqt deleted file mode 100644 index 291a7d9..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_02/0.gqt +++ /dev/null @@ -1,10 +0,0 @@ -query { - a { - a0( - a0_0: val = 1 - ) { - a00 - } - a1 - } -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_02/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_02/test.yml deleted file mode 100644 index fe8505c..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_02/test.yml +++ /dev/null @@ -1,15 +0,0 @@ -query: | - query X { - a { - a0( - a0_0: 1 - ) { - a00 - } - a1 - } - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_03/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_03/0.gqt deleted file mode 100644 index 1111c75..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_03/0.gqt +++ /dev/null @@ -1,12 +0,0 @@ -query { - a { - a0( - a0_0: val != { - a0_00: val = 1 - a0_01: val >= 0 - } - ) { - a00 - } - } -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_03/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_03/test.yml deleted file mode 100644 index d96d8da..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_03/test.yml +++ /dev/null @@ -1,7 +0,0 @@ -query: | - query X { - a - } -operationName: X -variables: -expect: diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_04/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_04/0.gqt deleted file mode 100644 index 1111c75..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_04/0.gqt +++ /dev/null @@ -1,12 +0,0 @@ -query { - a { - a0( - a0_0: val != { - a0_00: val = 1 - a0_01: val >= 0 - } - ) { - a00 - } - } -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_04/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_04/test.yml deleted file mode 100644 index 9487b80..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_04/test.yml +++ /dev/null @@ -1,12 +0,0 @@ -query: | - query X { - a { - a0 { - a00 - } - } - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_05/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_05/0.gqt deleted file mode 100644 index 1111c75..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_05/0.gqt +++ /dev/null @@ -1,12 +0,0 @@ -query { - a { - a0( - a0_0: val != { - a0_00: val = 1 - a0_01: val >= 0 - } - ) { - a00 - } - } -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_05/1.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_05/1.gqt deleted file mode 100644 index d09aa45..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_05/1.gqt +++ /dev/null @@ -1,5 +0,0 @@ -query { - a { - a0 - } -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_05/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_05/test.yml deleted file mode 100644 index 76e5039..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_05/test.yml +++ /dev/null @@ -1,10 +0,0 @@ -query: | - query X { - a { - a0 - } - } -operationName: X -variables: -expect: - - 1 diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_06/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_06/0.gqt deleted file mode 100644 index 2909e60..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_06/0.gqt +++ /dev/null @@ -1,10 +0,0 @@ -query { - a { - a0( - a0_0: val != { - a0_00: val = 1 - a0_01: val >= 0 - } - ) - } -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_06/1.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_06/1.gqt deleted file mode 100644 index d09aa45..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_06/1.gqt +++ /dev/null @@ -1,5 +0,0 @@ -query { - a { - a0 - } -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_06/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_06/test.yml deleted file mode 100644 index 76e5039..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_06/test.yml +++ /dev/null @@ -1,10 +0,0 @@ -query: | - query X { - a { - a0 - } - } -operationName: X -variables: -expect: - - 1 diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_07/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_07/0.gqt deleted file mode 100644 index 5b02b5b..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_07/0.gqt +++ /dev/null @@ -1,13 +0,0 @@ -query { - a { - a0( - a0_0: val != { - a0_00: val = 1 - a0_01: val >= 0 - } - a0_1: val != "never" - ) { - a00 - } - } -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_07/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_07/test.yml deleted file mode 100644 index 6f08eb4..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_07/test.yml +++ /dev/null @@ -1,16 +0,0 @@ -query: | - query X { - a { - a0( - a0_0: { - a0_00: 1 - } - a0_1: "never" - ) { - a00 - } - } - } -operationName: X -variables: -expect: diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_08/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_08/0.gqt deleted file mode 100644 index 76232f9..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_08/0.gqt +++ /dev/null @@ -1,15 +0,0 @@ -mutation { - a { - a0( - a0_0: val = { - a0_00: val = 1 - a0_01: val >= 0 - } - a0_1: val != "never" - ) { - a00 { - a001 - } - } - } -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_08/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_08/test.yml deleted file mode 100644 index e5e809e..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_08/test.yml +++ /dev/null @@ -1,18 +0,0 @@ -query: | - mutation X { - a { - a0( - a0_0: { - a0_00: 1 - } - a0_1: "never" - ) { - a00 { - a000 - } - } - } - } -operationName: X -variables: -expect: diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_09/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_09/0.gqt deleted file mode 100644 index 991cd8a..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_09/0.gqt +++ /dev/null @@ -1,6 +0,0 @@ -query { - a( - a_0: val < 0 - a_1: val > 0 - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_09/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_09/test.yml deleted file mode 100644 index ab7a563..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_09/test.yml +++ /dev/null @@ -1,10 +0,0 @@ -query: | - query X { - a( - a_0: -1 - ) - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_10/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_10/0.gqt deleted file mode 100644 index 9b0fd65..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_10/0.gqt +++ /dev/null @@ -1,12 +0,0 @@ -query { - a( - a_0: val = [val = 1, val = 2, val = 3] - a_1: val = [ - val = { - a_10: val = 1 - a_11: val = 2 - a_12: val = 3 - } - ] - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_10/1.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_10/1.gqt deleted file mode 100644 index cb539c5..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_10/1.gqt +++ /dev/null @@ -1,14 +0,0 @@ -query { - a( - a_0: val = [val = 1, val = 2] - a_1: val = [ - val = { - a_10: val = 1 - a_11: val = 2 - } - ] - a_2: val = { - a_20: val = 0 - } - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_10/2.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_10/2.gqt deleted file mode 100644 index 1cf47d9..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_10/2.gqt +++ /dev/null @@ -1,15 +0,0 @@ -query { - a( - a_0: val = [val = 0, val = 1, val = 2] - a_1: val = [ - val = { - a_10: val = 0 - a_11: val = 1 - a_12: val = 2 - } - ] - a_2: val = { - a_20: val = 0 - } - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_10/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_10/test.yml deleted file mode 100644 index c705260..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_10/test.yml +++ /dev/null @@ -1,14 +0,0 @@ -query: | - query X { - a( - a_0: [1, 2] - a_1: [{ - a_10: 1 - a_11: 2 - }] - ) - } -operationName: X -variables: -expect: - - 1 diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_11/0.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_11/0.gqt deleted file mode 100644 index 449f3ea..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_11/0.gqt +++ /dev/null @@ -1,10 +0,0 @@ -query { - a( - a_0: bytelen > 0 - a_1: any - a_2: val < 0 - ) { - a0 - a1 - } -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_11/1.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_11/1.gqt deleted file mode 100644 index 73cf539..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_11/1.gqt +++ /dev/null @@ -1,10 +0,0 @@ -query { - a( - a_0: bytelen > 0 - a_1: val != null - a_2: val >= 0 - ) { - a0 - a1 - } -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_11/2.gqt b/pkg/engine/tests/assets/testassets/test_general_logic_11/2.gqt deleted file mode 100644 index d90cca5..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_11/2.gqt +++ /dev/null @@ -1,10 +0,0 @@ -query { - a( - a_0: bytelen > 0 - a_1: any - a_2: val >= 0 - ) { - a0 - a1 - } -} diff --git a/pkg/engine/tests/assets/testassets/test_general_logic_11/test.yml b/pkg/engine/tests/assets/testassets/test_general_logic_11/test.yml deleted file mode 100644 index a5c22dd..0000000 --- a/pkg/engine/tests/assets/testassets/test_general_logic_11/test.yml +++ /dev/null @@ -1,15 +0,0 @@ -query: | - query X { - a( - a_0: "well" - a_1: null - a_2: 0 - ) { - a0 - a1 - } - } -operationName: X -variables: -expect: - - 2 diff --git a/pkg/engine/tests/assets/testassets/test_identical_fields_00/0.gqt b/pkg/engine/tests/assets/testassets/test_identical_fields_00/0.gqt deleted file mode 100644 index fcdc1e1..0000000 --- a/pkg/engine/tests/assets/testassets/test_identical_fields_00/0.gqt +++ /dev/null @@ -1,3 +0,0 @@ -query { - a -} diff --git a/pkg/engine/tests/assets/testassets/test_identical_fields_00/test.yml b/pkg/engine/tests/assets/testassets/test_identical_fields_00/test.yml deleted file mode 100644 index d35be77..0000000 --- a/pkg/engine/tests/assets/testassets/test_identical_fields_00/test.yml +++ /dev/null @@ -1,9 +0,0 @@ -query: | - query X { - a - a - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_map_00/0.gqt b/pkg/engine/tests/assets/testassets/test_map_00/0.gqt deleted file mode 100644 index bde5736..0000000 --- a/pkg/engine/tests/assets/testassets/test_map_00/0.gqt +++ /dev/null @@ -1,24 +0,0 @@ -query { - a { - a0( - a0_0: any - a0_1: val = { - a0_10: val = null - } - ) { - a00( - a00_0: val = [any, val = 0, any] - a00_1: val = [ - ... - val = { - a00_1x0: any - } - ] - a00_2: val = [ ... any ] - ) { - a000 - } - } - a1 - } -} diff --git a/pkg/engine/tests/assets/testassets/test_map_00/test.yml b/pkg/engine/tests/assets/testassets/test_map_00/test.yml deleted file mode 100644 index 0969ccf..0000000 --- a/pkg/engine/tests/assets/testassets/test_map_00/test.yml +++ /dev/null @@ -1,27 +0,0 @@ -query: | - query X { - a { - a0( - a0_0: 1 - a0_1: { - a0_10: null - } - ) { - a00( - a00_0: [1, 0, 2] - a00_1: [ - { a00_1x0: 0 } - { a00_1x0: 1 } - ] - a00_2: [0, 1] - ) { - a000 - } - } - a1 - } - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_max_00/0.gqt b/pkg/engine/tests/assets/testassets/test_max_00/0.gqt deleted file mode 100644 index fe5b4b7..0000000 --- a/pkg/engine/tests/assets/testassets/test_max_00/0.gqt +++ /dev/null @@ -1,6 +0,0 @@ -query { - combine 1 { - a - b - } -} diff --git a/pkg/engine/tests/assets/testassets/test_max_00/test.yml b/pkg/engine/tests/assets/testassets/test_max_00/test.yml deleted file mode 100644 index 3e5ab8c..0000000 --- a/pkg/engine/tests/assets/testassets/test_max_00/test.yml +++ /dev/null @@ -1,8 +0,0 @@ -query: | - query X { - a - b - } -operationName: X -variables: -expect: diff --git a/pkg/engine/tests/assets/testassets/test_max_02/0.gqt b/pkg/engine/tests/assets/testassets/test_max_02/0.gqt deleted file mode 100644 index 080f2ad..0000000 --- a/pkg/engine/tests/assets/testassets/test_max_02/0.gqt +++ /dev/null @@ -1,7 +0,0 @@ -query { - combine 1 { - a - b - c - } -} diff --git a/pkg/engine/tests/assets/testassets/test_max_02/1.gqt b/pkg/engine/tests/assets/testassets/test_max_02/1.gqt deleted file mode 100644 index 1b303b1..0000000 --- a/pkg/engine/tests/assets/testassets/test_max_02/1.gqt +++ /dev/null @@ -1,7 +0,0 @@ -query { - combine 2 { - a - b - c - } -} diff --git a/pkg/engine/tests/assets/testassets/test_max_02/test.yml b/pkg/engine/tests/assets/testassets/test_max_02/test.yml deleted file mode 100644 index ffce84d..0000000 --- a/pkg/engine/tests/assets/testassets/test_max_02/test.yml +++ /dev/null @@ -1,9 +0,0 @@ -query: | - query X { - a - b - } -operationName: X -variables: -expect: - - 1 diff --git a/pkg/engine/tests/assets/testassets/test_max_03/0.gqt b/pkg/engine/tests/assets/testassets/test_max_03/0.gqt deleted file mode 100644 index 8260b49..0000000 --- a/pkg/engine/tests/assets/testassets/test_max_03/0.gqt +++ /dev/null @@ -1,10 +0,0 @@ -query { - combine 1 { - a - b - } - combine 1 { - c - d - } -} diff --git a/pkg/engine/tests/assets/testassets/test_max_03/test.yml b/pkg/engine/tests/assets/testassets/test_max_03/test.yml deleted file mode 100644 index e41fdb9..0000000 --- a/pkg/engine/tests/assets/testassets/test_max_03/test.yml +++ /dev/null @@ -1,9 +0,0 @@ -query: | - query X { - a - b - c - } -operationName: X -variables: -expect: diff --git a/pkg/engine/tests/assets/testassets/test_max_complex_00/0.gqt b/pkg/engine/tests/assets/testassets/test_max_complex_00/0.gqt deleted file mode 100644 index b6e6f55..0000000 --- a/pkg/engine/tests/assets/testassets/test_max_complex_00/0.gqt +++ /dev/null @@ -1,38 +0,0 @@ -query { - combine 2 { - a - b - c { - combine 1 { - c0( - c0_0: val > 0 - ) { - combine 1 { - c00( - c00_0: val < 0 - ) { - combine 2 { - c000 - c001 - c002 - } - } - c01 - } - } - c1 - } - } - d { - combine 1 { - d0 - d1 - } - } - } - combine 2 { - e - f - g - } -} diff --git a/pkg/engine/tests/assets/testassets/test_max_complex_00/test.yml b/pkg/engine/tests/assets/testassets/test_max_complex_00/test.yml deleted file mode 100644 index a9a5747..0000000 --- a/pkg/engine/tests/assets/testassets/test_max_complex_00/test.yml +++ /dev/null @@ -1,19 +0,0 @@ -query: | - query X { - a - c { - c0( - c0_0: 1 - ) { - c00(c00_0: -1) { - c000 - c002 - } - } - } - e - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_negation_00/0.gqt b/pkg/engine/tests/assets/testassets/test_negation_00/0.gqt deleted file mode 100644 index 55b8d45..0000000 --- a/pkg/engine/tests/assets/testassets/test_negation_00/0.gqt +++ /dev/null @@ -1,7 +0,0 @@ -query { - a { - a0( - a0_0: val = [ val != 0 ] - ) - } -} diff --git a/pkg/engine/tests/assets/testassets/test_negation_00/test.yml b/pkg/engine/tests/assets/testassets/test_negation_00/test.yml deleted file mode 100644 index 0af810a..0000000 --- a/pkg/engine/tests/assets/testassets/test_negation_00/test.yml +++ /dev/null @@ -1,11 +0,0 @@ -query: | - query X { - a { - a0( - a0_0: [ 0 ] - ) - } - } -operationName: X -variables: -expect: diff --git a/pkg/engine/tests/assets/testassets/test_negation_01/0.gqt b/pkg/engine/tests/assets/testassets/test_negation_01/0.gqt deleted file mode 100644 index 568ba34..0000000 --- a/pkg/engine/tests/assets/testassets/test_negation_01/0.gqt +++ /dev/null @@ -1,7 +0,0 @@ -query { - a { - a0( - a0_0: val != [ val != 0 ] - ) - } -} diff --git a/pkg/engine/tests/assets/testassets/test_negation_01/test.yml b/pkg/engine/tests/assets/testassets/test_negation_01/test.yml deleted file mode 100644 index 393efd5..0000000 --- a/pkg/engine/tests/assets/testassets/test_negation_01/test.yml +++ /dev/null @@ -1,12 +0,0 @@ -query: | - query X { - a { - a0( - a0_0: [ 0 ] - ) - } - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_nesting_00/0.gqt b/pkg/engine/tests/assets/testassets/test_nesting_00/0.gqt deleted file mode 100644 index 15ac88c..0000000 --- a/pkg/engine/tests/assets/testassets/test_nesting_00/0.gqt +++ /dev/null @@ -1,18 +0,0 @@ -query { - a { - a0 { - a00 { - a000 { - a0000( - a0000_0: val = { - a0000_00: val = 65535 - } - ) - } - a001( - a001_0: val = "surprise!" - ) - } - } - } -} diff --git a/pkg/engine/tests/assets/testassets/test_nesting_00/test.yml b/pkg/engine/tests/assets/testassets/test_nesting_00/test.yml deleted file mode 100644 index e3e5ff2..0000000 --- a/pkg/engine/tests/assets/testassets/test_nesting_00/test.yml +++ /dev/null @@ -1,23 +0,0 @@ -query: | - query X { - a { - a0 { - a00 { - a000 { - a0000( - a0000_0: { - a0000_00: 65535 - } - ) - } - a001( - a001_0: "surprise!" - ) - } - } - } - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_special_fields_00/0.gqt b/pkg/engine/tests/assets/testassets/test_special_fields_00/0.gqt deleted file mode 100644 index c57ec0f..0000000 --- a/pkg/engine/tests/assets/testassets/test_special_fields_00/0.gqt +++ /dev/null @@ -1,4 +0,0 @@ -query { - __typename - a -} diff --git a/pkg/engine/tests/assets/testassets/test_special_fields_00/test.yml b/pkg/engine/tests/assets/testassets/test_special_fields_00/test.yml deleted file mode 100644 index f5bb561..0000000 --- a/pkg/engine/tests/assets/testassets/test_special_fields_00/test.yml +++ /dev/null @@ -1,8 +0,0 @@ -query: | - query X { - a - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_token_null_00/0.gqt b/pkg/engine/tests/assets/testassets/test_token_null_00/0.gqt deleted file mode 100644 index f9d3fb5..0000000 --- a/pkg/engine/tests/assets/testassets/test_token_null_00/0.gqt +++ /dev/null @@ -1,4 +0,0 @@ -query { - a(a_0: val = null) - b(b_0: val != null) -} diff --git a/pkg/engine/tests/assets/testassets/test_token_null_00/test.yml b/pkg/engine/tests/assets/testassets/test_token_null_00/test.yml deleted file mode 100644 index 701ba8c..0000000 --- a/pkg/engine/tests/assets/testassets/test_token_null_00/test.yml +++ /dev/null @@ -1,9 +0,0 @@ -query: | - query X { - a(a_0: null) - b(b_0: 0) - } -operationName: X -variables: -expect: - - 0 diff --git a/pkg/engine/tests/assets/testassets/test_variables_00/0.gqt b/pkg/engine/tests/assets/testassets/test_variables_00/0.gqt deleted file mode 100644 index cca8675..0000000 --- a/pkg/engine/tests/assets/testassets/test_variables_00/0.gqt +++ /dev/null @@ -1,42 +0,0 @@ -query { - a( - a_0: val = 0 - a_1: val = 0.0 - a_2: val = true - a_3: val = "alive" - a_4: val = "id" - ) - b( - b_0: val = [ val = 0 ] - b_1: val = [ val = 0.0 ] - b_2: val = [ val = true ] - b_3: val = [ val = "alive" ] - b_4: val = [ val = "id" ] - ) - c( - c_0: val = { - c_00: val = 0 - c_01: val = 0.0 - c_02: val = true - c_03: val = "alive" - } - c_1: val = [ - val = { - c_10: val = 0 - c_11: val = 0.0 - c_12: val = true - c_13: val = "alive" - } - ] - ) - d( - d_0: val = { - d_00: val = [ val = 0 ] - } - d_1: val = [ - val = { - d_1x0: val = [ val = 0 ] - } - ] - ) -} diff --git a/pkg/engine/tests/assets/testassets/test_variables_00/test.yml b/pkg/engine/tests/assets/testassets/test_variables_00/test.yml deleted file mode 100644 index 9b6d823..0000000 --- a/pkg/engine/tests/assets/testassets/test_variables_00/test.yml +++ /dev/null @@ -1,83 +0,0 @@ -query: | - query X ( - $a_0: Int! - $a_1: Float! - $a_2: Boolean! - $a_3: String! - $a_4: ID! - - $b_0: [Int!]! - $b_1: [Float!]! - $b_2: [Boolean!]! - $b_3: [String!]! - $b_4: [ID!]! - - $c_0: Input! - $c_1: [Input!]! - - $d_00: [Int!]! - $d_1x: Input! - ) { - a( - a_0: $a_0 - a_1: $a_1 - a_2: $a_2 - a_3: $a_3 - a_4: $a_4 - ) - - b( - b_0: $b_0 - b_1: $b_1 - b_2: $b_2 - b_3: $b_3 - b_4: $b_4 - ) - - c( - c_0: $c_0 - c_1: $c_1 - ) - - d( - d_0: { - d_00: $d_00 - } - d_1: [ $d_1x ] - ) - } -operationName: X -variables: | - { - "a_0": 0, - "a_1": 0.0, - "a_2": true, - "a_3": "alive", - "a_4": "id", - - "b_0": [0], - "b_1": [0.0], - "b_2": [true], - "b_3": ["alive"], - "b_4": ["id"], - - "c_0": { - "c_00": 0, - "c_01": 0.0, - "c_02": true, - "c_03": "alive" - }, - "c_1": [{ - "c_10": 0, - "c_11": 0.0, - "c_12": true, - "c_13": "alive" - }], - - "d_00": [0], - "d_1x": { - "d_1x0": [0] - } - } -expect: - - 0 diff --git a/pkg/engine/tests/engine_test.go b/pkg/engine/tests/engine_test.go index 53e6541..c826a5a 100644 --- a/pkg/engine/tests/engine_test.go +++ b/pkg/engine/tests/engine_test.go @@ -1,202 +1,94 @@ package engine_test -// TODO: either reimplement the tests or make them playmon compatible. +import ( + "embed" + _ "embed" + "io/fs" + "path/filepath" + "strings" + "testing" -// import ( -// "bytes" -// "embed" -// _ "embed" -// "fmt" -// "io" -// "io/fs" -// "path/filepath" -// "strings" -// "testing" + "github.com/graph-guard/ggproxy/pkg/config" + "github.com/graph-guard/ggproxy/pkg/engine/playmon" + "github.com/graph-guard/ggproxy/pkg/gqlparse" + "github.com/graph-guard/ggproxy/pkg/testsetup" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) -// "github.com/graph-guard/ggproxy/pkg/config" -// "github.com/graph-guard/ggproxy/pkg/config/metadata" -// "github.com/graph-guard/ggproxy/pkg/gqlparse" -// "github.com/graph-guard/ggproxy/pkg/container/hamap" -// "github.com/graph-guard/ggproxy/pkg/xxhash" -// "github.com/graph-guard/gqt" -// "github.com/stretchr/testify/require" -// "gopkg.in/yaml.v3" -// ) +//go:embed tests +var testsFS embed.FS -// func TestConstraintIdAndValue(t *testing.T) { -// for _, td := range []struct { -// input gqt.Constraint -// id rmap.Constraint -// value any -// err error -// }{ -// { -// input: gqt.ConstraintMap{ -// Constraint: new(gqt.Constraint), -// }, -// id: rmap.ConstraintMap, -// value: new(gqt.Constraint), -// }, -// { -// input: gqt.ConstraintAny{}, -// id: rmap.ConstraintAny, -// value: nil, -// }, -// { -// input: gqt.ConstraintValEqual{ -// Value: gqt.ValueObject{ -// Fields: []gqt.ObjectField{ -// { -// Name: "a", -// Value: gqt.ConstraintValLessOrEqual{ -// Value: 42.0, -// }, -// }, -// }, -// }, -// }, -// id: rmap.ConstraintValEqual, -// value: gqt.ValueObject{ -// Fields: []gqt.ObjectField{ -// { -// Name: "a", -// Value: gqt.ConstraintValLessOrEqual{ -// Value: 42.0, -// }, -// }, -// }, -// }, -// }, -// { -// input: gqt.ConstraintValGreater{ -// Value: 42.0, -// }, -// id: rmap.ConstraintValGreater, -// value: 42.0, -// }, -// { -// input: gqt.ConstraintValLess{ -// Value: 42.0, -// }, -// id: rmap.ConstraintValLess, -// value: 42.0, -// }, -// { -// input: gqt.ConstraintValGreaterOrEqual{ -// Value: 69.0, -// }, -// id: rmap.ConstraintValGreaterOrEqual, -// value: 69.0, -// }, -// { -// input: gqt.ConstraintValLessOrEqual{ -// Value: 69.0, -// }, -// id: rmap.ConstraintValLessOrEqual, -// value: 69.0, -// }, -// { -// input: gqt.ConstraintBytelenEqual{ -// Value: 1984, -// }, -// id: rmap.ConstraintBytelenEqual, -// value: uint(1984), -// }, -// { -// input: gqt.ConstraintBytelenNotEqual{ -// Value: 1984, -// }, -// id: rmap.ConstraintBytelenNotEqual, -// value: uint(1984), -// }, -// { -// input: gqt.ConstraintBytelenGreater{ -// Value: 282, -// }, -// id: rmap.ConstraintBytelenGreater, -// value: uint(282), -// }, -// { -// input: gqt.ConstraintBytelenLess{ -// Value: 282, -// }, -// id: rmap.ConstraintBytelenLess, -// value: uint(282), -// }, -// { -// input: gqt.ConstraintBytelenGreaterOrEqual{ -// Value: 27015, -// }, -// id: rmap.ConstraintBytelenGreaterOrEqual, -// value: uint(27015), -// }, -// { -// input: gqt.ConstraintBytelenLessOrEqual{ -// Value: 27015, -// }, -// id: rmap.ConstraintBytelenLessOrEqual, -// value: uint(27015), -// }, -// { -// input: gqt.ConstraintLenEqual{ -// Value: 997, -// }, -// id: rmap.ConstraintLenEqual, -// value: uint(997), -// }, -// { -// input: gqt.ConstraintLenNotEqual{ -// Value: 997, -// }, -// id: rmap.ConstraintLenNotEqual, -// value: uint(997), -// }, -// { -// input: gqt.ConstraintLenGreater{ -// Value: 47, -// }, -// id: rmap.ConstraintLenGreater, -// value: uint(47), -// }, -// { -// input: gqt.ConstraintLenLess{ -// Value: 47, -// }, -// id: rmap.ConstraintLenLess, -// value: uint(47), -// }, -// { -// input: gqt.ConstraintLenGreaterOrEqual{ -// Value: 404, -// }, -// id: rmap.ConstraintLenGreaterOrEqual, -// value: uint(404), -// }, -// { -// input: gqt.ConstraintLenLessOrEqual{ -// Value: 404, -// }, -// id: rmap.ConstraintLenLessOrEqual, -// value: uint(404), -// }, -// } { -// t.Run("", func(t *testing.T) { -// id, value := rmap.ConstraintIdAndValue(td.input) -// require.Equal(t, td.id, id) -// require.Equal(t, td.value, value) -// }) -// } -// } +func TestPlaymonMatch(t *testing.T) { + testSets, err := testsFS.ReadDir("tests") + require.NoError(t, err) + for _, d := range testSets { + t.Run(d.Name(), func(t *testing.T) { + if !d.IsDir() { + t.Skip("not a directory") + } + setup, ok := testsetup.ByName(d.Name()) + if !ok { + t.Fatalf("unknown test setup: %q", d.Name()) + } -// //go:embed assets/testassets -// var testassets embed.FS + engine := playmon.New(setup.Config.ServicesEnabled[0]) -// type QueryModel struct { -// Query string `yaml:"query"` -// OperationName string `yaml:"operationName"` -// Variables string `yaml:"variables"` -// Expect []string `yaml:"expect"` -// } + tests, err := fs.ReadDir(testsFS, filepath.Join("tests", d.Name())) + require.NoError(t, err) + for _, f := range tests { + name := strings.TrimSuffix(f.Name(), ".yaml") + t.Run(name, func(t *testing.T) { + if !strings.HasSuffix(f.Name(), ".yaml") { + t.Skip("missing '.yaml' extension") + } + if f.IsDir() { + t.Skip("directory") + } + c, err := fs.ReadFile(testsFS, filepath.Join("tests", d.Name(), f.Name())) + require.NoError(t, err) + var ts Test + err = yaml.Unmarshal(c, &ts) + require.NoError(t, err) + var errMsg string + var matches []string + + var operationName []byte + if ts.OperationName != "" { + operationName = []byte(ts.OperationName) + } + var variablesJSON []byte + if ts.VariablesJSON != "" { + variablesJSON = []byte(ts.VariablesJSON) + } + + engine.Match( + []byte(ts.Query), operationName, variablesJSON, + func(operation, selectionSet []gqlparse.Token) (stop bool) { + return false + }, + func(template *config.Template) (stop bool) { + matches = append(matches, template.ID) + return false + }, func(err error) { + errMsg = err.Error() + }, + ) + require.Equal(t, ts.ExpectError, errMsg) + require.Equal(t, ts.ExpectMatches, matches) + }) + } + }) + } +} + +type Test struct { + Query string `yaml:"query"` + OperationName string `yaml:"operation-name"` + VariablesJSON string `yaml:"variables-json"` + ExpectError string `yaml:"expect-error"` + ExpectMatches []string `yaml:"expect-matches"` +} // type MatchTest struct { // ID string @@ -292,7 +184,7 @@ package engine_test // } // func TestMatchAllPartedQuery(t *testing.T) { -// for _, td := range readTestAssets(testassets, "assets/testassets", "test_") { +// for _, td := range readTestAssets(testsFS, "assets/testassets", "test_") { // t.Run(td.ID, func(t *testing.T) { // rules := make(map[string]gqt.Doc, len(td.Templates)) // for _, r := range td.Templates { diff --git a/pkg/engine/tests/tests/starwars/hero_minimal.yaml b/pkg/engine/tests/tests/starwars/hero_minimal.yaml new file mode 100644 index 0000000..5b6baa9 --- /dev/null +++ b/pkg/engine/tests/tests/starwars/hero_minimal.yaml @@ -0,0 +1,12 @@ +query: > + query { + hero(episode: JEDI) { + id + appearsIn + } + } +expect-matches: + - query_hero_and_reviews + - query_hero_expanded + - query_hero_friends + - query_hero_with_friends_and_reviews \ No newline at end of file diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index 87340a3..135ed14 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -1,362 +1,343 @@ package server_test -import ( - "encoding/json" - "fmt" - "net" - "regexp" - "sync" - "testing" - "time" - - "github.com/graph-guard/ggproxy/pkg/server" - "github.com/graph-guard/ggproxy/pkg/testsetup" - plog "github.com/phuslu/log" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/valyala/fasthttp" - "github.com/valyala/fasthttp/fasthttputil" -) - -func TestProxy(t *testing.T) { - setups := GetSetups(t) - for _, setup := range setups { - t.Run(setup.Name, func(t *testing.T) { - clientProxy, forwarded, respSetter, logs := launchSetup(t, setup) - - for _, test := range setup.Tests { - t.Run(test.Name, func(t *testing.T) { - if test.Destination != nil { - body := test.Destination.Response.Body - if j := test.Destination.Response.BodyJSON; j != nil { - b, err := json.Marshal(j) - require.NoError(t, err) - body = string(b) - } - respSetter.Set(&SendResponse{ - Status: test.Destination.Response.Status, - Body: body, - Headers: copyMap(test.Destination.Response.Headers), - }) - } else { - respSetter.Set(nil) - } - - respStatus, respHeaders, respBody := doRequest( - t, clientProxy, - test.Client.Input.Method, - "localhost:8000", - test.Client.Input.Endpoint, - func(r *fasthttp.Request) { - r.Header.Set("Content-Type", "application/json") - body := test.Client.Input.Body - if j := test.Client.Input.BodyJSON; j != nil { - b, err := json.Marshal(j) - require.NoError(t, err) - body = string(b) - } - r.SetBodyString(body) - }, - ) - - if test.Destination != nil { - var f ReceivedRequest - ok := false - select { - case x := <-forwarded: - ok = true - f = x - default: - t.Errorf("the request wansn't forwarded as expected") - } - if ok { - compareHeaders( - t, "forwarded", - test.Destination.ExpectForwarded.Headers, - f.Headers, - ) - j := test.Destination.ExpectForwarded.BodyJSON - body := test.Destination.ExpectForwarded.Body - if j != nil { - b, err := json.Marshal(j) - require.NoError(t, err) - body = string(b) - } - assert.Equal( - t, body, f.Body, - "unexpected body was forwarded to destination", - ) - } - } - - // Compare results - if e := test.Client.ExpectResponse.Status; e != respStatus { - t.Errorf( - "unexpected response status: %d; expected: %d", - respStatus, e, - ) - } - compareHeaders( - t, "response", - test.Client.ExpectResponse.Headers, respHeaders, - ) - { - body := test.Client.ExpectResponse.Body - if j := test.Client.ExpectResponse.BodyJSON; j != nil { - b, err := json.Marshal(j) - require.NoError(t, err) - body = string(b) - } - assert.Equal( - t, body, respBody, - "unexpected response body", - ) - } - - // Check logs - logs.ReadLogs(func(m []map[string]any) { - for i, x := range m { - if i >= len(test.Logs) { - t.Errorf("unexpected log: %v", m[i]) - continue - } - assert.Equal(t, - test.Logs[i], x, - "unexpected log at index %d", i, - ) - } - }) - logs.Reset() - }) - } - }) - } -} - -func GetSetups(t *testing.T) []testsetup.Setup { - return []testsetup.Setup{ - testsetup.Starwars(), - testsetup.Test1(), - testsetup.InputsSchema(), - } -} - -type SendResponse struct { - Status int - Body string - Headers map[string]string -} -type ReceivedRequest struct { - Body string - Headers map[string]string -} - -func launchSetup(t *testing.T, s testsetup.Setup) ( - clientProxy *fasthttp.Client, - forwarded <-chan ReceivedRequest, - resp *Syncronized[*SendResponse], - logRecorder *LogRecorder, -) { - resp = new(Syncronized[*SendResponse]) - - lnDest := fasthttputil.NewInmemoryListener() - t.Cleanup(func() { lnDest.Close() }) - - lnProxy := fasthttputil.NewInmemoryListener() - t.Cleanup(func() { lnProxy.Close() }) - - forwardedRW := make(chan ReceivedRequest, 1) - forwarded = forwardedRW - - go func() { - s := &fasthttp.Server{ - Handler: func(ctx *fasthttp.RequestCtx) { - // Send the received request context for the check - var rr ReceivedRequest - rr.Headers = make(map[string]string, ctx.Request.Header.Len()) - ctx.Request.Header.VisitAll(func(key, value []byte) { - rr.Headers[string(key)] = string(value) - }) - rr.Body = string(ctx.Request.Body()) - forwardedRW <- rr - - // Send response - sr := resp.Get() - if sr == nil { - ctx.Error( - fasthttp.StatusMessage(fasthttp.StatusInternalServerError), - fasthttp.StatusInternalServerError, - ) - return - } - ctx.Response.SetStatusCode(sr.Status) - for k, v := range sr.Headers { - ctx.Response.Header.Set(k, v) - } - ctx.Response.SetBodyString(sr.Body) - }, - } - if err := s.Serve(lnDest); err != nil { - panic(err) - } - }() - - // Launch proxy server - logRecorder = new(LogRecorder) - log := plog.Logger{ - Level: plog.DebugLevel, - TimeField: "time", - TimeFormat: "23:59:59", - Writer: &plog.IOWriter{Writer: logRecorder}, - } - server := server.NewProxy( - s.Config, - time.Second*10, - time.Second*10, - 1024*64, - 1024*64, - log, - &fasthttp.Client{ - Dial: func(addr string) (net.Conn, error) { - return lnDest.Dial() - }, - }, - nil, - ) - - go func() { - server.Serve(lnProxy) - }() - - clientProxy = &fasthttp.Client{ - Dial: func(addr string) (net.Conn, error) { - return lnProxy.Dial() - }, - } - - return -} - -func doRequest( - t *testing.T, - client *fasthttp.Client, - method, host, path string, - prepareReq func(*fasthttp.Request), -) (status int, headers map[string]string, body string) { - req := fasthttp.AcquireRequest() - resp := fasthttp.AcquireResponse() - defer fasthttp.ReleaseRequest(req) - defer fasthttp.ReleaseResponse(resp) - - if prepareReq != nil { - prepareReq(req) - } - req.Header.SetMethod(method) - req.SetHost(host) - req.URI().SetPath(path) - - err := client.Do(req, resp) - require.NoError(t, err) - - status = resp.StatusCode() - headers = make(map[string]string, resp.Header.Len()) - resp.Header.VisitAll(func(key, value []byte) { - headers[string(key)] = string(value) - }) - body = string(resp.Body()) - return -} - -type Syncronized[T any] struct { - lock sync.Mutex - t T -} - -func (s *Syncronized[T]) Get() T { - s.lock.Lock() - defer s.lock.Unlock() - return s.t -} - -func (s *Syncronized[T]) Set(t T) { - s.lock.Lock() - defer s.lock.Unlock() - s.t = t -} - -func compareHeaders(t *testing.T, title string, expected, actual map[string]string) { - t.Helper() - e := make(map[string]string, len(expected)) - for k, v := range expected { - e[k] = v - } - a := make(map[string]string, len(actual)) - for k, v := range actual { - a[k] = v - } - for k, ev := range expected { - delete(e, k) - delete(a, k) - if av, ok := actual[k]; ok { - expr, err := regexp.Compile(ev) - if err != nil { - t.Fatalf( - "compiling regexp for %s header %q (%q): %v", - title, k, ev, err, - ) - } - if !expr.MatchString(av) { - t.Errorf( - "%s header %q expected regexp: %q; received: %q", - title, k, ev, av, - ) - } - } - } - for k, v := range e { - t.Errorf("missing %s header %q (%q)", title, k, v) - } - for k, v := range a { - t.Errorf("unexpected %s header %q (%q)", title, k, v) - } -} - -type LogRecorder struct { - Lock sync.Mutex - Recorded []map[string]any -} - -func (w *LogRecorder) Write(d []byte) (int, error) { - var m map[string]any - if err := json.Unmarshal(d, &m); err != nil { - return 0, fmt.Errorf("unmarshalling JSON: %w", err) - } - delete(m, "time") // We don't need to check the log time - w.Lock.Lock() - defer w.Lock.Unlock() - w.Recorded = append(w.Recorded, m) - return len(d), nil -} - -func (w *LogRecorder) Reset() { - w.Lock.Lock() - defer w.Lock.Unlock() - w.Recorded = nil -} - -func (w *LogRecorder) ReadLogs(fn func([]map[string]any)) { - w.Lock.Lock() - defer w.Lock.Unlock() - fn(w.Recorded) -} - -func copyMap[K comparable, V any](m map[K]V) (copy map[K]V) { - copy = make(map[K]V, len(m)) - for k, v := range m { - copy[k] = v - } - return copy -} +// func TestProxy(t *testing.T) { +// setups := GetSetups(t) +// for _, setup := range setups { +// t.Run(setup.Name, func(t *testing.T) { +// clientProxy, forwarded, respSetter, logs := launchSetup(t, setup) + +// for _, test := range setup.Tests { +// t.Run(test.Name, func(t *testing.T) { +// if test.Destination != nil { +// body := test.Destination.Response.Body +// if j := test.Destination.Response.BodyJSON; j != nil { +// b, err := json.Marshal(j) +// require.NoError(t, err) +// body = string(b) +// } +// respSetter.Set(&SendResponse{ +// Status: test.Destination.Response.Status, +// Body: body, +// Headers: copyMap(test.Destination.Response.Headers), +// }) +// } else { +// respSetter.Set(nil) +// } + +// respStatus, respHeaders, respBody := doRequest( +// t, clientProxy, +// test.Client.Input.Method, +// "localhost:8000", +// test.Client.Input.Endpoint, +// func(r *fasthttp.Request) { +// r.Header.Set("Content-Type", "application/json") +// body := test.Client.Input.Body +// if j := test.Client.Input.BodyJSON; j != nil { +// b, err := json.Marshal(j) +// require.NoError(t, err) +// body = string(b) +// } +// r.SetBodyString(body) +// }, +// ) + +// if test.Destination != nil { +// var f ReceivedRequest +// ok := false +// select { +// case x := <-forwarded: +// ok = true +// f = x +// default: +// t.Errorf("the request wansn't forwarded as expected") +// } +// if ok { +// compareHeaders( +// t, "forwarded", +// test.Destination.ExpectForwarded.Headers, +// f.Headers, +// ) +// j := test.Destination.ExpectForwarded.BodyJSON +// body := test.Destination.ExpectForwarded.Body +// if j != nil { +// b, err := json.Marshal(j) +// require.NoError(t, err) +// body = string(b) +// } +// assert.Equal( +// t, body, f.Body, +// "unexpected body was forwarded to destination", +// ) +// } +// } + +// // Compare results +// if e := test.Client.ExpectResponse.Status; e != respStatus { +// t.Errorf( +// "unexpected response status: %d; expected: %d", +// respStatus, e, +// ) +// } +// compareHeaders( +// t, "response", +// test.Client.ExpectResponse.Headers, respHeaders, +// ) +// { +// body := test.Client.ExpectResponse.Body +// if j := test.Client.ExpectResponse.BodyJSON; j != nil { +// b, err := json.Marshal(j) +// require.NoError(t, err) +// body = string(b) +// } +// assert.Equal( +// t, body, respBody, +// "unexpected response body", +// ) +// } + +// // Check logs +// logs.ReadLogs(func(m []map[string]any) { +// for i, x := range m { +// if i >= len(test.Logs) { +// t.Errorf("unexpected log: %v", m[i]) +// continue +// } +// assert.Equal(t, +// test.Logs[i], x, +// "unexpected log at index %d", i, +// ) +// } +// }) +// logs.Reset() +// }) +// } +// }) +// } +// } + +// func GetSetups(t *testing.T) []testsetup.Setup { +// return []testsetup.Setup{ +// testsetup.Starwars(), +// testsetup.InputsSchema(), +// } +// } + +// type SendResponse struct { +// Status int +// Body string +// Headers map[string]string +// } +// type ReceivedRequest struct { +// Body string +// Headers map[string]string +// } + +// func launchSetup(t *testing.T, s testsetup.Setup) ( +// clientProxy *fasthttp.Client, +// forwarded <-chan ReceivedRequest, +// resp *Syncronized[*SendResponse], +// logRecorder *LogRecorder, +// ) { +// resp = new(Syncronized[*SendResponse]) + +// lnDest := fasthttputil.NewInmemoryListener() +// t.Cleanup(func() { lnDest.Close() }) + +// lnProxy := fasthttputil.NewInmemoryListener() +// t.Cleanup(func() { lnProxy.Close() }) + +// forwardedRW := make(chan ReceivedRequest, 1) +// forwarded = forwardedRW + +// go func() { +// s := &fasthttp.Server{ +// Handler: func(ctx *fasthttp.RequestCtx) { +// // Send the received request context for the check +// var rr ReceivedRequest +// rr.Headers = make(map[string]string, ctx.Request.Header.Len()) +// ctx.Request.Header.VisitAll(func(key, value []byte) { +// rr.Headers[string(key)] = string(value) +// }) +// rr.Body = string(ctx.Request.Body()) +// forwardedRW <- rr + +// // Send response +// sr := resp.Get() +// if sr == nil { +// ctx.Error( +// fasthttp.StatusMessage(fasthttp.StatusInternalServerError), +// fasthttp.StatusInternalServerError, +// ) +// return +// } +// ctx.Response.SetStatusCode(sr.Status) +// for k, v := range sr.Headers { +// ctx.Response.Header.Set(k, v) +// } +// ctx.Response.SetBodyString(sr.Body) +// }, +// } +// if err := s.Serve(lnDest); err != nil { +// panic(err) +// } +// }() + +// // Launch proxy server +// logRecorder = new(LogRecorder) +// log := plog.Logger{ +// Level: plog.DebugLevel, +// TimeField: "time", +// TimeFormat: "23:59:59", +// Writer: &plog.IOWriter{Writer: logRecorder}, +// } +// server := server.NewProxy( +// s.Config, +// time.Second*10, +// time.Second*10, +// 1024*64, +// 1024*64, +// log, +// &fasthttp.Client{ +// Dial: func(addr string) (net.Conn, error) { +// return lnDest.Dial() +// }, +// }, +// nil, +// ) + +// go func() { +// server.Serve(lnProxy) +// }() + +// clientProxy = &fasthttp.Client{ +// Dial: func(addr string) (net.Conn, error) { +// return lnProxy.Dial() +// }, +// } + +// return +// } + +// func doRequest( +// t *testing.T, +// client *fasthttp.Client, +// method, host, path string, +// prepareReq func(*fasthttp.Request), +// ) (status int, headers map[string]string, body string) { +// req := fasthttp.AcquireRequest() +// resp := fasthttp.AcquireResponse() +// defer fasthttp.ReleaseRequest(req) +// defer fasthttp.ReleaseResponse(resp) + +// if prepareReq != nil { +// prepareReq(req) +// } +// req.Header.SetMethod(method) +// req.SetHost(host) +// req.URI().SetPath(path) + +// err := client.Do(req, resp) +// require.NoError(t, err) + +// status = resp.StatusCode() +// headers = make(map[string]string, resp.Header.Len()) +// resp.Header.VisitAll(func(key, value []byte) { +// headers[string(key)] = string(value) +// }) +// body = string(resp.Body()) +// return +// } + +// type Syncronized[T any] struct { +// lock sync.Mutex +// t T +// } + +// func (s *Syncronized[T]) Get() T { +// s.lock.Lock() +// defer s.lock.Unlock() +// return s.t +// } + +// func (s *Syncronized[T]) Set(t T) { +// s.lock.Lock() +// defer s.lock.Unlock() +// s.t = t +// } + +// func compareHeaders(t *testing.T, title string, expected, actual map[string]string) { +// t.Helper() +// e := make(map[string]string, len(expected)) +// for k, v := range expected { +// e[k] = v +// } +// a := make(map[string]string, len(actual)) +// for k, v := range actual { +// a[k] = v +// } +// for k, ev := range expected { +// delete(e, k) +// delete(a, k) +// if av, ok := actual[k]; ok { +// expr, err := regexp.Compile(ev) +// if err != nil { +// t.Fatalf( +// "compiling regexp for %s header %q (%q): %v", +// title, k, ev, err, +// ) +// } +// if !expr.MatchString(av) { +// t.Errorf( +// "%s header %q expected regexp: %q; received: %q", +// title, k, ev, av, +// ) +// } +// } +// } +// for k, v := range e { +// t.Errorf("missing %s header %q (%q)", title, k, v) +// } +// for k, v := range a { +// t.Errorf("unexpected %s header %q (%q)", title, k, v) +// } +// } + +// type LogRecorder struct { +// Lock sync.Mutex +// Recorded []map[string]any +// } + +// func (w *LogRecorder) Write(d []byte) (int, error) { +// var m map[string]any +// if err := json.Unmarshal(d, &m); err != nil { +// return 0, fmt.Errorf("unmarshalling JSON: %w", err) +// } +// delete(m, "time") // We don't need to check the log time +// w.Lock.Lock() +// defer w.Lock.Unlock() +// w.Recorded = append(w.Recorded, m) +// return len(d), nil +// } + +// func (w *LogRecorder) Reset() { +// w.Lock.Lock() +// defer w.Lock.Unlock() +// w.Recorded = nil +// } + +// func (w *LogRecorder) ReadLogs(fn func([]map[string]any)) { +// w.Lock.Lock() +// defer w.Lock.Unlock() +// fn(w.Recorded) +// } + +// func copyMap[K comparable, V any](m map[K]V) (copy map[K]V) { +// copy = make(map[K]V, len(m)) +// for k, v := range m { +// copy[k] = v +// } +// return copy +// } // func checkLogs(t *testing.T, expected, actual []map[string]any) { // for i, x := range expected { diff --git a/pkg/testsetup/inputs_schema/test_arr_str_eq.yaml b/pkg/testsetup/inputs_schema/test_arr_str_eq.yaml deleted file mode 100644 index ee600b2..0000000 --- a/pkg/testsetup/inputs_schema/test_arr_str_eq.yaml +++ /dev/null @@ -1,72 +0,0 @@ -client: - input: - method: POST - endpoint: /service_a - body(JSON): - query: > - mutation( - $foo:String! = "foo", - $bar:String! = "bar", - ) { - arr_str_eq(a: [$foo, $bar]) - } - - expect-response: - status: 200 - headers: - Content-Length: ^27$ - Content-Type: ^text/plain; charset=utf-8$ - Server: ^fasthttp$ - Date: . - X-Custom-Header: value - body(JSON): - data: - arr_str_eq: ["foo", "bar"] - -destination: - expect-forwarded: - headers: - X-Forwarded-Host: ^localhost:8000$ - X-Forwarded-For: 0.0.0.0 - X-Forwarded-Proto: ^HTTP/1.1$ - Host: ^localhost:8081$ - Content-Length: ^68$ - Content-Type: ^application/json$ - User-Agent: ^fasthttp$ - Date: . - body(JSON): - query: > - mutation( - $foo:String! = "foo", - $bar:String! = "bar", - ) { - arr_str_eq(a: [$foo, $bar]) - } - - response: - status: 200 - headers: - X-Custom-Header: value - body(JSON): - data: - arr_str_eq: ["foo", "bar"] - -logs: - - level: info - message: 'listening' - host: localhost:8080 - tls: false - services: - - service_a - - level: info - message: 'handling request' - path: /service_a - - level: debug - path: /service_a - query: > - mutation( - $foo:String! = "foo", - $bar:String! = "bar", - ) { - arr_str_eq(a: [$foo, $bar]) - } \ No newline at end of file diff --git a/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/f.gqt b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_and_reviews.gqt similarity index 65% rename from pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/f.gqt rename to pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_and_reviews.gqt index 91cd3be..f6738a7 100644 --- a/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/f.gqt +++ b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_and_reviews.gqt @@ -1,5 +1,10 @@ --- -name: "F" +tags: + - query + - field:hero + - field:reviews + - constraint:equal + - expression:or --- query { hero(episode: JEDI) { diff --git a/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/c.gqt b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_expanded.gqt similarity index 69% rename from pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/c.gqt rename to pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_expanded.gqt index 6cbb20e..57da698 100644 --- a/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/c.gqt +++ b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_expanded.gqt @@ -1,5 +1,15 @@ --- -name: "C" +tags: + - query + - field:hero + - field:hero.friends + - field:hero.friendsConnection + - field:reviews + - constraint:equal + - constraint:greater + - constraint:greaterOrEqual + - expression:or + - recursive --- query { hero(episode: EMPIRE || JEDI) { diff --git a/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/b.gqt b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_friends.gqt similarity index 72% rename from pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/b.gqt rename to pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_friends.gqt index 47fefd9..6aceda2 100644 --- a/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/b.gqt +++ b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_friends.gqt @@ -1,5 +1,11 @@ --- -name: "B" +tags: + - query + - field:hero + - field:hero.friends + - constraint:equal + - expression:or + - recursive --- query { hero(episode: EMPIRE || JEDI) { diff --git a/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/e.gqt b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_friends_connection_and_starship.gqt similarity index 66% rename from pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/e.gqt rename to pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_friends_connection_and_starship.gqt index 4c0fda6..fd39751 100644 --- a/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/e.gqt +++ b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_friends_connection_and_starship.gqt @@ -1,10 +1,14 @@ --- -name: "E" tags: - - constraint equal - - constraint not equal - - constraint any - - type conditions + - query + - field:hero + - field:starship + - field:hero.friendsConnection + - fragments:inline + - constraint:any + - constraint:equal + - constraint:notEqual + - expression:or --- query { hero(episode: EMPIRE) { diff --git a/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/a.gqt b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_minimal.gqt similarity index 64% rename from pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/a.gqt rename to pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_minimal.gqt index dcdbdfa..9f5b8a7 100644 --- a/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/a.gqt +++ b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_minimal.gqt @@ -1,5 +1,9 @@ --- -name: "A" +tags: + - query + - field:hero + - field:hero.friends + - constraint:any --- query { hero(episode: *) { diff --git a/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/d.gqt b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_with_friends_and_reviews.gqt similarity index 70% rename from pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/d.gqt rename to pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_with_friends_and_reviews.gqt index 24217ab..a202694 100644 --- a/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/d.gqt +++ b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_with_friends_and_reviews.gqt @@ -1,5 +1,11 @@ --- -name: "D" +tags: + - query + - field:hero + - field:hero.friends + - field:reviews + - constraint:equal + - expression:or --- query { hero(episode: JEDI) { diff --git a/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/max_1.gqt b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_human_max2.gqt similarity index 66% rename from pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/max_1.gqt rename to pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_human_max2.gqt index 49286f2..c638e02 100644 --- a/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/max_1.gqt +++ b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_human_max2.gqt @@ -1,7 +1,9 @@ --- -name: "Max1" tags: - - max_selection + - query + - field:human + - constraint:any + - expression:max2 --- query { human(id: *) { diff --git a/pkg/testsetup/test1/all_services/testservice.yaml b/pkg/testsetup/test1/all_services/testservice.yaml deleted file mode 100644 index fde9ed5..0000000 --- a/pkg/testsetup/test1/all_services/testservice.yaml +++ /dev/null @@ -1,6 +0,0 @@ -name: "Test Service" -path: "/testservice" -forward-url: "http://localhost:8081/test" -forward-reduced: false -all-templates: ../all_templates -enabled-templates: ../enabled_templates diff --git a/pkg/testsetup/test1/all_templates/template_mut.gqt b/pkg/testsetup/test1/all_templates/template_mut.gqt deleted file mode 100644 index 8c4c934..0000000 --- a/pkg/testsetup/test1/all_templates/template_mut.gqt +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: "Template Mutation" -tags: - - mutation ---- -mutation { - someMutations( - firstArg: "first", - secondArg: "second", - ) { - fieldA - fieldB { - subFieldC - } - } -} diff --git a/pkg/testsetup/test1/all_templates/template_qry.gqt b/pkg/testsetup/test1/all_templates/template_qry.gqt deleted file mode 100644 index be5a0f0..0000000 --- a/pkg/testsetup/test1/all_templates/template_qry.gqt +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: "Template Query" -tags: - - query ---- -query { - queryFirstField { - queryFirstSubfield - querySecondSubfield - } - querySecondField -} diff --git a/pkg/testsetup/test1/config.yml b/pkg/testsetup/test1/config.yml deleted file mode 100644 index 543e016..0000000 --- a/pkg/testsetup/test1/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -proxy: - host: localhost:8080 - -all-services: all_services -enabled-services: enabled_services diff --git a/pkg/testsetup/test1/enabled_services/testservice.yaml b/pkg/testsetup/test1/enabled_services/testservice.yaml deleted file mode 100644 index fde9ed5..0000000 --- a/pkg/testsetup/test1/enabled_services/testservice.yaml +++ /dev/null @@ -1,6 +0,0 @@ -name: "Test Service" -path: "/testservice" -forward-url: "http://localhost:8081/test" -forward-reduced: false -all-templates: ../all_templates -enabled-templates: ../enabled_templates diff --git a/pkg/testsetup/test1/enabled_templates/template_mut.gqt b/pkg/testsetup/test1/enabled_templates/template_mut.gqt deleted file mode 100644 index 8c4c934..0000000 --- a/pkg/testsetup/test1/enabled_templates/template_mut.gqt +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: "Template Mutation" -tags: - - mutation ---- -mutation { - someMutations( - firstArg: "first", - secondArg: "second", - ) { - fieldA - fieldB { - subFieldC - } - } -} diff --git a/pkg/testsetup/test1/enabled_templates/template_qry.gqt b/pkg/testsetup/test1/enabled_templates/template_qry.gqt deleted file mode 100644 index be5a0f0..0000000 --- a/pkg/testsetup/test1/enabled_templates/template_qry.gqt +++ /dev/null @@ -1,12 +0,0 @@ ---- -name: "Template Query" -tags: - - query ---- -query { - queryFirstField { - queryFirstSubfield - querySecondSubfield - } - querySecondField -} diff --git a/pkg/testsetup/test1/test_qry_ok.yaml b/pkg/testsetup/test1/test_qry_ok.yaml deleted file mode 100644 index f3a74d0..0000000 --- a/pkg/testsetup/test1/test_qry_ok.yaml +++ /dev/null @@ -1,73 +0,0 @@ -client: - input: - method: POST - endpoint: /testservice - body(JSON): - query: 'query { - queryFirstField { - queryFirstSubfield - querySecondSubfield - } - querySecondField - }' - - expect-response: - status: 200 - headers: - Content-Length: ^166$ - Content-Type: ^text/plain; charset=utf-8$ - Server: ^fasthttp$ - Date: . - X-Custom-Header: value - body(JSON): - data: - queryFirstField: - queryFirstSubfield: 'first subfield value' - querySecondSubfield: 'second subfield value' - querySecondField: 'second query field value' - -destination: - expect-forwarded: - headers: - X-Forwarded-Host: ^localhost:8000$ - X-Forwarded-For: 0.0.0.0 - X-Forwarded-Proto: ^HTTP/1.1$ - Host: ^localhost:8081$ - Content-Length: ^97$ - Content-Type: ^application/json$ - User-Agent: ^fasthttp$ - Date: . - body(JSON): - query: 'query { - queryFirstField { - queryFirstSubfield - querySecondSubfield - } - querySecondField - }' - - response: - status: 200 - headers: - X-Custom-Header: value - body(JSON): - data: - queryFirstField: - queryFirstSubfield: 'first subfield value' - querySecondSubfield: 'second subfield value' - querySecondField: 'second query field value' - -logs: - - level: info - message: 'listening' - host: localhost:8080 - tls: false - services: - - testservice - - level: info - message: 'handling request' - path: /testservice - - level: debug - path: /testservice - query: '{"query":"query { queryFirstField { queryFirstSubfield querySecondSubfield } querySecondField }"}' - diff --git a/pkg/testsetup/test1/x_test_mut_ok.yaml b/pkg/testsetup/test1/x_test_mut_ok.yaml deleted file mode 100644 index 45d027e..0000000 --- a/pkg/testsetup/test1/x_test_mut_ok.yaml +++ /dev/null @@ -1,65 +0,0 @@ -client: - input: - method: POST - endpoint: /testservice - body(JSON): - query: 'mutation { - someMutations( - firstArg: val = "first" - secondArg: val = "second" - ) { - fieldA - fieldB { - subFieldC - } - } - }' - - expect-response: - status: 200 - headers: - Content-Length: ^166$ - Content-Type: ^text/plain; charset=utf-8$ - Server: ^fasthttp$ - Date: . - X-Custom-Header: value - body(JSON): - data: - someMutations: - fieldA - fieldB - subFieldC - -destination: - expect-forwarded: - headers: - X-Forwarded-Host: ^localhost:8000$ - X-Forwarded-For: 0.0.0.0 - X-Forwarded-Proto: ^HTTP/1.1$ - Host: ^localhost:8081$ - Content-Length: ^97$ - Content-Type: ^application/json$ - User-Agent: ^fasthttp$ - Date: . - body(JSON): - query: 'query { - queryFirstField { - queryFirstSubfield - querySecondSubfield - } - querySecondField - }' - - response: - status: 200 - headers: - X-Custom-Header: value - body(JSON): - data: - queryFirstField: - queryFirstSubfield: 'first subfield value' - querySecondSubfield: 'second subfield value' - querySecondField: 'second query field value' - -logs: - diff --git a/pkg/testsetup/testsetup.go b/pkg/testsetup/testsetup.go index 8b65947..5aa6311 100644 --- a/pkg/testsetup/testsetup.go +++ b/pkg/testsetup/testsetup.go @@ -2,166 +2,48 @@ package testsetup import ( "embed" - "fmt" "io/fs" "path/filepath" - "strings" "github.com/graph-guard/ggproxy/pkg/config" - "gopkg.in/yaml.v3" ) /* SPECIAL NOTE: *\ \* Symlinks are not allowed in embedded filesystems! */ +const ( + SetupNameStarwars = "starwars" + SetupNameInputsSchema = "inputs_schema" +) + +func ByName(name string) (s Setup, ok bool) { + switch name { + case SetupNameStarwars: + return read(fsStarwars, SetupNameStarwars), true + case SetupNameInputsSchema: + return read(fsInputsSchema, SetupNameInputsSchema), true + } + return s, false +} + //go:embed starwars var fsStarwars embed.FS -//go:embed test1 -var fsTest1 embed.FS - //go:embed inputs_schema var fsInputsSchema embed.FS -func Starwars() Setup { return read(fsStarwars, "starwars") } -func Test1() Setup { return read(fsTest1, "test1") } -func InputsSchema() Setup { return read(fsInputsSchema, "inputs_schema") } - func read(fsys fs.FS, root string) Setup { c, err := config.Read(fsys, root, filepath.Join(root, "config.yml")) - panicOnErr(err) - t, err := readTests(fsys, root) - panicOnErr(err) + if err != nil { + panic(err) + } return Setup{ Name: root, Config: c, - Tests: t, - } -} - -func panicOnErr(err error) { - if err != nil { - panic(err) } } -type TestModel struct { - Client struct { - Input struct { - Method string `yaml:"method"` - Endpoint string `yaml:"endpoint"` - Body string `yaml:"body"` - BodyJSON map[string]any `yaml:"body(JSON)"` - } `yaml:"input"` - ExpectResponse struct { - Status int `yaml:"status"` - Headers map[string]string `yaml:"headers"` // Key -> Regexp - Body string `yaml:"body"` - BodyJSON map[string]any `yaml:"body(JSON)"` - } `yaml:"expect-response"` - } `yaml:"client"` - Destination *struct { - ExpectForwarded struct { - Headers map[string]string `yaml:"headers"` // Key -> Regexp - Body string `yaml:"body"` - BodyJSON map[string]any `yaml:"body(JSON)"` - } `yaml:"expect-forwarded"` - Response struct { - Status int `yaml:"status"` - Headers map[string]string `yaml:"headers"` - Body string `yaml:"body"` - BodyJSON map[string]any `yaml:"body(JSON)"` - } - } `yaml:"destination"` - Logs []map[string]any `yaml:"logs"` -} - type Setup struct { Name string Config *config.Config - Tests []Test -} - -type Test struct { - Name string - TestModel -} - -func readTests(fsys fs.FS, root string) ([]Test, error) { - var tests []Test - d, err := fs.ReadDir(fsys, root) - if err != nil { - return nil, fmt.Errorf("reading test dir: %w", err) - } - for _, testDir := range d { - n := testDir.Name() - if !strings.HasPrefix(n, "test_") || !strings.HasSuffix(n, ".yaml") { - continue - } - - p := filepath.Join(root, n) - f, err := fsys.Open(p) - if err != nil { - return nil, fmt.Errorf("reading test file %q: %w", p, err) - } - defer f.Close() - - var m TestModel - d := yaml.NewDecoder(f) - d.KnownFields(true) - if err := d.Decode(&m); err != nil { - return nil, fmt.Errorf("decoding YAML %q: %w", p, err) - } - - if err := isXOR( - m.Client.Input.Body, - m.Client.Input.BodyJSON, - "client.input.body", - "client.input.body(JSON)", - ); err != nil { - return nil, err - } - if err := isXOR( - m.Client.ExpectResponse.Body, - m.Client.ExpectResponse.BodyJSON, - "client.expect-response.body", - "client.expect-response.body(JSON)", - ); err != nil { - return nil, err - } - if m.Destination != nil { - if err := isXOR( - m.Destination.ExpectForwarded.Body, - m.Destination.ExpectForwarded.BodyJSON, - "destination.expect-forwarded.body", - "destination.expect-forwarded.body(JSON)", - ); err != nil { - return nil, err - } - if err := isXOR( - m.Destination.Response.Body, - m.Destination.Response.BodyJSON, - "destination.expect-forwarded.body", - "destination.expect-forwarded.body(JSON)", - ); err != nil { - return nil, err - } - } - - tests = append(tests, Test{ - Name: n, - TestModel: m, - }) - } - return tests, nil -} - -func isXOR(a string, b map[string]any, aTitle, bTitle string) error { - if (a != "" && b == nil) || (a == "" && b != nil) { - return nil - } - return fmt.Errorf(`%q (%q) and %q (%v) are mutually exclusive, `+ - `make sure you're using either of them, not both at the same time!`, - aTitle, a, bTitle, b, - ) } diff --git a/pkg/testsetup/testsetup_test.go b/pkg/testsetup/testsetup_test.go index 57d7e8c..618fabe 100644 --- a/pkg/testsetup/testsetup_test.go +++ b/pkg/testsetup/testsetup_test.go @@ -7,12 +7,19 @@ import ( "github.com/stretchr/testify/require" ) -func TestStarwars(t *testing.T) { checkSetup(t, testsetup.Starwars()) } -func TestTest1(t *testing.T) { checkSetup(t, testsetup.Test1()) } -func TestInputsSchema(t *testing.T) { checkSetup(t, testsetup.InputsSchema()) } +func TestStarwars(t *testing.T) { + s, ok := testsetup.ByName(testsetup.SetupNameStarwars) + require.True(t, ok) + checkSetup(t, s) +} + +func TestInputsSchema(t *testing.T) { + s, ok := testsetup.ByName(testsetup.SetupNameInputsSchema) + require.True(t, ok) + checkSetup(t, s) +} func checkSetup(t *testing.T, s testsetup.Setup) { require.NotZero(t, s.Config) require.NotZero(t, s.Name) - require.NotZero(t, s.Tests) } From 8baa980e3860e4a86dbb41c907e8f06013116637 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sat, 11 Mar 2023 02:19:14 +0100 Subject: [PATCH 31/35] fix: Fix type name search for array types --- pkg/gqlparse/gqlparse.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/gqlparse/gqlparse.go b/pkg/gqlparse/gqlparse.go index cf8c02f..3fe3ad9 100644 --- a/pkg/gqlparse/gqlparse.go +++ b/pkg/gqlparse/gqlparse.go @@ -333,7 +333,7 @@ func (r *Parser) Parse( onError(&r.errFieldUndef) return true } - tp := r.schema.Types[recentField.Type.NamedType] + tp := r.schema.Types[getTypeName(recentField.Type)] r.schemaTypeStack.TopOffsetFn(0, func(f *typeStackFrame) { f.FieldType = tp }) @@ -1443,3 +1443,9 @@ func astTypeKindToString(d *ast.Definition) string { } return string(d.Kind) } + +func getTypeName(t *ast.Type) string { + for ; t.Elem != nil; t = t.Elem { + } + return t.NamedType +} From 72cc3651d4dcc15e753440c581444679cb578df4 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sat, 11 Mar 2023 02:19:36 +0100 Subject: [PATCH 32/35] test: Add test --- .../tests/tests/starwars/hero_expanded.yaml | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 pkg/engine/tests/tests/starwars/hero_expanded.yaml diff --git a/pkg/engine/tests/tests/starwars/hero_expanded.yaml b/pkg/engine/tests/tests/starwars/hero_expanded.yaml new file mode 100644 index 0000000..bb91e93 --- /dev/null +++ b/pkg/engine/tests/tests/starwars/hero_expanded.yaml @@ -0,0 +1,28 @@ +query: > + query { + hero(episode: EMPIRE) { + id + name + appearsIn + friends { + id + name + appearsIn + friends { + id + name + appearsIn + } + } + friendsConnection(first: 0, after: "something") { + totalCount + friends { + id + name + appearsIn + } + } + } + } +expect-matches: + - query_hero_expanded \ No newline at end of file From f2377ec9bd1390f0013cf40dff762ba6c935cd1b Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sat, 11 Mar 2023 02:56:12 +0100 Subject: [PATCH 33/35] fix: Fix type check after fragment spread --- pkg/gqlparse/gqlparse.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/gqlparse/gqlparse.go b/pkg/gqlparse/gqlparse.go index 3fe3ad9..deb7d5d 100644 --- a/pkg/gqlparse/gqlparse.go +++ b/pkg/gqlparse/gqlparse.go @@ -419,11 +419,6 @@ func (r *Parser) Parse( } case gqlscan.TokenNamedSpread: if r.schema != nil { - t := r.schemaTypeStack.Top() - r.schemaTypeStack.Push(typeStackFrame{ - HostType: t.HostType, - }) - hostType := r.schemaTypeStack.Top().HostType index := len(r.fragSpreadHostTypes) r.fragSpreadHostTypes = append(r.fragSpreadHostTypes, hostType) From bac661a1873f7b69e7ca92d1e4546ee91db45c7a Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sat, 11 Mar 2023 02:57:05 +0100 Subject: [PATCH 34/35] test: Add tests --- .../starwars/friends_connection_args.yaml | 15 ++++++++++++ .../starwars/friends_connection_noargs.yaml | 15 ++++++++++++ .../friends_connection_noargs_frags.yaml | 24 +++++++++++++++++++ ...riends_connection_noargs_frags_inline.yaml | 22 +++++++++++++++++ .../tests/tests/starwars/hero_expanded.yaml | 2 +- .../templates_enabled/query_hero_expanded.gqt | 2 +- 6 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 pkg/engine/tests/tests/starwars/friends_connection_args.yaml create mode 100644 pkg/engine/tests/tests/starwars/friends_connection_noargs.yaml create mode 100644 pkg/engine/tests/tests/starwars/friends_connection_noargs_frags.yaml create mode 100644 pkg/engine/tests/tests/starwars/friends_connection_noargs_frags_inline.yaml diff --git a/pkg/engine/tests/tests/starwars/friends_connection_args.yaml b/pkg/engine/tests/tests/starwars/friends_connection_args.yaml new file mode 100644 index 0000000..b28c10f --- /dev/null +++ b/pkg/engine/tests/tests/starwars/friends_connection_args.yaml @@ -0,0 +1,15 @@ +query: > + query { + hero(episode: EMPIRE) { + friendsConnection(first: 5, after: "x") { + friends { + appearsIn + name + id + } + totalCount + } + } + } +expect-matches: + - query_hero_expanded diff --git a/pkg/engine/tests/tests/starwars/friends_connection_noargs.yaml b/pkg/engine/tests/tests/starwars/friends_connection_noargs.yaml new file mode 100644 index 0000000..32b4dc4 --- /dev/null +++ b/pkg/engine/tests/tests/starwars/friends_connection_noargs.yaml @@ -0,0 +1,15 @@ +query: > + query { + hero(episode: EMPIRE) { + friendsConnection { + friends { + appearsIn + name + id + } + totalCount + } + } + } +expect-matches: + - query_hero_friends_connection_and_starship diff --git a/pkg/engine/tests/tests/starwars/friends_connection_noargs_frags.yaml b/pkg/engine/tests/tests/starwars/friends_connection_noargs_frags.yaml new file mode 100644 index 0000000..298ed20 --- /dev/null +++ b/pkg/engine/tests/tests/starwars/friends_connection_noargs_frags.yaml @@ -0,0 +1,24 @@ +query: > + query { + hero(episode: EMPIRE) { + friendsConnection { + friends { + appearsIn + name + id + ...humanFrag + ...droidFrag + } + totalCount + } + } + } + fragment humanFrag on Human { + mass + height(unit: METER) + } + fragment droidFrag on Droid { + primaryFunction + } +expect-matches: + - query_hero_friends_connection_and_starship diff --git a/pkg/engine/tests/tests/starwars/friends_connection_noargs_frags_inline.yaml b/pkg/engine/tests/tests/starwars/friends_connection_noargs_frags_inline.yaml new file mode 100644 index 0000000..a108941 --- /dev/null +++ b/pkg/engine/tests/tests/starwars/friends_connection_noargs_frags_inline.yaml @@ -0,0 +1,22 @@ +query: > + query { + hero(episode: EMPIRE) { + friendsConnection { + friends { + appearsIn + name + id + ... on Human { + mass + height(unit: METER) + } + ...on Droid { + primaryFunction + } + } + totalCount + } + } + } +expect-matches: + - query_hero_friends_connection_and_starship diff --git a/pkg/engine/tests/tests/starwars/hero_expanded.yaml b/pkg/engine/tests/tests/starwars/hero_expanded.yaml index bb91e93..3747276 100644 --- a/pkg/engine/tests/tests/starwars/hero_expanded.yaml +++ b/pkg/engine/tests/tests/starwars/hero_expanded.yaml @@ -14,7 +14,7 @@ query: > appearsIn } } - friendsConnection(first: 0, after: "something") { + friendsConnection(first: 1, after: "something") { totalCount friends { id diff --git a/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_expanded.gqt b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_expanded.gqt index 57da698..ce3f151 100644 --- a/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_expanded.gqt +++ b/pkg/testsetup/starwars/services_enabled/starwars/templates_enabled/query_hero_expanded.gqt @@ -26,7 +26,7 @@ query { appearsIn } } - friendsConnection(first: >= 0, after: len > 0) { + friendsConnection(first: > 0, after: len > 0 || null) { totalCount friends { id From 0098586a01ec528c7d0f7c48947334c2abd63511 Mon Sep 17 00:00:00 2001 From: Roman Sharkov Date: Sat, 11 Mar 2023 03:47:41 +0100 Subject: [PATCH 35/35] fix: Upgrade GQT to v4.0.12 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e8b58d2..b191733 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/dustin/go-humanize v1.0.0 github.com/google/go-cmp v0.5.8 github.com/graph-guard/gqlscan v1.0.0 - github.com/graph-guard/gqt/v4 v4.0.10 + github.com/graph-guard/gqt/v4 v4.0.12 github.com/phuslu/log v1.0.81 github.com/pierrec/xxHash v0.1.5 github.com/stretchr/testify v1.7.2 diff --git a/go.sum b/go.sum index 8f044a0..9c78f82 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-guard/gqlscan v1.0.0 h1:hUEDqnJQJ7nV9S8hCPJuYI/H97KP3unu0Gu7wd0wV8A= github.com/graph-guard/gqlscan v1.0.0/go.mod h1:A7TeIFxoiP39gw/r4sWjDnTZJnNy+4kaytP9Kh7Luuk= -github.com/graph-guard/gqt/v4 v4.0.10 h1:keAxTwkopb44eR1CwRYL4/u0PGUHHEd6d8RjR85b3c0= -github.com/graph-guard/gqt/v4 v4.0.10/go.mod h1:tXBLe4Gr3S/y8pM6ABkcIWFcspyU1c1YfrsXdBf0hKE= +github.com/graph-guard/gqt/v4 v4.0.12 h1:BjG4wEq5Cze6X7bEsono2fVunWVcK9RyyzAUtbvG70Y= +github.com/graph-guard/gqt/v4 v4.0.12/go.mod h1:tXBLe4Gr3S/y8pM6ABkcIWFcspyU1c1YfrsXdBf0hKE= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM=