graphql 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graph_ql/directive.rb +9 -5
- data/lib/graph_ql/directives/include_directive.rb +2 -2
- data/lib/graph_ql/directives/skip_directive.rb +2 -2
- data/lib/graph_ql/enum.rb +5 -8
- data/lib/graph_ql/field.rb +50 -0
- data/lib/graph_ql/interface.rb +4 -0
- data/lib/graph_ql/introspection/arguments_field.rb +2 -2
- data/lib/graph_ql/introspection/directive_type.rb +10 -10
- data/lib/graph_ql/introspection/enum_value_type.rb +11 -8
- data/lib/graph_ql/introspection/enum_values_field.rb +5 -5
- data/lib/graph_ql/introspection/field_type.rb +14 -10
- data/lib/graph_ql/introspection/fields_field.rb +4 -4
- data/lib/graph_ql/introspection/input_fields_field.rb +3 -3
- data/lib/graph_ql/introspection/input_value_type.rb +8 -8
- data/lib/graph_ql/introspection/interfaces_field.rb +5 -0
- data/lib/graph_ql/introspection/of_type_field.rb +3 -9
- data/lib/graph_ql/introspection/possible_types_field.rb +3 -10
- data/lib/graph_ql/introspection/schema_type.rb +8 -14
- data/lib/graph_ql/introspection/type_kind_enum.rb +1 -1
- data/lib/graph_ql/introspection/type_type.rb +17 -19
- data/lib/graph_ql/introspection/typename_field.rb +15 -0
- data/lib/graph_ql/parser.rb +9 -0
- data/lib/graph_ql/parser/nodes.rb +17 -7
- data/lib/graph_ql/parser/parser.rb +7 -7
- data/lib/graph_ql/parser/transform.rb +23 -20
- data/lib/graph_ql/parser/visitor.rb +29 -13
- data/lib/graph_ql/query.rb +39 -16
- data/lib/graph_ql/query/field_resolution_strategy.rb +15 -11
- data/lib/graph_ql/query/type_resolver.rb +4 -2
- data/lib/graph_ql/repl.rb +1 -1
- data/lib/graph_ql/schema.rb +19 -7
- data/lib/graph_ql/schema/each_item_validator.rb +12 -0
- data/lib/graph_ql/schema/field_validator.rb +13 -0
- data/lib/graph_ql/schema/implementation_validator.rb +21 -0
- data/lib/graph_ql/schema/schema_validator.rb +10 -0
- data/lib/graph_ql/schema/type_reducer.rb +6 -6
- data/lib/graph_ql/schema/type_validator.rb +47 -0
- data/lib/graph_ql/static_validation.rb +18 -0
- data/lib/graph_ql/static_validation/argument_literals_are_compatible.rb +13 -0
- data/lib/graph_ql/static_validation/arguments_are_defined.rb +10 -0
- data/lib/graph_ql/static_validation/arguments_validator.rb +16 -0
- data/lib/graph_ql/static_validation/directives_are_defined.rb +18 -0
- data/lib/graph_ql/static_validation/fields_are_defined_on_type.rb +29 -0
- data/lib/graph_ql/static_validation/fields_have_appropriate_selections.rb +31 -0
- data/lib/graph_ql/static_validation/fields_will_merge.rb +93 -0
- data/lib/graph_ql/static_validation/fragment_types_exist.rb +24 -0
- data/lib/graph_ql/static_validation/fragments_are_used.rb +30 -0
- data/lib/graph_ql/static_validation/literal_validator.rb +27 -0
- data/lib/graph_ql/static_validation/message.rb +29 -0
- data/lib/graph_ql/static_validation/required_arguments_are_present.rb +13 -0
- data/lib/graph_ql/static_validation/type_stack.rb +87 -0
- data/lib/graph_ql/static_validation/validator.rb +48 -0
- data/lib/graph_ql/type_kinds.rb +50 -12
- data/lib/graph_ql/types/argument_definer.rb +7 -0
- data/lib/graph_ql/types/boolean_type.rb +3 -3
- data/lib/graph_ql/types/field_definer.rb +19 -0
- data/lib/graph_ql/types/float_type.rb +3 -3
- data/lib/graph_ql/types/input_object_type.rb +4 -0
- data/lib/graph_ql/types/input_value.rb +1 -1
- data/lib/graph_ql/types/int_type.rb +4 -4
- data/lib/graph_ql/types/list_type.rb +5 -1
- data/lib/graph_ql/types/non_null_type.rb +4 -0
- data/lib/graph_ql/types/object_type.rb +9 -20
- data/lib/graph_ql/types/scalar_type.rb +4 -0
- data/lib/graph_ql/types/string_type.rb +3 -3
- data/lib/graph_ql/types/type_definer.rb +5 -9
- data/lib/graph_ql/union.rb +6 -17
- data/lib/graph_ql/version.rb +1 -1
- data/lib/graphql.rb +58 -78
- data/readme.md +80 -7
- data/spec/graph_ql/interface_spec.rb +15 -1
- data/spec/graph_ql/introspection/directive_type_spec.rb +2 -2
- data/spec/graph_ql/introspection/schema_type_spec.rb +2 -1
- data/spec/graph_ql/introspection/type_type_spec.rb +16 -1
- data/spec/graph_ql/parser/parser_spec.rb +3 -1
- data/spec/graph_ql/parser/transform_spec.rb +12 -2
- data/spec/graph_ql/parser/visitor_spec.rb +13 -5
- data/spec/graph_ql/query_spec.rb +25 -13
- data/spec/graph_ql/schema/field_validator_spec.rb +21 -0
- data/spec/graph_ql/schema/type_reducer_spec.rb +2 -2
- data/spec/graph_ql/schema/type_validator_spec.rb +54 -0
- data/spec/graph_ql/static_validation/argument_literals_are_compatible_spec.rb +41 -0
- data/spec/graph_ql/static_validation/arguments_are_defined_spec.rb +40 -0
- data/spec/graph_ql/static_validation/directives_are_defined_spec.rb +33 -0
- data/spec/graph_ql/static_validation/fields_are_defined_on_type_spec.rb +59 -0
- data/spec/graph_ql/static_validation/fields_have_appropriate_selections_spec.rb +30 -0
- data/spec/graph_ql/{validations → static_validation}/fields_will_merge_spec.rb +24 -17
- data/spec/graph_ql/static_validation/fragment_types_exist_spec.rb +38 -0
- data/spec/graph_ql/static_validation/fragments_are_used_spec.rb +24 -0
- data/spec/graph_ql/static_validation/required_arguments_are_present_spec.rb +41 -0
- data/spec/graph_ql/static_validation/type_stack_spec.rb +35 -0
- data/spec/graph_ql/static_validation/validator_spec.rb +28 -0
- data/spec/graph_ql/types/object_type_spec.rb +1 -1
- data/spec/graph_ql/union_spec.rb +1 -14
- data/spec/support/dummy_app.rb +75 -53
- metadata +53 -31
- data/lib/graph_ql/fields/abstract_field.rb +0 -37
- data/lib/graph_ql/fields/access_field.rb +0 -24
- data/lib/graph_ql/fields/field.rb +0 -34
- data/lib/graph_ql/types/abstract_type.rb +0 -14
- data/lib/graph_ql/validations/fields_are_defined_on_type.rb +0 -44
- data/lib/graph_ql/validations/fields_will_merge.rb +0 -80
- data/lib/graph_ql/validations/fragments_are_used.rb +0 -24
- data/lib/graph_ql/validator.rb +0 -29
- data/spec/graph_ql/validations/fields_are_defined_on_type_spec.rb +0 -28
- data/spec/graph_ql/validations/fragments_are_used_spec.rb +0 -28
- data/spec/graph_ql/validator_spec.rb +0 -24
@@ -53,12 +53,20 @@ describe GraphQL::Transform do
|
|
53
53
|
assert_equal("someInfo", res.name)
|
54
54
|
assert_equal(3, res.selections.length)
|
55
55
|
|
56
|
-
res = get_result(
|
56
|
+
res = get_result(
|
57
|
+
"mutation changeThings(
|
58
|
+
$var: Float = 4.5E+6,
|
59
|
+
$arr: [Int]!
|
60
|
+
) @flag, @skip(if: 1) {
|
61
|
+
changeThings(var: $var) { a,b,c }
|
62
|
+
}", parse: :operation_definition)
|
57
63
|
assert_equal("mutation", res.operation_type)
|
58
64
|
assert_equal("var", res.variables.first.name)
|
59
65
|
assert_equal("Float", res.variables.first.type.name)
|
60
|
-
assert_equal(
|
66
|
+
assert_equal(4_500_000.0, res.variables.first.default_value)
|
61
67
|
assert_equal("arr", res.variables.last.name)
|
68
|
+
assert_equal(3, res.variables.last.line)
|
69
|
+
assert_equal(10, res.variables.last.col)
|
62
70
|
assert_equal("Int", res.variables.last.type.of_type.of_type.name)
|
63
71
|
assert_equal(2, res.directives.length)
|
64
72
|
end
|
@@ -80,6 +88,8 @@ describe GraphQL::Transform do
|
|
80
88
|
it 'transforms fields' do
|
81
89
|
res = get_result(%|best_pals: friends(first: 3, coolnessLevel: SO_COOL, query: {nice: {very: true}})|, parse: :field)
|
82
90
|
assert_equal(GraphQL::Nodes::Field, res.class)
|
91
|
+
assert_equal(1, res.line)
|
92
|
+
assert_equal(1, res.col)
|
83
93
|
assert_equal("friends", res.name)
|
84
94
|
assert_equal("best_pals", res.alias)
|
85
95
|
assert_equal("first", res.arguments[0].name)
|
@@ -9,13 +9,13 @@ describe GraphQL::Visitor do
|
|
9
9
|
|
10
10
|
let(:visitor) do
|
11
11
|
v = GraphQL::Visitor.new
|
12
|
-
v[GraphQL::Nodes::Field] << -> (node) { counts[:fields_entered] += 1 }
|
12
|
+
v[GraphQL::Nodes::Field] << -> (node, parent) { counts[:fields_entered] += 1 }
|
13
13
|
# two ways to set up enter hooks:
|
14
|
-
v[GraphQL::Nodes::Argument] << -> (node) { counts[:argument_names] << node.name }
|
15
|
-
v[GraphQL::Nodes::Argument].enter << -> (node) { counts[:arguments_entered] += 1}
|
16
|
-
v[GraphQL::Nodes::Argument].leave << -> (node) { counts[:arguments_left] += 1 }
|
14
|
+
v[GraphQL::Nodes::Argument] << -> (node, parent) { counts[:argument_names] << node.name }
|
15
|
+
v[GraphQL::Nodes::Argument].enter << -> (node, parent) { counts[:arguments_entered] += 1}
|
16
|
+
v[GraphQL::Nodes::Argument].leave << -> (node, parent) { counts[:arguments_left] += 1 }
|
17
17
|
|
18
|
-
v[GraphQL::Nodes::Document].leave << -> (node) { counts[:finished] = true }
|
18
|
+
v[GraphQL::Nodes::Document].leave << -> (node, parent) { counts[:finished] = true }
|
19
19
|
v
|
20
20
|
end
|
21
21
|
|
@@ -28,4 +28,12 @@ describe GraphQL::Visitor do
|
|
28
28
|
assert_equal(["id", "first"], counts[:argument_names])
|
29
29
|
assert(counts[:finished])
|
30
30
|
end
|
31
|
+
|
32
|
+
describe 'Visitor::SKIP' do
|
33
|
+
it 'skips the rest of the node' do
|
34
|
+
visitor[GraphQL::Nodes::Document] << -> (node, parent) { GraphQL::Visitor::SKIP }
|
35
|
+
visitor.visit(document)
|
36
|
+
assert_equal(0, counts[:fields_entered])
|
37
|
+
end
|
38
|
+
end
|
31
39
|
end
|
data/spec/graph_ql/query_spec.rb
CHANGED
@@ -4,14 +4,14 @@ describe GraphQL::Query do
|
|
4
4
|
describe '#execute' do
|
5
5
|
let(:query_string) { %|
|
6
6
|
query getFlavor($cheeseId: Int!) {
|
7
|
-
brie: cheese(id: 1) { ...cheeseFields, ...
|
7
|
+
brie: cheese(id: 1) { ...cheeseFields, ... milkFields, taste: flavor },
|
8
8
|
cheese(id: $cheeseId) {
|
9
9
|
__typename,
|
10
10
|
id,
|
11
11
|
...cheeseFields,
|
12
12
|
... edibleFields,
|
13
13
|
... on Cheese { cheeseKind: flavor },
|
14
|
-
... on
|
14
|
+
... on Milk { source }
|
15
15
|
}
|
16
16
|
fromSource(source: COW) { id }
|
17
17
|
firstSheep: searchDairy(product: {source: SHEEP}) { ... dairyFields }
|
@@ -19,27 +19,29 @@ describe GraphQL::Query do
|
|
19
19
|
}
|
20
20
|
fragment cheeseFields on Cheese { flavor }
|
21
21
|
fragment edibleFields on Edible { fatContent }
|
22
|
-
fragment
|
23
|
-
fragment dairyFields on
|
22
|
+
fragment milkFields on Milk { source }
|
23
|
+
fragment dairyFields on AnimalProduct {
|
24
24
|
... on Cheese { flavor }
|
25
25
|
... on Milk { source }
|
26
26
|
}
|
27
27
|
|}
|
28
|
-
let(:
|
28
|
+
let(:debug) { false }
|
29
|
+
let(:query) { GraphQL::Query.new(DummySchema, query_string, context: {}, params: {"cheeseId" => 2}, debug: debug)}
|
29
30
|
let(:result) { query.result }
|
31
|
+
|
30
32
|
it 'returns fields on objects' do
|
31
33
|
expected = {"data"=> { "getFlavor" => {
|
32
34
|
"brie" => { "flavor" => "Brie", "taste" => "Brie" },
|
33
35
|
"cheese" => {
|
34
36
|
"__typename" => "Cheese",
|
35
37
|
"id" => 2,
|
36
|
-
"fatContent" => 0.3,
|
37
38
|
"flavor" => "Gouda",
|
39
|
+
"fatContent" => 0.3,
|
38
40
|
"cheeseKind" => "Gouda",
|
39
41
|
},
|
40
42
|
"fromSource" => [{ "id" => 1 }, {"id" => 2}],
|
41
43
|
"firstSheep" => { "flavor" => "Manchego" },
|
42
|
-
"favoriteEdible"=>{"__typename"=>"
|
44
|
+
"favoriteEdible"=>{"__typename"=>"Milk", "fatContent"=>0.04},
|
43
45
|
}}}
|
44
46
|
assert_equal(expected, result)
|
45
47
|
end
|
@@ -49,12 +51,22 @@ describe GraphQL::Query do
|
|
49
51
|
end
|
50
52
|
|
51
53
|
describe 'runtime errors' do
|
52
|
-
let(:query_string) {%| query noMilk {
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
54
|
+
let(:query_string) {%| query noMilk { error }|}
|
55
|
+
describe 'if debug: false' do
|
56
|
+
let(:debug) { false }
|
57
|
+
it 'turns into error messages' do
|
58
|
+
expected = {"errors"=>[
|
59
|
+
{"message"=>"Something went wrong during query execution: This error was raised on purpose"}
|
60
|
+
]}
|
61
|
+
assert_equal(expected, result)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe 'if debug: true' do
|
66
|
+
let(:debug) { true }
|
67
|
+
it 'raises error' do
|
68
|
+
assert_raises(RuntimeError) { result }
|
69
|
+
end
|
58
70
|
end
|
59
71
|
end
|
60
72
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::Schema::FieldValidator do
|
4
|
+
let(:field_defn) {{
|
5
|
+
name: "Field",
|
6
|
+
description: "Invalid field",
|
7
|
+
deprecation_reason: nil,
|
8
|
+
arguments: {symbol_arg: nil},
|
9
|
+
type: DairyAnimalEnum,
|
10
|
+
}}
|
11
|
+
let(:field) {
|
12
|
+
f = OpenStruct.new(field_defn)
|
13
|
+
def f.to_s; f.name; end
|
14
|
+
f
|
15
|
+
}
|
16
|
+
let(:errors) { e = []; GraphQL::Schema::FieldValidator.new.validate(field, e); e }
|
17
|
+
it "requires argument names to be strings" do
|
18
|
+
expected = ["Field.arguments keys must be Strings, but some aren't: symbol_arg"]
|
19
|
+
assert_equal(expected, errors)
|
20
|
+
end
|
21
|
+
end
|
@@ -9,9 +9,9 @@ describe GraphQL::Schema::TypeReducer do
|
|
9
9
|
"String" => GraphQL::STRING_TYPE,
|
10
10
|
"DairyAnimal" => DairyAnimalEnum,
|
11
11
|
"Float" => GraphQL::FLOAT_TYPE,
|
12
|
-
"Edible" =>
|
12
|
+
"Edible" => EdibleInterface,
|
13
13
|
"Milk" => MilkType,
|
14
|
-
"AnimalProduct" =>
|
14
|
+
"AnimalProduct" => AnimalProductInterface,
|
15
15
|
}
|
16
16
|
assert_equal(expected.keys, reducer.result.keys)
|
17
17
|
assert_equal(expected, reducer.result)
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::Schema::TypeValidator do
|
4
|
+
let(:base_type_defn) {
|
5
|
+
{
|
6
|
+
name: "InvalidType",
|
7
|
+
description: "...",
|
8
|
+
deprecation_reason: nil,
|
9
|
+
kind: GraphQL::TypeKinds::OBJECT,
|
10
|
+
interfaces: [],
|
11
|
+
fields: {},
|
12
|
+
}
|
13
|
+
}
|
14
|
+
let(:object) {
|
15
|
+
o = OpenStruct.new(type_defn)
|
16
|
+
def o.to_s; "InvalidType"; end
|
17
|
+
o
|
18
|
+
}
|
19
|
+
let(:validator) { GraphQL::Schema::TypeValidator.new }
|
20
|
+
let(:errors) { e = []; validator.validate(object, e); e;}
|
21
|
+
describe 'when name isnt defined' do
|
22
|
+
let(:type_defn) { base_type_defn.delete_if {|k,v| k == :name }}
|
23
|
+
it 'requires name' do
|
24
|
+
assert_equal(
|
25
|
+
["InvalidType must respond to #name() to be a Type"],
|
26
|
+
errors
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "when a field name isnt a string" do
|
32
|
+
let(:type_defn) { base_type_defn.merge(fields: {symbol_field: (GraphQL::Field.new {|f|}) }) }
|
33
|
+
it "requires string names" do
|
34
|
+
assert_equal(
|
35
|
+
["InvalidType.fields keys must be Strings, but some aren't: symbol_field"],
|
36
|
+
errors
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "when a Union isnt valid" do
|
42
|
+
let(:object) {
|
43
|
+
GraphQL::Union.new("Something", "some union", [DairyProductInputType])
|
44
|
+
}
|
45
|
+
let(:errors) { e = []; GraphQL::Schema::TypeValidator.new.validate(object, e); e;}
|
46
|
+
it 'must be 2+ types, must be only object types' do
|
47
|
+
expected = [
|
48
|
+
"Something.possible_types must be objects, but some aren't: <GraphQL::InputObjectType DairyProductInput>",
|
49
|
+
"Union Something must be defined with 2 or more types, not 1",
|
50
|
+
]
|
51
|
+
assert_equal(expected, errors)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::StaticValidation::ArgumentLiteralsAreCompatible do
|
4
|
+
let(:document) { GraphQL.parse(%|
|
5
|
+
query getCheese {
|
6
|
+
cheese(id: "aasdlkfj") { source }
|
7
|
+
cheese(id: 1) { source @skip(if: {id: 1})}
|
8
|
+
yakSource: searchDairy(product: {source: YAK, fatContent: 1.1}) { source }
|
9
|
+
badSource: searchDairy(product: {source: 1.1}) { source }
|
10
|
+
}
|
11
|
+
|
12
|
+
fragment cheeseFields on Cheese {
|
13
|
+
similarCheeses(source: 4.5)
|
14
|
+
}
|
15
|
+
|)}
|
16
|
+
|
17
|
+
let(:validator) { GraphQL::StaticValidation::Validator.new(schema: DummySchema, validators: [GraphQL::StaticValidation::ArgumentLiteralsAreCompatible]) }
|
18
|
+
let(:errors) { validator.validate(document) }
|
19
|
+
|
20
|
+
it 'finds undefined arguments to fields and directives' do
|
21
|
+
assert_equal(3, errors.length)
|
22
|
+
|
23
|
+
query_root_error = {
|
24
|
+
"message"=>"id on Field 'cheese' has an invalid value",
|
25
|
+
"locations"=>[{"line"=>3, "column"=>7}]
|
26
|
+
}
|
27
|
+
assert_includes(errors, query_root_error)
|
28
|
+
|
29
|
+
input_object_error = {
|
30
|
+
"message"=>"product on Field 'searchDairy' has an invalid value",
|
31
|
+
"locations"=>[{"line"=>6, "column"=>7}]
|
32
|
+
}
|
33
|
+
assert_includes(errors, input_object_error)
|
34
|
+
|
35
|
+
fragment_error = {
|
36
|
+
"message"=>"source on Field 'similarCheeses' has an invalid value",
|
37
|
+
"locations"=>[{"line"=>10, "column"=>7}]
|
38
|
+
}
|
39
|
+
assert_includes(errors, fragment_error)
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::StaticValidation::ArgumentsAreDefined do
|
4
|
+
let(:document) { GraphQL.parse("
|
5
|
+
query getCheese {
|
6
|
+
cheese(id: 1) { source }
|
7
|
+
cheese(silly: false) { source }
|
8
|
+
}
|
9
|
+
|
10
|
+
fragment cheeseFields on Cheese {
|
11
|
+
similarCheeses(source: SHEEP, nonsense: 1)
|
12
|
+
id @skip(something: 3.4)
|
13
|
+
}
|
14
|
+
")}
|
15
|
+
|
16
|
+
let(:validator) { GraphQL::StaticValidation::Validator.new(schema: DummySchema, validators: [GraphQL::StaticValidation::ArgumentsAreDefined]) }
|
17
|
+
let(:errors) { validator.validate(document) }
|
18
|
+
|
19
|
+
it 'finds undefined arguments to fields and directives' do
|
20
|
+
assert_equal(3, errors.length)
|
21
|
+
|
22
|
+
query_root_error = {
|
23
|
+
"message"=>"Field 'cheese' doesn't accept argument silly",
|
24
|
+
"locations"=>[{"line"=>4, "column"=>7}]
|
25
|
+
}
|
26
|
+
assert_includes(errors, query_root_error)
|
27
|
+
|
28
|
+
fragment_error = {
|
29
|
+
"message"=>"Field 'similarCheeses' doesn't accept argument nonsense",
|
30
|
+
"locations"=>[{"line"=>8, "column"=>7}]
|
31
|
+
}
|
32
|
+
assert_includes(errors, fragment_error)
|
33
|
+
|
34
|
+
directive_error = {
|
35
|
+
"message"=>"Directive 'skip' doesn't accept argument something",
|
36
|
+
"locations"=>[{"line"=>9, "column"=>11}]
|
37
|
+
}
|
38
|
+
assert_includes(errors, directive_error)
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::StaticValidation::DirectivesAreDefined do
|
4
|
+
let(:document) { GraphQL.parse("
|
5
|
+
query getCheese {
|
6
|
+
okCheese: cheese(id: 1) {
|
7
|
+
id @skip(if: true),
|
8
|
+
source @nonsense(if: false)
|
9
|
+
... on Cheese {
|
10
|
+
flavor @moreNonsense
|
11
|
+
}
|
12
|
+
}
|
13
|
+
}
|
14
|
+
")}
|
15
|
+
|
16
|
+
let(:validator) { GraphQL::StaticValidation::Validator.new(schema: DummySchema, validators: [GraphQL::StaticValidation::DirectivesAreDefined]) }
|
17
|
+
let(:errors) { validator.validate(document) }
|
18
|
+
|
19
|
+
describe 'non-existent directives' do
|
20
|
+
it 'makes errors for them' do
|
21
|
+
expected = [
|
22
|
+
{
|
23
|
+
"message"=>"Directive @nonsense is not defined",
|
24
|
+
"locations"=>[{"line"=>5, "column"=>17}]
|
25
|
+
}, {
|
26
|
+
"message"=>"Directive @moreNonsense is not defined",
|
27
|
+
"locations"=>[{"line"=>7, "column"=>19}]
|
28
|
+
}
|
29
|
+
]
|
30
|
+
assert_equal(expected, errors)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::StaticValidation::FieldsAreDefinedOnType do
|
4
|
+
let(:query_string) { "
|
5
|
+
query getCheese($sourceVar: DairyAnimal!) {
|
6
|
+
notDefinedField { name }
|
7
|
+
cheese(id: 1) { nonsenseField, flavor }
|
8
|
+
fromSource(source: COW) { bogusField }
|
9
|
+
}
|
10
|
+
|
11
|
+
fragment cheeseFields on Cheese { fatContent, hogwashField }
|
12
|
+
"}
|
13
|
+
|
14
|
+
let(:validator) { GraphQL::StaticValidation::Validator.new(schema: DummySchema, validators: [GraphQL::StaticValidation::FieldsAreDefinedOnType]) }
|
15
|
+
let(:errors) { validator.validate(GraphQL.parse(query_string)) }
|
16
|
+
let(:error_messages) { errors.map { |e| e["message" ] }}
|
17
|
+
|
18
|
+
it "finds fields that are requested on types that don't have that field" do
|
19
|
+
expected_errors = [
|
20
|
+
"Field 'notDefinedField' doesn't exist on type 'Query'", # from query root
|
21
|
+
"Field 'nonsenseField' doesn't exist on type 'Cheese'", # from another field
|
22
|
+
"Field 'bogusField' doesn't exist on type 'Cheese'", # from a list
|
23
|
+
"Field 'hogwashField' doesn't exist on type 'Cheese'", # from a fragment
|
24
|
+
]
|
25
|
+
assert_equal(expected_errors, error_messages)
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'on interfaces' do
|
29
|
+
let(:query_string) { "query getStuff { favoriteEdible { amountThatILikeIt } }"}
|
30
|
+
|
31
|
+
it 'finds invalid fields' do
|
32
|
+
expected_errors = [
|
33
|
+
{"message"=>"Field 'amountThatILikeIt' doesn't exist on type 'Edible'", "locations"=>[{"line"=>1, "column"=>18}]}
|
34
|
+
]
|
35
|
+
assert_equal(expected_errors, errors)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'on unions' do
|
40
|
+
let(:query_string) { "
|
41
|
+
query notOnUnion { favoriteEdible { ...dpFields } }
|
42
|
+
fragment dbFields on DairyProduct { source }
|
43
|
+
fragment dbIndirectFields on DairyProduct { ... on Cheese { source } }
|
44
|
+
"}
|
45
|
+
|
46
|
+
|
47
|
+
it 'doesnt allow selections on unions' do
|
48
|
+
expected_errors = [
|
49
|
+
{
|
50
|
+
"message"=>"Selections can't be made directly on unions (see selections on DairyProduct)",
|
51
|
+
"locations"=>[
|
52
|
+
{"line"=>3, "column"=>7}
|
53
|
+
]
|
54
|
+
}
|
55
|
+
]
|
56
|
+
assert_equal(expected_errors, errors)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::StaticValidation::FieldsHaveAppropriateSelections do
|
4
|
+
let(:document) { GraphQL.parse("
|
5
|
+
query getCheese {
|
6
|
+
okCheese: cheese(id: 1) { fatContent, similarCheeses(source: YAK) { source } }
|
7
|
+
missingFieldsCheese: cheese(id: 1)
|
8
|
+
illegalSelectionCheese: cheese(id: 1) { id { something, ... someFields } }
|
9
|
+
}
|
10
|
+
")}
|
11
|
+
|
12
|
+
let(:validator) { GraphQL::StaticValidation::Validator.new(schema: DummySchema, validators: [GraphQL::StaticValidation::FieldsHaveAppropriateSelections]) }
|
13
|
+
let(:errors) { validator.validate(document) }
|
14
|
+
|
15
|
+
it 'adds errors for selections on scalars' do
|
16
|
+
assert_equal(2, errors.length)
|
17
|
+
|
18
|
+
illegal_selection_error = {
|
19
|
+
"message"=>"Selections can't be made on scalars (field 'id' returns Int but has selections [something, someFields])",
|
20
|
+
"locations"=>[{"line"=>5, "column"=>47}]
|
21
|
+
}
|
22
|
+
assert_includes(errors, illegal_selection_error, 'finds illegal selections on scalarss')
|
23
|
+
|
24
|
+
selection_required_error = {
|
25
|
+
"message"=>"Objects must have selections (field 'cheese' returns Cheese but has no selections)",
|
26
|
+
"locations"=>[{"line"=>4, "column"=>7}]
|
27
|
+
}
|
28
|
+
assert_includes(errors, selection_required_error, 'finds objects without selections')
|
29
|
+
end
|
30
|
+
end
|
@@ -1,40 +1,47 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe GraphQL::
|
3
|
+
describe GraphQL::StaticValidation::FieldsWillMerge do
|
4
4
|
let(:document) { GraphQL.parse("
|
5
5
|
query getCheese($sourceVar: DairyAnimal!) {
|
6
|
-
id
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
cheese(id: 1) {
|
7
|
+
id,
|
8
|
+
nickname: name,
|
9
|
+
nickname: fatContent,
|
10
|
+
fatContent
|
11
|
+
differentLevel: fatContent
|
12
|
+
similarCheeses(source: $sourceVar)
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
similarCow: similarCheeses(source: COW) {
|
15
|
+
similarCowSource: source,
|
16
|
+
differentLevel: fatContent
|
17
|
+
}
|
18
|
+
...cheeseFields
|
19
|
+
... on Cheese {
|
20
|
+
fatContent: name
|
21
|
+
similarCheeses(source: SHEEP)
|
22
|
+
}
|
18
23
|
}
|
19
24
|
}
|
20
25
|
fragment cheeseFields on Cheese {
|
21
26
|
fatContent,
|
22
|
-
|
27
|
+
similarCow: similarCheeses(source: COW) { similarCowSource: id, id }
|
23
28
|
id @someFlag
|
24
29
|
}
|
25
30
|
")}
|
26
31
|
|
27
|
-
let(:validator) { GraphQL::Validator.new(schema:
|
32
|
+
let(:validator) { GraphQL::StaticValidation::Validator.new(schema: DummySchema, validators: [GraphQL::StaticValidation::FieldsWillMerge]) }
|
28
33
|
let(:errors) { validator.validate(document) }
|
34
|
+
let(:error_messages) { errors.map { |e| e["message" ] }}
|
35
|
+
|
29
36
|
it 'finds field naming conflicts' do
|
30
37
|
expected_errors = [
|
31
|
-
"Field 'nickname' has a field conflict: name or fatContent?", # alias conflict in query
|
32
|
-
"Field 'originName' has a field conflict: id or name?", # nested conflict
|
33
38
|
"Field 'id' has a directive conflict: [] or [someFlag]?", # different directives
|
34
39
|
"Field 'id' has a directive argument conflict: [] or [{}]?", # not sure this is a great way to handle it but here we are!
|
40
|
+
"Field 'nickname' has a field conflict: name or fatContent?", # alias conflict in query
|
35
41
|
"Field 'fatContent' has a field conflict: fatContent or name?", # alias/name conflict in query and fragment
|
36
42
|
"Field 'similarCheeses' has an argument conflict: {\"source\":\"sourceVar\"} or {\"source\":\"SHEEP\"}?", # different arguments
|
43
|
+
"Field 'similarCowSource' has a field conflict: source or id?", # nested conflict
|
37
44
|
]
|
38
|
-
assert_equal(expected_errors,
|
45
|
+
assert_equal(expected_errors, error_messages)
|
39
46
|
end
|
40
47
|
end
|