graphql 0.18.5 → 0.18.6
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/graphql.rb +1 -0
- data/lib/graphql/argument.rb +14 -0
- data/lib/graphql/enum_type.rb +5 -1
- data/lib/graphql/field.rb +1 -1
- data/lib/graphql/input_object_type.rb +1 -0
- data/lib/graphql/interface_type.rb +1 -1
- data/lib/graphql/language/parser_tests.rb +577 -0
- data/lib/graphql/object_type.rb +1 -1
- data/lib/graphql/schema/loader.rb +133 -0
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/argument_spec.rb +5 -0
- data/spec/graphql/enum_type_spec.rb +7 -0
- data/spec/graphql/field_spec.rb +6 -0
- data/spec/graphql/language/parser_spec.rb +3 -558
- data/spec/graphql/object_type_spec.rb +6 -0
- data/spec/graphql/schema/loader_spec.rb +147 -0
- metadata +6 -2
data/lib/graphql/object_type.rb
CHANGED
@@ -21,7 +21,7 @@ module GraphQL
|
|
21
21
|
# end
|
22
22
|
#
|
23
23
|
class ObjectType < GraphQL::BaseType
|
24
|
-
accepts_definitions :interfaces, field: GraphQL::Define::AssignObjectField
|
24
|
+
accepts_definitions :interfaces, :fields, field: GraphQL::Define::AssignObjectField
|
25
25
|
|
26
26
|
# @return [Hash<String => GraphQL::Field>] Map String fieldnames to their {GraphQL::Field} implementations
|
27
27
|
lazy_defined_attr_accessor :fields
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module GraphQL
|
2
|
+
class Schema
|
3
|
+
# You can use the result of {GraphQL::Introspection::INTROSPECTION_QUERY}
|
4
|
+
# to make a schema. This schema is missing some important details like
|
5
|
+
# `resolve` functions, but it does include the full type system,
|
6
|
+
# so you can use it to validate queries.
|
7
|
+
module Loader
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# Create schema with the result of an introspection query.
|
11
|
+
# @param introspection_result [Hash] A response from {GraphQL::Introspection::INTROSPECTION_QUERY}
|
12
|
+
# @return [GraphQL::Schema] the schema described by `input`
|
13
|
+
def load(introspection_result)
|
14
|
+
schema = introspection_result.fetch("data").fetch("__schema")
|
15
|
+
|
16
|
+
types = {}
|
17
|
+
type_resolver = -> (type) { -> { resolve_type(types, type) } }
|
18
|
+
|
19
|
+
schema.fetch("types").each do |type|
|
20
|
+
next if type.fetch("name").start_with?("__")
|
21
|
+
type_object = define_type(type, type_resolver)
|
22
|
+
types[type_object.name] = type_object
|
23
|
+
end
|
24
|
+
|
25
|
+
query = types.fetch(schema.fetch("queryType").fetch("name"))
|
26
|
+
|
27
|
+
Schema.new(query: query, types: types.values)
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
private
|
32
|
+
|
33
|
+
def resolve_type(types, type)
|
34
|
+
case kind = type.fetch("kind")
|
35
|
+
when "ENUM", "INTERFACE", "INPUT_OBJECT", "OBJECT", "SCALAR", "UNION"
|
36
|
+
types.fetch(type.fetch("name"))
|
37
|
+
when "LIST"
|
38
|
+
ListType.new(of_type: resolve_type(types, type.fetch("ofType")))
|
39
|
+
when "NON_NULL"
|
40
|
+
NonNullType.new(of_type: resolve_type(types, type.fetch("ofType")))
|
41
|
+
else
|
42
|
+
fail NotImplementedError, "#{kind} not implemented"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def define_type(type, type_resolver)
|
47
|
+
case type.fetch("kind")
|
48
|
+
when "ENUM"
|
49
|
+
EnumType.define(
|
50
|
+
name: type["name"],
|
51
|
+
description: type["description"],
|
52
|
+
values: type["enumValues"].map { |enum|
|
53
|
+
EnumType::EnumValue.new(
|
54
|
+
name: enum["name"],
|
55
|
+
description: enum["description"],
|
56
|
+
deprecation_reason: enum["deprecationReason"],
|
57
|
+
value: enum["value"]
|
58
|
+
)
|
59
|
+
})
|
60
|
+
when "INTERFACE"
|
61
|
+
InterfaceType.define(
|
62
|
+
name: type["name"],
|
63
|
+
description: type["description"],
|
64
|
+
fields: Hash[type["fields"].map { |field|
|
65
|
+
[field["name"], define_type(field.merge("kind" => "FIELD"), type_resolver)]
|
66
|
+
}]
|
67
|
+
)
|
68
|
+
when "INPUT_OBJECT"
|
69
|
+
InputObjectType.define(
|
70
|
+
name: type["name"],
|
71
|
+
description: type["description"],
|
72
|
+
arguments: Hash[type["inputFields"].map { |arg|
|
73
|
+
[arg["name"], define_type(arg.merge("kind" => "ARGUMENT"), type_resolver)]
|
74
|
+
}]
|
75
|
+
)
|
76
|
+
when "OBJECT"
|
77
|
+
ObjectType.define(
|
78
|
+
name: type["name"],
|
79
|
+
description: type["description"],
|
80
|
+
fields: Hash[type["fields"].map { |field|
|
81
|
+
[field["name"], define_type(field.merge("kind" => "FIELD"), type_resolver)]
|
82
|
+
}]
|
83
|
+
)
|
84
|
+
when "FIELD"
|
85
|
+
Field.define(
|
86
|
+
name: type["name"],
|
87
|
+
type: type_resolver.call(type["type"]),
|
88
|
+
description: type["description"],
|
89
|
+
arguments: Hash[type["args"].map { |arg|
|
90
|
+
[arg["name"], define_type(arg.merge("kind" => "ARGUMENT"), type_resolver)]
|
91
|
+
}]
|
92
|
+
)
|
93
|
+
when "ARGUMENT"
|
94
|
+
Argument.define(
|
95
|
+
name: type["name"],
|
96
|
+
type: type_resolver.call(type["type"]),
|
97
|
+
description: type["description"],
|
98
|
+
default_value: type["defaultValue"]
|
99
|
+
)
|
100
|
+
when "SCALAR"
|
101
|
+
case type.fetch("name")
|
102
|
+
when "Int"
|
103
|
+
INT_TYPE
|
104
|
+
when "String"
|
105
|
+
STRING_TYPE
|
106
|
+
when "Float"
|
107
|
+
FLOAT_TYPE
|
108
|
+
when "Boolean"
|
109
|
+
BOOLEAN_TYPE
|
110
|
+
when "ID"
|
111
|
+
ID_TYPE
|
112
|
+
else
|
113
|
+
ScalarType.define(
|
114
|
+
name: type["name"],
|
115
|
+
description: type["description"]
|
116
|
+
)
|
117
|
+
end
|
118
|
+
when "UNION"
|
119
|
+
UnionType.define(
|
120
|
+
name: type["name"],
|
121
|
+
description: type["description"],
|
122
|
+
possible_types: type["possibleTypes"].map { |possible_type|
|
123
|
+
type_resolver.call(possible_type)
|
124
|
+
}
|
125
|
+
)
|
126
|
+
else
|
127
|
+
fail NotImplementedError, "#{type["kind"]} not implemented"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/lib/graphql/version.rb
CHANGED
@@ -17,4 +17,9 @@ describe GraphQL::Argument do
|
|
17
17
|
expected_error = %|Query is invalid: field "invalid" argument "invalid" default value ["123"] is not valid for type Float|
|
18
18
|
assert_equal expected_error, err.message
|
19
19
|
end
|
20
|
+
|
21
|
+
it "accepts proc type" do
|
22
|
+
argument = GraphQL::Argument.define(name: :favoriteFood, type: -> { GraphQL::STRING_TYPE })
|
23
|
+
assert_equal GraphQL::STRING_TYPE, argument.type
|
24
|
+
end
|
20
25
|
end
|
@@ -28,4 +28,11 @@ describe GraphQL::EnumType do
|
|
28
28
|
assert(!result.valid?)
|
29
29
|
end
|
30
30
|
end
|
31
|
+
|
32
|
+
it "accepts values array" do
|
33
|
+
cow = GraphQL::EnumType::EnumValue.define(name: "COW")
|
34
|
+
goat = GraphQL::EnumType::EnumValue.define(name: "GOAT")
|
35
|
+
enum = GraphQL::EnumType.define(name: "DairyAnimal", values: [cow, goat])
|
36
|
+
assert_equal({ "COW" => cow, "GOAT" => goat }, enum.values)
|
37
|
+
end
|
31
38
|
end
|
data/spec/graphql/field_spec.rb
CHANGED
@@ -17,6 +17,12 @@ describe GraphQL::Field do
|
|
17
17
|
assert_equal(DairyProductUnion, field.type)
|
18
18
|
end
|
19
19
|
|
20
|
+
it "accepts arguments definition" do
|
21
|
+
number = GraphQL::Argument.define(name: :number, type: -> { GraphQL::INT_TYPE })
|
22
|
+
field = GraphQL::Field.define(type: DairyProductUnion, arguments: [number])
|
23
|
+
assert_equal([number], field.arguments)
|
24
|
+
end
|
25
|
+
|
20
26
|
describe ".property " do
|
21
27
|
let(:field) do
|
22
28
|
GraphQL::Field.define do
|
@@ -1,562 +1,7 @@
|
|
1
1
|
require "spec_helper"
|
2
|
+
require 'graphql/language/parser_tests'
|
2
3
|
|
3
4
|
describe GraphQL::Language::Parser do
|
4
|
-
|
5
|
-
|
6
|
-
query getStuff($someVar: Int = 1, $anotherVar: [String!] ) @skip(if: false) {
|
7
|
-
myField: someField(someArg: $someVar, ok: 1.4) @skip(if: $anotherVar) @thing(or: "Whatever")
|
8
|
-
|
9
|
-
anotherField(someArg: [1,2,3]) {
|
10
|
-
nestedField
|
11
|
-
... moreNestedFields @skip(if: true)
|
12
|
-
}
|
13
|
-
|
14
|
-
... on OtherType @include(unless: false){
|
15
|
-
field(arg: [{key: "value", anotherKey: 0.9, anotherAnotherKey: WHATEVER}])
|
16
|
-
anotherField
|
17
|
-
}
|
18
|
-
|
19
|
-
... {
|
20
|
-
id
|
21
|
-
}
|
22
|
-
}
|
23
|
-
|
24
|
-
fragment moreNestedFields on NestedType @or(something: "ok") {
|
25
|
-
anotherNestedField
|
26
|
-
}
|
27
|
-
|}
|
28
|
-
|
29
|
-
describe ".parse" do
|
30
|
-
it "parses queries" do
|
31
|
-
assert document
|
32
|
-
end
|
33
|
-
|
34
|
-
describe "visited nodes" do
|
35
|
-
let(:query) { document.definitions.first }
|
36
|
-
let(:fragment_def) { document.definitions.last }
|
37
|
-
|
38
|
-
it "creates a valid document" do
|
39
|
-
assert document.is_a?(GraphQL::Language::Nodes::Document)
|
40
|
-
assert_equal 2, document.definitions.length
|
41
|
-
end
|
42
|
-
|
43
|
-
it "creates a valid operation" do
|
44
|
-
assert query.is_a?(GraphQL::Language::Nodes::OperationDefinition)
|
45
|
-
assert_equal "getStuff", query.name
|
46
|
-
assert_equal "query", query.operation_type
|
47
|
-
assert_equal 2, query.variables.length
|
48
|
-
assert_equal 4, query.selections.length
|
49
|
-
assert_equal 1, query.directives.length
|
50
|
-
assert_equal [2, 5], [query.line, query.col]
|
51
|
-
end
|
52
|
-
|
53
|
-
it "creates a valid fragment definition" do
|
54
|
-
assert fragment_def.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
|
55
|
-
assert_equal "moreNestedFields", fragment_def.name
|
56
|
-
assert_equal 1, fragment_def.selections.length
|
57
|
-
assert_equal "NestedType", fragment_def.type
|
58
|
-
assert_equal 1, fragment_def.directives.length
|
59
|
-
assert_equal [20, 5], fragment_def.position
|
60
|
-
end
|
61
|
-
|
62
|
-
describe "variable definitions" do
|
63
|
-
let(:optional_var) { query.variables.first }
|
64
|
-
it "gets name and type" do
|
65
|
-
assert_equal "someVar", optional_var.name
|
66
|
-
assert_equal "Int", optional_var.type.name
|
67
|
-
end
|
68
|
-
|
69
|
-
it "gets default value" do
|
70
|
-
assert_equal 1, optional_var.default_value
|
71
|
-
end
|
72
|
-
|
73
|
-
it "gets position info" do
|
74
|
-
assert_equal [2, 20], optional_var.position
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
describe "fields" do
|
79
|
-
let(:leaf_field) { query.selections.first }
|
80
|
-
let(:parent_field) { query.selections[1] }
|
81
|
-
|
82
|
-
it "gets name, alias, arguments and directives" do
|
83
|
-
assert_equal "someField", leaf_field.name
|
84
|
-
assert_equal "myField", leaf_field.alias
|
85
|
-
assert_equal 2, leaf_field.directives.length
|
86
|
-
assert_equal 2, leaf_field.arguments.length
|
87
|
-
end
|
88
|
-
|
89
|
-
it "gets nested fields" do
|
90
|
-
assert_equal 2, parent_field.selections.length
|
91
|
-
end
|
92
|
-
|
93
|
-
it "gets location info" do
|
94
|
-
assert_equal [3 ,7], leaf_field.position
|
95
|
-
end
|
96
|
-
|
97
|
-
describe "when the arguments list is empty" do
|
98
|
-
let(:query_string) { "{ field() }"}
|
99
|
-
let(:field) { query.selections.first }
|
100
|
-
it "has zero arguments" do
|
101
|
-
assert_equal 0, field.arguments.length
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
describe "when selections are empty" do
|
106
|
-
let(:query_string) { "{ field { } }"}
|
107
|
-
let(:field) { query.selections.first }
|
108
|
-
it "has zero selections" do
|
109
|
-
assert_equal 0, field.selections.length
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
describe "arguments" do
|
115
|
-
let(:literal_argument) { query.selections.first.arguments.last }
|
116
|
-
let(:variable_argument) { query.selections.first.arguments.first }
|
117
|
-
|
118
|
-
it "gets name and literal value" do
|
119
|
-
assert_equal "ok", literal_argument.name
|
120
|
-
assert_equal 1.4, literal_argument.value
|
121
|
-
end
|
122
|
-
|
123
|
-
it "gets name and variable value" do
|
124
|
-
assert_equal "someArg", variable_argument.name
|
125
|
-
assert_equal "someVar", variable_argument.value.name
|
126
|
-
end
|
127
|
-
|
128
|
-
|
129
|
-
it "gets position info" do
|
130
|
-
assert_equal [3, 26], variable_argument.position
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
describe "fragment spreads" do
|
135
|
-
let(:fragment_spread) { query.selections[1].selections.last }
|
136
|
-
it "gets the name and directives" do
|
137
|
-
assert_equal "moreNestedFields", fragment_spread.name
|
138
|
-
assert_equal 1, fragment_spread.directives.length
|
139
|
-
end
|
140
|
-
|
141
|
-
it "gets position info" do
|
142
|
-
assert_equal [7, 9], fragment_spread.position
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
describe "directives" do
|
147
|
-
let(:variable_directive) { query.selections.first.directives.first }
|
148
|
-
|
149
|
-
it "gets the name and arguments" do
|
150
|
-
assert_equal "skip", variable_directive.name
|
151
|
-
assert_equal "if", variable_directive.arguments.first.name
|
152
|
-
assert_equal 1, variable_directive.arguments.length
|
153
|
-
end
|
154
|
-
|
155
|
-
it "gets position info" do
|
156
|
-
assert_equal [3, 54], variable_directive.position
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
describe "inline fragments" do
|
161
|
-
let(:inline_fragment) { query.selections[2] }
|
162
|
-
let(:typeless_inline_fragment) { query.selections[3] }
|
163
|
-
|
164
|
-
it "gets the type and directives" do
|
165
|
-
assert_equal "OtherType", inline_fragment.type
|
166
|
-
assert_equal 2, inline_fragment.selections.length
|
167
|
-
assert_equal 1, inline_fragment.directives.length
|
168
|
-
end
|
169
|
-
|
170
|
-
it "gets inline fragments without type conditions" do
|
171
|
-
assert_equal nil, typeless_inline_fragment.type
|
172
|
-
assert_equal 1, typeless_inline_fragment.selections.length
|
173
|
-
assert_equal 0, typeless_inline_fragment.directives.length
|
174
|
-
end
|
175
|
-
|
176
|
-
it "gets position info" do
|
177
|
-
assert_equal [10, 7], inline_fragment.position
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
describe "inputs" do
|
182
|
-
let(:query_string) {%|
|
183
|
-
{
|
184
|
-
field(
|
185
|
-
int: 3,
|
186
|
-
float: 4.7e-24,
|
187
|
-
bool: false,
|
188
|
-
string: "☀︎🏆\\n escaped \\" unicode \\u00b6 /",
|
189
|
-
enum: ENUM_NAME,
|
190
|
-
array: [7, 8, 9]
|
191
|
-
object: {a: [1,2,3], b: {c: "4"}}
|
192
|
-
unicode_bom: "\xef\xbb\xbfquery"
|
193
|
-
keywordEnum: on
|
194
|
-
)
|
195
|
-
}
|
196
|
-
|}
|
197
|
-
|
198
|
-
let(:inputs) { document.definitions.first.selections.first.arguments }
|
199
|
-
|
200
|
-
it "parses ints" do
|
201
|
-
assert_equal 3, inputs[0].value
|
202
|
-
end
|
203
|
-
|
204
|
-
it "parses floats" do
|
205
|
-
assert_equal 0.47e-23, inputs[1].value
|
206
|
-
end
|
207
|
-
|
208
|
-
it "parses booleans" do
|
209
|
-
assert_equal false, inputs[2].value
|
210
|
-
end
|
211
|
-
|
212
|
-
it "parses UTF-8 strings" do
|
213
|
-
assert_equal %|☀︎🏆\n escaped " unicode ¶ /|, inputs[3].value
|
214
|
-
end
|
215
|
-
|
216
|
-
it "parses enums" do
|
217
|
-
assert_instance_of GraphQL::Language::Nodes::Enum, inputs[4].value
|
218
|
-
assert_equal "ENUM_NAME", inputs[4].value.name
|
219
|
-
end
|
220
|
-
|
221
|
-
it "parses arrays" do
|
222
|
-
assert_equal [7,8,9], inputs[5].value
|
223
|
-
end
|
224
|
-
|
225
|
-
it "parses objects" do
|
226
|
-
obj = inputs[6].value
|
227
|
-
assert_equal "a", obj.arguments[0].name
|
228
|
-
assert_equal [1,2,3], obj.arguments[0].value
|
229
|
-
assert_equal "b", obj.arguments[1].name
|
230
|
-
assert_equal "c", obj.arguments[1].value.arguments[0].name
|
231
|
-
assert_equal "4", obj.arguments[1].value.arguments[0].value
|
232
|
-
end
|
233
|
-
|
234
|
-
it "parses unicode bom" do
|
235
|
-
obj = inputs[7].value
|
236
|
-
assert_equal %|\xef\xbb\xbfquery|, inputs[7].value
|
237
|
-
end
|
238
|
-
|
239
|
-
it "parses enum 'on''" do
|
240
|
-
assert_equal "on", inputs[8].value.name
|
241
|
-
end
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
|
-
describe "unnamed queries" do
|
246
|
-
let(:query_string) {%|
|
247
|
-
{ name, age, height }
|
248
|
-
|}
|
249
|
-
let(:operation) { document.definitions.first }
|
250
|
-
|
251
|
-
it "parses unnamed queries" do
|
252
|
-
assert_equal 1, document.definitions.length
|
253
|
-
assert_equal "query", operation.operation_type
|
254
|
-
assert_equal nil, operation.name
|
255
|
-
assert_equal 3, operation.selections.length
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
describe "introspection query" do
|
260
|
-
let(:query_string) { GraphQL::Introspection::INTROSPECTION_QUERY }
|
261
|
-
|
262
|
-
it "parses a big ol' query" do
|
263
|
-
assert(document)
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
describe "schema" do
|
268
|
-
it "parses the test schema" do
|
269
|
-
schema = DummySchema
|
270
|
-
schema_string = GraphQL::Schema::Printer.print_schema(schema)
|
271
|
-
document = GraphQL::Language::Parser.parse(schema_string)
|
272
|
-
|
273
|
-
assert_equal schema_string, document.to_query_string
|
274
|
-
end
|
275
|
-
|
276
|
-
it "parses mimal schema definition" do
|
277
|
-
document = GraphQL::Language::Parser.parse('schema { query: QueryRoot }')
|
278
|
-
|
279
|
-
schema = document.definitions.first
|
280
|
-
assert_equal 'QueryRoot', schema.query
|
281
|
-
assert_equal nil, schema.mutation
|
282
|
-
assert_equal nil, schema.subscription
|
283
|
-
end
|
284
|
-
|
285
|
-
it "parses full schema definitions" do
|
286
|
-
document = GraphQL::Language::Parser.parse('
|
287
|
-
schema {
|
288
|
-
query: QueryRoot
|
289
|
-
mutation: MutationRoot
|
290
|
-
subscription: SubscriptionRoot
|
291
|
-
}
|
292
|
-
')
|
293
|
-
|
294
|
-
schema = document.definitions.first
|
295
|
-
assert_equal 'QueryRoot', schema.query
|
296
|
-
assert_equal 'MutationRoot', schema.mutation
|
297
|
-
assert_equal 'SubscriptionRoot', schema.subscription
|
298
|
-
end
|
299
|
-
|
300
|
-
it "parses object types" do
|
301
|
-
document = GraphQL::Language::Parser.parse('
|
302
|
-
type Comment implements Node {
|
303
|
-
id: ID!
|
304
|
-
}
|
305
|
-
')
|
306
|
-
|
307
|
-
type = document.definitions.first
|
308
|
-
assert_equal GraphQL::Language::Nodes::ObjectTypeDefinition, type.class
|
309
|
-
assert_equal 'Comment', type.name
|
310
|
-
assert_equal ['Node'], type.interfaces
|
311
|
-
assert_equal ['id'], type.fields.map(&:name)
|
312
|
-
assert_equal [], type.fields[0].arguments
|
313
|
-
assert_equal 'ID', type.fields[0].type.of_type.name
|
314
|
-
end
|
315
|
-
|
316
|
-
it "parses field arguments" do
|
317
|
-
document = GraphQL::Language::Parser.parse('
|
318
|
-
type Mutation {
|
319
|
-
post(id: ID!, data: PostData = { message: "First!1!", type: BLOG, tags: ["Test", "Annoying"] }): Post
|
320
|
-
}
|
321
|
-
')
|
322
|
-
|
323
|
-
field = document.definitions.first.fields.first
|
324
|
-
assert_equal ['id', 'data'], field.arguments.map(&:name)
|
325
|
-
data_arg = field.arguments[1]
|
326
|
-
assert_equal 'PostData', data_arg.type.name
|
327
|
-
assert_equal ['message', 'type', 'tags'], data_arg.default_value.arguments.map(&:name)
|
328
|
-
tags_arg = data_arg.default_value.arguments[2]
|
329
|
-
assert_equal ['Test', 'Annoying'], tags_arg.value
|
330
|
-
end
|
331
|
-
|
332
|
-
it "parses scalar types" do
|
333
|
-
document = GraphQL::Language::Parser.parse('scalar DateTime')
|
334
|
-
|
335
|
-
type = document.definitions.first
|
336
|
-
assert_equal GraphQL::Language::Nodes::ScalarTypeDefinition, type.class
|
337
|
-
assert_equal 'DateTime', type.name
|
338
|
-
end
|
339
|
-
|
340
|
-
it "parses interface types" do
|
341
|
-
document = GraphQL::Language::Parser.parse('
|
342
|
-
interface Node {
|
343
|
-
id: ID!
|
344
|
-
}
|
345
|
-
')
|
346
|
-
|
347
|
-
type = document.definitions.first
|
348
|
-
assert_equal GraphQL::Language::Nodes::InterfaceTypeDefinition, type.class
|
349
|
-
assert_equal 'Node', type.name
|
350
|
-
assert_equal ['id'], type.fields.map(&:name)
|
351
|
-
assert_equal [], type.fields[0].arguments
|
352
|
-
assert_equal 'ID', type.fields[0].type.of_type.name
|
353
|
-
end
|
354
|
-
|
355
|
-
it "parses enum types" do
|
356
|
-
document = GraphQL::Language::Parser.parse('
|
357
|
-
enum DogCommand { SIT, DOWN, HEEL }
|
358
|
-
')
|
359
|
-
|
360
|
-
type = document.definitions.first
|
361
|
-
assert_equal GraphQL::Language::Nodes::EnumTypeDefinition, type.class
|
362
|
-
assert_equal 'DogCommand', type.name
|
363
|
-
assert_equal ['SIT', 'DOWN', 'HEEL'], type.values
|
364
|
-
end
|
365
|
-
|
366
|
-
it "parses input object types" do
|
367
|
-
document = GraphQL::Language::Parser.parse('
|
368
|
-
input EmptyMutationInput {
|
369
|
-
clientMutationId: String
|
370
|
-
}
|
371
|
-
')
|
372
|
-
|
373
|
-
type = document.definitions.first
|
374
|
-
assert_equal GraphQL::Language::Nodes::InputObjectTypeDefinition, type.class
|
375
|
-
assert_equal 'EmptyMutationInput', type.name
|
376
|
-
assert_equal ['clientMutationId'], type.fields.map(&:name)
|
377
|
-
assert_equal 'String', type.fields[0].type.name
|
378
|
-
assert_equal nil, type.fields[0].default_value
|
379
|
-
end
|
380
|
-
end
|
381
|
-
end
|
382
|
-
|
383
|
-
describe "errors" do
|
384
|
-
let(:query_string) {%| query doSomething { bogus { } |}
|
385
|
-
it "raises a parse error" do
|
386
|
-
err = assert_raises(GraphQL::ParseError) { document }
|
387
|
-
end
|
388
|
-
|
389
|
-
it "correctly identifies parse error location and content" do
|
390
|
-
e = assert_raises(GraphQL::ParseError) do
|
391
|
-
GraphQL.parse("
|
392
|
-
query getCoupons {
|
393
|
-
allCoupons: {data{id}}
|
394
|
-
}
|
395
|
-
")
|
396
|
-
end
|
397
|
-
assert_includes(e.message, '"{"')
|
398
|
-
assert_includes(e.message, "RCURLY")
|
399
|
-
assert_equal(3, e.line)
|
400
|
-
assert_equal(25, e.col)
|
401
|
-
end
|
402
|
-
|
403
|
-
it "handles unexpected ends" do
|
404
|
-
err = assert_raises(GraphQL::ParseError) { GraphQL.parse("{ ") }
|
405
|
-
assert_equal "Unexpected end of document", err.message
|
406
|
-
end
|
407
|
-
|
408
|
-
it "rejects unsupported characters" do
|
409
|
-
e = assert_raises(GraphQL::ParseError) do
|
410
|
-
GraphQL.parse("{ field; }")
|
411
|
-
end
|
412
|
-
|
413
|
-
assert_includes(e.message, "Parse error on \";\"")
|
414
|
-
end
|
415
|
-
|
416
|
-
it "rejects control characters" do
|
417
|
-
e = assert_raises(GraphQL::ParseError) do
|
418
|
-
GraphQL.parse("{ \afield }")
|
419
|
-
end
|
420
|
-
|
421
|
-
assert_includes(e.message, "Parse error on \"\\a\"")
|
422
|
-
end
|
423
|
-
|
424
|
-
it "rejects partial BOM" do
|
425
|
-
e = assert_raises(GraphQL::ParseError) do
|
426
|
-
GraphQL.parse("{ \xeffield }")
|
427
|
-
end
|
428
|
-
|
429
|
-
assert_includes(e.message, "Parse error on \"\\xEF\"")
|
430
|
-
end
|
431
|
-
|
432
|
-
it "rejects vertical tabs" do
|
433
|
-
e = assert_raises(GraphQL::ParseError) do
|
434
|
-
GraphQL.parse("{ \vfield }")
|
435
|
-
end
|
436
|
-
|
437
|
-
assert_includes(e.message, "Parse error on \"\\v\"")
|
438
|
-
end
|
439
|
-
|
440
|
-
it "rejects form feed" do
|
441
|
-
e = assert_raises(GraphQL::ParseError) do
|
442
|
-
GraphQL.parse("{ \ffield }")
|
443
|
-
end
|
444
|
-
|
445
|
-
assert_includes(e.message, "Parse error on \"\\f\"")
|
446
|
-
end
|
447
|
-
|
448
|
-
it "rejects no break space" do
|
449
|
-
e = assert_raises(GraphQL::ParseError) do
|
450
|
-
GraphQL.parse("{ \xa0field }")
|
451
|
-
end
|
452
|
-
|
453
|
-
assert_includes(e.message, "Parse error on \"\\xA0\"")
|
454
|
-
end
|
455
|
-
|
456
|
-
it "rejects unterminated strings" do
|
457
|
-
e = assert_raises(GraphQL::ParseError) do
|
458
|
-
GraphQL.parse("\"")
|
459
|
-
end
|
460
|
-
|
461
|
-
assert_includes(e.message, "Parse error on \"\\\"\"")
|
462
|
-
|
463
|
-
e = assert_raises(GraphQL::ParseError) do
|
464
|
-
GraphQL.parse("\"\n\"")
|
465
|
-
end
|
466
|
-
|
467
|
-
assert_includes(e.message, "Parse error on \"\\n\"")
|
468
|
-
end
|
469
|
-
|
470
|
-
it "rejects bad escape sequence in strings" do
|
471
|
-
e = assert_raises(GraphQL::ParseError) do
|
472
|
-
GraphQL.parse("{ field(arg:\"\\x\") }")
|
473
|
-
end
|
474
|
-
|
475
|
-
assert_includes(e.message, "Parse error on bad Unicode escape sequence")
|
476
|
-
end
|
477
|
-
|
478
|
-
it "rejects incomplete escape sequence in strings" do
|
479
|
-
e = assert_raises(GraphQL::ParseError) do
|
480
|
-
GraphQL.parse("{ field(arg:\"\\u1\") }")
|
481
|
-
end
|
482
|
-
|
483
|
-
assert_includes(e.message, "bad Unicode escape sequence")
|
484
|
-
end
|
485
|
-
|
486
|
-
it "rejects unicode escape with bad chars" do
|
487
|
-
e = assert_raises(GraphQL::ParseError) do
|
488
|
-
GraphQL.parse("{ field(arg:\"\\u0XX1\") }")
|
489
|
-
end
|
490
|
-
|
491
|
-
assert_includes(e.message, "bad Unicode escape sequence")
|
492
|
-
|
493
|
-
e = assert_raises(GraphQL::ParseError) do
|
494
|
-
GraphQL.parse("{ field(arg:\"\\uXXXX\") }")
|
495
|
-
end
|
496
|
-
|
497
|
-
assert_includes(e.message, "bad Unicode escape sequence")
|
498
|
-
|
499
|
-
|
500
|
-
e = assert_raises(GraphQL::ParseError) do
|
501
|
-
GraphQL.parse("{ field(arg:\"\\uFXXX\") }")
|
502
|
-
end
|
503
|
-
|
504
|
-
assert_includes(e.message, "bad Unicode escape sequence")
|
505
|
-
|
506
|
-
|
507
|
-
e = assert_raises(GraphQL::ParseError) do
|
508
|
-
GraphQL.parse("{ field(arg:\"\\uXXXF\") }")
|
509
|
-
end
|
510
|
-
|
511
|
-
assert_includes(e.message, "bad Unicode escape sequence")
|
512
|
-
end
|
513
|
-
|
514
|
-
it "rejects fragments named 'on'" do
|
515
|
-
e = assert_raises(GraphQL::ParseError) do
|
516
|
-
GraphQL.parse("fragment on on on { on }")
|
517
|
-
end
|
518
|
-
|
519
|
-
assert_includes(e.message, "Parse error on \"on\"")
|
520
|
-
end
|
521
|
-
|
522
|
-
it "rejects fragment spread of 'on'" do
|
523
|
-
e = assert_raises(GraphQL::ParseError) do
|
524
|
-
GraphQL.parse("{ ...on }")
|
525
|
-
end
|
526
|
-
|
527
|
-
assert_includes(e.message, "Parse error on \"}\"")
|
528
|
-
end
|
529
|
-
|
530
|
-
it "rejects null value" do
|
531
|
-
e = assert_raises(GraphQL::ParseError) do
|
532
|
-
GraphQL.parse("{ fieldWithNullableStringInput(input: null) }")
|
533
|
-
end
|
534
|
-
|
535
|
-
assert_includes(e.message, "Parse error on \"null\"")
|
536
|
-
end
|
537
|
-
end
|
538
|
-
|
539
|
-
|
540
|
-
describe "whitespace" do
|
541
|
-
describe "whitespace-only queries" do
|
542
|
-
let(:query_string) { " " }
|
543
|
-
it "doesn't blow up" do
|
544
|
-
assert_equal [], document.definitions
|
545
|
-
end
|
546
|
-
end
|
547
|
-
|
548
|
-
describe "empty string queries" do
|
549
|
-
let(:query_string) { "" }
|
550
|
-
it "doesn't blow up" do
|
551
|
-
assert_equal [], document.definitions
|
552
|
-
end
|
553
|
-
end
|
554
|
-
|
555
|
-
describe "using tabs as whitespace" do
|
556
|
-
let(:query_string) { "\t{\t\tid, \tname}"}
|
557
|
-
it "parses the query" do
|
558
|
-
assert_equal 1, document.definitions.length
|
559
|
-
end
|
560
|
-
end
|
561
|
-
end
|
5
|
+
include GraphQL::Language::ParserTests
|
6
|
+
subject { GraphQL::Language::Parser }
|
562
7
|
end
|