graphql 0.12.1 → 0.13.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 (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