Created
March 12, 2025 01:03
-
-
Save rmosolgo/f8e8828878c43851228e2a39b673a001 to your computer and use it in GitHub Desktop.
Exposting GraphQL-Ruby Query Complexity Value to Clients
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "bundler/inline" | |
gemfile do | |
gem "graphql", "2.4.12" | |
end | |
# This example shows three ways of showing a query's complexity to clients. | |
# All three cases require a customized analyzer which adds the value to be used later. | |
class MySchema < GraphQL::Schema | |
class MaxComplexityWithReport < GraphQL::Analysis::MaxQueryComplexity | |
# Only run this analyzer if the result will be used | |
def analyze? | |
!subject.max_complexity.nil? || | |
query.lookahead.selects?(:query_complexity) || | |
query.context[:add_complexity_extension] | |
end | |
# Re-implement this based on GraphQL-Ruby, but also adding it to context and extensions | |
def result | |
puts "Calculating complexity..." | |
total_complexity = max_possible_complexity | |
if query.context[:add_complexity_extension] | |
# This will make it appear in the response extensions, eg | |
# { "data" => ..., "extensions" => { "complexity" => 100 } } | |
subject.context.response_extensions["complexity"] = total_complexity | |
end | |
if query.lookahead.selects?(:query_complexity) | |
# This can be accessed later, see below: | |
subject.context[:query_complexity] = total_complexity | |
end | |
# Return an error if over limit: | |
if !subject.max_complexity.nil? && total_complexity > subject.max_complexity | |
GraphQL::AnalysisError.new("Query has complexity of #{total_complexity}, which exceeds max complexity of #{subject.max_complexity}") | |
else | |
nil | |
end | |
end | |
end | |
# Just some nestable object for demonstration purposes: | |
class Thing < GraphQL::Schema::Object | |
field :itself, self | |
end | |
class Query < GraphQL::Schema::Object | |
field :thing, Thing, fallback_value: :thing | |
# Expose complexity in the graphql response: | |
field :query_complexity, Integer | |
def query_complexity | |
context[:query_complexity] | |
end | |
end | |
query(Query) | |
max_complexity(100) | |
query_analyzer(MaxComplexityWithReport) | |
end | |
query_str = "{ | |
thing { itself { itself { itself { itself { __typename } } } } } | |
queryComplexity | |
}" | |
result = MySchema.execute(query_str, context: { add_complexity_extension: true }) | |
# Calculating complexity... | |
# This could be used to add a header to the response: | |
pp result.context[:query_complexity] | |
# => 7 | |
# This demonstrates both the extension and the GraphQL field: | |
pp result.to_h | |
# { | |
# "data" => { | |
# "thing" => {"itself" => {"itself" => {"itself" => {"itself" => {"__typename" => "Thing"}}}}}, | |
# "queryComplexity" => 7 | |
# }, | |
# "extensions" => { | |
# "complexity" => 7 | |
# } | |
# } | |
# A query that doesn't use complexity won't run the calculation | |
res = MySchema.execute("{ thing { __typename } }", max_complexity: nil) | |
pp res.to_h | |
# {"data" => {"thing" => {"__typename" => "Thing"}}} | |
pp res.context[:query_complexity] | |
# nil | |
# It errors if the query is over the limit | |
pp MySchema.execute(query_str, max_complexity: 5).to_h | |
# Calculating complexity... | |
# {"errors" => | |
# [{"message" => "Query has complexity of 7, which exceeds max complexity of 5"}, {"message" => "Query has complexity of 7, which exceeds max complexity of 5"}]} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment