graphql 1.0.0 → 1.1.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 (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?