graphql 0.12.1 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +31 -41
  3. data/lib/graphql/argument.rb +23 -21
  4. data/lib/graphql/base_type.rb +5 -8
  5. data/lib/graphql/define/assign_argument.rb +5 -2
  6. data/lib/graphql/define/type_definer.rb +2 -1
  7. data/lib/graphql/directive.rb +34 -36
  8. data/lib/graphql/directive/include_directive.rb +3 -7
  9. data/lib/graphql/directive/skip_directive.rb +3 -7
  10. data/lib/graphql/enum_type.rb +78 -76
  11. data/lib/graphql/execution_error.rb +1 -3
  12. data/lib/graphql/field.rb +99 -95
  13. data/lib/graphql/input_object_type.rb +49 -47
  14. data/lib/graphql/interface_type.rb +31 -34
  15. data/lib/graphql/introspection.rb +19 -18
  16. data/lib/graphql/introspection/directive_location_enum.rb +8 -0
  17. data/lib/graphql/introspection/directive_type.rb +1 -3
  18. data/lib/graphql/introspection/field_type.rb +1 -1
  19. data/lib/graphql/introspection/fields_field.rb +1 -1
  20. data/lib/graphql/introspection/introspection_query.rb +1 -3
  21. data/lib/graphql/introspection/possible_types_field.rb +7 -1
  22. data/lib/graphql/introspection/schema_field.rb +13 -9
  23. data/lib/graphql/introspection/type_by_name_field.rb +13 -17
  24. data/lib/graphql/introspection/typename_field.rb +12 -8
  25. data/lib/graphql/language.rb +5 -9
  26. data/lib/graphql/language/lexer.rb +668 -0
  27. data/lib/graphql/language/lexer.rl +149 -0
  28. data/lib/graphql/language/parser.rb +842 -116
  29. data/lib/graphql/language/parser.y +264 -0
  30. data/lib/graphql/language/token.rb +21 -0
  31. data/lib/graphql/list_type.rb +33 -31
  32. data/lib/graphql/non_null_type.rb +33 -31
  33. data/lib/graphql/object_type.rb +52 -55
  34. data/lib/graphql/query.rb +83 -80
  35. data/lib/graphql/query/context.rb +5 -1
  36. data/lib/graphql/query/directive_resolution.rb +16 -0
  37. data/lib/graphql/query/executor.rb +3 -3
  38. data/lib/graphql/query/input_validation_result.rb +17 -15
  39. data/lib/graphql/query/serial_execution.rb +5 -5
  40. data/lib/graphql/query/serial_execution/execution_context.rb +4 -3
  41. data/lib/graphql/query/serial_execution/selection_resolution.rb +19 -21
  42. data/lib/graphql/query/serial_execution/value_resolution.rb +1 -1
  43. data/lib/graphql/query/type_resolver.rb +22 -18
  44. data/lib/graphql/query/variable_validation_error.rb +14 -12
  45. data/lib/graphql/schema.rb +87 -77
  46. data/lib/graphql/schema/each_item_validator.rb +16 -12
  47. data/lib/graphql/schema/field_validator.rb +14 -10
  48. data/lib/graphql/schema/implementation_validator.rb +26 -22
  49. data/lib/graphql/schema/middleware_chain.rb +2 -1
  50. data/lib/graphql/schema/possible_types.rb +34 -0
  51. data/lib/graphql/schema/printer.rb +122 -120
  52. data/lib/graphql/schema/type_expression.rb +1 -0
  53. data/lib/graphql/schema/type_map.rb +3 -10
  54. data/lib/graphql/schema/type_reducer.rb +65 -81
  55. data/lib/graphql/schema/type_validator.rb +45 -41
  56. data/lib/graphql/static_validation.rb +7 -9
  57. data/lib/graphql/static_validation/all_rules.rb +29 -24
  58. data/lib/graphql/static_validation/arguments_validator.rb +39 -35
  59. data/lib/graphql/static_validation/literal_validator.rb +44 -40
  60. data/lib/graphql/static_validation/message.rb +30 -26
  61. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +15 -11
  62. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +14 -10
  63. data/lib/graphql/static_validation/rules/directives_are_defined.rb +16 -12
  64. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +59 -0
  65. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +25 -21
  66. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +28 -24
  67. data/lib/graphql/static_validation/rules/fields_will_merge.rb +84 -80
  68. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +49 -43
  69. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +22 -17
  70. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +19 -15
  71. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +25 -20
  72. data/lib/graphql/static_validation/rules/fragments_are_used.rb +36 -23
  73. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +29 -25
  74. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +21 -17
  75. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +79 -70
  76. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +24 -20
  77. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +122 -119
  78. data/lib/graphql/static_validation/type_stack.rb +138 -129
  79. data/lib/graphql/static_validation/validator.rb +29 -25
  80. data/lib/graphql/type_kinds.rb +42 -40
  81. data/lib/graphql/union_type.rb +22 -16
  82. data/lib/graphql/version.rb +1 -1
  83. data/readme.md +12 -27
  84. data/spec/graphql/base_type_spec.rb +3 -3
  85. data/spec/graphql/directive_spec.rb +10 -18
  86. data/spec/graphql/enum_type_spec.rb +7 -7
  87. data/spec/graphql/execution_error_spec.rb +1 -1
  88. data/spec/graphql/field_spec.rb +14 -13
  89. data/spec/graphql/id_type_spec.rb +6 -6
  90. data/spec/graphql/input_object_type_spec.rb +39 -39
  91. data/spec/graphql/interface_type_spec.rb +16 -32
  92. data/spec/graphql/introspection/directive_type_spec.rb +5 -9
  93. data/spec/graphql/introspection/input_value_type_spec.rb +10 -4
  94. data/spec/graphql/introspection/introspection_query_spec.rb +2 -2
  95. data/spec/graphql/introspection/schema_type_spec.rb +2 -2
  96. data/spec/graphql/introspection/type_type_spec.rb +34 -6
  97. data/spec/graphql/language/parser_spec.rb +299 -105
  98. data/spec/graphql/language/visitor_spec.rb +4 -4
  99. data/spec/graphql/list_type_spec.rb +11 -11
  100. data/spec/graphql/object_type_spec.rb +10 -10
  101. data/spec/graphql/query/arguments_spec.rb +7 -7
  102. data/spec/graphql/query/context_spec.rb +11 -3
  103. data/spec/graphql/query/executor_spec.rb +26 -19
  104. data/spec/graphql/query/serial_execution/execution_context_spec.rb +6 -6
  105. data/spec/graphql/query/serial_execution/value_resolution_spec.rb +2 -2
  106. data/spec/graphql/query/type_resolver_spec.rb +3 -3
  107. data/spec/graphql/query_spec.rb +6 -38
  108. data/spec/graphql/scalar_type_spec.rb +28 -19
  109. data/spec/graphql/schema/field_validator_spec.rb +1 -1
  110. data/spec/graphql/schema/middleware_chain_spec.rb +12 -1
  111. data/spec/graphql/schema/printer_spec.rb +12 -4
  112. data/spec/graphql/schema/rescue_middleware_spec.rb +1 -1
  113. data/spec/graphql/schema/type_expression_spec.rb +2 -2
  114. data/spec/graphql/schema/type_reducer_spec.rb +21 -36
  115. data/spec/graphql/schema/type_validator_spec.rb +9 -9
  116. data/spec/graphql/schema_spec.rb +1 -1
  117. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +4 -4
  118. data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +4 -4
  119. data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +5 -5
  120. data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +39 -0
  121. data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +5 -5
  122. data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +4 -4
  123. data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +2 -2
  124. data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +1 -1
  125. data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +2 -2
  126. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +2 -2
  127. data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +2 -2
  128. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +3 -3
  129. data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +3 -3
  130. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +5 -5
  131. data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +3 -1
  132. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +4 -4
  133. data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +3 -3
  134. data/spec/graphql/static_validation/type_stack_spec.rb +3 -2
  135. data/spec/graphql/static_validation/validator_spec.rb +26 -6
  136. data/spec/graphql/union_type_spec.rb +5 -4
  137. data/spec/spec_helper.rb +2 -5
  138. data/spec/support/dairy_app.rb +30 -9
  139. data/spec/support/dairy_data.rb +1 -1
  140. data/spec/support/star_wars_data.rb +26 -26
  141. data/spec/support/star_wars_schema.rb +1 -1
  142. metadata +40 -21
  143. data/lib/graphql/language/transform.rb +0 -113
  144. data/lib/graphql/query/directive_chain.rb +0 -44
  145. data/lib/graphql/repl.rb +0 -27
  146. data/spec/graphql/language/transform_spec.rb +0 -156
