graphql 0.0.4 → 0.1.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 +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)
|