graphql 0.19.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.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
|