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.
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