@@ -1,27 +1,31 @@
1
- class GraphQL::StaticValidation::VariablesAreInputTypes
2
- include GraphQL::StaticValidation::Message::MessageHelper
1
+ module GraphQL
2
+ module StaticValidation
3
+ class VariablesAreInputTypes
4
+ include GraphQL::StaticValidation::Message::MessageHelper
3
5
 
4
- def validate(context)
5
- context.visitor[GraphQL::Language::Nodes::VariableDefinition] << -> (node, parent) {
6
- validate_is_input_type(node, context)
7
- }
8
- end
6
+ def validate(context)
7
+ context.visitor[GraphQL::Language::Nodes::VariableDefinition] << -> (node, parent) {
8
+ validate_is_input_type(node, context)
9
+ }
10
+ end
9
11
 
10
- private
12
+ private
11
13
 
12
- def validate_is_input_type(node, context)
13
- type_name = get_type_name(node.type)
14
- type = context.schema.types[type_name]
15
- if !type.kind.input?
16
- context.errors << message("#{type.name} isn't a valid input type (on $#{node.name})", node)
17
- end
18
- end
14
+ def validate_is_input_type(node, context)
15
+ type_name = get_type_name(node.type)
16
+ type = context.schema.types[type_name]
17
+ if !type.kind.input?
18
+ context.errors << message("#{type.name} isn't a valid input type (on $#{node.name})", node)
19
+ end
20
+ end
19
21
 
