graphql 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +10 -0
  3. data/lib/graphql/base_type.rb +8 -5
  4. data/lib/graphql/compatibility.rb +3 -0
  5. data/lib/graphql/compatibility/execution_specification.rb +414 -0
  6. data/lib/graphql/compatibility/query_parser_specification.rb +117 -0
  7. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +81 -0
  8. data/lib/graphql/compatibility/query_parser_specification/query_assertions.rb +78 -0
  9. data/lib/graphql/compatibility/schema_parser_specification.rb +239 -0
  10. data/lib/graphql/define/instance_definable.rb +53 -21
  11. data/lib/graphql/directive.rb +1 -1
  12. data/lib/graphql/enum_type.rb +31 -8
  13. data/lib/graphql/execution/directive_checks.rb +0 -6
  14. data/lib/graphql/input_object_type.rb +6 -4
  15. data/lib/graphql/introspection/arguments_field.rb +3 -1
  16. data/lib/graphql/introspection/enum_values_field.rb +10 -5
  17. data/lib/graphql/introspection/fields_field.rb +1 -1
  18. data/lib/graphql/introspection/input_fields_field.rb +2 -2
  19. data/lib/graphql/introspection/interfaces_field.rb +7 -1
  20. data/lib/graphql/introspection/possible_types_field.rb +1 -1
  21. data/lib/graphql/introspection/schema_type.rb +1 -1
  22. data/lib/graphql/introspection/type_by_name_field.rb +4 -2
  23. data/lib/graphql/introspection/type_type.rb +7 -6
  24. data/lib/graphql/language/lexer.rl +0 -4
  25. data/lib/graphql/language/parser.rb +1 -1
  26. data/lib/graphql/language/parser.y +1 -1
  27. data/lib/graphql/list_type.rb +3 -4
  28. data/lib/graphql/non_null_type.rb +4 -8
  29. data/lib/graphql/object_type.rb +5 -3
  30. data/lib/graphql/query.rb +48 -12
  31. data/lib/graphql/query/context.rb +7 -1
  32. data/lib/graphql/query/serial_execution/execution_context.rb +8 -3
  33. data/lib/graphql/query/serial_execution/field_resolution.rb +8 -5
  34. data/lib/graphql/query/serial_execution/operation_resolution.rb +2 -2
  35. data/lib/graphql/query/serial_execution/selection_resolution.rb +4 -21
  36. data/lib/graphql/query/serial_execution/value_resolution.rb +59 -99
  37. data/lib/graphql/query/variables.rb +7 -2
  38. data/lib/graphql/scalar_type.rb +1 -1
  39. data/lib/graphql/schema.rb +49 -18
  40. data/lib/graphql/schema/build_from_definition.rb +248 -0
  41. data/lib/graphql/schema/instrumented_field_map.rb +23 -0
  42. data/lib/graphql/schema/loader.rb +4 -11
  43. data/lib/graphql/schema/possible_types.rb +4 -2
  44. data/lib/graphql/schema/printer.rb +1 -1
  45. data/lib/graphql/schema/type_expression.rb +4 -4
  46. data/lib/graphql/schema/type_map.rb +1 -1
  47. data/lib/graphql/schema/validation.rb +4 -0
  48. data/lib/graphql/schema/warden.rb +114 -0
  49. data/lib/graphql/static_validation/literal_validator.rb +10 -7
  50. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -3
  51. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  52. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
  53. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -14
  54. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  55. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  56. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +3 -4
  57. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +2 -1
  58. data/lib/graphql/static_validation/validation_context.rb +7 -1
  59. data/lib/graphql/union_type.rb +6 -3
  60. data/lib/graphql/unresolved_type_error.rb +1 -2
  61. data/lib/graphql/version.rb +1 -1
  62. data/readme.md +1 -5
  63. data/spec/graphql/compatibility/execution_specification_spec.rb +3 -0
  64. data/spec/graphql/compatibility/query_parser_specification_spec.rb +5 -0
  65. data/spec/graphql/compatibility/schema_parser_specification_spec.rb +5 -0
  66. data/spec/graphql/define/instance_definable_spec.rb +20 -0
  67. data/spec/graphql/directive_spec.rb +11 -0
  68. data/spec/graphql/enum_type_spec.rb +20 -1
  69. data/spec/graphql/input_object_type_spec.rb +9 -9
  70. data/spec/graphql/introspection/directive_type_spec.rb +4 -4
  71. data/spec/graphql/introspection/input_value_type_spec.rb +6 -6
  72. data/spec/graphql/introspection/type_type_spec.rb +28 -26
  73. data/spec/graphql/language/parser_spec.rb +27 -17
  74. data/spec/graphql/list_type_spec.rb +2 -2
  75. data/spec/graphql/query/variables_spec.rb +1 -0
  76. data/spec/graphql/scalar_type_spec.rb +3 -3
  77. data/spec/graphql/schema/build_from_definition_spec.rb +693 -0
  78. data/spec/graphql/schema/type_expression_spec.rb +3 -3
  79. data/spec/graphql/schema/validation_spec.rb +7 -3
  80. data/spec/graphql/schema/warden_spec.rb +510 -0
  81. data/spec/graphql/schema_spec.rb +129 -0
  82. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +1 -1
  83. data/spec/graphql/static_validation/type_stack_spec.rb +3 -3
  84. data/spec/spec_helper.rb +27 -1
  85. data/spec/support/dairy_app.rb +8 -5
  86. metadata +21 -3
  87. data/lib/graphql/language/parser_tests.rb +0 -809
