module GraphQL::StaticValidation::VariablesAreUsedAndDefined
The problem is
- Variable $usage must be determined at the OperationDefinition level - You can't tell how fragments use variables until you visit FragmentDefinitions (which may be at the end of the document) So, this validator includes some crazy logic to follow fragment spreads recursively, while avoiding infinite loops.
‘graphql-js` solves this problem by:
- re-visiting the AST for each validator - allowing validators to say `followSpreads: true`
Public Class Methods
new(*)
click to toggle source
Calls superclass method
# File lib/graphql/static_validation/rules/variables_are_used_and_defined.rb, line 26 def initialize(*) super @variable_usages_for_context = Hash.new {|hash, key| hash[key] = Hash.new {|h, k| h[k] = VariableUsage.new } } @spreads_for_context = Hash.new {|hash, key| hash[key] = [] } @variable_context_stack = [] end
Public Instance Methods
on_document(node, parent)
click to toggle source
Calls superclass method
# File lib/graphql/static_validation/rules/variables_are_used_and_defined.rb, line 78 def on_document(node, parent) super fragment_definitions = @variable_usages_for_context.select { |key, value| key.is_a?(GraphQL::Language::Nodes::FragmentDefinition) } operation_definitions = @variable_usages_for_context.select { |key, value| key.is_a?(GraphQL::Language::Nodes::OperationDefinition) } operation_definitions.each do |node, node_variables| follow_spreads(node, node_variables, @spreads_for_context, fragment_definitions, []) create_errors(node_variables) end end
on_fragment_definition(node, parent)
click to toggle source
Calls superclass method
# File lib/graphql/static_validation/rules/variables_are_used_and_defined.rb, line 48 def on_fragment_definition(node, parent) # initialize the hash of vars for this context: @variable_usages_for_context[node] @variable_context_stack.push(node) super @variable_context_stack.pop end
on_fragment_spread(node, parent)
click to toggle source
For FragmentSpreads:
- find the context on the stack - mark the context as containing this spread
Calls superclass method
# File lib/graphql/static_validation/rules/variables_are_used_and_defined.rb, line 59 def on_fragment_spread(node, parent) variable_context = @variable_context_stack.last @spreads_for_context[variable_context] << node.name super end
on_operation_definition(node, parent)
click to toggle source
Calls superclass method
# File lib/graphql/static_validation/rules/variables_are_used_and_defined.rb, line 33 def on_operation_definition(node, parent) # initialize the hash of vars for this context: @variable_usages_for_context[node] @variable_context_stack.push(node) # mark variables as defined: var_hash = @variable_usages_for_context[node] node.variables.each { |var| var_usage = var_hash[var.name] var_usage.declared_by = node var_usage.path = context.path } super @variable_context_stack.pop end
on_variable_identifier(node, parent)
click to toggle source
For VariableIdentifiers:
- mark the variable as used - assign its AST node
Calls superclass method
# File lib/graphql/static_validation/rules/variables_are_used_and_defined.rb, line 68 def on_variable_identifier(node, parent) usage_context = @variable_context_stack.last declared_variables = @variable_usages_for_context[usage_context] usage = declared_variables[node.name] usage.used_by = usage_context usage.ast_node = node usage.path = context.path super end
Private Instance Methods
create_errors(node_variables)
click to toggle source
Determine all the error messages, Then push messages into the validation context
# File lib/graphql/static_validation/rules/variables_are_used_and_defined.rb, line 124 def create_errors(node_variables) # Declared but not used: node_variables .select { |name, usage| usage.declared? && !usage.used? } .each { |var_name, usage| declared_by_error_name = usage.declared_by.name || "anonymous #{usage.declared_by.operation_type}" add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new( "Variable $#{var_name} is declared by #{declared_by_error_name} but not used", nodes: usage.declared_by, path: usage.path, name: var_name, error_type: VariablesAreUsedAndDefinedError::VIOLATIONS[:VARIABLE_NOT_USED] )) } # Used but not declared: node_variables .select { |name, usage| usage.used? && !usage.declared? } .each { |var_name, usage| used_by_error_name = usage.used_by.name || "anonymous #{usage.used_by.operation_type}" add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new( "Variable $#{var_name} is used by #{used_by_error_name} but not declared", nodes: usage.ast_node, path: usage.path, name: var_name, error_type: VariablesAreUsedAndDefinedError::VIOLATIONS[:VARIABLE_NOT_DEFINED] )) } end
follow_spreads(node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments)
click to toggle source
Follow spreads in ‘node`, looking them up from `spreads_for_context` and finding their match in `fragment_definitions`. Use those fragments to update {VariableUsage}s in `parent_variables`. Avoid infinite loops by skipping anything in `visited_fragments`.
# File lib/graphql/static_validation/rules/variables_are_used_and_defined.rb, line 94 def follow_spreads(node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments) spreads = spreads_for_context[node] - visited_fragments spreads.each do |spread_name| def_node = nil variables = nil # Implement `.find` by hand to avoid Ruby's internal allocations fragment_definitions.each do |frag_def_node, vars| if frag_def_node.name == spread_name def_node = frag_def_node variables = vars break end end next if !def_node visited_fragments << spread_name variables.each do |name, child_usage| parent_usage = parent_variables[name] if child_usage.used? parent_usage.ast_node = child_usage.ast_node parent_usage.used_by = child_usage.used_by parent_usage.path = child_usage.path end end follow_spreads(def_node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments) end end