graphql 0.19.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -0
  3. data/lib/graphql/define/assign_enum_value.rb +4 -3
  4. data/lib/graphql/enum_type.rb +3 -10
  5. data/lib/graphql/language/generation.rb +6 -2
  6. data/lib/graphql/language/parser.rb +270 -270
  7. data/lib/graphql/language/parser.y +2 -2
  8. data/lib/graphql/language/parser_tests.rb +2 -2
  9. data/lib/graphql/query.rb +33 -38
  10. data/lib/graphql/query/serial_execution/field_resolution.rb +29 -33
  11. data/lib/graphql/query/serial_execution/value_resolution.rb +5 -2
  12. data/lib/graphql/query/variables.rb +5 -1
  13. data/lib/graphql/relay/base_connection.rb +5 -16
  14. data/lib/graphql/relay/mutation.rb +0 -17
  15. data/lib/graphql/schema.rb +2 -10
  16. data/lib/graphql/schema/loader.rb +1 -1
  17. data/lib/graphql/schema/printer.rb +1 -1
  18. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +1 -1
  19. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +3 -2
  20. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +10 -6
  21. data/lib/graphql/static_validation/type_stack.rb +2 -2
  22. data/lib/graphql/static_validation/validator.rb +1 -1
  23. data/lib/graphql/unresolved_type_error.rb +0 -5
  24. data/lib/graphql/version.rb +1 -1
  25. data/readme.md +3 -79
  26. data/spec/graphql/language/parser_spec.rb +1 -1
  27. data/spec/graphql/query/serial_execution/value_resolution_spec.rb +4 -6
  28. data/spec/graphql/relay/base_connection_spec.rb +19 -0
  29. data/spec/graphql/relay/mutation_spec.rb +1 -1
  30. data/spec/graphql/relay/relation_connection_spec.rb +7 -0
  31. data/spec/graphql/schema/printer_spec.rb +6 -2
  32. data/spec/graphql/schema/timeout_middleware_spec.rb +3 -2
  33. data/spec/graphql/static_validation/validator_spec.rb +1 -1
  34. data/spec/spec_helper.rb +1 -3
  35. data/spec/support/static_validation_helpers.rb +1 -1
  36. 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 name directives_list_opt selection_set {
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 name_without_on directives_list_opt selection_set {
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
@@ -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: {}, validate: true, operation_name: nil, root_value: nil, max_depth: nil, max_complexity: nil)
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
- # Raises if a non-null variable isn't provided at runtime.
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 ||= GraphQL::Query::Variables.new(
100
- schema,
101
- selected_operation.variables,
102
- @provided_variables
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
- def selected_operation
140
- @selected_operation ||= find_operation(@operations, @operation_name)
141
- end
154
+ attr_reader :selected_operation
142
155
 
143
156
  def valid?
144
- if !@was_validated
157
+ @was_validated ||= begin
145
158
  @was_validated = true
146
- @valid = if @validate
147
- document_valid? && query_possible? && query_valid?
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 = validation_result[: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
- .flatten # accept n-dimensional array
186
- .select { |r| r.is_a?(GraphQL::AnalysisError) }
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
- field_resolve_step = FieldResolveStep.new(irep_node)
65
- resolve_arguments = [parent_type, target, field, arguments, execution_context.query.context]
66
- # only run a middleware chain if there are any middleware
67
- if middlewares.any?
68
- chain = GraphQL::Schema::MiddlewareChain.new(
69
- steps: middlewares + [field_resolve_step],
70
- arguments: resolve_arguments
71
- )
72
- chain.call
73
- else
74
- field_resolve_step.call(*resolve_arguments)
75
- end
76
- rescue GraphQL::ExecutionError => err
77
- err
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
- class FieldResolveStep
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
- # setup
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
- return nil if value.nil? || value.is_a?(GraphQL::ExecutionError)
23
- non_null_result
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
- raise GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, provided_value, validation_result)
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
- implementation = CONNECTION_IMPLEMENTATIONS.find do |nodes_class_name, connection_class|
34
- ancestor_names.include? nodes_class_name
29
+ implementation_class_name = ancestor_names.find do |ancestor_class_name|
30
+ CONNECTION_IMPLEMENTATIONS.include? ancestor_class_name
35
31
  end
36
- if implementation.nil?
32
+
33
+ if implementation_class_name.nil?
37
34
  raise("No connection implementation to wrap #{nodes.class} (#{nodes})")
38
35
  else
39
- implementation[1]
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
@@ -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(query: nil, mutation: nil, subscription: nil, max_depth: nil, max_complexity: nil, types: [])
78
- if query
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.new(
61
+ EnumType::EnumValue.define(
62
62
  name: enum["name"],
63
63
  description: enum["description"],
64
64
  deprecation_reason: enum["deprecationReason"],
@@ -116,7 +116,7 @@ module GraphQL
116
116
  "#{print_description(arg, " #{indentation}", i == 0)} #{indentation}"\
117
117
  "#{print_input_value(arg)}"
118
118
  }.join("\n")
119
- out << "\n)"
119
+ out << "\n#{indentation})"
120
120
  end
121
121
 
122
122
  def print_input_value(arg)
@@ -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
- type = context.schema.types.fetch(node.type, nil)
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 #{node.type}, so it can't be a fragment condition", node, context: context)
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
- type_name = node.type
23
- return unless type_name
24
- type_def = context.schema.types[type_name]
25
- if type_def.nil? || !type_def.kind.composite?
26
- context.errors << message("Invalid fragment on type #{type_name} (must be Union, Interface or Object)", node, context: context)
27
- GraphQL::Language::Visitor::SKIP
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