graphql 0.0.4 → 0.1.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 +36 -0
- data/lib/graph_ql/directives/directive_chain.rb +33 -0
- data/lib/graph_ql/directives/include_directive.rb +15 -0
- data/lib/graph_ql/directives/skip_directive.rb +15 -0
- data/lib/graph_ql/enum.rb +34 -0
- data/lib/graph_ql/fields/abstract_field.rb +37 -0
- data/lib/graph_ql/fields/access_field.rb +24 -0
- data/lib/graph_ql/fields/field.rb +34 -0
- data/lib/graph_ql/interface.rb +14 -0
- data/lib/graph_ql/introspection/arguments_field.rb +5 -0
- data/lib/graph_ql/introspection/directive_type.rb +12 -0
- data/lib/graph_ql/introspection/enum_value_type.rb +10 -0
- data/lib/graph_ql/introspection/enum_values_field.rb +15 -0
- data/lib/graph_ql/introspection/field_type.rb +11 -0
- data/lib/graph_ql/introspection/fields_field.rb +14 -0
- data/lib/graph_ql/introspection/input_fields_field.rb +12 -0
- data/lib/graph_ql/introspection/input_value_type.rb +10 -0
- data/lib/graph_ql/introspection/of_type_field.rb +12 -0
- data/lib/graph_ql/introspection/possible_types_field.rb +12 -0
- data/lib/graph_ql/introspection/schema_type.rb +32 -0
- data/lib/graph_ql/introspection/type_kind_enum.rb +7 -0
- data/lib/graph_ql/introspection/type_type.rb +22 -0
- data/lib/graph_ql/parser/nodes.rb +72 -0
- data/lib/graph_ql/parser/parser.rb +108 -0
- data/lib/graph_ql/parser/transform.rb +86 -0
- data/lib/graph_ql/parser/visitor.rb +47 -0
- data/lib/graph_ql/query.rb +50 -0
- data/lib/graph_ql/query/arguments.rb +25 -0
- data/lib/graph_ql/query/field_resolution_strategy.rb +83 -0
- data/lib/graph_ql/query/fragment_spread_resolution_strategy.rb +16 -0
- data/lib/graph_ql/query/inline_fragment_resolution_strategy.rb +14 -0
- data/lib/graph_ql/query/operation_resolver.rb +28 -0
- data/lib/graph_ql/query/selection_resolver.rb +20 -0
- data/lib/graph_ql/query/type_resolver.rb +19 -0
- data/lib/graph_ql/repl.rb +27 -0
- data/lib/graph_ql/schema.rb +30 -0
- data/lib/graph_ql/schema/type_reducer.rb +44 -0
- data/lib/graph_ql/type_kinds.rb +15 -0
- data/lib/graph_ql/types/abstract_type.rb +14 -0
- data/lib/graph_ql/types/boolean_type.rb +6 -0
- data/lib/graph_ql/types/float_type.rb +6 -0
- data/lib/graph_ql/types/input_object_type.rb +17 -0
- data/lib/graph_ql/types/input_value.rb +10 -0
- data/lib/graph_ql/types/int_type.rb +6 -0
- data/lib/graph_ql/types/list_type.rb +10 -0
- data/lib/graph_ql/types/non_null_type.rb +18 -0
- data/lib/graph_ql/types/non_null_with_bang.rb +5 -0
- data/lib/graph_ql/types/object_type.rb +62 -0
- data/lib/graph_ql/types/scalar_type.rb +5 -0
- data/lib/graph_ql/types/string_type.rb +6 -0
- data/lib/graph_ql/types/type_definer.rb +16 -0
- data/lib/graph_ql/union.rb +35 -0
- data/lib/graph_ql/validations/fields_are_defined_on_type.rb +44 -0
- data/lib/graph_ql/validations/fields_will_merge.rb +80 -0
- data/lib/graph_ql/validations/fragments_are_used.rb +24 -0
- data/lib/graph_ql/validator.rb +29 -0
- data/lib/graph_ql/version.rb +3 -0
- data/lib/graphql.rb +92 -99
- data/readme.md +17 -177
- data/spec/graph_ql/directive_spec.rb +81 -0
- data/spec/graph_ql/enum_spec.rb +5 -0
- data/spec/graph_ql/fields/field_spec.rb +10 -0
- data/spec/graph_ql/interface_spec.rb +13 -0
- data/spec/graph_ql/introspection/directive_type_spec.rb +40 -0
- data/spec/graph_ql/introspection/schema_type_spec.rb +39 -0
- data/spec/graph_ql/introspection/type_type_spec.rb +104 -0
- data/spec/graph_ql/parser/parser_spec.rb +120 -0
- data/spec/graph_ql/parser/transform_spec.rb +109 -0
- data/spec/graph_ql/parser/visitor_spec.rb +31 -0
- data/spec/graph_ql/query/operation_resolver_spec.rb +14 -0
- data/spec/graph_ql/query_spec.rb +82 -0
- data/spec/graph_ql/schema/type_reducer_spec.rb +24 -0
- data/spec/graph_ql/types/input_object_type_spec.rb +12 -0
- data/spec/graph_ql/types/object_type_spec.rb +35 -0
- data/spec/graph_ql/union_spec.rb +27 -0
- data/spec/graph_ql/validations/fields_are_defined_on_type_spec.rb +28 -0
- data/spec/graph_ql/validations/fields_will_merge_spec.rb +40 -0
- data/spec/graph_ql/validations/fragments_are_used_spec.rb +28 -0
- data/spec/graph_ql/validator_spec.rb +24 -0
- data/spec/spec_helper.rb +2 -2
- data/spec/support/dummy_app.rb +123 -63
- data/spec/support/dummy_data.rb +11 -0
- metadata +107 -59
- data/lib/graphql/call.rb +0 -8
- data/lib/graphql/connection.rb +0 -65
- data/lib/graphql/field.rb +0 -12
- data/lib/graphql/field_definer.rb +0 -25
- data/lib/graphql/introspection/call_type.rb +0 -13
- data/lib/graphql/introspection/connection.rb +0 -9
- data/lib/graphql/introspection/field_type.rb +0 -10
- data/lib/graphql/introspection/root_call_argument_node.rb +0 -5
- data/lib/graphql/introspection/root_call_type.rb +0 -20
- data/lib/graphql/introspection/schema_call.rb +0 -8
- data/lib/graphql/introspection/schema_type.rb +0 -17
- data/lib/graphql/introspection/type_call.rb +0 -8
- data/lib/graphql/introspection/type_type.rb +0 -18
- data/lib/graphql/node.rb +0 -244
- data/lib/graphql/parser/parser.rb +0 -39
- data/lib/graphql/parser/transform.rb +0 -22
- data/lib/graphql/query.rb +0 -109
- data/lib/graphql/root_call.rb +0 -202
- data/lib/graphql/root_call_argument.rb +0 -11
- data/lib/graphql/root_call_argument_definer.rb +0 -17
- data/lib/graphql/schema/all.rb +0 -46
- data/lib/graphql/schema/schema.rb +0 -87
- data/lib/graphql/schema/schema_validation.rb +0 -32
- data/lib/graphql/syntax/call.rb +0 -8
- data/lib/graphql/syntax/field.rb +0 -9
- data/lib/graphql/syntax/fragment.rb +0 -7
- data/lib/graphql/syntax/node.rb +0 -8
- data/lib/graphql/syntax/query.rb +0 -8
- data/lib/graphql/syntax/variable.rb +0 -7
- data/lib/graphql/types/boolean_type.rb +0 -3
- data/lib/graphql/types/number_type.rb +0 -3
- data/lib/graphql/types/object_type.rb +0 -6
- data/lib/graphql/types/string_type.rb +0 -3
- data/lib/graphql/version.rb +0 -3
- data/spec/graphql/node_spec.rb +0 -69
- data/spec/graphql/parser/parser_spec.rb +0 -168
- data/spec/graphql/parser/transform_spec.rb +0 -157
- data/spec/graphql/query_spec.rb +0 -274
- data/spec/graphql/root_call_spec.rb +0 -69
- data/spec/graphql/schema/schema_spec.rb +0 -93
- data/spec/graphql/schema/schema_validation_spec.rb +0 -48
- data/spec/support/nodes.rb +0 -175
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::Visitor do
|
4
|
+
let(:document) { GraphQL.parse("
|
5
|
+
query cheese { cheese(id: 1) { flavor, source, producers(first: 3) { name } } }
|
6
|
+
fragment cheeseFields on Cheese { flavor }
|
7
|
+
")}
|
8
|
+
let(:counts) { {fields_entered: 0, arguments_entered: 0, arguments_left: 0, argument_names: []} }
|
9
|
+
|
10
|
+
let(:visitor) do
|
11
|
+
v = GraphQL::Visitor.new
|
12
|
+
v[GraphQL::Nodes::Field] << -> (node) { counts[:fields_entered] += 1 }
|
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 }
|
17
|
+
|
18
|
+
v[GraphQL::Nodes::Document].leave << -> (node) { counts[:finished] = true }
|
19
|
+
v
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'calls hooks during a depth-first tree traversal' do
|
23
|
+
assert_equal(2, visitor[GraphQL::Nodes::Argument].enter.length)
|
24
|
+
visitor.visit(document)
|
25
|
+
assert_equal(6, counts[:fields_entered])
|
26
|
+
assert_equal(2, counts[:arguments_entered])
|
27
|
+
assert_equal(2, counts[:arguments_left])
|
28
|
+
assert_equal(["id", "first"], counts[:argument_names])
|
29
|
+
assert(counts[:finished])
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::Query::OperationResolver do
|
4
|
+
let(:operation) { GraphQL.parse("query getCheese($cheeseId: Int!) { cheese(id: $cheeseId) { name }}", as: :operation_definition) }
|
5
|
+
let(:params) { {"cheeseId" => 1}}
|
6
|
+
let(:query) { q = Minitest::Mock.new; q.expect(:params, params); q}
|
7
|
+
let(:resolver) { GraphQL::Query::OperationResolver.new(operation, query)}
|
8
|
+
|
9
|
+
describe "variables" do
|
10
|
+
it 'returns variables by name' do
|
11
|
+
assert_equal(1, resolver.variables["cheeseId"])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::Query do
|
4
|
+
describe '#execute' do
|
5
|
+
let(:query_string) { %|
|
6
|
+
query getFlavor($cheeseId: Int!) {
|
7
|
+
brie: cheese(id: 1) { ...cheeseFields, ... meatFields, taste: flavor },
|
8
|
+
cheese(id: $cheeseId) {
|
9
|
+
__typename,
|
10
|
+
id,
|
11
|
+
...cheeseFields,
|
12
|
+
... edibleFields,
|
13
|
+
... on Cheese { cheeseKind: flavor },
|
14
|
+
... on Meat { cut }
|
15
|
+
}
|
16
|
+
fromSource(source: COW) { id }
|
17
|
+
firstSheep: searchDairy(product: {source: SHEEP}) { ... dairyFields }
|
18
|
+
favoriteEdible { __typename, fatContent }
|
19
|
+
}
|
20
|
+
fragment cheeseFields on Cheese { flavor }
|
21
|
+
fragment edibleFields on Edible { fatContent }
|
22
|
+
fragment meatFields on Meat { cut }
|
23
|
+
fragment dairyFields on DairyProduct {
|
24
|
+
... on Cheese { flavor }
|
25
|
+
... on Milk { source }
|
26
|
+
}
|
27
|
+
|}
|
28
|
+
let(:query) { GraphQL::Query.new(DummySchema, query_string, context: {}, params: {"cheeseId" => 2})}
|
29
|
+
let(:result) { query.result }
|
30
|
+
it 'returns fields on objects' do
|
31
|
+
expected = {"data"=> { "getFlavor" => {
|
32
|
+
"brie" => { "flavor" => "Brie", "taste" => "Brie" },
|
33
|
+
"cheese" => {
|
34
|
+
"__typename" => "Cheese",
|
35
|
+
"id" => 2,
|
36
|
+
"fatContent" => 0.3,
|
37
|
+
"flavor" => "Gouda",
|
38
|
+
"cheeseKind" => "Gouda",
|
39
|
+
},
|
40
|
+
"fromSource" => [{ "id" => 1 }, {"id" => 2}],
|
41
|
+
"firstSheep" => { "flavor" => "Manchego" },
|
42
|
+
"favoriteEdible"=>{"__typename"=>"Edible", "fatContent"=>0.04},
|
43
|
+
}}}
|
44
|
+
assert_equal(expected, result)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'exposes fragments' do
|
48
|
+
assert_equal(GraphQL::Nodes::FragmentDefinition, query.fragments['cheeseFields'].class)
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'runtime errors' do
|
52
|
+
let(:query_string) {%| query noMilk { milk(id: 1000) { name } }|}
|
53
|
+
it 'turns into error messages' do
|
54
|
+
expected = {"errors"=>[
|
55
|
+
{"message"=>"Something went wrong during query execution: No field found on Query 'Query' for 'milk'"}
|
56
|
+
]}
|
57
|
+
assert_equal(expected, result)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
describe 'execution order' do
|
63
|
+
let(:query_string) {%|
|
64
|
+
mutation setInOrder {
|
65
|
+
first: pushValue(value: 1)
|
66
|
+
second: pushValue(value: 5)
|
67
|
+
third: pushValue(value: 2)
|
68
|
+
}
|
69
|
+
|}
|
70
|
+
it 'executes mutations in order' do
|
71
|
+
expected = {"data"=>{
|
72
|
+
"setInOrder"=>{
|
73
|
+
"first"=> [1],
|
74
|
+
"second"=>[1, 5],
|
75
|
+
"third"=> [1, 5, 2],
|
76
|
+
}
|
77
|
+
}}
|
78
|
+
assert_equal(expected, result)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::Schema::TypeReducer do
|
4
|
+
it 'finds types from a single type and its fields' do
|
5
|
+
reducer = GraphQL::Schema::TypeReducer.new(CheeseType, {})
|
6
|
+
expected = {
|
7
|
+
"Cheese" => CheeseType,
|
8
|
+
"Int" => GraphQL::INT_TYPE,
|
9
|
+
"String" => GraphQL::STRING_TYPE,
|
10
|
+
"DairyAnimal" => DairyAnimalEnum,
|
11
|
+
"Float" => GraphQL::FLOAT_TYPE,
|
12
|
+
"Edible" => Edible,
|
13
|
+
"Milk" => MilkType,
|
14
|
+
"AnimalProduct" => AnimalProduct,
|
15
|
+
}
|
16
|
+
assert_equal(expected.keys, reducer.result.keys)
|
17
|
+
assert_equal(expected, reducer.result)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'finds type from arguments' do
|
21
|
+
reducer = GraphQL::Schema::TypeReducer.new(QueryType, {})
|
22
|
+
assert_equal(DairyProductInputType, reducer.result["DairyProductInput"])
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::InputObjectType do
|
4
|
+
let(:input_object) { DairyProductInputType }
|
5
|
+
it 'has a description' do
|
6
|
+
assert(input_object.description)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'has input fields' do
|
10
|
+
assert(DairyProductInputType.input_fields["fatContent"])
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::ObjectType do
|
4
|
+
let(:type) { CheeseType }
|
5
|
+
|
6
|
+
it 'has a name' do
|
7
|
+
assert_equal("Cheese", type.name)
|
8
|
+
type.name("Fromage")
|
9
|
+
assert_equal("Fromage", type.name)
|
10
|
+
type.name("Cheese")
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'has a description' do
|
14
|
+
assert_equal(22, type.description.length)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'may have interfaces' do
|
18
|
+
assert_equal([Edible, AnimalProduct], type.interfaces)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'becomes non-null with !' do
|
22
|
+
non_null_type = !type
|
23
|
+
assert_equal(GraphQL::TypeKinds::NON_NULL, non_null_type.kind)
|
24
|
+
assert_equal(type, non_null_type.of_type)
|
25
|
+
assert_equal(GraphQL::TypeKinds::NON_NULL, (!GraphQL::STRING_TYPE).kind)
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '.fields ' do
|
29
|
+
it 'exposes fields' do
|
30
|
+
field = type.fields["id"]
|
31
|
+
assert_equal(GraphQL::TypeKinds::NON_NULL, field.type.kind)
|
32
|
+
assert_equal(GraphQL::TypeKinds::SCALAR, field.type.of_type.kind)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::Union do
|
4
|
+
let(:type_1) { OpenStruct.new(kind: GraphQL::TypeKinds::OBJECT)}
|
5
|
+
let(:type_2) { OpenStruct.new(kind: GraphQL::TypeKinds::OBJECT)}
|
6
|
+
let(:union) { GraphQL::Union.new("MyUnion", [type_1, type_2]) }
|
7
|
+
it 'has a name' do
|
8
|
+
assert_equal("MyUnion", union.name)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'identifies members' do
|
12
|
+
assert(union.include?(type_1))
|
13
|
+
assert(!union.include?(:type_3))
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'must be 2+ types' do
|
17
|
+
assert_raises(ArgumentError) { GraphQL::Union.new("Something", [type_1])}
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'must be all object types' do
|
21
|
+
assert_raises(ArgumentError) { GraphQL::Union.new("Something", [type_1, type_2, union])}
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'infers type from an object' do
|
25
|
+
assert_equal(CheeseType, DairyProductUnion.resolve_type(CHEESES[1]))
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::Validations::FieldsAreDefinedOnType do
|
4
|
+
let(:document) { GraphQL.parse("
|
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::Validator.new(schema: DummySchema, validators: [GraphQL::Validations::FieldsAreDefinedOnType]) }
|
15
|
+
let(:errors) { validator.validate(document) }
|
16
|
+
it "finds fields that are requested on types that don't have that field" do
|
17
|
+
expected_errors = [
|
18
|
+
"Field 'notDefinedField' doesn't exist on type 'Query'", # from query root
|
19
|
+
"Field 'nonsenseField' doesn't exist on type 'Cheese'", # from another field
|
20
|
+
"Field 'bogusField' doesn't exist on type 'Cheese'", # from a list
|
21
|
+
"Field 'hogwashField' doesn't exist on type 'Cheese'", # from a fragment
|
22
|
+
]
|
23
|
+
assert_equal(expected_errors, errors)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'finds invalid fields on interfaces'
|
27
|
+
it 'finds invalid fields on unions'
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::Validations::FieldsWillMerge do
|
4
|
+
let(:document) { GraphQL.parse("
|
5
|
+
query getCheese($sourceVar: DairyAnimal!) {
|
6
|
+
id
|
7
|
+
nickname: name,
|
8
|
+
nickname: fatContent,
|
9
|
+
fatContent
|
10
|
+
|
11
|
+
similarCheeses(source: $sourceVar)
|
12
|
+
|
13
|
+
origin { originName: id },
|
14
|
+
...cheeseFields
|
15
|
+
... on Cheese {
|
16
|
+
fatContent: name
|
17
|
+
similarCheeses(source: SHEEP)
|
18
|
+
}
|
19
|
+
}
|
20
|
+
fragment cheeseFields on Cheese {
|
21
|
+
fatContent,
|
22
|
+
origin { originName: name }
|
23
|
+
id @someFlag
|
24
|
+
}
|
25
|
+
")}
|
26
|
+
|
27
|
+
let(:validator) { GraphQL::Validator.new(schema: nil, validators: [GraphQL::Validations::FieldsWillMerge]) }
|
28
|
+
let(:errors) { validator.validate(document) }
|
29
|
+
it 'finds field naming conflicts' do
|
30
|
+
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
|
+
"Field 'id' has a directive conflict: [] or [someFlag]?", # different directives
|
34
|
+
"Field 'id' has a directive argument conflict: [] or [{}]?", # not sure this is a great way to handle it but here we are!
|
35
|
+
"Field 'fatContent' has a field conflict: fatContent or name?", # alias/name conflict in query and fragment
|
36
|
+
"Field 'similarCheeses' has an argument conflict: {\"source\":\"sourceVar\"} or {\"source\":\"SHEEP\"}?", # different arguments
|
37
|
+
]
|
38
|
+
assert_equal(expected_errors, errors)
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::Validations::FragmentsAreUsed do
|
4
|
+
let(:document) { GraphQL.parse("
|
5
|
+
query getCheese {
|
6
|
+
name,
|
7
|
+
...cheeseFields,
|
8
|
+
origin {
|
9
|
+
...originFields
|
10
|
+
...undefinedFields
|
11
|
+
}
|
12
|
+
}
|
13
|
+
fragment cheeseFields on Cheese { fatContent }
|
14
|
+
fragment originFields on Country { name, continent { ...continentFields }}
|
15
|
+
fragment continentFields on Continent { name }
|
16
|
+
fragment unusedFields on Cheese { is, not, used }
|
17
|
+
")}
|
18
|
+
|
19
|
+
let(:validator) { GraphQL::Validator.new(schema: nil, validators: [GraphQL::Validations::FragmentsAreUsed]) }
|
20
|
+
let(:errors) { validator.validate(document) }
|
21
|
+
|
22
|
+
it 'adds errors for unused fragment definitions' do
|
23
|
+
assert_includes(errors, 'Some fragments were defined but not used: unusedFields')
|
24
|
+
end
|
25
|
+
it 'adds errors for undefined fragment spreads' do
|
26
|
+
assert_includes(errors, 'Some fragments were used but not defined: undefinedFields')
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class SchemaErrorValidator
|
4
|
+
def validate(context)
|
5
|
+
context.errors << "Something is wrong: #{context.schema}"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class DocumentErrorValidator
|
10
|
+
def validate(context)
|
11
|
+
context.errors << "Something is wrong: #{context.document.name}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe GraphQL::Validator do
|
16
|
+
let(:document) { OpenStruct.new(name: "This is not a document", children: []) }
|
17
|
+
let(:validator) { GraphQL::Validator.new(schema: "This is not a schema", validators: [SchemaErrorValidator, DocumentErrorValidator]) }
|
18
|
+
|
19
|
+
it 'uses validators' do
|
20
|
+
errors = validator.validate(document)
|
21
|
+
expected_errors = ["Something is wrong: This is not a schema", "Something is wrong: This is not a document"]
|
22
|
+
assert_equal(expected_errors, errors)
|
23
|
+
end
|
24
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require "codeclimate-test-reporter"
|
2
2
|
CodeClimate::TestReporter.start
|
3
|
-
|
3
|
+
require 'graphql'
|
4
4
|
require 'date'
|
5
5
|
require "minitest/autorun"
|
6
6
|
require "minitest/focus"
|
@@ -16,4 +16,4 @@ Minitest.backtrace_filter = Minitest::BacktraceFilter.new
|
|
16
16
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
17
17
|
|
18
18
|
require 'parslet'
|
19
|
-
require 'parslet/convenience'
|
19
|
+
require 'parslet/convenience'
|
data/spec/support/dummy_app.rb
CHANGED
@@ -1,81 +1,141 @@
|
|
1
|
-
|
2
|
-
def initialize(attributes={})
|
3
|
-
attributes.each do |key, value|
|
4
|
-
self.send("#{key}=", value)
|
5
|
-
end
|
6
|
-
end
|
1
|
+
require_relative './dummy_data'
|
7
2
|
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
Edible = GraphQL::Interface.new do
|
4
|
+
name "Edible"
|
5
|
+
description "Something you can eat, yum"
|
6
|
+
fields({
|
7
|
+
fatContent: field(type: !type.Float, desc: "Percentage which is fat"),
|
8
|
+
})
|
9
|
+
end
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
all.find { |object| object.id.to_s == id.to_s }
|
20
|
-
end
|
21
|
-
|
22
|
-
def where(query={})
|
23
|
-
result = []
|
24
|
-
all.each do |object|
|
25
|
-
match = true
|
26
|
-
|
27
|
-
query.each do |key, value|
|
28
|
-
if object.send(key) != value
|
29
|
-
match = false
|
30
|
-
end
|
31
|
-
end
|
11
|
+
AnimalProduct = GraphQL::Interface.new do
|
12
|
+
name "AnimalProduct"
|
13
|
+
description "Comes from an animal, no joke"
|
14
|
+
fields({
|
15
|
+
source: field(type: !type.String, desc: "Animal which produced this product"),
|
16
|
+
})
|
17
|
+
end
|
32
18
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
19
|
+
DairyAnimalEnum = GraphQL::Enum.new do |e|
|
20
|
+
e.name "DairyAnimal"
|
21
|
+
e.description "An animal which can yield milk"
|
22
|
+
e.value("COW", "Animal with black and white spots")
|
23
|
+
e.value("GOAT", "Animal with horns")
|
24
|
+
e.value("SHEEP", "Animal with wool")
|
25
|
+
e.value("YAK", "Animal with long hair", deprecation_reason: "Out of fashion")
|
26
|
+
end
|
27
|
+
|
28
|
+
CheeseType = GraphQL::ObjectType.new do
|
29
|
+
name "Cheese"
|
30
|
+
description "Cultured dairy product"
|
31
|
+
interfaces [Edible, AnimalProduct]
|
32
|
+
self.fields = {
|
33
|
+
id: field(type: !type.Int, desc: "Unique identifier"),
|
34
|
+
flavor: field(type: !type.String, desc: "Kind of cheese"),
|
35
|
+
source: field(type: !DairyAnimalEnum, desc: "Animal which produced the milk for this cheese"),
|
36
|
+
fatContent: field(type: !type.Float, desc: "Percentage which is milkfat", deprecation_reason: "Diet fashion has changed"),
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
MilkType = GraphQL::ObjectType.new do
|
41
|
+
name 'Milk'
|
42
|
+
description "Dairy beverage"
|
43
|
+
interfaces [Edible, AnimalProduct]
|
44
|
+
self.fields = {
|
45
|
+
id: field(type: !type.Int, desc: "Unique identifier"),
|
46
|
+
source: field(type: DairyAnimalEnum, desc: "Animal which produced this milk"),
|
47
|
+
fatContent: field(type: !type.Float, desc: "Percentage which is milkfat"),
|
48
|
+
flavors: field(
|
49
|
+
type: type[type.String],
|
50
|
+
desc: "Chocolate, Strawberry, etc",
|
51
|
+
args: {limit: {type: type.Int}}
|
52
|
+
),
|
53
|
+
}
|
46
54
|
end
|
47
55
|
|
48
|
-
|
49
|
-
attr_accessor :id, :title, :content, :published_at
|
56
|
+
DairyProductUnion = GraphQL::Union.new("DairyProduct", [MilkType, CheeseType])
|
50
57
|
|
51
|
-
|
52
|
-
|
58
|
+
DairyProductInputType = GraphQL::InputObjectType.new {
|
59
|
+
name "DairyProductInput"
|
60
|
+
description "Properties for finding a dairy product"
|
61
|
+
input_fields({
|
62
|
+
source: arg({type: DairyAnimalEnum}),
|
63
|
+
fatContent: arg({type: type.Float}),
|
64
|
+
})
|
65
|
+
}
|
66
|
+
|
67
|
+
|
68
|
+
class FetchField < GraphQL::AbstractField
|
69
|
+
attr_reader :type
|
70
|
+
attr_accessor :name
|
71
|
+
def initialize(type:, data:)
|
72
|
+
@type = type
|
73
|
+
@data = data
|
53
74
|
end
|
54
75
|
|
55
|
-
def
|
56
|
-
|
76
|
+
def description
|
77
|
+
"Find a #{@type.name} by id"
|
78
|
+
end
|
79
|
+
|
80
|
+
def resolve(target, arguments, context)
|
81
|
+
@data[arguments["id"]]
|
57
82
|
end
|
58
83
|
end
|
59
84
|
|
60
|
-
|
61
|
-
|
85
|
+
SourceField = GraphQL::Field.new do |f|
|
86
|
+
f.type GraphQL::ListType.new(of_type: CheeseType)
|
87
|
+
f.description "Cheese from source"
|
88
|
+
f.resolve -> (target, arguments, context) {
|
89
|
+
CHEESES.values.select{ |c| c.source == arguments["source"] }
|
90
|
+
}
|
91
|
+
end
|
62
92
|
|
63
|
-
|
64
|
-
|
65
|
-
|
93
|
+
FavoriteField = GraphQL::Field.new do |f|
|
94
|
+
f.description "My favorite food"
|
95
|
+
f.type Edible
|
96
|
+
f.resolve -> (t, a, c) { MILKS[1] }
|
66
97
|
end
|
67
98
|
|
68
|
-
class Like < InadequateRecordBase
|
69
|
-
attr_accessor :id, :post_id
|
70
99
|
|
71
|
-
|
72
|
-
|
73
|
-
|
100
|
+
QueryType = GraphQL::ObjectType.new do
|
101
|
+
name "Query"
|
102
|
+
description "Query root of the system"
|
103
|
+
fields({
|
104
|
+
cheese: FetchField.new(type: CheeseType, data: CHEESES),
|
105
|
+
fromSource: SourceField,
|
106
|
+
favoriteEdible: FavoriteField,
|
107
|
+
searchDairy: GraphQL::Field.new { |f|
|
108
|
+
f.name "searchDairy"
|
109
|
+
f.description "Find dairy products matching a description"
|
110
|
+
f.type DairyProductUnion
|
111
|
+
f.arguments({product: {type: DairyProductInputType}})
|
112
|
+
f.resolve -> (t, a, c) {
|
113
|
+
products = CHEESES.values + MILKS.values
|
114
|
+
source = a["product"]["source"]
|
115
|
+
if !source.nil?
|
116
|
+
products = products.select { |p| p.source == source }
|
117
|
+
end
|
118
|
+
products.first
|
119
|
+
}
|
120
|
+
}
|
121
|
+
})
|
74
122
|
end
|
75
123
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
124
|
+
GLOBAL_VALUES = []
|
125
|
+
|
126
|
+
MutationType = GraphQL::ObjectType.new do
|
127
|
+
name "Mutation"
|
128
|
+
description "The root for mutations in this schema"
|
129
|
+
fields({
|
130
|
+
pushValue: GraphQL::Field.new { |f|
|
131
|
+
f.description("Push a value onto a global array :D")
|
132
|
+
f.type(!type[!type.Int])
|
133
|
+
f.arguments(value: arg(type: !type.Int))
|
134
|
+
f.resolve -> (o, args, ctx) {
|
135
|
+
GLOBAL_VALUES << args["value"]
|
136
|
+
GLOBAL_VALUES
|
137
|
+
}
|
138
|
+
}
|
139
|
+
})
|
81
140
|
end
|
141
|
+
DummySchema = GraphQL::Schema.new(query: QueryType, mutation: MutationType)
|