graphql 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|