You can provide logic for validating incoming queries and rejecting them if they don’t pass. Query analyzers inspect the query and may return GraphQL::AnalysisError to halt execution.
GraphQL’s max_depth and max_complexity are implemented with query analyzers, you can see those for reference:
GraphQL::Analysis::QueryDepthGraphQL::Analysis::QueryComplexityA query analyzer visits each field in the query before the query is executed. It can accumulate data during the visits, then return a value. If the returned value is a GraphQL::AnalysisError (or an array of those errors), the query won’t be executed and the error will be returned to the user. You can use this feature to assert that queries are permitted before running them!
Query analyzers reuse concepts from Array#reduce, so let’s briefly revisit how that method works:
items = [1, 2, 3, 4, 5]
initial_value = 0
reduce_result = items.reduce(initial_value) { |memo, item| memo + item }
final_value = "Sum: #{reduce_result}"
puts final_value
# Sum: 15
reduce accepts an initial value and a callback (as a block)memo) and each item of the array (item)memo value) is returnedA query analyzer has the same basic parts. Here’s the scaffold for an analyzer:
class MyQueryAnalyzer
# Called before initializing the analyzer.
# Returns true to run this analyzer, or false to skip it.
def analyze?(query)
end
# Called before the visit.
# Returns the initial value for `memo`
def initial_value(query)
end
# This is like the `reduce` callback.
# The return value is passed to the next call as `memo`
def call(memo, visit_type, irep_node)
end
# Called when we're done the whole visit.
# The return value may be a GraphQL::AnalysisError (or an array of them).
# Or, you can use this hook to write to a log, etc
def final_value(memo)
end
end
#analyze? is called before initializing any analyzer if it is defined. When #analyze? returns false, the analyzer won’t be ran.#initial_value is a chance to initialize the state for your analysis. For example, you can return a hash with keys for the query, schema, and any other values you want to store.#call is called for each node in the query. memo is the analyzer state. visit_type is either :enter or :leave. irep_node is the GraphQL::InternalRepresentation::Node for the current field in the query. (It is like item in the Array#reduce callback.)#final_value is called after the visit. It provides a chance to write to your log or return a GraphQL::AnalysisError to halt query execution.Query analyzers are added to the schema with query_analyzer, for example:
class MySchema < GraphQL::Schema
query_analyzer MyQueryAnalyzer.new
end