graphql 0.19.4 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +5 -0
- data/lib/graphql/define/assign_enum_value.rb +4 -3
- data/lib/graphql/enum_type.rb +3 -10
- data/lib/graphql/language/generation.rb +6 -2
- data/lib/graphql/language/parser.rb +270 -270
- data/lib/graphql/language/parser.y +2 -2
- data/lib/graphql/language/parser_tests.rb +2 -2
- data/lib/graphql/query.rb +33 -38
- data/lib/graphql/query/serial_execution/field_resolution.rb +29 -33
- data/lib/graphql/query/serial_execution/value_resolution.rb +5 -2
- data/lib/graphql/query/variables.rb +5 -1
- data/lib/graphql/relay/base_connection.rb +5 -16
- data/lib/graphql/relay/mutation.rb +0 -17
- data/lib/graphql/schema.rb +2 -10
- data/lib/graphql/schema/loader.rb +1 -1
- data/lib/graphql/schema/printer.rb +1 -1
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +1 -1
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +3 -2
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +10 -6
- data/lib/graphql/static_validation/type_stack.rb +2 -2
- data/lib/graphql/static_validation/validator.rb +1 -1
- data/lib/graphql/unresolved_type_error.rb +0 -5
- data/lib/graphql/version.rb +1 -1
- data/readme.md +3 -79
- data/spec/graphql/language/parser_spec.rb +1 -1
- data/spec/graphql/query/serial_execution/value_resolution_spec.rb +4 -6
- data/spec/graphql/relay/base_connection_spec.rb +19 -0
- data/spec/graphql/relay/mutation_spec.rb +1 -1
- data/spec/graphql/relay/relation_connection_spec.rb +7 -0
- data/spec/graphql/schema/printer_spec.rb +6 -2
- data/spec/graphql/schema/timeout_middleware_spec.rb +3 -2
- data/spec/graphql/static_validation/validator_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -3
- data/spec/support/static_validation_helpers.rb +1 -1
- metadata +5 -44
@@ -214,7 +214,7 @@ rule
|
|
214
214
|
ELLIPSIS name_without_on directives_list_opt { return make_node(:FragmentSpread, name: val[1], directives: val[2], position_source: val[0]) }
|
215
215
|
|
216
216
|
inline_fragment:
|
217
|
-
ELLIPSIS ON
|
217
|
+
ELLIPSIS ON type directives_list_opt selection_set {
|
218
218
|
return make_node(:InlineFragment, {
|
219
219
|
type: val[2],
|
220
220
|
directives: val[3],
|
@@ -232,7 +232,7 @@ rule
|
|
232
232
|
}
|
233
233
|
|
234
234
|
fragment_definition:
|
235
|
-
FRAGMENT fragment_name_opt ON
|
235
|
+
FRAGMENT fragment_name_opt ON type directives_list_opt selection_set {
|
236
236
|
return make_node(:FragmentDefinition, {
|
237
237
|
name: val[1],
|
238
238
|
type: val[3],
|
@@ -65,7 +65,7 @@ module GraphQL
|
|
65
65
|
assert fragment_def.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
|
66
66
|
assert_equal "moreNestedFields", fragment_def.name
|
67
67
|
assert_equal 1, fragment_def.selections.length
|
68
|
-
assert_equal "NestedType", fragment_def.type
|
68
|
+
assert_equal "NestedType", fragment_def.type.name
|
69
69
|
assert_equal 1, fragment_def.directives.length
|
70
70
|
assert_equal [20, 13], fragment_def.position
|
71
71
|
end
|
@@ -173,7 +173,7 @@ module GraphQL
|
|
173
173
|
let(:typeless_inline_fragment) { query.selections[3] }
|
174
174
|
|
175
175
|
it "gets the type and directives" do
|
176
|
-
assert_equal "OtherType", inline_fragment.type
|
176
|
+
assert_equal "OtherType", inline_fragment.type.name
|
177
177
|
assert_equal 2, inline_fragment.selections.length
|
178
178
|
assert_equal 1, inline_fragment.directives.length
|
179
179
|
end
|
data/lib/graphql/query.rb
CHANGED
@@ -24,12 +24,11 @@ module GraphQL
|
|
24
24
|
# @param query_string [String]
|
25
25
|
# @param context [#[]] an arbitrary hash of values which you can access in {GraphQL::Field#resolve}
|
26
26
|
# @param variables [Hash] values for `$variables` in the query
|
27
|
-
# @param validate [Boolean] if true, `query_string` will be validated with {StaticValidation::Validator}
|
28
27
|
# @param operation_name [String] if the query string contains many operations, this is the one which should be executed
|
29
28
|
# @param root_value [Object] the object used to resolve fields on the root type
|
30
29
|
# @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value)
|
31
30
|
# @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
|
32
|
-
def initialize(schema, query_string = nil, document: nil, context: nil, variables: {},
|
31
|
+
def initialize(schema, query_string = nil, document: nil, context: nil, variables: {}, operation_name: nil, root_value: nil, max_depth: nil, max_complexity: nil)
|
33
32
|
fail ArgumentError, "a query string or document is required" unless query_string || document
|
34
33
|
|
35
34
|
@schema = schema
|
@@ -44,7 +43,6 @@ module GraphQL
|
|
44
43
|
end
|
45
44
|
@context = Context.new(query: self, values: context)
|
46
45
|
@root_value = root_value
|
47
|
-
@validate = validate
|
48
46
|
@operation_name = operation_name
|
49
47
|
@fragments = {}
|
50
48
|
@operations = {}
|
@@ -66,6 +64,18 @@ module GraphQL
|
|
66
64
|
@analysis_errors = []
|
67
65
|
@internal_representation = nil
|
68
66
|
@was_validated = false
|
67
|
+
|
68
|
+
# Trying to execute a document
|
69
|
+
# with no operations returns an empty hash
|
70
|
+
@ast_variables = []
|
71
|
+
if @operations.any?
|
72
|
+
@selected_operation = find_operation(@operations, @operation_name)
|
73
|
+
if @selected_operation.nil?
|
74
|
+
@validation_errors << GraphQL::Query::OperationNameMissingError.new(@operations.keys)
|
75
|
+
else
|
76
|
+
@ast_variables = @selected_operation.variables
|
77
|
+
end
|
78
|
+
end
|
69
79
|
end
|
70
80
|
|
71
81
|
# Get the result for this query, executing it once
|
@@ -74,7 +84,7 @@ module GraphQL
|
|
74
84
|
if !valid?
|
75
85
|
all_errors = validation_errors + analysis_errors
|
76
86
|
if all_errors.any?
|
77
|
-
{ "errors" => all_errors }
|
87
|
+
{ "errors" => all_errors.map(&:to_h) }
|
78
88
|
else
|
79
89
|
nil
|
80
90
|
end
|
@@ -93,14 +103,19 @@ module GraphQL
|
|
93
103
|
# Determine the values for variables of this query, using default values
|
94
104
|
# if a value isn't provided at runtime.
|
95
105
|
#
|
96
|
-
#
|
106
|
+
# If some variable is invalid, errors are added to {#validation_errors}.
|
107
|
+
#
|
97
108
|
# @return [GraphQL::Query::Variables] Variables to apply to this query
|
98
109
|
def variables
|
99
|
-
@variables ||=
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
110
|
+
@variables ||= begin
|
111
|
+
vars = GraphQL::Query::Variables.new(
|
112
|
+
@schema,
|
113
|
+
@ast_variables,
|
114
|
+
@provided_variables,
|
115
|
+
)
|
116
|
+
@validation_errors.concat(vars.errors)
|
117
|
+
vars
|
118
|
+
end
|
104
119
|
end
|
105
120
|
|
106
121
|
# @return [Hash<String, nil => GraphQL::InternalRepresentation::Node] Operation name -> Irep node pairs
|
@@ -136,18 +151,13 @@ module GraphQL
|
|
136
151
|
end
|
137
152
|
|
138
153
|
# @return [GraphQL::Language::Nodes::Document, nil]
|
139
|
-
|
140
|
-
@selected_operation ||= find_operation(@operations, @operation_name)
|
141
|
-
end
|
154
|
+
attr_reader :selected_operation
|
142
155
|
|
143
156
|
def valid?
|
144
|
-
|
157
|
+
@was_validated ||= begin
|
145
158
|
@was_validated = true
|
146
|
-
@valid =
|
147
|
-
|
148
|
-
else
|
149
|
-
true
|
150
|
-
end
|
159
|
+
@valid = document_valid? && query_valid? && variables.errors.none?
|
160
|
+
true
|
151
161
|
end
|
152
162
|
|
153
163
|
@valid
|
@@ -158,23 +168,11 @@ module GraphQL
|
|
158
168
|
# Assert that the passed-in query string is internally consistent
|
159
169
|
def document_valid?
|
160
170
|
validation_result = schema.static_validator.validate(self)
|
161
|
-
@validation_errors
|
171
|
+
@validation_errors.concat(validation_result[:errors])
|
162
172
|
@internal_representation = validation_result[:irep]
|
163
173
|
@validation_errors.none?
|
164
174
|
end
|
165
175
|
|
166
|
-
# Given that the document is valid, do we have what we need to
|
167
|
-
# execute the document this time?
|
168
|
-
# - Is there an operation to run?
|
169
|
-
# - Are all variables accounted for?
|
170
|
-
def query_possible?
|
171
|
-
!selected_operation.nil? && variables
|
172
|
-
true
|
173
|
-
rescue GraphQL::Query::OperationNameMissingError, GraphQL::Query::VariableValidationError => err
|
174
|
-
@validation_errors << err.to_h
|
175
|
-
false
|
176
|
-
end
|
177
|
-
|
178
176
|
# Given that we _could_ execute this query, _should_ we?
|
179
177
|
# - Does it violate any query analyzers?
|
180
178
|
def query_valid?
|
@@ -182,9 +180,8 @@ module GraphQL
|
|
182
180
|
if @query_analyzers.any?
|
183
181
|
reduce_results = GraphQL::Analysis.analyze_query(self, @query_analyzers)
|
184
182
|
reduce_results
|
185
|
-
|
186
|
-
|
187
|
-
.map(&:to_h)
|
183
|
+
.flatten # accept n-dimensional array
|
184
|
+
.select { |r| r.is_a?(GraphQL::AnalysisError) }
|
188
185
|
else
|
189
186
|
[]
|
190
187
|
end
|
@@ -195,10 +192,8 @@ module GraphQL
|
|
195
192
|
def find_operation(operations, operation_name)
|
196
193
|
if operations.length == 1
|
197
194
|
operations.values.first
|
198
|
-
elsif operations.length == 0
|
195
|
+
elsif operations.length == 0 || !operations.key?(operation_name)
|
199
196
|
nil
|
200
|
-
elsif !operations.key?(operation_name)
|
201
|
-
raise OperationNameMissingError, operations.keys
|
202
197
|
else
|
203
198
|
operations[operation_name]
|
204
199
|
end
|
@@ -61,44 +61,40 @@ module GraphQL
|
|
61
61
|
# is added to the "errors" key.
|
62
62
|
def get_raw_value
|
63
63
|
middlewares = execution_context.query.schema.middleware
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
64
|
+
query_context = execution_context.query.context
|
65
|
+
# setup
|
66
|
+
query_context.ast_node = @irep_node.ast_node
|
67
|
+
query_context.irep_node = @irep_node
|
68
|
+
|
69
|
+
resolve_arguments = [parent_type, target, field, arguments, query_context]
|
70
|
+
|
71
|
+
resolve_value = begin
|
72
|
+
# only run a middleware chain if there are any middleware
|
73
|
+
if middlewares.any?
|
74
|
+
chain = GraphQL::Schema::MiddlewareChain.new(
|
75
|
+
steps: middlewares + [FieldResolveStep],
|
76
|
+
arguments: resolve_arguments
|
77
|
+
)
|
78
|
+
chain.call
|
79
|
+
else
|
80
|
+
FieldResolveStep.call(*resolve_arguments)
|
81
|
+
end
|
82
|
+
rescue GraphQL::ExecutionError => err
|
83
|
+
err
|
84
|
+
end
|
85
|
+
ensure
|
86
|
+
# teardown
|
87
|
+
query_context.ast_node = nil
|
88
|
+
query_context.irep_node = nil
|
89
|
+
resolve_value
|
78
90
|
end
|
79
91
|
|
80
92
|
|
81
93
|
# A `.call`-able suitable to be the last step in a middleware chain
|
82
|
-
|
83
|
-
def initialize(irep_node)
|
84
|
-
@irep_node = irep_node
|
85
|
-
end
|
86
|
-
|
94
|
+
module FieldResolveStep
|
87
95
|
# Execute the field's resolve method
|
88
|
-
def call(_parent_type, parent_object, field_definition, field_args, context, _next = nil)
|
89
|
-
|
90
|
-
context.ast_node = @irep_node.ast_node
|
91
|
-
context.irep_node = @irep_node
|
92
|
-
|
93
|
-
# resolve
|
94
|
-
value = field_definition.resolve(parent_object, field_args, context)
|
95
|
-
|
96
|
-
# teardown
|
97
|
-
context.ast_node = nil
|
98
|
-
context.irep_node = nil
|
99
|
-
|
100
|
-
# return
|
101
|
-
value
|
96
|
+
def self.call(_parent_type, parent_object, field_definition, field_args, context, _next = nil)
|
97
|
+
field_definition.resolve(parent_object, field_args, context)
|
102
98
|
end
|
103
99
|
end
|
104
100
|
end
|
@@ -19,8 +19,11 @@ module GraphQL
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def result
|
22
|
-
|
23
|
-
|
22
|
+
if value.nil? || value.is_a?(GraphQL::ExecutionError)
|
23
|
+
nil
|
24
|
+
else
|
25
|
+
non_null_result
|
26
|
+
end
|
24
27
|
end
|
25
28
|
|
26
29
|
def non_null_result
|
@@ -2,9 +2,13 @@ module GraphQL
|
|
2
2
|
class Query
|
3
3
|
# Read-only access to query variables, applying default values if needed.
|
4
4
|
class Variables
|
5
|
+
# @return [Array<GraphQL::Query::VariableValidationError>] Any errors encountered when parsing the provided variables and literal values
|
6
|
+
attr_reader :errors
|
7
|
+
|
5
8
|
def initialize(schema, ast_variables, provided_variables)
|
6
9
|
@schema = schema
|
7
10
|
@provided_variables = provided_variables
|
11
|
+
@errors = []
|
8
12
|
@storage = ast_variables.each_with_object({}) do |ast_variable, memo|
|
9
13
|
variable_name = ast_variable.name
|
10
14
|
memo[variable_name] = get_graphql_value(ast_variable)
|
@@ -29,7 +33,7 @@ module GraphQL
|
|
29
33
|
|
30
34
|
validation_result = variable_type.validate_input(provided_value)
|
31
35
|
if !validation_result.valid?
|
32
|
-
|
36
|
+
@errors << GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, provided_value, validation_result)
|
33
37
|
elsif provided_value.nil?
|
34
38
|
GraphQL::Query::LiteralInput.coerce(variable_type, default_value, {})
|
35
39
|
else
|
@@ -11,8 +11,6 @@ module GraphQL
|
|
11
11
|
# - {#max_page_size} (the specified maximum page size that can be returned from a connection)
|
12
12
|
#
|
13
13
|
class BaseConnection
|
14
|
-
extend Gem::Deprecate
|
15
|
-
|
16
14
|
# Just to encode data in the cursor, use something that won't conflict
|
17
15
|
CURSOR_SEPARATOR = "---"
|
18
16
|
|
@@ -21,8 +19,6 @@ module GraphQL
|
|
21
19
|
CONNECTION_IMPLEMENTATIONS = {}
|
22
20
|
|
23
21
|
class << self
|
24
|
-
extend Gem::Deprecate
|
25
|
-
|
26
22
|
# Find a connection implementation suitable for exposing `nodes`
|
27
23
|
#
|
28
24
|
# @param [Object] A collection of nodes (eg, Array, AR::Relation)
|
@@ -30,13 +26,14 @@ module GraphQL
|
|
30
26
|
def connection_for_nodes(nodes)
|
31
27
|
# Check for class _names_ because classes can be redefined in Rails development
|
32
28
|
ancestor_names = nodes.class.ancestors.map(&:name)
|
33
|
-
|
34
|
-
|
29
|
+
implementation_class_name = ancestor_names.find do |ancestor_class_name|
|
30
|
+
CONNECTION_IMPLEMENTATIONS.include? ancestor_class_name
|
35
31
|
end
|
36
|
-
|
32
|
+
|
33
|
+
if implementation_class_name.nil?
|
37
34
|
raise("No connection implementation to wrap #{nodes.class} (#{nodes})")
|
38
35
|
else
|
39
|
-
|
36
|
+
CONNECTION_IMPLEMENTATIONS[implementation_class_name]
|
40
37
|
end
|
41
38
|
end
|
42
39
|
|
@@ -47,10 +44,6 @@ module GraphQL
|
|
47
44
|
def register_connection_implementation(nodes_class, connection_class)
|
48
45
|
CONNECTION_IMPLEMENTATIONS[nodes_class.name] = connection_class
|
49
46
|
end
|
50
|
-
|
51
|
-
# @deprecated use {#connection_for_nodes} instead
|
52
|
-
alias :connection_for_items :connection_for_nodes
|
53
|
-
deprecate(:connection_for_items, :connection_for_nodes, 2016, 9)
|
54
47
|
end
|
55
48
|
|
56
49
|
attr_reader :nodes, :arguments, :max_page_size, :parent, :field
|
@@ -69,10 +62,6 @@ module GraphQL
|
|
69
62
|
@parent = parent
|
70
63
|
end
|
71
64
|
|
72
|
-
# @deprecated use {#nodes} instead
|
73
|
-
alias :object :nodes
|
74
|
-
deprecate(:object, :nodes, 2016, 9)
|
75
|
-
|
76
65
|
# Provide easy access to provided arguments:
|
77
66
|
METHODS_FROM_ARGUMENTS = [:first, :after, :last, :before]
|
78
67
|
|
@@ -76,13 +76,6 @@ module GraphQL
|
|
76
76
|
|
77
77
|
def resolve=(new_resolve_proc)
|
78
78
|
ensure_defined
|
79
|
-
|
80
|
-
resolve_arity = get_arity(new_resolve_proc)
|
81
|
-
if resolve_arity == 2
|
82
|
-
warn("Mutation#resolve functions should be defined with three arguments: (root_obj, input, context). Two-argument mutation resolves are deprecated.")
|
83
|
-
new_resolve_proc = DeprecatedMutationResolve.new(new_resolve_proc)
|
84
|
-
end
|
85
|
-
|
86
79
|
@resolve_proc = MutationResolve.new(self, new_resolve_proc, wrap_result: has_generated_return_type?)
|
87
80
|
end
|
88
81
|
|
@@ -176,16 +169,6 @@ module GraphQL
|
|
176
169
|
end
|
177
170
|
end
|
178
171
|
|
179
|
-
class DeprecatedMutationResolve
|
180
|
-
def initialize(two_argument_resolve)
|
181
|
-
@two_argument_resolve = two_argument_resolve
|
182
|
-
end
|
183
|
-
|
184
|
-
def call(obj, args, ctx)
|
185
|
-
@two_argument_resolve.call(args[:input], ctx)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
172
|
class MutationResolve
|
190
173
|
def initialize(mutation, resolve, wrap_result:)
|
191
174
|
@mutation = mutation
|
data/lib/graphql/schema.rb
CHANGED
@@ -74,16 +74,8 @@ module GraphQL
|
|
74
74
|
# @param subscription [GraphQL::ObjectType] the subscription root for the schema
|
75
75
|
# @param max_depth [Integer] maximum query nesting (if it's greater, raise an error)
|
76
76
|
# @param types [Array<GraphQL::BaseType>] additional types to include in this schema
|
77
|
-
def initialize
|
78
|
-
|
79
|
-
warn("Schema.new is deprecated, use Schema.define instead")
|
80
|
-
end
|
81
|
-
@query = query
|
82
|
-
@mutation = mutation
|
83
|
-
@subscription = subscription
|
84
|
-
@max_depth = max_depth
|
85
|
-
@max_complexity = max_complexity
|
86
|
-
@orphan_types = types
|
77
|
+
def initialize
|
78
|
+
@orphan_types = []
|
87
79
|
@directives = DIRECTIVES.reduce({}) { |m, d| m[d.name] = d; m }
|
88
80
|
@static_validator = GraphQL::StaticValidation::Validator.new(schema: self)
|
89
81
|
@middleware = []
|
@@ -58,7 +58,7 @@ module GraphQL
|
|
58
58
|
name: type["name"],
|
59
59
|
description: type["description"],
|
60
60
|
values: type["enumValues"].map { |enum|
|
61
|
-
EnumType::EnumValue.
|
61
|
+
EnumType::EnumValue.define(
|
62
62
|
name: enum["name"],
|
63
63
|
description: enum["description"],
|
64
64
|
deprecation_reason: enum["deprecationReason"],
|
@@ -22,7 +22,7 @@ module GraphQL
|
|
22
22
|
|
23
23
|
context.visitor[GraphQL::Language::Nodes::Document].leave << ->(doc_node, parent) {
|
24
24
|
spreads_to_validate.each do |frag_spread|
|
25
|
-
fragment_child_name = context.fragments[frag_spread.node.name].type
|
25
|
+
fragment_child_name = context.fragments[frag_spread.node.name].type.name
|
26
26
|
fragment_child = context.schema.types.fetch(fragment_child_name, nil) # Might be non-existent type name
|
27
27
|
if fragment_child
|
28
28
|
validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path)
|
@@ -18,9 +18,10 @@ module GraphQL
|
|
18
18
|
|
19
19
|
def validate_type_exists(node, context)
|
20
20
|
return unless node.type
|
21
|
-
|
21
|
+
type_name = node.type.name
|
22
|
+
type = context.schema.types.fetch(type_name, nil)
|
22
23
|
if type.nil?
|
23
|
-
context.errors << message("No such type #{
|
24
|
+
context.errors << message("No such type #{type_name}, so it can't be a fragment condition", node, context: context)
|
24
25
|
GraphQL::Language::Visitor::SKIP
|
25
26
|
end
|
26
27
|
end
|
@@ -19,12 +19,16 @@ module GraphQL
|
|
19
19
|
private
|
20
20
|
|
21
21
|
def validate_type_is_composite(node, context)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
node_type = node.type
|
23
|
+
if node_type.nil?
|
24
|
+
# Inline fragment on the same type
|
25
|
+
else
|
26
|
+
type_name = node_type.to_query_string
|
27
|
+
type_def = context.schema.types.fetch(type_name, nil)
|
28
|
+
if type_def.nil? || !type_def.kind.composite?
|
29
|
+
context.errors << message("Invalid fragment on type #{type_name} (must be Union, Interface or Object)", node, context: context)
|
30
|
+
GraphQL::Language::Visitor::SKIP
|
31
|
+
end
|
28
32
|
end
|
29
33
|
end
|
30
34
|
end
|