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