graphql 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyze_query.rb +4 -2
  3. data/lib/graphql/analysis/field_usage.rb +4 -4
  4. data/lib/graphql/analysis/query_complexity.rb +16 -21
  5. data/lib/graphql/argument.rb +13 -6
  6. data/lib/graphql/base_type.rb +2 -1
  7. data/lib/graphql/compatibility/execution_specification.rb +76 -0
  8. data/lib/graphql/compatibility/query_parser_specification.rb +16 -2
  9. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +0 -5
  10. data/lib/graphql/compatibility/schema_parser_specification.rb +6 -0
  11. data/lib/graphql/define/assign_argument.rb +8 -2
  12. data/lib/graphql/define/instance_definable.rb +12 -15
  13. data/lib/graphql/directive.rb +2 -1
  14. data/lib/graphql/enum_type.rb +5 -7
  15. data/lib/graphql/field.rb +6 -11
  16. data/lib/graphql/field/resolve.rb +1 -0
  17. data/lib/graphql/input_object_type.rb +9 -9
  18. data/lib/graphql/interface_type.rb +2 -1
  19. data/lib/graphql/internal_representation.rb +1 -0
  20. data/lib/graphql/internal_representation/node.rb +31 -9
  21. data/lib/graphql/internal_representation/rewrite.rb +26 -26
  22. data/lib/graphql/internal_representation/selections.rb +41 -0
  23. data/lib/graphql/introspection/input_value_type.rb +6 -2
  24. data/lib/graphql/language/generation.rb +2 -0
  25. data/lib/graphql/language/lexer.rl +4 -0
  26. data/lib/graphql/language/nodes.rb +3 -0
  27. data/lib/graphql/language/parser.rb +525 -509
  28. data/lib/graphql/language/parser.y +2 -0
  29. data/lib/graphql/object_type.rb +2 -2
  30. data/lib/graphql/query.rb +21 -0
  31. data/lib/graphql/query/context.rb +52 -4
  32. data/lib/graphql/query/serial_execution.rb +3 -4
  33. data/lib/graphql/query/serial_execution/field_resolution.rb +35 -36
  34. data/lib/graphql/query/serial_execution/operation_resolution.rb +9 -15
  35. data/lib/graphql/query/serial_execution/selection_resolution.rb +14 -11
  36. data/lib/graphql/query/serial_execution/value_resolution.rb +18 -17
  37. data/lib/graphql/query/variables.rb +1 -1
  38. data/lib/graphql/relay/mutation.rb +5 -8
  39. data/lib/graphql/scalar_type.rb +1 -2
  40. data/lib/graphql/schema.rb +2 -13
  41. data/lib/graphql/schema/build_from_definition.rb +28 -13
  42. data/lib/graphql/schema/loader.rb +4 -1
  43. data/lib/graphql/schema/printer.rb +10 -3
  44. data/lib/graphql/schema/timeout_middleware.rb +18 -2
  45. data/lib/graphql/schema/unique_within_type.rb +6 -3
  46. data/lib/graphql/static_validation/literal_validator.rb +3 -1
  47. data/lib/graphql/union_type.rb +1 -2
  48. data/lib/graphql/version.rb +1 -1
  49. data/readme.md +1 -0
  50. data/spec/graphql/analysis/analyze_query_spec.rb +6 -8
  51. data/spec/graphql/argument_spec.rb +18 -0
  52. data/spec/graphql/define/assign_argument_spec.rb +48 -0
  53. data/spec/graphql/define/instance_definable_spec.rb +4 -2
  54. data/spec/graphql/execution_error_spec.rb +66 -0
  55. data/spec/graphql/input_object_type_spec.rb +81 -0
  56. data/spec/graphql/internal_representation/rewrite_spec.rb +104 -21
  57. data/spec/graphql/introspection/input_value_type_spec.rb +43 -6
  58. data/spec/graphql/introspection/schema_type_spec.rb +1 -0
  59. data/spec/graphql/introspection/type_type_spec.rb +2 -0
  60. data/spec/graphql/language/generation_spec.rb +3 -2
  61. data/spec/graphql/query/arguments_spec.rb +17 -4
  62. data/spec/graphql/query/context_spec.rb +23 -0
  63. data/spec/graphql/query/variables_spec.rb +15 -1
  64. data/spec/graphql/relay/mutation_spec.rb +42 -2
  65. data/spec/graphql/schema/build_from_definition_spec.rb +4 -2
  66. data/spec/graphql/schema/loader_spec.rb +59 -1
  67. data/spec/graphql/schema/printer_spec.rb +2 -0
  68. data/spec/graphql/schema/reduce_types_spec.rb +1 -1
  69. data/spec/graphql/schema/timeout_middleware_spec.rb +2 -2
  70. data/spec/graphql/schema/unique_within_type_spec.rb +9 -0
  71. data/spec/graphql/schema/validation_spec.rb +15 -3
  72. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +122 -0
  73. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +78 -0
  74. data/spec/support/dairy_app.rb +9 -0
  75. data/spec/support/minimum_input_object.rb +4 -0
  76. data/spec/support/star_wars_schema.rb +1 -1
  77. metadata +5 -5
  78. data/lib/graphql/query/serial_execution/execution_context.rb +0 -37
  79. data/spec/graphql/query/serial_execution/execution_context_spec.rb +0 -54
