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::QueryDepth
GraphQL::Analysis::QueryComplexity
A 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