graphql 1.2.4 → 1.2.5
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/define/assign_object_field.rb +2 -2
- data/lib/graphql/define/instance_definable.rb +12 -15
- data/lib/graphql/field.rb +9 -2
- data/lib/graphql/input_object_type.rb +1 -2
- data/lib/graphql/language/visitor.rb +1 -10
- data/lib/graphql/query.rb +1 -0
- data/lib/graphql/query/arguments.rb +14 -10
- data/lib/graphql/relay/connection_field.rb +1 -1
- data/lib/graphql/relay/mutation.rb +7 -1
- data/lib/graphql/relay/node.rb +2 -6
- data/lib/graphql/schema.rb +16 -15
- data/lib/graphql/schema/instrumented_field_map.rb +14 -1
- data/lib/graphql/schema/validation.rb +1 -1
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/message.rb +7 -8
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +38 -0
- data/lib/graphql/static_validation/type_stack.rb +35 -12
- data/lib/graphql/version.rb +1 -1
- data/spec/graphql/define/instance_definable_spec.rb +16 -3
- data/spec/graphql/field_spec.rb +30 -0
- data/spec/graphql/input_object_type_spec.rb +12 -0
- data/spec/graphql/query/variables_spec.rb +45 -6
- data/spec/graphql/relay/connection_field_spec.rb +22 -0
- data/spec/graphql/relay/mutation_spec.rb +42 -0
- data/spec/graphql/schema_spec.rb +20 -2
- data/spec/graphql/static_validation/rules/unique_directives_per_location_spec.rb +180 -0
- data/spec/support/star_wars_schema.rb +1 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b49f51c993d3f37fb6596a0547175b9cb1e6dfe0
|
4
|
+
data.tar.gz: 4e639bf70ca713dd8a795690a30b2eafca16c5be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ba6eec79d60b4e4fac5a3779dce895c9e757e3bd923b64d6d77b2702638ed2dcb6b57ae914a6be2e582cff6cad29a3f3f18662e90b7ff5076f56610c75c2519
|
7
|
+
data.tar.gz: 39e999428ae079e15e942852e295518bb242b6c4dcb65ca32f31cdcde7d2518427f34dbd39d5b92a8e72d70762f85d0e85079d4efe4d670bf2ea674f98e542f8
|
@@ -16,13 +16,13 @@ module GraphQL
|
|
16
16
|
# Figure out how to find or initialize the field instance:
|
17
17
|
if type_or_field.is_a?(GraphQL::Field)
|
18
18
|
field = type_or_field
|
19
|
-
field.name
|
19
|
+
field = field.redefine(name: name_s)
|
20
20
|
elsif block_given?
|
21
21
|
field = GraphQL::Field.define(kwargs, &block)
|
22
22
|
elsif field.nil?
|
23
23
|
field = GraphQL::Field.define(kwargs)
|
24
24
|
elsif field.is_a?(GraphQL::Field)
|
25
|
-
field.name
|
25
|
+
field = field.redefine(name: name_s)
|
26
26
|
else
|
27
27
|
raise("Couldn't find a field argument, received: #{field || type_or_field}")
|
28
28
|
end
|
@@ -100,7 +100,7 @@ module GraphQL
|
|
100
100
|
def redefine(**kwargs, &block)
|
101
101
|
ensure_defined
|
102
102
|
new_instance = self.class.new
|
103
|
-
applied_definitions.each { |defn| defn.
|
103
|
+
applied_definitions.each { |defn| new_instance.define(defn.define_keywords, &defn.define_proc) }
|
104
104
|
new_instance.define(**kwargs, &block)
|
105
105
|
new_instance
|
106
106
|
end
|
@@ -116,7 +116,16 @@ module GraphQL
|
|
116
116
|
if @pending_definition
|
117
117
|
defn = @pending_definition
|
118
118
|
@pending_definition = nil
|
119
|
-
|
119
|
+
defn_proxy = DefinedObjectProxy.new(self)
|
120
|
+
# Apply definition from `define(...)` kwargs
|
121
|
+
defn.define_keywords.each do |keyword, value|
|
122
|
+
defn_proxy.public_send(keyword, value)
|
123
|
+
end
|
124
|
+
# and/or apply definition from `define { ... }` block
|
125
|
+
if defn.define_proc
|
126
|
+
defn_proxy.instance_eval(&defn.define_proc)
|
127
|
+
end
|
128
|
+
|
120
129
|
applied_definitions << defn
|
121
130
|
end
|
122
131
|
nil
|
@@ -126,24 +135,12 @@ module GraphQL
|
|
126
135
|
@applied_definitions ||= []
|
127
136
|
end
|
128
137
|
|
129
|
-
|
130
138
|
class Definition
|
139
|
+
attr_reader :define_keywords, :define_proc
|
131
140
|
def initialize(define_keywords, define_proc)
|
132
141
|
@define_keywords = define_keywords
|
133
142
|
@define_proc = define_proc
|
134
143
|
end
|
135
|
-
|
136
|
-
def apply(instance)
|
137
|
-
defn_proxy = DefinedObjectProxy.new(instance)
|
138
|
-
|
139
|
-
@define_keywords.each do |keyword, value|
|
140
|
-
defn_proxy.public_send(keyword, value)
|
141
|
-
end
|
142
|
-
|
143
|
-
if @define_proc
|
144
|
-
defn_proxy.instance_eval(&@define_proc)
|
145
|
-
end
|
146
|
-
end
|
147
144
|
end
|
148
145
|
|
149
146
|
module ClassMethods
|
data/lib/graphql/field.rb
CHANGED
@@ -123,13 +123,19 @@ module GraphQL
|
|
123
123
|
accepts_definitions :name, :description, :deprecation_reason,
|
124
124
|
:resolve, :type, :arguments,
|
125
125
|
:property, :hash_key, :complexity, :mutation,
|
126
|
+
:relay_node_field,
|
126
127
|
argument: GraphQL::Define::AssignArgument
|
127
128
|
|
128
129
|
|
129
130
|
attr_accessor :name, :deprecation_reason, :description, :property, :hash_key, :mutation, :arguments, :complexity
|
131
|
+
|
132
|
+
# @return [Boolean] True if this is the Relay find-by-id field
|
133
|
+
attr_accessor :relay_node_field
|
134
|
+
|
130
135
|
ensure_defined(
|
131
|
-
:name, :deprecation_reason, :description, :property, :hash_key, :mutation, :arguments, :complexity,
|
132
|
-
:resolve, :resolve=, :type, :type=, :name=, :property=, :hash_key
|
136
|
+
:name, :deprecation_reason, :description, :description=, :property, :hash_key, :mutation, :arguments, :complexity,
|
137
|
+
:resolve, :resolve=, :type, :type=, :name=, :property=, :hash_key=,
|
138
|
+
:relay_node_field,
|
133
139
|
)
|
134
140
|
|
135
141
|
# @!attribute [r] resolve_proc
|
@@ -152,6 +158,7 @@ module GraphQL
|
|
152
158
|
@complexity = 1
|
153
159
|
@arguments = {}
|
154
160
|
@resolve_proc = build_default_resolver
|
161
|
+
@relay_node_field = false
|
155
162
|
end
|
156
163
|
|
157
164
|
# Get a value for this field
|
@@ -30,9 +30,8 @@ module GraphQL
|
|
30
30
|
)
|
31
31
|
|
32
32
|
attr_accessor :mutation, :arguments
|
33
|
-
alias :input_fields :arguments
|
34
|
-
|
35
33
|
ensure_defined(:mutation, :arguments)
|
34
|
+
alias :input_fields :arguments
|
36
35
|
|
37
36
|
# @!attribute mutation
|
38
37
|
# @return [GraphQL::Relay::Mutation, nil] The mutation this field was derived from, if it was derived from a mutation
|
@@ -17,16 +17,9 @@ module GraphQL
|
|
17
17
|
# node right away
|
18
18
|
SKIP = :_skip
|
19
19
|
|
20
|
-
# @return [Array<Proc>] Hooks to call when entering _any_ node
|
21
|
-
attr_reader :enter
|
22
|
-
# @return [Array<Proc>] Hooks to call when leaving _any_ node
|
23
|
-
attr_reader :leave
|
24
|
-
|
25
20
|
def initialize(document)
|
26
21
|
@document = document
|
27
22
|
@visitors = {}
|
28
|
-
@enter = []
|
29
|
-
@leave = []
|
30
23
|
end
|
31
24
|
|
32
25
|
# Get a {NodeVisitor} for `node_class`
|
@@ -50,20 +43,18 @@ module GraphQL
|
|
50
43
|
def visit_node(node, parent)
|
51
44
|
begin_hooks_ok = begin_visit(node, parent)
|
52
45
|
if begin_hooks_ok
|
53
|
-
node.children.
|
46
|
+
node.children.each { |child| visit_node(child, node) }
|
54
47
|
end
|
55
48
|
end_visit(node, parent)
|
56
49
|
end
|
57
50
|
|
58
51
|
def begin_visit(node, parent)
|
59
|
-
self.class.apply_hooks(enter, node, parent)
|
60
52
|
node_visitor = self[node.class]
|
61
53
|
self.class.apply_hooks(node_visitor.enter, node, parent)
|
62
54
|
end
|
63
55
|
|
64
56
|
# Should global `leave` visitors come first or last?
|
65
57
|
def end_visit(node, parent)
|
66
|
-
self.class.apply_hooks(leave, node, parent)
|
67
58
|
node_visitor = self[node.class]
|
68
59
|
self.class.apply_hooks(node_visitor.leave, node, parent)
|
69
60
|
end
|
data/lib/graphql/query.rb
CHANGED
@@ -60,19 +60,23 @@ module GraphQL
|
|
60
60
|
NULL_ARGUMENT_VALUE = ArgumentValue.new(nil, nil, nil)
|
61
61
|
|
62
62
|
def wrap_value(value, arg_defn_type)
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
63
|
+
if value.nil?
|
64
|
+
nil
|
65
|
+
else
|
66
|
+
case arg_defn_type
|
67
|
+
when GraphQL::ListType
|
68
|
+
value.map { |item| wrap_value(item, arg_defn_type.of_type) }
|
69
|
+
when GraphQL::NonNullType
|
70
|
+
wrap_value(value, arg_defn_type.of_type)
|
71
|
+
when GraphQL::InputObjectType
|
72
|
+
if value.is_a?(Hash)
|
73
|
+
self.class.new(value, argument_definitions: arg_defn_type.arguments)
|
74
|
+
else
|
75
|
+
value
|
76
|
+
end
|
71
77
|
else
|
72
78
|
value
|
73
79
|
end
|
74
|
-
else
|
75
|
-
value
|
76
80
|
end
|
77
81
|
end
|
78
82
|
|
@@ -27,7 +27,7 @@ module GraphQL
|
|
27
27
|
# Turn A GraphQL::Field into a connection by:
|
28
28
|
# - Merging in the default arguments
|
29
29
|
# - Transforming its resolve function to return a connection object
|
30
|
-
# @param [GraphQL::Field] A field which returns nodes to be wrapped as a connection
|
30
|
+
# @param underlying_field [GraphQL::Field] A field which returns nodes to be wrapped as a connection
|
31
31
|
# @param max_page_size [Integer] The maximum number of nodes which may be requested (if a larger page is requested, it is limited to this number)
|
32
32
|
# @return [GraphQL::Field] The same field, modified to resolve to a connection object
|
33
33
|
def self.create(underlying_field, max_page_size: nil)
|
@@ -148,7 +148,7 @@ module GraphQL
|
|
148
148
|
attr_reader :client_mutation_id
|
149
149
|
def initialize(client_mutation_id:, result:)
|
150
150
|
@client_mutation_id = client_mutation_id
|
151
|
-
result.each do |key, value|
|
151
|
+
result && result.each do |key, value|
|
152
152
|
self.public_send("#{key}=", value)
|
153
153
|
end
|
154
154
|
end
|
@@ -175,6 +175,12 @@ module GraphQL
|
|
175
175
|
|
176
176
|
def call(obj, args, ctx)
|
177
177
|
mutation_result = @resolve.call(obj, args[:input], ctx)
|
178
|
+
|
179
|
+
if mutation_result.is_a?(GraphQL::ExecutionError)
|
180
|
+
ctx.add_error(mutation_result)
|
181
|
+
mutation_result = nil
|
182
|
+
end
|
183
|
+
|
178
184
|
if @wrap_result
|
179
185
|
@mutation.result_class.new(client_mutation_id: args[:input][:clientMutationId], result: mutation_result)
|
180
186
|
else
|
data/lib/graphql/relay/node.rb
CHANGED
@@ -7,17 +7,13 @@ module GraphQL
|
|
7
7
|
# We have to define it fresh each time because
|
8
8
|
# its name will be modified and its description
|
9
9
|
# _may_ be modified.
|
10
|
-
|
10
|
+
GraphQL::Field.define do
|
11
11
|
type(GraphQL::Relay::Node.interface)
|
12
12
|
description("Fetches an object given its ID.")
|
13
13
|
argument(:id, !types.ID, "ID of the object.")
|
14
14
|
resolve(GraphQL::Relay::Node::FindNode)
|
15
|
+
relay_node_field(true)
|
15
16
|
end
|
16
|
-
|
17
|
-
# This is used to identify generated fields in the schema
|
18
|
-
node_field.metadata[:relay_node_field] = true
|
19
|
-
|
20
|
-
node_field
|
21
17
|
end
|
22
18
|
|
23
19
|
# @return [GraphQL::InterfaceType] The interface which all Relay types must implement
|
data/lib/graphql/schema.rb
CHANGED
@@ -109,27 +109,24 @@ module GraphQL
|
|
109
109
|
ensure_defined
|
110
110
|
all_types = orphan_types + [query, mutation, subscription, GraphQL::Introspection::SchemaType]
|
111
111
|
@types = GraphQL::Schema::ReduceTypes.reduce(all_types.compact)
|
112
|
-
|
113
|
-
@instrumented_field_map = InstrumentedFieldMap.new(self)
|
114
|
-
field_instrumenters = @instrumenters[:field]
|
115
|
-
types.each do |type_name, type|
|
116
|
-
if type.kind.fields?
|
117
|
-
type.all_fields.each do |field_defn|
|
118
|
-
|
119
|
-
instrumented_field_defn = field_instrumenters.reduce(field_defn) do |defn, inst|
|
120
|
-
inst.instrument(type, defn)
|
121
|
-
end
|
122
|
-
|
123
|
-
@instrumented_field_map.set(type.name, field_defn.name, instrumented_field_defn)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
112
|
+
build_instrumented_field_map
|
127
113
|
# Assert that all necessary configs are present:
|
128
114
|
validation_error = Validation.validate(self)
|
129
115
|
validation_error && raise(NotImplementedError, validation_error)
|
130
116
|
nil
|
131
117
|
end
|
132
118
|
|
119
|
+
# Attach `instrumenter` to this schema for instrumenting events of `instrumentation_type`.
|
120
|
+
# @param instrumentation_type [Symbol]
|
121
|
+
# @param instrumenter
|
122
|
+
# @return [void]
|
123
|
+
def instrument(instrumentation_type, instrumenter)
|
124
|
+
@instrumenters[instrumentation_type] << instrumenter
|
125
|
+
if instrumentation_type == :field
|
126
|
+
build_instrumented_field_map
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
133
130
|
|
134
131
|
# @see [GraphQL::Schema::Warden] Restricted access to members of a schema
|
135
132
|
# @return [GraphQL::Schema::TypeMap] `{ name => type }` pairs of types in this schema
|
@@ -289,5 +286,9 @@ module GraphQL
|
|
289
286
|
middleware
|
290
287
|
end
|
291
288
|
end
|
289
|
+
|
290
|
+
def build_instrumented_field_map
|
291
|
+
@instrumented_field_map = InstrumentedFieldMap.new(self, @instrumenters[:field])
|
292
|
+
end
|
292
293
|
end
|
293
294
|
end
|
@@ -6,8 +6,21 @@ module GraphQL
|
|
6
6
|
#
|
7
7
|
# The catch is, the fields in this map _may_ have been modified by being instrumented.
|
8
8
|
class InstrumentedFieldMap
|
9
|
-
|
9
|
+
# Build a map using types from `schema` and instrumenters in `field_instrumenters`
|
10
|
+
# @param schema [GraphQL::Schema]
|
11
|
+
# @param field_instrumenters [Array<#instrument(type, field)>]
|
12
|
+
def initialize(schema, field_instrumenters)
|
10
13
|
@storage = Hash.new { |h, k| h[k] = {} }
|
14
|
+
schema.types.each do |type_name, type|
|
15
|
+
if type.kind.fields?
|
16
|
+
type.all_fields.each do |field_defn|
|
17
|
+
instrumented_field_defn = field_instrumenters.reduce(field_defn) do |defn, inst|
|
18
|
+
inst.instrument(type, defn)
|
19
|
+
end
|
20
|
+
self.set(type.name, field_defn.name, instrumented_field_defn)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
11
24
|
end
|
12
25
|
|
13
26
|
def set(type_name, field_name, field)
|
@@ -139,7 +139,7 @@ module GraphQL
|
|
139
139
|
}
|
140
140
|
|
141
141
|
SCHEMA_CAN_FETCH_IDS = ->(schema) {
|
142
|
-
has_node_field = schema.query && schema.query.all_fields.any?
|
142
|
+
has_node_field = schema.query && schema.query.all_fields.any?(&:relay_node_field)
|
143
143
|
if has_node_field && schema.object_from_id_proc.nil?
|
144
144
|
"schema contains `node(id:...)` field, so you must define a `object_from_id (id, ctx) -> { ... }` function"
|
145
145
|
else
|
@@ -8,6 +8,7 @@ module GraphQL
|
|
8
8
|
ALL_RULES = [
|
9
9
|
GraphQL::StaticValidation::DirectivesAreDefined,
|
10
10
|
GraphQL::StaticValidation::DirectivesAreInValidLocations,
|
11
|
+
GraphQL::StaticValidation::UniqueDirectivesPerLocation,
|
11
12
|
GraphQL::StaticValidation::FragmentsAreFinite,
|
12
13
|
GraphQL::StaticValidation::FragmentsAreNamed,
|
13
14
|
GraphQL::StaticValidation::FragmentsAreUsed,
|
@@ -1,23 +1,22 @@
|
|
1
1
|
module GraphQL
|
2
2
|
module StaticValidation
|
3
3
|
# Generates GraphQL-compliant validation message.
|
4
|
-
# Only supports one "location", too bad :(
|
5
4
|
class Message
|
6
5
|
# Convenience for validators
|
7
6
|
module MessageHelper
|
8
7
|
# Error `message` is located at `node`
|
9
|
-
def message(message,
|
8
|
+
def message(message, nodes, context: nil, path: nil)
|
10
9
|
path ||= context.path
|
11
|
-
|
10
|
+
nodes = Array(nodes)
|
11
|
+
GraphQL::StaticValidation::Message.new(message, nodes: nodes, path: path)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
attr_reader :message, :
|
15
|
+
attr_reader :message, :path
|
16
16
|
|
17
|
-
def initialize(message,
|
17
|
+
def initialize(message, path: [], nodes: [])
|
18
18
|
@message = message
|
19
|
-
@
|
20
|
-
@col = col
|
19
|
+
@nodes = nodes
|
21
20
|
@path = path
|
22
21
|
end
|
23
22
|
|
@@ -33,7 +32,7 @@ module GraphQL
|
|
33
32
|
private
|
34
33
|
|
35
34
|
def locations
|
36
|
-
@
|
35
|
+
@nodes.map{|node| {"line" => node.line, "column" => node.col}}
|
37
36
|
end
|
38
37
|
end
|
39
38
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module StaticValidation
|
3
|
+
class UniqueDirectivesPerLocation
|
4
|
+
include GraphQL::StaticValidation::Message::MessageHelper
|
5
|
+
|
6
|
+
NODES_WITH_DIRECTIVES = GraphQL::Language::Nodes.constants
|
7
|
+
.map{|c| GraphQL::Language::Nodes.const_get(c)}
|
8
|
+
.select{|c| c.is_a?(Class) && c.instance_methods.include?(:directives)}
|
9
|
+
|
10
|
+
def validate(context)
|
11
|
+
NODES_WITH_DIRECTIVES.each do |node_class|
|
12
|
+
context.visitor[node_class] << ->(node, _) {
|
13
|
+
validate_directives(node, context) unless node.directives.empty?
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def validate_directives(node, context)
|
21
|
+
used_directives = {}
|
22
|
+
|
23
|
+
node.directives.each do |ast_directive|
|
24
|
+
directive_name = ast_directive.name
|
25
|
+
if used_directives[directive_name]
|
26
|
+
context.errors << message(
|
27
|
+
"The directive \"#{directive_name}\" can only be used once at this location.",
|
28
|
+
[used_directives[directive_name], ast_directive],
|
29
|
+
context: context
|
30
|
+
)
|
31
|
+
else
|
32
|
+
used_directives[directive_name] = ast_directive
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -41,18 +41,15 @@ module GraphQL
|
|
41
41
|
@directive_definitions = []
|
42
42
|
@argument_definitions = []
|
43
43
|
@path = []
|
44
|
-
|
45
|
-
|
44
|
+
|
45
|
+
PUSH_STRATEGIES.each do |node_class, strategy|
|
46
|
+
visitor[node_class].enter << EnterWithStrategy.new(self, strategy)
|
47
|
+
visitor[node_class].leave << LeaveWithStrategy.new(self, strategy)
|
48
|
+
end
|
46
49
|
end
|
47
50
|
|
48
51
|
private
|
49
52
|
|
50
|
-
# Look up strategies by name and use singleton instance to push and pop
|
51
|
-
PUSH_STRATEGIES = Hash.new do |hash, key|
|
52
|
-
node_class_name = key.name.split("::").last
|
53
|
-
strategy_key = "#{node_class_name}Strategy"
|
54
|
-
hash[key] = const_defined?(strategy_key) ? const_get(strategy_key) : NullStrategy
|
55
|
-
end
|
56
53
|
|
57
54
|
module FragmentWithTypeStrategy
|
58
55
|
def push(stack, node)
|
@@ -182,10 +179,36 @@ module GraphQL
|
|
182
179
|
end
|
183
180
|
end
|
184
181
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
182
|
+
PUSH_STRATEGIES = {
|
183
|
+
GraphQL::Language::Nodes::FragmentDefinition => FragmentDefinitionStrategy,
|
184
|
+
GraphQL::Language::Nodes::InlineFragment => InlineFragmentStrategy,
|
185
|
+
GraphQL::Language::Nodes::FragmentSpread => FragmentSpreadStrategy,
|
186
|
+
GraphQL::Language::Nodes::Argument => ArgumentStrategy,
|
187
|
+
GraphQL::Language::Nodes::Field => FieldStrategy,
|
188
|
+
GraphQL::Language::Nodes::Directive => DirectiveStrategy,
|
189
|
+
GraphQL::Language::Nodes::OperationDefinition => OperationDefinitionStrategy,
|
190
|
+
}
|
191
|
+
|
192
|
+
class EnterWithStrategy
|
193
|
+
def initialize(stack, strategy)
|
194
|
+
@stack = stack
|
195
|
+
@strategy = strategy
|
196
|
+
end
|
197
|
+
|
198
|
+
def call(node, parent)
|
199
|
+
@strategy.push(@stack, node)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class LeaveWithStrategy
|
204
|
+
def initialize(stack, strategy)
|
205
|
+
@stack = stack
|
206
|
+
@strategy = strategy
|
207
|
+
end
|
208
|
+
|
209
|
+
def call(node, parent)
|
210
|
+
@strategy.pop(@stack, node)
|
211
|
+
end
|
189
212
|
end
|
190
213
|
end
|
191
214
|
end
|
data/lib/graphql/version.rb
CHANGED
@@ -18,6 +18,10 @@ module Garden
|
|
18
18
|
# definition added later:
|
19
19
|
attr_accessor :height
|
20
20
|
ensure_defined(:height)
|
21
|
+
|
22
|
+
def color
|
23
|
+
metadata[:color]
|
24
|
+
end
|
21
25
|
end
|
22
26
|
end
|
23
27
|
|
@@ -84,15 +88,24 @@ describe GraphQL::Define::InstanceDefinable do
|
|
84
88
|
name "Renamed Red Arugula"
|
85
89
|
end
|
86
90
|
|
87
|
-
assert_equal :green, arugula.
|
91
|
+
assert_equal :green, arugula.color
|
88
92
|
assert_equal "Arugula", arugula.name
|
89
93
|
|
90
|
-
assert_equal :red, red_arugula.
|
94
|
+
assert_equal :red, red_arugula.color
|
91
95
|
assert_equal "Arugula", red_arugula.name
|
92
96
|
|
93
|
-
assert_equal :red, renamed_red_arugula.
|
97
|
+
assert_equal :red, renamed_red_arugula.color
|
94
98
|
assert_equal "Renamed Red Arugula", renamed_red_arugula.name
|
95
99
|
end
|
100
|
+
|
101
|
+
it "can be chained several times" do
|
102
|
+
arugula_1 = Garden::Vegetable.define(name: "Arugula") { color :green }
|
103
|
+
arugula_2 = arugula_1.redefine { color :red }
|
104
|
+
arugula_3 = arugula_2.redefine { plant_between(1..3) }
|
105
|
+
assert_equal ["Arugula", :green], [arugula_1.name, arugula_1.color]
|
106
|
+
assert_equal ["Arugula", :red], [arugula_2.name, arugula_2.color]
|
107
|
+
assert_equal ["Arugula", :red], [arugula_3.name, arugula_3.color]
|
108
|
+
end
|
96
109
|
end
|
97
110
|
|
98
111
|
describe "#metadata" do
|
data/spec/graphql/field_spec.rb
CHANGED
@@ -120,4 +120,34 @@ describe GraphQL::Field do
|
|
120
120
|
assert_equal [:cheeses, :milks], similar_cheese_field.metadata[:joins]
|
121
121
|
end
|
122
122
|
end
|
123
|
+
|
124
|
+
describe "reusing a GraphQL::Field" do
|
125
|
+
let(:schema) {
|
126
|
+
int_field = GraphQL::Field.define do
|
127
|
+
type types.Int
|
128
|
+
argument :value, types.Int
|
129
|
+
resolve -> (obj, args, ctx) { args[:value] }
|
130
|
+
end
|
131
|
+
|
132
|
+
query_type = GraphQL::ObjectType.define do
|
133
|
+
name "Query"
|
134
|
+
field :int, int_field
|
135
|
+
field :int2, int_field
|
136
|
+
field :int3, field: int_field
|
137
|
+
end
|
138
|
+
|
139
|
+
GraphQL::Schema.define do
|
140
|
+
query(query_type)
|
141
|
+
end
|
142
|
+
}
|
143
|
+
|
144
|
+
it "can be used in two places" do
|
145
|
+
res = schema.execute %|{ int(value: 1) int2(value: 2) int3(value: 3) }|
|
146
|
+
assert_equal({ "int" => 1, "int2" => 2, "int3" => 3}, res["data"], "It works in queries")
|
147
|
+
|
148
|
+
res = schema.execute %|{ __type(name: "Query") { fields { name } } }|
|
149
|
+
query_field_names = res["data"]["__type"]["fields"].map { |f| f["name"] }
|
150
|
+
assert_equal ["int", "int2", "int3"], query_field_names, "It works in introspection"
|
151
|
+
end
|
152
|
+
end
|
123
153
|
end
|
@@ -10,6 +10,18 @@ describe GraphQL::InputObjectType do
|
|
10
10
|
assert(DairyProductInputType.input_fields["fatContent"])
|
11
11
|
end
|
12
12
|
|
13
|
+
describe "on a type unused by the schema" do
|
14
|
+
it "has input fields" do
|
15
|
+
UnreachedInputType = GraphQL::InputObjectType.define do
|
16
|
+
name 'UnreachedInputType'
|
17
|
+
description 'An input object type not directly used in the schema.'
|
18
|
+
|
19
|
+
input_field :field, types.String
|
20
|
+
end
|
21
|
+
assert(UnreachedInputType.input_fields['field'])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
13
25
|
describe "input validation" do
|
14
26
|
it "Accepts anything that yields key-value pairs to #all?" do
|
15
27
|
values_obj = MinimumInputObject.new({"source" => "COW", "fatContent" => 0.4})
|
@@ -2,30 +2,69 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
describe GraphQL::Query::Variables do
|
4
4
|
let(:query_string) {%|
|
5
|
-
query getCheese($animals: [DairyAnimal], $int: Int, $intWithDefault: Int = 10) {
|
5
|
+
query getCheese($animals: [DairyAnimal!], $int: Int, $intWithDefault: Int = 10) {
|
6
6
|
cheese(id: 1) {
|
7
7
|
similarCheese(source: $animals)
|
8
8
|
}
|
9
9
|
}
|
10
10
|
|}
|
11
11
|
let(:ast_variables) { GraphQL.parse(query_string).definitions.first.variables }
|
12
|
+
let(:schema) { DummySchema }
|
12
13
|
let(:variables) { GraphQL::Query::Variables.new(
|
13
|
-
|
14
|
-
GraphQL::Schema::Warden.new(
|
14
|
+
schema,
|
15
|
+
GraphQL::Schema::Warden.new(schema, GraphQL::Query::NullExcept),
|
15
16
|
ast_variables,
|
16
17
|
provided_variables)
|
17
18
|
}
|
18
19
|
|
19
20
|
describe "#initialize" do
|
20
21
|
describe "coercing inputs" do
|
21
|
-
let(:provided_variables) {
|
22
|
-
|
23
|
-
}
|
22
|
+
let(:provided_variables) { { "animals" => "YAK" } }
|
23
|
+
|
24
24
|
it "coerces single items into one-element lists" do
|
25
25
|
assert_equal ["YAK"], variables["animals"]
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
+
describe "nullable variables" do
|
30
|
+
let(:schema) { GraphQL::Schema.from_definition(%|
|
31
|
+
type Query {
|
32
|
+
thingsCount(ids: [ID!]): Int!
|
33
|
+
}
|
34
|
+
|)
|
35
|
+
}
|
36
|
+
let(:query_string) {%|
|
37
|
+
query getThingsCount($ids: [ID!]) {
|
38
|
+
thingsCount(ids: $ids)
|
39
|
+
}
|
40
|
+
|}
|
41
|
+
let(:result) {
|
42
|
+
schema.execute(query_string, variables: provided_variables, root_value: OpenStruct.new(thingsCount: 1))
|
43
|
+
}
|
44
|
+
|
45
|
+
describe "when they are present, but null" do
|
46
|
+
let(:provided_variables) { { "ids" => nil } }
|
47
|
+
it "ignores them" do
|
48
|
+
assert_equal 1, result["data"]["thingsCount"]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "when they are not present" do
|
53
|
+
let(:provided_variables) { {} }
|
54
|
+
it "ignores them" do
|
55
|
+
assert_equal 1, result["data"]["thingsCount"]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "when a nullable list has a null in it" do
|
60
|
+
let(:provided_variables) { { "ids" => [nil] } }
|
61
|
+
it "returns an error" do
|
62
|
+
assert_equal 1, result["errors"].length
|
63
|
+
assert_equal nil, result["data"]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
29
68
|
describe "coercing null" do
|
30
69
|
let(:provided_variables) {
|
31
70
|
{"int" => nil, "intWithDefault" => nil}
|
@@ -9,4 +9,26 @@ describe GraphQL::Relay::ConnectionField do
|
|
9
9
|
|
10
10
|
assert_equal ["tests"], test_type.fields.keys
|
11
11
|
end
|
12
|
+
|
13
|
+
it "leaves the original field untouched" do
|
14
|
+
test_type = nil
|
15
|
+
|
16
|
+
test_field = GraphQL::Field.define do
|
17
|
+
type(test_type.connection_type)
|
18
|
+
property(:something)
|
19
|
+
end
|
20
|
+
|
21
|
+
test_type = GraphQL::ObjectType.define do
|
22
|
+
name "Test"
|
23
|
+
connection :tests, test_field
|
24
|
+
end
|
25
|
+
|
26
|
+
conn_field = test_type.fields["tests"]
|
27
|
+
|
28
|
+
assert_equal 0, test_field.arguments.length
|
29
|
+
assert_equal 4, conn_field.arguments.length
|
30
|
+
|
31
|
+
assert_instance_of GraphQL::Field::Resolve::MethodResolve, test_field.resolve_proc
|
32
|
+
assert_instance_of GraphQL::Relay::ConnectionResolve, conn_field.resolve_proc
|
33
|
+
end
|
12
34
|
end
|
@@ -77,6 +77,23 @@ describe GraphQL::Relay::Mutation do
|
|
77
77
|
assert_equal IntroduceShipMutation, IntroduceShipMutation.result_class.mutation
|
78
78
|
end
|
79
79
|
|
80
|
+
describe "aliased methods" do
|
81
|
+
describe "on an unreached mutation" do
|
82
|
+
it 'still ensures definitions' do
|
83
|
+
UnreachedMutation = GraphQL::Relay::Mutation.define do
|
84
|
+
name 'UnreachedMutation'
|
85
|
+
description 'A mutation type not directly used in the schema.'
|
86
|
+
|
87
|
+
input_field :input, types.String
|
88
|
+
return_field :return, types.String
|
89
|
+
end
|
90
|
+
|
91
|
+
assert UnreachedMutation.input_fields['input']
|
92
|
+
assert UnreachedMutation.return_fields['return']
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
80
97
|
describe "providing a return type" do
|
81
98
|
let(:custom_return_type) {
|
82
99
|
GraphQL::ObjectType.define do
|
@@ -143,4 +160,29 @@ describe GraphQL::Relay::Mutation do
|
|
143
160
|
assert_equal "String", input.arguments['stringDefault'].default_value
|
144
161
|
end
|
145
162
|
end
|
163
|
+
|
164
|
+
describe "handling errors" do
|
165
|
+
it "supports returning an error in resolve" do
|
166
|
+
result = star_wars_query(query_string, "clientMutationId" => "5678", "shipName" => "Millennium Falcon")
|
167
|
+
|
168
|
+
expected = {
|
169
|
+
"data" => {
|
170
|
+
"introduceShip" => {
|
171
|
+
"clientMutationId" => "5678",
|
172
|
+
"shipEdge" => nil,
|
173
|
+
"faction" => nil,
|
174
|
+
}
|
175
|
+
},
|
176
|
+
"errors" => [
|
177
|
+
{
|
178
|
+
"message" => "Sorry, Millennium Falcon ship is reserved",
|
179
|
+
"locations" => [ { "line" => 3 , "column" => 7}],
|
180
|
+
"path" => ["introduceShip"]
|
181
|
+
}
|
182
|
+
]
|
183
|
+
}
|
184
|
+
|
185
|
+
assert_equal(expected, result)
|
186
|
+
end
|
187
|
+
end
|
146
188
|
end
|
data/spec/graphql/schema_spec.rb
CHANGED
@@ -217,6 +217,7 @@ type Query {
|
|
217
217
|
end
|
218
218
|
|
219
219
|
def after_query(query)
|
220
|
+
@counts << :end
|
220
221
|
end
|
221
222
|
end
|
222
223
|
|
@@ -228,7 +229,7 @@ type Query {
|
|
228
229
|
name "Query"
|
229
230
|
field :int, types.Int do
|
230
231
|
argument :value, types.Int
|
231
|
-
resolve -> (obj, args, ctx) { args[:value] }
|
232
|
+
resolve -> (obj, args, ctx) { args[:value] == 13 ? raise("13 is unlucky") : args[:value] }
|
232
233
|
end
|
233
234
|
end
|
234
235
|
}
|
@@ -250,7 +251,24 @@ type Query {
|
|
250
251
|
it "can wrap query execution" do
|
251
252
|
schema.execute("query getInt($val: Int = 5){ int(value: $val) } ")
|
252
253
|
schema.execute("query getInt($val: Int = 5, $val2: Int = 3){ int(value: $val) int2: int(value: $val2) } ")
|
253
|
-
assert_equal [1, 2], variable_counter.counts
|
254
|
+
assert_equal [1, :end, 2, :end], variable_counter.counts
|
255
|
+
end
|
256
|
+
|
257
|
+
it "runs even when a runtime error occurs" do
|
258
|
+
schema.execute("query getInt($val: Int = 5){ int(value: $val) } ")
|
259
|
+
assert_raises(RuntimeError) {
|
260
|
+
schema.execute("query getInt($val: Int = 13){ int(value: $val) } ")
|
261
|
+
}
|
262
|
+
assert_equal [1, :end, 1, :end], variable_counter.counts
|
263
|
+
end
|
264
|
+
|
265
|
+
it "can be applied after the fact" do
|
266
|
+
res = schema.execute("query { int(value: 2) } ")
|
267
|
+
assert_equal 6, res["data"]["int"]
|
268
|
+
|
269
|
+
schema.instrument(:field, MultiplyInstrumenter.new(4))
|
270
|
+
res = schema.execute("query { int(value: 2) } ")
|
271
|
+
assert_equal 24, res["data"]["int"]
|
254
272
|
end
|
255
273
|
end
|
256
274
|
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe GraphQL::StaticValidation::UniqueDirectivesPerLocation do
|
4
|
+
include StaticValidationHelpers
|
5
|
+
|
6
|
+
let(:schema) { GraphQL::Schema.from_definition("
|
7
|
+
type Query {
|
8
|
+
type: Type
|
9
|
+
}
|
10
|
+
|
11
|
+
type Type {
|
12
|
+
field: String
|
13
|
+
}
|
14
|
+
|
15
|
+
directive @A on FIELD
|
16
|
+
directive @B on FIELD
|
17
|
+
") }
|
18
|
+
|
19
|
+
describe "query with no directives" do
|
20
|
+
let(:query_string) {"
|
21
|
+
{
|
22
|
+
type {
|
23
|
+
field
|
24
|
+
}
|
25
|
+
}
|
26
|
+
"}
|
27
|
+
|
28
|
+
it "passes rule" do
|
29
|
+
assert_equal [], errors
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "query with unique directives in different locations" do
|
34
|
+
let(:query_string) {"
|
35
|
+
{
|
36
|
+
type @A {
|
37
|
+
field @B
|
38
|
+
}
|
39
|
+
}
|
40
|
+
"}
|
41
|
+
|
42
|
+
it "passes rule" do
|
43
|
+
assert_equal [], errors
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "query with unique directives in same locations" do
|
48
|
+
let(:query_string) {"
|
49
|
+
{
|
50
|
+
type @A @B {
|
51
|
+
field @A @B
|
52
|
+
}
|
53
|
+
}
|
54
|
+
"}
|
55
|
+
|
56
|
+
it "passes rule" do
|
57
|
+
assert_equal [], errors
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "query with same directives in different locations" do
|
62
|
+
let(:query_string) {"
|
63
|
+
{
|
64
|
+
type @A {
|
65
|
+
field @A
|
66
|
+
}
|
67
|
+
}
|
68
|
+
"}
|
69
|
+
|
70
|
+
it "passes rule" do
|
71
|
+
assert_equal [], errors
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "query with same directives in similar locations" do
|
76
|
+
let(:query_string) {"
|
77
|
+
{
|
78
|
+
type {
|
79
|
+
field @A
|
80
|
+
field @A
|
81
|
+
}
|
82
|
+
}
|
83
|
+
"}
|
84
|
+
|
85
|
+
it "passes rule" do
|
86
|
+
assert_equal [], errors
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "query with duplicate directives in one location" do
|
91
|
+
let(:query_string) {"
|
92
|
+
{
|
93
|
+
type {
|
94
|
+
field @A @A
|
95
|
+
}
|
96
|
+
}
|
97
|
+
"}
|
98
|
+
|
99
|
+
it "fails rule" do
|
100
|
+
assert_includes errors, {
|
101
|
+
"message" => 'The directive "A" can only be used once at this location.',
|
102
|
+
"locations" => [{ "line" => 4, "column" => 17 }, { "line" => 4, "column" => 20 }],
|
103
|
+
"fields" => ["query", "type", "field"],
|
104
|
+
}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
describe "query with many duplicate directives in one location" do
|
110
|
+
let(:query_string) {"
|
111
|
+
{
|
112
|
+
type {
|
113
|
+
field @A @A @A
|
114
|
+
}
|
115
|
+
}
|
116
|
+
"}
|
117
|
+
|
118
|
+
it "fails rule" do
|
119
|
+
assert_includes errors, {
|
120
|
+
"message" => 'The directive "A" can only be used once at this location.',
|
121
|
+
"locations" => [{ "line" => 4, "column" => 17 }, { "line" => 4, "column" => 20 }],
|
122
|
+
"fields" => ["query", "type", "field"],
|
123
|
+
}
|
124
|
+
|
125
|
+
assert_includes errors, {
|
126
|
+
"message" => 'The directive "A" can only be used once at this location.',
|
127
|
+
"locations" => [{ "line" => 4, "column" => 17 }, { "line" => 4, "column" => 23 }],
|
128
|
+
"fields" => ["query", "type", "field"],
|
129
|
+
}
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "query with different duplicate directives in one location" do
|
134
|
+
let(:query_string) {"
|
135
|
+
{
|
136
|
+
type {
|
137
|
+
field @A @B @A @B
|
138
|
+
}
|
139
|
+
}
|
140
|
+
"}
|
141
|
+
|
142
|
+
it "fails rule" do
|
143
|
+
assert_includes errors, {
|
144
|
+
"message" => 'The directive "A" can only be used once at this location.',
|
145
|
+
"locations" => [{ "line" => 4, "column" => 17 }, { "line" => 4, "column" => 23 }],
|
146
|
+
"fields" => ["query", "type", "field"],
|
147
|
+
}
|
148
|
+
|
149
|
+
assert_includes errors, {
|
150
|
+
"message" => 'The directive "B" can only be used once at this location.',
|
151
|
+
"locations" => [{ "line" => 4, "column" => 20 }, { "line" => 4, "column" => 26 }],
|
152
|
+
"fields" => ["query", "type", "field"],
|
153
|
+
}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe "query with duplicate directives in many locations" do
|
158
|
+
let(:query_string) {"
|
159
|
+
{
|
160
|
+
type @A @A {
|
161
|
+
field @A @A
|
162
|
+
}
|
163
|
+
}
|
164
|
+
"}
|
165
|
+
|
166
|
+
it "fails rule" do
|
167
|
+
assert_includes errors, {
|
168
|
+
"message" => 'The directive "A" can only be used once at this location.',
|
169
|
+
"locations" => [{ "line" => 3, "column" => 14 }, { "line" => 3, "column" => 17 }],
|
170
|
+
"fields" => ["query", "type"],
|
171
|
+
}
|
172
|
+
|
173
|
+
assert_includes errors, {
|
174
|
+
"message" => 'The directive "A" can only be used once at this location.',
|
175
|
+
"locations" => [{ "line" => 4, "column" => 17 }, { "line" => 4, "column" => 20 }],
|
176
|
+
"fields" => ["query", "type", "field"],
|
177
|
+
}
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -157,6 +157,7 @@ IntroduceShipMutation = GraphQL::Relay::Mutation.define do
|
|
157
157
|
# Here's the mutation operation:
|
158
158
|
resolve ->(root_obj, inputs, ctx) {
|
159
159
|
faction_id = inputs["factionId"]
|
160
|
+
return GraphQL::ExecutionError.new("Sorry, Millennium Falcon ship is reserved") if inputs["shipName"] == 'Millennium Falcon'
|
160
161
|
ship = STAR_WARS_DATA.create_ship(inputs["shipName"], faction_id)
|
161
162
|
faction = STAR_WARS_DATA["Faction"][faction_id]
|
162
163
|
connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(faction.ships)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-11-
|
11
|
+
date: 2016-11-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: codeclimate-test-reporter
|
@@ -407,6 +407,7 @@ files:
|
|
407
407
|
- lib/graphql/static_validation/rules/mutation_root_exists.rb
|
408
408
|
- lib/graphql/static_validation/rules/required_arguments_are_present.rb
|
409
409
|
- lib/graphql/static_validation/rules/subscription_root_exists.rb
|
410
|
+
- lib/graphql/static_validation/rules/unique_directives_per_location.rb
|
410
411
|
- lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb
|
411
412
|
- lib/graphql/static_validation/rules/variable_usages_are_allowed.rb
|
412
413
|
- lib/graphql/static_validation/rules/variables_are_input_types.rb
|
@@ -504,6 +505,7 @@ files:
|
|
504
505
|
- spec/graphql/static_validation/rules/mutation_root_exists_spec.rb
|
505
506
|
- spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb
|
506
507
|
- spec/graphql/static_validation/rules/subscription_root_exists_spec.rb
|
508
|
+
- spec/graphql/static_validation/rules/unique_directives_per_location_spec.rb
|
507
509
|
- spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb
|
508
510
|
- spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb
|
509
511
|
- spec/graphql/static_validation/rules/variables_are_input_types_spec.rb
|
@@ -628,6 +630,7 @@ test_files:
|
|
628
630
|
- spec/graphql/static_validation/rules/mutation_root_exists_spec.rb
|
629
631
|
- spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb
|
630
632
|
- spec/graphql/static_validation/rules/subscription_root_exists_spec.rb
|
633
|
+
- spec/graphql/static_validation/rules/unique_directives_per_location_spec.rb
|
631
634
|
- spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb
|
632
635
|
- spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb
|
633
636
|
- spec/graphql/static_validation/rules/variables_are_input_types_spec.rb
|