@@ -46,9 +46,11 @@ module GraphQL
46
46
  def interfaces
47
47
  @clean_interfaces ||= begin
48
48
  ensure_defined
49
- @dirty_interfaces.map { |i_type| GraphQL::BaseType.resolve_related_type(i_type) }
50
- rescue
51
- @dirty_interfaces
49
+ if @dirty_interfaces.respond_to?(:map)
50
+ @dirty_interfaces.map { |i_type| GraphQL::BaseType.resolve_related_type(i_type) }
51
+ else
52
+ @dirty_interfaces
53
+ end
52
54
  end
53
55
  end
54
56
 
@@ -17,7 +17,15 @@ module GraphQL
17
17
  end
18
18
  end
19
19
 
20
- attr_reader :schema, :document, :context, :fragments, :operations, :root_value, :max_depth, :query_string
20
+ module NullExcept
21
+ module_function
22
+
23
+ def call(member)
24
+ false
25
+ end
26
+ end
27
+
28
+ attr_reader :schema, :document, :context, :fragments, :operations, :root_value, :max_depth, :query_string, :warden
21
29
 
22
30
  # Prepare query `query_string` on `schema`
23
31
  # @param schema [GraphQL::Schema]
@@ -28,10 +36,12 @@ module GraphQL
28
36
  # @param root_value [Object] the object used to resolve fields on the root type
29
37
  # @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value)
30
38
  # @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
31
- def initialize(schema, query_string = nil, document: nil, context: nil, variables: {}, operation_name: nil, root_value: nil, max_depth: nil, max_complexity: nil)
39
+ # @param except [<#call(schema_member)>] If provided, objects will be hidden from the schema when `.call(schema_member)` returns truthy
40
+ 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, except: NullExcept)
32
41
  fail ArgumentError, "a query string or document is required" unless query_string || document
33
42
 
34
43
  @schema = schema
44
+ @warden = GraphQL::Schema::Warden.new(schema, except)
35
45
  @max_depth = max_depth || schema.max_depth
36
46
  @max_complexity = max_complexity || schema.max_complexity
37
47
  @query_analyzers = schema.query_analyzers.dup
@@ -81,16 +91,9 @@ module GraphQL
81
91
  # Get the result for this query, executing it once
82
92
  def result