@@ -172,11 +172,13 @@ rule
172
172
  | STRING { return val[0].to_s }
173
173
  | TRUE { return true }
174
174
  | FALSE { return false }
175
+ | null_value
175
176
  | variable
176
177
  | list_value
177
178
  | object_value
178
179
  | enum_value
179
180
 
181
+ null_value: NULL { return make_node(:NullValue, name: val[0], position_source: val[0]) }
180
182
  variable: VAR_SIGN name { return make_node(:VariableIdentifier, name: val[1], position_source: val[0]) }
181
183
 
182
184
  list_value:
@@ -23,7 +23,8 @@ module GraphQL
23
23
  class ObjectType < GraphQL::BaseType
24
24
  accepts_definitions :interfaces, :fields, :mutation, field: GraphQL::Define::AssignObjectField
25
25
 
26
- lazy_defined_attr_accessor :fields, :mutation
26
+ attr_accessor :fields, :mutation
27
+ ensure_defined(:fields, :mutation, :interfaces)
27
28
 
28
29
  # @!attribute fields
29
30
  # @return [Hash<String => GraphQL::Field>] Map String fieldnames to their {GraphQL::Field} implementations
@@ -45,7 +46,6 @@ module GraphQL
45
46
 
46
47
  def interfaces
47
48
  @clean_interfaces ||= begin
48
- ensure_defined
49
49
  if @dirty_interfaces.respond_to?(:map)
50
50
  @dirty_interfaces.map { |i_type| GraphQL::BaseType.resolve_related_type(i_type) }
51
51
  else
@@ -167,6 +167,27 @@ module GraphQL
167
167
  @valid
168
168
  end
169
169
 
170
+ def selections(nodes, type)
171
+ @selections ||= Hash.new { |h, k| h[k] = GraphQL::InternalRepresentation::Selections.build(self, k) }
172
+ @selections[nodes][type]
173
+ end
174
+
175
+ def get_type(type_name)
176
+ @warden.get_type(type_name)
177
+ end
178
+
179
+ def get_field(type, name)
180
+ @warden.get_field(type, name)
181
+ end
182
+
183
+ def possible_types(type)
184
+ @warden.possible_types(type)
185
+ end
186
+
187
+ def resolve_type(type)
188
+ @schema.resolve_type(type, @context)
189
+ end
190
+
170
191
  private
171
192
 
172
193
  # Assert that the passed-in query string is internally consistent
