Skip to content

Instantly share code, notes, and snippets.

@rmosolgo
Created March 12, 2025 01:03
Show Gist options
  • Save rmosolgo/f8e8828878c43851228e2a39b673a001 to your computer and use it in GitHub Desktop.
Save rmosolgo/f8e8828878c43851228e2a39b673a001 to your computer and use it in GitHub Desktop.
Exposting GraphQL-Ruby Query Complexity Value to Clients
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