83
93
  @result ||= begin
84
- if !valid?
85
- all_errors = validation_errors + analysis_errors
86
- if all_errors.any?
87
- { "errors" => all_errors.map(&:to_h) }
88
- else
89
- nil
90
- end
91
- else
92
- Executor.new(self).result
93
- end
94
+ instrumenters = @schema.instrumenters[:query]
95
+ execution_call = ExecutionCall.new(self, instrumenters)
96
+ execution_call.call
94
97
  end
95
98
  end
96
99
 
@@ -110,6 +113,7 @@ module GraphQL
110
113
  @variables ||= begin
111
114
  vars = GraphQL::Query::Variables.new(
112
115
  @schema,
116
+ @warden,
113
117
  @ast_variables,
114
118
  @provided_variables,
115
119
  )
@@ -198,5 +202,37 @@ module GraphQL
198
202
  operations[operation_name]
199
203
  end
200
204
  end
205
+
206
+ class ExecutionCall
207
+ def initialize(query, instrumenters)
208
+ @query = query
209
+ @instrumenters = instrumenters
210
+ end
211
+
212
+ # Check if the query is valid, and if it is,
213
+ # execute it, calling instrumenters along the way
214
+ # @return [Hash] The GraphQL response
215
+ def call
216
+ @instrumenters.each { |i| i.before_query(@query) }
217
+ result = get_result
218
+ @instrumenters.each { |i| i.after_query(@query) }
219
+ result
220
+ end
221
+
222
+ private
223
+
224
+ def get_result
225
+ if !@query.valid?
226
+ all_errors = @query.validation_errors + @query.analysis_errors
227
+ if all_errors.any?
228
+ { "errors" => all_errors.map(&:to_h) }
229
+ else
230
+ nil
231
+ end
232
+ else
233
+ Executor.new(@query).result
234
+ end
235
+ end
236
+ end
201
237
  end
202
238
  end
@@ -6,7 +6,9 @@ module GraphQL
6
6
  attr_accessor :execution_strategy
7
7
 
8
8
  # @return [GraphQL::Language::Nodes::Field] The AST node for the currently-executing field
9
- attr_accessor :ast_node
9
+ def ast_node
10
+ irep_node.ast_node
11
+ end
10
12
 
11
13
  # @return [GraphQL::InternalRepresentation::Node] The internal representation for this query node
12
14
  attr_accessor :irep_node
@@ -20,6 +22,9 @@ module GraphQL
20
22
  # @return [GraphQL::Schema]
21
23
  attr_reader :schema
22
24
 
25
+ # @return [GraphQL::Schema::Mask::Warden]
26
+ attr_reader :warden
27
+
23
28
  # Make a new context which delegates key lookup to `values`
24
29
  # @param query [GraphQL::Query] the query who owns this context
25
30
  # @param values [Hash] A hash of arbitrary values which will be accessible at query-time
@@ -28,6 +33,7 @@ module GraphQL
28
33
  @schema = query.schema
29
34
  @values = values || {}
30
35
  @errors = []
36
+ @warden = query.warden
31
37
  end
32
38
 
33
39
  # Lookup `key` from the hash passed to {Schema#execute} as `context:`
@@ -8,10 +8,11 @@ module GraphQL
8
8
  @query = query
9
9
  @schema = query.schema
10
10
  @strategy = strategy
11
+ @warden = query.warden
11
12
  end
12
13
 
13
- def get_type(type)
14
- @schema.types[type]
14
+ def get_type(type_name)
15
+ @warden.get_type(type_name)
15
16
  end
16
17
 
17
18
  def get_fragment(name)
@@ -20,7 +21,11 @@ module GraphQL
20
21
 
21
22
  def get_field(type, irep_node)
22
23
  # fall back for dynamic fields (eg __typename)