@@ -3,16 +3,23 @@ module GraphQL
3
3
  # Expose some query-specific info to field resolve functions.
4
4
  # It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`.
5
5
  class Context
6
- attr_accessor :execution_strategy
6
+ attr_reader :execution_strategy
7
7
 
8
- # @return [GraphQL::Language::Nodes::Field] The AST node for the currently-executing field
9
- def ast_node
10
- irep_node.ast_node
8
+ def execution_strategy=(new_strategy)
9
+ # GraphQL::Batch re-assigns this value but it was previously not used
10
+ # (ExecutionContext#strategy was used instead)
11
+ # now it _is_ used, but it breaks GraphQL::Batch tests
12
+ @execution_strategy ||= new_strategy
11
13
  end
12
14
 
13
15
  # @return [GraphQL::InternalRepresentation::Node] The internal representation for this query node
14
16
  attr_accessor :irep_node
15
17
 
18
+ # @return [GraphQL::Language::Nodes::Field] The AST node for the currently-executing field
19
+ def ast_node
20
+ @irep_node.ast_node
21
+ end
22
+
16
23
  # @return [Array<GraphQL::ExecutionError>] errors returned during execution
17
24
  attr_reader :errors
18
25
 
@@ -25,6 +32,9 @@ module GraphQL
25
32
  # @return [GraphQL::Schema::Mask::Warden]
26
33
  attr_reader :warden
27
34
 
35
+ # @return [Array<String, Integer>] The current position in the result
36
+ attr_reader :path
37
+
28
38
  # Make a new context which delegates key lookup to `values`
29
39
  # @param query [GraphQL::Query] the query who owns this context
30
40
  # @param values [Hash] A hash of arbitrary values which will be accessible at query-time
@@ -34,6 +44,7 @@ module GraphQL
34
44
  @values = values || {}
35
45
  @errors = []
36
46
  @warden = query.warden
47
+ @path = []
37
48
  end
38
49
 
39
50
  # Lookup `key` from the hash passed to {Schema#execute} as `context:`
@@ -45,6 +56,43 @@ module GraphQL
45
56
  def []=(key, value)
46
57
  @values[key] = value
47
58
  end
59
+
60
+ def spawn(path:, irep_node:)
61
+ FieldResolutionContext.new(context: self, path: path, irep_node: irep_node)
62
+ end
63
+
64
+ class FieldResolutionContext
65
+ extend Forwardable
66
+
67
+ attr_reader :path, :irep_node
68
+
69
+ def initialize(context:, path:, irep_node:)
70
+ @context = context
71
+ @path = path
72
+ @irep_node = irep_node
73
+ end
74
+
75
+ def_delegators :@context, :[], :[]=, :spawn, :query, :schema, :warden, :errors, :execution_strategy
76
+
77
+ # @return [GraphQL::Language::Nodes::Field] The AST node for the currently-executing field
78
+ def ast_node
79
+ @irep_node.ast_node
80
+ end
81
+
82
+ # Add error to current field resolution.
83
+ # @param error [GraphQL::ExecutionError] an execution error
84
+ # @return [void]
85
+ def add_error(error)
86
+ unless error.is_a?(ExecutionError)
87
+ raise TypeError, "expected error to be a ExecutionError, but was #{error.class}"
88
+ end
89
+
90
+ error.ast_node ||= irep_node.ast_node
91
+ error.path ||= path
92
+ errors << error
93
+ nil
94
+ end
95
+ end
48
96
  end
49
97
  end
50
98
  end
@@ -1,4 +1,3 @@
1
- require "graphql/query/serial_execution/execution_context"
2
1
  require "graphql/query/serial_execution/value_resolution"
3
2
  require "graphql/query/serial_execution/field_resolution"
4
3
  require "graphql/query/serial_execution/operation_resolution"
@@ -18,11 +17,11 @@ module GraphQL
18
17
  def execute(ast_operation, root_type, query_object)
