graphql 1.1.0 → 1.2.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 (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)