23
- irep_node.definitions[type] || @schema.get_field(type, irep_node.definition_name) || raise("No field found on #{type.name} for '#{irep_node.definition_name}' (#{irep_node.ast_node.name})")
24
+ irep_node.definitions[type] || @warden.get_field(type, irep_node.definition_name) || raise("No field found on #{type.name} for '#{irep_node.definition_name}' (#{irep_node.ast_node.name})")
25
+ end
26
+
27
+ def possible_types(type)
28
+ @warden.possible_types(type)
24
29
  end
25
30
 
26
31
  def add_error(err)
@@ -40,10 +40,15 @@ module GraphQL
40
40
  end
41
41
  end
42
42
 
43
- strategy_class = GraphQL::Query::SerialExecution::ValueResolution.get_strategy_for_kind(field.type.kind)
44
- result_strategy = strategy_class.new(raw_value, field.type, target, parent_type, irep_node, execution_context)
45
43
  begin
46
- result_strategy.result
44
+ GraphQL::Query::SerialExecution::ValueResolution.resolve(
45
+ parent_type,
46
+ field,
47
+ field.type,
48
+ raw_value,
49
+ irep_node,
50
+ execution_context,
51
+ )
47
52
  rescue GraphQL::InvalidNullError => err
48
53
  if field.type.kind.non_null?
49
54
  raise(err)
@@ -63,7 +68,6 @@ module GraphQL
63
68
  middlewares = execution_context.query.schema.middleware
64
69
  query_context = execution_context.query.context
65
70
  # setup
66
- query_context.ast_node = @irep_node.ast_node
67
71
  query_context.irep_node = @irep_node
68
72
 
69
73
  resolve_arguments = [parent_type, target, field, arguments, query_context]
@@ -84,7 +88,6 @@ module GraphQL
84
88
  end
85
89
  ensure
86
90
  # teardown
87
- query_context.ast_node = nil
88
91
  query_context.irep_node = nil
89
92
  resolve_value
90
93
  end
@@ -11,12 +11,12 @@ module GraphQL
11
11
  end
12
12
 
13
13
  def result
14
- execution_context.strategy.selection_resolution.new(
14
+ execution_context.strategy.selection_resolution.resolve(
15
15
  execution_context.query.root_value,
16
16
  target,
17
17
  irep_node,
18
18
  execution_context
19
- ).result
19
+ )
20
20
  rescue GraphQL::InvalidNullError => err
21
21
  err.parent_error? || execution_context.add_error(err)
22
22
  nil
@@ -1,22 +1,13 @@
1
1
  module GraphQL
2
2
  class Query
3
3
  class SerialExecution
4
- class SelectionResolution
5
- attr_reader :target, :type, :irep_node, :execution_context
6
-
7
- def initialize(target, type, irep_node, execution_context)
8
- @target = target
9
- @type = type
10
- @irep_node = irep_node
11
- @execution_context = execution_context
12
- end
13
-
14
- def result
4
+ module SelectionResolution
5
+ def self.resolve(target, current_type, irep_node, execution_context)
15
6
  irep_node.children.each_with_object({}) do |(name, irep_node), memo|
16
- if irep_node.included? && applies_to_type?(irep_node, type)
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) }
17
8
  field_result = execution_context.strategy.field_resolution.new(
18
9
  irep_node,
19
- type,
10
+ current_type,
20
11
  target,
21
12
  execution_context
22
13
  ).result
@@ -24,14 +15,6 @@ module GraphQL
24
15
  end
25
16
  end
26
17
  end
27
-
28
- private
29
-
30
- def applies_to_type?(irep_node, current_type)
31
- irep_node.definitions.any? { |potential_type, field_defn|
32
- GraphQL::Execution::Typecast.compatible?(current_type, potential_type, execution_context.query.context)
33
- }
34
- end
35
18
  end
36
19
  end
37
20
  end
@@ -2,112 +2,72 @@ module GraphQL
2
2
  class Query
3
3
  class SerialExecution
4
4
  module ValueResolution