20
- def get_type_name(ast_type)
21
- if ast_type.respond_to?(:of_type)
22
- get_type_name(ast_type.of_type)
23
- else
24
- ast_type.name
22
+ def get_type_name(ast_type)
23
+ if ast_type.respond_to?(:of_type)
24
+ get_type_name(ast_type.of_type)
25
+ else
26
+ ast_type.name
27
+ end
28
+ end
25
29
  end
26
30
  end
27
31
  end
@@ -1,130 +1,133 @@
1
- # The problem is
2
- # - Variable usage must be determined at the OperationDefinition level
3
- # - You can't tell how fragments use variables until you visit FragmentDefinitions (which may be at the end of the document)
4
- #
5
- # So, this validator includes some crazy logic to follow fragment spreads recursively, while avoiding infinite loops.
6
- #
7
- # `graphql-js` solves this problem by:
8
- # - re-visiting the AST for each validator
9
- # - allowing validators to say `followSpreads: true`
10
- #
11
- class GraphQL::StaticValidation::VariablesAreUsedAndDefined
12
- include GraphQL::StaticValidation::Message::MessageHelper
13
-
14
- class VariableUsage
15
- attr_accessor :ast_node, :used_by, :declared_by
16
- def used?
17
- !!@used_by
18
- end
1
+ module GraphQL
2
+ module StaticValidation
3
+ # The problem is
4
+ # - Variable usage must be determined at the OperationDefinition level
5
+ # - You can't tell how fragments use variables until you visit FragmentDefinitions (which may be at the end of the document)
6
+ #
7
+ # So, this validator includes some crazy logic to follow fragment spreads recursively, while avoiding infinite loops.
8
+ #
9
+ # `graphql-js` solves this problem by:
10
+ # - re-visiting the AST for each validator
11
+ # - allowing validators to say `followSpreads: true`
12
+ #
13
+ class VariablesAreUsedAndDefined
14
+ include GraphQL::StaticValidation::Message::MessageHelper
15
+
16
+ class VariableUsage
17
+ attr_accessor :ast_node, :used_by, :declared_by
18
+ def used?
19
+ !!@used_by
20
+ end
19
21
 
20
- def declared?
21
- !!@declared_by
22
- end
23
- end
22
+ def declared?
23
+ !!@declared_by
24
+ end
25
+ end
24
26
 
25
- def variable_hash
26
- Hash.new {|h, k| h[k] = VariableUsage.new }
27
- end
27
+ def variable_hash
28
+ Hash.new {|h, k| h[k] = VariableUsage.new }
29
+ end
28
30
 