19
18
  irep_root = query_object.internal_representation[ast_operation.name]
20
19
 
21
- operation_resolution.new(
20
+ operation_resolution.resolve(
22
21
  irep_root,
23
22
  root_type,
24
- ExecutionContext.new(query_object, self)
25
- ).result
23
+ query_object
24
+ )
26
25
  end
27
26
 
28
27
  def field_resolution
@@ -2,15 +2,17 @@ module GraphQL
2
2
  class Query
3
3
  class SerialExecution
4
4
  class FieldResolution
5
- attr_reader :irep_node, :parent_type, :target, :execution_context, :field, :arguments
5
+ attr_reader :irep_node, :parent_type, :target, :field, :arguments, :query
6
6
 
7
- def initialize(irep_node, parent_type, target, execution_context)
8
- @irep_node = irep_node
7
+ def initialize(irep_nodes, parent_type, target, query_ctx)
8
+ @irep_node = irep_nodes.first
9
+ @irep_nodes = irep_nodes
9
10
  @parent_type = parent_type
10
11
  @target = target
11
- @execution_context = execution_context
12
- @field = execution_context.get_field(parent_type, irep_node)
13
- @arguments = execution_context.query.arguments_for(irep_node, @field)
12
+ @field_ctx = query_ctx.spawn(path: query_ctx.path + [irep_node.name], irep_node: irep_node)
13
+ @query = query_ctx.query
14
+ @field = @query.get_field(parent_type, irep_node.definition_name)
15
+ @arguments = @query.arguments_for(irep_node, @field)
14
16
  end
15
17
 
16
18
  def result
@@ -19,6 +21,11 @@ module GraphQL
19
21
  { result_name => get_finished_value(raw_value) }
20
22
  end
21
23
 
24
+ # GraphQL::Batch depends on this
25
+ def execution_context
26
+ @field_ctx
27
+ end
28
+
22
29
  private
23
30
 
24
31
  # After getting the value from the field's resolve method,
@@ -27,15 +34,15 @@ module GraphQL
27
34
  case raw_value
28
35
  when GraphQL::ExecutionError
29
36
  raw_value.ast_node = irep_node.ast_node
30
- raw_value.path = irep_node.path
31
- execution_context.add_error(raw_value)
37
+ raw_value.path = @field_ctx.path
38
+ @query.context.errors.push(raw_value)
32
39
  when Array
33
40
  list_errors = raw_value.each_with_index.select { |value, _| value.is_a?(GraphQL::ExecutionError) }
34
41
  if list_errors.any?
35
42
  list_errors.each do |error, index|
36
43
  error.ast_node = irep_node.ast_node
37
- error.path = irep_node.path + [index]
38
- execution_context.add_error(error)
44
+ error.path = @field_ctx.path + [index]
45
+ @query.context.errors.push(error)
39
46
  end
40
47
  end
41
48
  end
@@ -46,14 +53,14 @@ module GraphQL
46
53
  field,
47
54
  field.type,
48
55
  raw_value,
49
- irep_node,
50
- execution_context,
56
+ @irep_nodes,
57
+ @field_ctx,
51
58
  )
52
59
  rescue GraphQL::InvalidNullError => err
53
60
  if field.type.kind.non_null?
54
61
  raise(err)
55
62
  else
56
- err.parent_error? || execution_context.add_error(err)
63
+ err.parent_error? || @query.context.errors.push(err)
57
64
  nil
58
65
  end
59
66
  end
@@ -65,34 +72,26 @@ module GraphQL
65
72
  # If the middleware chain returns a GraphQL::ExecutionError, its message
66
73
  # is added to the "errors" key.
67
74
  def get_raw_value
68
- middlewares = execution_context.query.schema.middleware
69
- query_context = execution_context.query.context
70
- # setup
71
- query_context.irep_node = @irep_node
75
+ middlewares = @query.schema.middleware
72
76
 