5
- def self.get_strategy_for_kind(kind)
6
- TYPE_KIND_STRATEGIES[kind] || raise("No value resolution strategy for #{kind}!")
7
- end
8
-
9
- class BaseResolution
10
- attr_reader :value, :field_type, :target, :parent_type,
11
- :irep_node, :execution_context
12
- def initialize(value, field_type, target, parent_type, irep_node, execution_context)
13
- @value = value
14
- @field_type = field_type
15
- @target = target
16
- @parent_type = parent_type
17
- @irep_node = irep_node
18
- @execution_context = execution_context
19
- end
20
-
21
- def result
22
- if value.nil? || value.is_a?(GraphQL::ExecutionError)
23
- nil
5
+ def self.resolve(parent_type, field_defn, field_type, value, irep_node, execution_context)
6
+ if value.nil? || value.is_a?(GraphQL::ExecutionError)
7
+ if field_type.kind.non_null?
8
+ raise GraphQL::InvalidNullError.new(parent_type.name, field_defn.name, value)
24
9
  else
25
- non_null_result
26
- end
27
- end
28
-
29
- def non_null_result
30
- raise NotImplementedError, "Should return a value based on initialization params"
31
- end
32
-
33
- def get_strategy_for_kind(*args)
34
- GraphQL::Query::SerialExecution::ValueResolution.get_strategy_for_kind(*args)
35
- end
36
- end
37
-
38
- class ScalarResolution < BaseResolution
39
- # Apply the scalar's defined `coerce_result` method to the value
40
- def non_null_result
41
- field_type.coerce_result(value)
42
- end
43
- end
44
-
45
- class ListResolution < BaseResolution
46
- # For each item in the list,
47
- # Resolve it with the "wrapped" type of this list
48
- def non_null_result
49
- wrapped_type = field_type.of_type
50
- strategy_class = get_strategy_for_kind(wrapped_type.kind)
51
- result = value.each_with_index.map do |item, index|
52
- irep_node.index = index
53
- inner_strategy = strategy_class.new(item, wrapped_type, target, parent_type, irep_node, execution_context)
54
- inner_strategy.result
55
- end
56
- irep_node.index = nil
57
- result
58
- end
59
- end
60
-
61
- class HasPossibleTypeResolution < BaseResolution
62
- def non_null_result
63
- resolved_type = execution_context.schema.resolve_type(value, execution_context.query.context)
64
- possible_types = execution_context.schema.possible_types(field_type)
65
-
66
- unless resolved_type.is_a?(GraphQL::ObjectType) && possible_types.include?(resolved_type)
67
- raise GraphQL::UnresolvedTypeError.new(irep_node.definition_name, execution_context.schema, field_type, parent_type, resolved_type)
10
+ nil
68
11
  end
12
+ else
13
+ case field_type.kind
14
+ when GraphQL::TypeKinds::SCALAR
15
+ field_type.coerce_result(value)
16
+ when GraphQL::TypeKinds::ENUM
17
+ field_type.coerce_result(value, execution_context.query.warden)
18
+ when GraphQL::TypeKinds::LIST
19
+ wrapped_type = field_type.of_type
20
+ result = value.each_with_index.map do |inner_value, index|
21
+ irep_node.index = index
22
+ resolve(
23
+ parent_type,
24
+ field_defn,
25
+ wrapped_type,
26
+ inner_value,
27
+ irep_node,
28
+ execution_context,
29
+ )
30
+ end
31
+ irep_node.index = nil
32
+ result
33
+ when GraphQL::TypeKinds::NON_NULL
34
+ wrapped_type = field_type.of_type
35
+ resolve(
36
+ parent_type,
37
+ field_defn,
38
+ wrapped_type,
39
+ value,
40
+ irep_node,
41
+ execution_context,
42
+ )
43
+ when GraphQL::TypeKinds::OBJECT
44
+ execution_context.strategy.selection_resolution.resolve(
45
+ value,
46
+ field_type,
47
+ irep_node,
48
+ execution_context
49
+ )
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)
69
53
 