29
- def validate(context)
30
- variable_usages_for_context = Hash.new {|hash, key| hash[key] = variable_hash }
31
- spreads_for_context = Hash.new {|hash, key| hash[key] = [] }
32
- variable_context_stack = []
33
-
34
- # OperationDefinitions and FragmentDefinitions
35
- # both push themselves onto the context stack (and pop themselves off)
36
- push_variable_context_stack = -> (node, parent) {
37
- # initialize the hash of vars for this context:
38
- variable_usages_for_context[node]
39
- variable_context_stack.push(node)
40
- }
41
-
42
- pop_variable_context_stack = -> (node, parent) {
43
- variable_context_stack.pop
44
- }
45
-
46
-
47
- context.visitor[GraphQL::Language::Nodes::OperationDefinition] << push_variable_context_stack
48
- context.visitor[GraphQL::Language::Nodes::OperationDefinition] << -> (node, parent) {
49
- # mark variables as defined:
50
- var_hash = variable_usages_for_context[node]
51
- node.variables.each { |var| var_hash[var.name].declared_by = node }
52
- }
53
- context.visitor[GraphQL::Language::Nodes::OperationDefinition].leave << pop_variable_context_stack
54
-
55
- context.visitor[GraphQL::Language::Nodes::FragmentDefinition] << push_variable_context_stack
56
- context.visitor[GraphQL::Language::Nodes::FragmentDefinition].leave << pop_variable_context_stack
57
-
58
- # For FragmentSpreads:
59
- # - find the context on the stack
60
- # - mark the context as containing this spread
61
- context.visitor[GraphQL::Language::Nodes::FragmentSpread] << -> (node, parent) {
62
- variable_context = variable_context_stack.last
63
- spreads_for_context[variable_context] << node.name
64
- }
65
-
66
- # For VariableIdentifiers:
67
- # - mark the variable as used
68
- # - assign its AST node
69
- context.visitor[GraphQL::Language::Nodes::VariableIdentifier] << -> (node, parent) {
70
- usage_context = variable_context_stack.last
71
- declared_variables = variable_usages_for_context[usage_context]
72
- usage = declared_variables[node.name]
73
- usage.used_by = usage_context
74
- usage.ast_node = node
75
- }
76
-
77
-
78
- context.visitor[GraphQL::Language::Nodes::Document].leave << -> (node, parent) {
79
- fragment_definitions = variable_usages_for_context.select { |key, value| key.is_a?(GraphQL::Language::Nodes::FragmentDefinition) }
80
- operation_definitions = variable_usages_for_context.select { |key, value| key.is_a?(GraphQL::Language::Nodes::OperationDefinition) }
81
-
82
- operation_definitions.each do |node, node_variables|
83
- follow_spreads(node, node_variables, spreads_for_context, fragment_definitions, [])
84
- create_errors(node_variables, context)
31
+ def validate(context)
32
+ variable_usages_for_context = Hash.new {|hash, key| hash[key] = variable_hash }
33
+ spreads_for_context = Hash.new {|hash, key| hash[key] = [] }
34
+ variable_context_stack = []
35
+
36
+ # OperationDefinitions and FragmentDefinitions
37
+ # both push themselves onto the context stack (and pop themselves off)
38
+ push_variable_context_stack = -> (node, parent) {
39
+ # initialize the hash of vars for this context:
40
+ variable_usages_for_context[node]
41
+ variable_context_stack.push(node)
42
+ }
43
+
44
+ pop_variable_context_stack = -> (node, parent) {
45
+ variable_context_stack.pop
46
+ }
47
+
48
+
49
+ context.visitor[GraphQL::Language::Nodes::OperationDefinition] << push_variable_context_stack
50
+ context.visitor[GraphQL::Language::Nodes::OperationDefinition] << -> (node, parent) {
51
+ # mark variables as defined:
52
+ var_hash = variable_usages_for_context[node]
53
+ node.variables.each { |var| var_hash[var.name].declared_by = node }
54
+ }
55
+ context.visitor[GraphQL::Language::Nodes::OperationDefinition].leave << pop_variable_context_stack
56
+
57
+ context.visitor[GraphQL::Language::Nodes::FragmentDefinition] << push_variable_context_stack
58
+ context.visitor[GraphQL::Language::Nodes::FragmentDefinition].leave << pop_variable_context_stack
59
+
60
+ # For FragmentSpreads:
61
+ # - find the context on the stack
62
+ # - mark the context as containing this spread
63
+ context.visitor[GraphQL::Language::Nodes::FragmentSpread] << -> (node, parent) {
64
+ variable_context = variable_context_stack.last
65
+ spreads_for_context[variable_context] << node.name
66
+ }
67
+
68
+ # For VariableIdentifiers:
69
+ # - mark the variable as used
70
+ # - assign its AST node
71
+ context.visitor[GraphQL::Language::Nodes::VariableIdentifier] << -> (node, parent) {
72
+ usage_context = variable_context_stack.last
73
+ declared_variables = variable_usages_for_context[usage_context]
74
+ usage = declared_variables[node.name]
75
+ usage.used_by = usage_context
76
+ usage.ast_node = node
77
+ }
78
+
79
+
80
+ context.visitor[GraphQL::Language::Nodes::Document].leave << -> (node, parent) {
81
+ fragment_definitions = variable_usages_for_context.select { |key, value| key.is_a?(GraphQL::Language::Nodes::FragmentDefinition) }
82
+ operation_definitions = variable_usages_for_context.select { |key, value| key.is_a?(GraphQL::Language::Nodes::OperationDefinition) }
83
+
84
+ operation_definitions.each do |node, node_variables|
85
+ follow_spreads(node, node_variables, spreads_for_context, fragment_definitions, [])
86
+ create_errors(node_variables, context)
87
+ end
88
+ }
85
89
  end
86
- }
87
- end
88
90
 
89
- private
90
-
91
- # Follow spreads in `node`, looking them up from `spreads_for_context` and finding their match in `fragment_definitions`.
92
- # Use those fragments to update {VariableUsage}s in `parent_variables`.
93
- # Avoid infinite loops by skipping anything in `visited_fragments`.
94
- def follow_spreads(node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments)
95
- spreads = spreads_for_context[node] - visited_fragments
96
- spreads.each do |spread_name|
97
- def_node, variables = fragment_definitions.find { |def_node, vars| def_node.name == spread_name }
98
- next if !def_node
99
- visited_fragments << spread_name
100
- variables.each do |name, child_usage|
101
- parent_usage = parent_variables[name]
102
- if child_usage.used?
103
- parent_usage.ast_node = child_usage.ast_node
104
- parent_usage.used_by = child_usage.used_by
91
+ private
92
+
93
+ # Follow spreads in `node`, looking them up from `spreads_for_context` and finding their match in `fragment_definitions`.
94
+ # Use those fragments to update {VariableUsage}s in `parent_variables`.
95
+ # Avoid infinite loops by skipping anything in `visited_fragments`.
96
+ def follow_spreads(node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments)
97
+ spreads = spreads_for_context[node] - visited_fragments
98
+ spreads.each do |spread_name|
99
+ def_node, variables = fragment_definitions.find { |def_node, vars| def_node.name == spread_name }
100
+ next if !def_node
101
+ visited_fragments << spread_name
102
+ variables.each do |name, child_usage|
103
+ parent_usage = parent_variables[name]
104
+ if child_usage.used?
105
+ parent_usage.ast_node = child_usage.ast_node
106
+ parent_usage.used_by = child_usage.used_by
107
+ end
108
+ end
109
+ follow_spreads(def_node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments)
105
110
  end
106
111
  end
107
- follow_spreads(def_node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments)
108
- end
109
- end
110
112
 
111
- # Determine all the error messages,
112
- # Then push messages into the validation context
113
- def create_errors(node_variables, context)
114
- errors = []
115
- # Declared but not used:
116
- errors += node_variables
117
- .select { |name, usage| usage.declared? && !usage.used? }
118
- .map { |var_name, usage| ["Variable $#{var_name} is declared by #{usage.declared_by.name} but not used", usage.declared_by] }
119
-
120
- # Used but not declared:
121
- errors += node_variables
122
- .select { |name, usage| usage.used? && !usage.declared? }
123
- .map { |var_name, usage| ["Variable $#{var_name} is used by #{usage.used_by.name} but not declared", usage.ast_node] }
124
-
125
- errors.each do |error_args|
126
- context.errors << message(*error_args)
113
+ # Determine all the error messages,
114
+ # Then push messages into the validation context
115
+ def create_errors(node_variables, context)
116
+ errors = []
117
+ # Declared but not used:
118
+ errors += node_variables
119
+ .select { |name, usage| usage.declared? && !usage.used? }
120
+ .map { |var_name, usage| ["Variable $#{var_name} is declared by #{usage.declared_by.name} but not used", usage.declared_by] }
121
+
122
+ # Used but not declared:
123
+ errors += node_variables
124
+ .select { |name, usage| usage.used? && !usage.declared? }
125
+ .map { |var_name, usage| ["Variable $#{var_name} is used by #{usage.used_by.name} but not declared", usage.ast_node] }
126
+
127
+ errors.each do |error_args|
128
+ context.errors << message(*error_args)
129
+ end
130
+ end
127
131
  end
128
132
  end
129
-
130
133
  end
@@ -1,150 +1,159 @@
1
- # - Ride along with `GraphQL::Language::Visitor`
2
- # - Track type info, expose it to validators
3
- class GraphQL::StaticValidation::TypeStack
4
- # These are jumping-off points for infering types down the tree
5
- TYPE_INFERRENCE_ROOTS = [
6
- GraphQL::Language::Nodes::OperationDefinition,
7
- GraphQL::Language::Nodes::FragmentDefinition,
8
- ]
9
-
10
- # @return [GraphQL::Schema] the schema whose types are present in this document
11
- attr_reader :schema
12
-
13
- # When it enters an object (starting with query or mutation root), it's pushed on this stack.
14
- # When it exits, it's popped off.
15
- # @return [Array<GraphQL::ObjectType, GraphQL::Union, GraphQL::Interface>]
16
- attr_reader :object_types
17
-
18
- # When it enters a field, it's pushed on this stack (useful for nested fields, args).
19
- # When it exits, it's popped off.
20
- # @return [Array<GraphQL::Field>] fields which have been entered
21
- attr_reader :field_definitions
22
-
23
- # Directives are pushed on, then popped off while traversing the tree
24
- # @return [Array<GraphQL::Node::Directive>] directives which have been entered
25
- attr_reader :directive_definitions
26
-
27
- # @return [Array<GraphQL::Node::Argument>] arguments which have been entered
28
- attr_reader :argument_definitions
29
-
30
- # @param schema [GraphQL::Schema] the schema whose types to use when climbing this document
31
- # @param visitor [GraphQL::Language::Visitor] a visitor to follow & watch the types
32
- def initialize(schema, visitor)
33
- @schema = schema
34
- @object_types = []
35
- @field_definitions = []
36
- @directive_definitions = []
37
- @argument_definitions = []
38
- visitor.enter << -> (node, parent) { PUSH_STRATEGIES[node.class].push(self, node) }
39
- visitor.leave << -> (node, parent) { PUSH_STRATEGIES[node.class].pop(self, node) }
40
- end
1
+ module GraphQL
2
+ module StaticValidation
3
+ # - Ride along with `GraphQL::Language::Visitor`
4
+ # - Track type info, expose it to validators
5
+ class TypeStack
6
+ # These are jumping-off points for infering types down the tree
7
+ TYPE_INFERRENCE_ROOTS = [
8
+ GraphQL::Language::Nodes::OperationDefinition,
9
+ GraphQL::Language::Nodes::FragmentDefinition,
10
+ ]
11
+
12
+ # @return [GraphQL::Schema] the schema whose types are present in this document
13
+ attr_reader :schema
14
+
15
+ # When it enters an object (starting with query or mutation root), it's pushed on this stack.
16
+ # When it exits, it's popped off.
17
+ # @return [Array<GraphQL::ObjectType, GraphQL::Union, GraphQL::Interface>]
18
+ attr_reader :object_types
19
+
20
+ # When it enters a field, it's pushed on this stack (useful for nested fields, args).
21
+ # When it exits, it's popped off.
22
+ # @return [Array<GraphQL::Field>] fields which have been entered
23
+ attr_reader :field_definitions
24
+
25
+ # Directives are pushed on, then popped off while traversing the tree
26
+ # @return [Array<GraphQL::Node::Directive>] directives which have been entered
27
+ attr_reader :directive_definitions
28
+
29
+ # @return [Array<GraphQL::Node::Argument>] arguments which have been entered
30
+ attr_reader :argument_definitions
31
+
32
+ # @param schema [GraphQL::Schema] the schema whose types to use when climbing this document
33
+ # @param visitor [GraphQL::Language::Visitor] a visitor to follow & watch the types
34
+ def initialize(schema, visitor)
35
+ @schema = schema
36
+ @object_types = []
37
+ @field_definitions = []
38
+ @directive_definitions = []
39
+ @argument_definitions = []
40
+ visitor.enter << -> (node, parent) { PUSH_STRATEGIES[node.class].push(self, node) }
41
+ visitor.leave << -> (node, parent) { PUSH_STRATEGIES[node.class].pop(self, node) }
42
+ end
41
43
 