73
- resolve_arguments = [parent_type, target, field, arguments, query_context]
77
+ resolve_arguments = [parent_type, target, field, arguments, @field_ctx]
74
78
 
75
- resolve_value = begin
76
- # only run a middleware chain if there are any middleware
77
- if middlewares.any?
78
- chain = GraphQL::Schema::MiddlewareChain.new(
79
- steps: middlewares + [FieldResolveStep],
80
- arguments: resolve_arguments
81
- )
82
- chain.call
83
- else
84
- FieldResolveStep.call(*resolve_arguments)
85
- end
86
- rescue GraphQL::ExecutionError => err
87
- err
79
+ begin
80
+ # only run a middleware chain if there are any middleware
81
+ if middlewares.any?
82
+ chain = GraphQL::Schema::MiddlewareChain.new(
83
+ steps: middlewares + [FieldResolveStep],
84
+ arguments: resolve_arguments
85
+ )
86
+ chain.call
87
+ else
88
+ FieldResolveStep.call(*resolve_arguments)
88
89
  end
89
- ensure
90
- # teardown
91
- query_context.irep_node = nil
92
- resolve_value
90
+ rescue GraphQL::ExecutionError => err
91
+ err
92
+ end
93
93
  end
94
94
 
95
-
96
95
  # A `.call`-able suitable to be the last step in a middleware chain
97
96
  module FieldResolveStep
98
97
  # Execute the field's resolve method
@@ -1,24 +1,18 @@
1
1
  module GraphQL
2
2
  class Query
3
3
  class SerialExecution