70
- strategy_class = get_strategy_for_kind(resolved_type.kind)
71
- inner_strategy = strategy_class.new(value, resolved_type, target, parent_type, irep_node, execution_context)
72
- inner_strategy.result
73
- end
74
- end
75
-
76
- class ObjectResolution < BaseResolution
77
- # Resolve the selections on this object
78
- def non_null_result
79
- execution_context.strategy.selection_resolution.new(
80
- value,
81
- field_type,
82
- irep_node,
83
- execution_context
84
- ).result
85
- end
86
- end
87
-
88
- class NonNullResolution < BaseResolution
89
- # Get the "wrapped" type and resolve the value according to that type
90
- def result
91
- if value.nil? || value.is_a?(GraphQL::ExecutionError)
92
- raise GraphQL::InvalidNullError.new(parent_type.name, irep_node.definition_name, value)
54
+ if !possible_types.include?(resolved_type)
55
+ raise GraphQL::UnresolvedTypeError.new(irep_node.definition_name, field_type, parent_type, resolved_type, possible_types)
56
+ else
57
+ resolve(
58
+ parent_type,
59
+ field_defn,
60
+ resolved_type,
61
+ value,
62
+ irep_node,
63
+ execution_context,
64
+ )
65
+ end
93
66
  else
94
- wrapped_type = field_type.of_type
95
- strategy_class = get_strategy_for_kind(wrapped_type.kind)
96
- inner_strategy = strategy_class.new(value, wrapped_type, target, parent_type, irep_node, execution_context)
97
- inner_strategy.result
67
+ raise("Unknown type kind: #{field_type.kind}")
98
68
  end
99
69
  end
100
70
  end
101
-
102
- TYPE_KIND_STRATEGIES = {
103
- GraphQL::TypeKinds::SCALAR => ScalarResolution,
104
- GraphQL::TypeKinds::LIST => ListResolution,
105
- GraphQL::TypeKinds::OBJECT => ObjectResolution,
106
- GraphQL::TypeKinds::ENUM => ScalarResolution,
107
- GraphQL::TypeKinds::NON_NULL => NonNullResolution,
108
- GraphQL::TypeKinds::INTERFACE => HasPossibleTypeResolution,
109
- GraphQL::TypeKinds::UNION => HasPossibleTypeResolution,
110
- }
111
71
  end
112
72
  end
113
73
  end
@@ -2,11 +2,14 @@ module GraphQL
2
2
  class Query
3
3
  # Read-only access to query variables, applying default values if needed.
4
4
  class Variables
5
+ extend Forwardable
6
+
5
7
  # @return [Array<GraphQL::Query::VariableValidationError>] Any errors encountered when parsing the provided variables and literal values
6
8
  attr_reader :errors
7
9
 
8
- def initialize(schema, ast_variables, provided_variables)
10
+ def initialize(schema, warden, ast_variables, provided_variables)
9
11
  @schema = schema
12
+ @warden = warden
10
13
  @provided_variables = provided_variables
11
14
  @errors = []
12
15
  @storage = ast_variables.each_with_object({}) do |ast_variable, memo|
@@ -19,6 +22,8 @@ module GraphQL
19
22
  @storage.fetch(key)
20
23
  end
21
24
 
25
+ def_delegators :@storage, :length
26
+
22
27
  private
23
28
 
24
29
  # Find the right value for this variable:
@@ -31,7 +36,7 @@ module GraphQL
31
36
  default_value = ast_variable.default_value
32
37
  provided_value = @provided_variables[variable_name]
33
38
 
34
- validation_result = variable_type.validate_input(provided_value)
39
+ validation_result = variable_type.validate_input(provided_value, @warden)
35
40
  if !validation_result.valid?
36
41
  @errors << GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, provided_value, validation_result)
37
42
  elsif provided_value.nil?