42
- private
44
+ private
43
45
 
44
- # Look up strategies by name and use singleton instance to push and pop
45
- PUSH_STRATEGIES = Hash.new { |hash, key| hash[key] = get_strategy_for_node_class(key) }
46
+ # Look up strategies by name and use singleton instance to push and pop
47
+ PUSH_STRATEGIES = Hash.new { |hash, key| hash[key] = get_strategy_for_node_class(key) }
46
48
 
47
- def self.get_strategy_for_node_class(node_class)
48
- node_class_name = node_class.name.split("::").last
49
- strategy_key = "#{node_class_name}Strategy"
50
- const_defined?(strategy_key) ? const_get(strategy_key).new : NullStrategy.new
51
- end
49
+ def self.get_strategy_for_node_class(node_class)
50
+ node_class_name = node_class.name.split("::").last
51
+ strategy_key = "#{node_class_name}Strategy"
52
+ const_defined?(strategy_key) ? const_get(strategy_key).new : NullStrategy.new
53
+ end
52
54
 
53
- class FragmentWithTypeStrategy
54
- def push(stack, node)
55
- object_type = stack.schema.types.fetch(node.type, nil)
56
- if !object_type.nil?
57
- object_type = object_type.unwrap
55
+ class FragmentWithTypeStrategy
56
+ def push(stack, node)
57
+ object_type = if node.type
58
+ stack.schema.types.fetch(node.type, nil)
59
+ else
60
+ stack.object_types.last
61
+ end
62
+ if !object_type.nil?
63
+ object_type = object_type.unwrap
64
+ end
65
+ stack.object_types.push(object_type)
66
+ end
67
+
68
+ def pop(stack, node)
69
+ stack.object_types.pop
70
+ end
58
71
  end
59
- stack.object_types.push(object_type)
60
- end
61
72
 
62
- def pop(stack, node)
63
- stack.object_types.pop
64
- end
65
- end
73
+ class FragmentDefinitionStrategy < FragmentWithTypeStrategy; end
74
+ class InlineFragmentStrategy < FragmentWithTypeStrategy; end
66
75
 
67
- class FragmentDefinitionStrategy < FragmentWithTypeStrategy; end
68
- class InlineFragmentStrategy < FragmentWithTypeStrategy; end
76
+ class OperationDefinitionStrategy
77
+ def push(stack, node)
78
+ # eg, QueryType, MutationType
79
+ object_type = stack.schema.public_send(node.operation_type)
80
+ stack.object_types.push(object_type)
81
+ end
69
82
 
70
- class OperationDefinitionStrategy
71
- def push(stack, node)
72
- # eg, QueryType, MutationType
73
- object_type = stack.schema.public_send(node.operation_type)
74
- stack.object_types.push(object_type)
75
- end
76
- def pop(stack, node)
77
- stack.object_types.pop
78
- end
79
- end
83
+ def pop(stack, node)
84
+ stack.object_types.pop
85
+ end
86
+ end
80
87
 