4
- class OperationResolution
5
- attr_reader :target, :execution_context, :irep_node
6
-
7
- def initialize(irep_node, target, execution_context)
8
- @target = target
9
- @irep_node = irep_node
10
- @execution_context = execution_context
11
- end
12
-
13
- def result
14
- execution_context.strategy.selection_resolution.resolve(
15
- execution_context.query.root_value,
4
+ module OperationResolution
5
+ def self.resolve(irep_node, target, query)
6
+ result = query.context.execution_strategy.selection_resolution.resolve(
7
+ query.root_value,
16
8
  target,
17
- irep_node,
18
- execution_context
9
+ [irep_node],
10
+ query.context,
19
11
  )
12
+
13
+ result
20
14
  rescue GraphQL::InvalidNullError => err
21
- err.parent_error? || execution_context.add_error(err)
15
+ err.parent_error? || query.context.errors.push(err)
22
16
  nil
23
17
  end
24
18
  end
@@ -2,18 +2,21 @@ module GraphQL
2
2
  class Query
3
3
  class SerialExecution
4
4
  module SelectionResolution
5
- def self.resolve(target, current_type, irep_node, execution_context)
6
- irep_node.children.each_with_object({}) do |(name, irep_node), memo|
7
- if irep_node.included? && irep_node.definitions.any? { |potential_type, field_defn| GraphQL::Execution::Typecast.compatible?(current_type, potential_type, execution_context.query.context) }
8
- field_result = execution_context.strategy.field_resolution.new(
9
- irep_node,
10
- current_type,
11
- target,
12
- execution_context
13
- ).result
14
- memo.merge!(field_result)
15
- end
5
+ def self.resolve(target, current_type, irep_nodes, query_ctx)
6
+ own_selections = query_ctx.query.selections(irep_nodes, current_type)
7
+
8
+ selection_result = {}
9
+
10
+ own_selections.each do |name, child_irep_nodes|
11
+ selection_result.merge!(query_ctx.execution_strategy.field_resolution.new(
12
+ child_irep_nodes,
13
+ current_type,
14
+ target,
15
+ query_ctx
16
+ ).result)
16
17
  end
18
+
19
+ selection_result
17
20
  end
18
21
  end
19
22
  end
@@ -2,7 +2,7 @@ module GraphQL
2
2
  class Query
3
3
  class SerialExecution
4
4
  module ValueResolution
5
- def self.resolve(parent_type, field_defn, field_type, value, irep_node, execution_context)
5
+ def self.resolve(parent_type, field_defn, field_type, value, irep_nodes, query_ctx)
6
6
  if value.nil? || value.is_a?(GraphQL::ExecutionError)
7
7
  if field_type.kind.non_null?
8
8
  raise GraphQL::InvalidNullError.new(parent_type.name, field_defn.name, value)
@@ -14,21 +14,21 @@ module GraphQL
14
14
  when GraphQL::TypeKinds::SCALAR
15
15
  field_type.coerce_result(value)
16
16
  when GraphQL::TypeKinds::ENUM
17
- field_type.coerce_result(value, execution_context.query.warden)
17
+ field_type.coerce_result(value, query_ctx.query.warden)
18
18
  when GraphQL::TypeKinds::LIST
19
19
  wrapped_type = field_type.of_type
20
20
  result = value.each_with_index.map do |inner_value, index|
21
- irep_node.index = index
22
- resolve(
21
+ inner_ctx = query_ctx.spawn(path: query_ctx.path + [index], irep_node: query_ctx.irep_node)
22
+ inner_result = resolve(
23
23
  parent_type,
24
24
  field_defn,
25
25
  wrapped_type,
26
26
  inner_value,
27
- irep_node,
28
- execution_context,
27
+ irep_nodes,
28
+ inner_ctx,
29
29
  )
30
+ inner_result
30
31
  end
31
- irep_node.index = nil
32
32
  result
33
33
  when GraphQL::TypeKinds::NON_NULL
34
34
  wrapped_type = field_type.of_type
@@ -37,30 +37,31 @@ module GraphQL
37
37
  field_defn,
38
38
  wrapped_type,
39
39
  value,
40
- irep_node,
41
- execution_context,
40
+ irep_nodes,
41
+ query_ctx,
42
42
  )
43
43
  when GraphQL::TypeKinds::OBJECT
44
- execution_context.strategy.selection_resolution.resolve(
44
+ query_ctx.execution_strategy.selection_resolution.resolve(
45
45
  value,
46
46
  field_type,
47
- irep_node,
48
- execution_context
47
+ irep_nodes,
48
+ query_ctx
49
49
  )
50
50
  when GraphQL::TypeKinds::UNION, GraphQL::TypeKinds::INTERFACE
51
- resolved_type = execution_context.schema.resolve_type(value, execution_context.query.context)
52
- possible_types = execution_context.possible_types(field_type)
51
+ query = query_ctx.query
52
+ resolved_type = query.resolve_type(value)
53
+ possible_types = query.possible_types(field_type)
53
54
 
54
55
  if !possible_types.include?(resolved_type)
55
- raise GraphQL::UnresolvedTypeError.new(irep_node.definition_name, field_type, parent_type, resolved_type, possible_types)
56
+ raise GraphQL::UnresolvedTypeError.new(irep_nodes.first.definition_name, field_type, parent_type, resolved_type, possible_types)
56
57
  else
57
58
  resolve(
58
59
  parent_type,
59
60
  field_defn,
60
61
  resolved_type,
61
62
  value,
62
- irep_node,
63
- execution_context,
63
+ irep_nodes,
64
+ query_ctx,
64
65
  )
65
66
  end
66
67
  else
@@ -39,7 +39,7 @@ module GraphQL
39
39
  validation_result = variable_type.validate_input(provided_value, @warden)
40
40
  if !validation_result.valid?
41
41
  @errors << GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, provided_value, validation_result)
42
- elsif provided_value.nil?
42
+ elsif !@provided_variables.key?(variable_name) && provided_value.nil?
43
43
  GraphQL::Query::LiteralInput.coerce(variable_type, default_value, {})
44
44
  else
45
45
  variable_type.coerce_input(provided_value)