81
- class FieldStrategy
82
- def push(stack, node)
83
- parent_type = stack.object_types.last
84
- parent_type = parent_type.unwrap
85
- if parent_type.kind.fields?
86
- field_class = stack.schema.get_field(parent_type, node.name)
87
- stack.field_definitions.push(field_class)
88
- if !field_class.nil?
89
- next_object_type = field_class.type
90
- stack.object_types.push(next_object_type)
91
- else
92
- stack.object_types.push(nil)
88
+ class FieldStrategy
89
+ def push(stack, node)
90
+ parent_type = stack.object_types.last
91
+ parent_type = parent_type.unwrap
92
+ if parent_type.kind.fields?
93
+ field_class = stack.schema.get_field(parent_type, node.name)
94
+ stack.field_definitions.push(field_class)
95
+ if !field_class.nil?
96
+ next_object_type = field_class.type
97
+ stack.object_types.push(next_object_type)
98
+ else
99
+ stack.object_types.push(nil)
100
+ end
101
+ else
102
+ stack.field_definitions.push(nil)
103
+ stack.object_types.push(parent_type)
104
+ end
105
+ end
106
+
107
+ def pop(stack, node)
108
+ stack.field_definitions.pop
109
+ stack.object_types.pop
93
110
  end
94
- else
95
- stack.field_definitions.push(nil)
96
- stack.object_types.push(parent_type)
97
111
  end
98
- end
99
112
 
100
- def pop(stack, node)
101
- stack.field_definitions.pop
102
- stack.object_types.pop
103
- end
104
- end
113
+ class DirectiveStrategy
114
+ def push(stack, node)
115
+ directive_defn = stack.schema.directives[node.name]
116
+ stack.directive_definitions.push(directive_defn)
117
+ end
105
118
 
106
- class DirectiveStrategy
107
- def push(stack, node)
108
- directive_defn = stack.schema.directives[node.name]
109
- stack.directive_definitions.push(directive_defn)
110
- end
119
+ def pop(stack, node)
120
+ stack.directive_definitions.pop
121
+ end
122
+ end
111
123
 
112
- def pop(stack, node)
113
- stack.directive_definitions.pop
114
- end
115
- end
124
+ class ArgumentStrategy
125
+ # Push `argument_defn` onto the stack.
126
+ # It's possible that `argument_defn` will be nil.
127
+ # Push it anyways so `pop` has something to pop.
128
+ def push(stack, node)
129
+ if stack.argument_definitions.last
130
+ arg_type = stack.argument_definitions.last.type.unwrap
131
+ if arg_type.kind.input_object?
132
+ argument_defn = arg_type.input_fields[node.name]
133
+ else
134
+ argument_defn = nil
135
+ end
136
+ elsif stack.directive_definitions.last
137
+ argument_defn = stack.directive_definitions.last.arguments[node.name]
138
+ elsif stack.field_definitions.last
139
+ argument_defn = stack.field_definitions.last.arguments[node.name]
140
+ else
141
+ argument_defn = nil
142
+ end
143
+ stack.argument_definitions.push(argument_defn)
144
+ end
116
145
 
117
- class ArgumentStrategy
118
- # Push `argument_defn` onto the stack.
119
- # It's possible that `argument_defn` will be nil.
120
- # Push it anyways so `pop` has something to pop.
121
- def push(stack, node)
122
- if stack.argument_definitions.last
123
- arg_type = stack.argument_definitions.last.type.unwrap
124
- if arg_type.kind.input_object?
125
- argument_defn = arg_type.input_fields[node.name]
126
- else
127
- argument_defn = nil
146
+ def pop(stack, node)
147
+ stack.argument_definitions.pop
128
148
  end
129
- elsif stack.directive_definitions.last
130
- argument_defn = stack.directive_definitions.last.arguments[node.name]
131
- elsif stack.field_definitions.last
132
- argument_defn = stack.field_definitions.last.arguments[node.name]
133
- else
134
- argument_defn = nil
135
149
  end
136
- stack.argument_definitions.push(argument_defn)
137
- end
138
150
 
139
- def pop(stack, node)
140
- stack.argument_definitions.pop
151
+ # A no-op strategy (don't handle this node)
152
+ class NullStrategy
153
+ def self.new; self; end
154
+ def self.push(stack, node); end
155
+ def self.pop(stack, node); end
156
+ end
141
157
  end
142
158
  end
143
-
144
- # A no-op strategy (don't handle this node)
145
- class NullStrategy
146
- def self.new; self; end
147
- def self.push(stack, node); end
148
- def self.pop(stack, node); end
149
- end
150
159
  end