Query parsing, execution engine, and context management for running GraphQL operations against schemas in GraphQL Ruby.
Parse GraphQL query strings into Abstract Syntax Trees and validate syntax.
# Main parsing interface
def GraphQL.parse(graphql_string, trace: GraphQL::Tracing::NullTrace, filename: nil, max_tokens: nil)
def GraphQL.parse_file(filename)
def GraphQL.scan(graphql_string)
# Language parsing classes
class GraphQL::Language::Parser
def self.parse(string, filename: nil, trace: GraphQL::Tracing::NullTrace)
end
class GraphQL::Language::Lexer
def self.tokenize(graphql_string)
end
# Parse result
class GraphQL::Language::Nodes::Document
def definitions
def to_query_string
endUsage Examples:
# Parse a query string
query_string = '
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
'
document = GraphQL.parse(query_string)
puts document.definitions.first.name # "GetUser"
# Parse from file
document = GraphQL.parse_file("queries/get_user.graphql")
# Tokenize for analysis
tokens = GraphQL.scan(query_string)
tokens.each { |token| puts "#{token[0]}: #{token[1]}" }Represents a parsed GraphQL operation ready for execution with variables and context.
class GraphQL::Query
def initialize(schema, query_string = nil, document: nil, variables: {}, context: {}, operation_name: nil, root_value: nil, max_depth: nil, max_complexity: nil)
# Execution
def result
def result_name
# Validation and analysis
def valid?
def static_errors
def analyzers
# Query introspection
def operation_name
def selected_operation
def variables
def context
def root_value
def schema
def document
def query_string
# Utilities
def fingerprint
def sanitized_query_string(inline_variables: false)
endUsage Examples:
# Create and execute a query
schema = MySchema
query_string = 'query { users { id name } }'
query = GraphQL::Query.new(
schema,
query_string,
variables: {},
context: { current_user: current_user },
operation_name: nil
)
# Check if query is valid
unless query.valid?
puts "Query errors:"
query.static_errors.each { |error| puts error.message }
end
# Execute the query
result = query.result
puts result.to_json
# Query introspection
puts "Operation: #{query.operation_name}"
puts "Variables: #{query.variables}"
puts "Fingerprint: #{query.fingerprint}"The result of executing a GraphQL query, containing data and errors.
class GraphQL::Query::Result
# Data access
def [] (key)
def dig(*keys)
def to_h
def to_json(**options)
# Error handling
def context_errors
def execution_errors
# Metadata
def query
def extensions
endUsage Examples:
result = MySchema.execute('query { user(id: "1") { name email } }')
# Access data
user_data = result["data"]["user"]
user_name = result.dig("data", "user", "name")
# Check for errors
if result["errors"]&.any?
puts "GraphQL errors:"
result["errors"].each { |error| puts error["message"] }
end
# Convert to JSON for API response
json_response = result.to_json
# Access extensions (custom metadata)
if result["extensions"]
puts "Query complexity: #{result['extensions']['complexity']}"
endPass request-scoped data through the GraphQL execution pipeline.
class GraphQL::Query::Context
def initialize(query:, schema:, values: {})
# Data access
def [] (key)
def []= (key, value)
def key?(key)
def fetch(key, default = nil)
def delete(key)
def merge(new_values)
def merge!(new_values)
# Namespace support
def namespace(ns_name)
# Query access
def query
def schema
def warden
def interpreter?
# Execution state
def errors
def add_error(error)
def invalid_null?
def skip
def ast_node
def irep_node
def path
endUsage Examples:
# Setting up context in schema execution
result = MySchema.execute(
query_string,
context: {
current_user: current_user,
request: request,
abilities: current_user.abilities,
dataloader: MyDataLoader.new
}
)
# Accessing context in resolvers
class UserType < GraphQL::Schema::Object
field :posts, [PostType], null: true
def posts
current_user = context[:current_user]
abilities = context[:abilities]
# Filter posts based on user permissions
object.posts.accessible_by(abilities)
end
end
# Adding errors to context
def secure_field
unless context[:current_user]&.admin?
context.add_error(GraphQL::ExecutionError.new(
"Access denied",
path: context.path,
extensions: { code: "UNAUTHORIZED" }
))
return nil
end
# Return authorized data
object.secure_data
end
# Using namespaced context
def expensive_computation
cache = context.namespace(:cache)
cache[:computation_result] ||= perform_expensive_operation
endControl query execution behavior including multiplexing, timeouts, and analysis.
class GraphQL::Schema
# Single query execution
def self.execute(query_string, variables: {}, context: {}, operation_name: nil, root_value: nil, only: nil, except: nil, validate: true)
# Multiple query execution (multiplexing)
def self.multiplex(queries, context: {}, max_complexity: nil, validate: true)
# Analysis configuration
def self.query_analyzer(analyzer_class)
def self.analysis_engine(engine = nil)
# Execution hooks
def self.lazy_resolve(lazy_class, method_name)
def self.after_lazy(value, &block)
end
# Multiplex execution
class GraphQL::Execution::Multiplex
def initialize(schema:, queries:, context: {})
def self.run_all(schema, query_options, **kwargs)
endUsage Examples:
# Single query with options
result = MySchema.execute(
query_string,
variables: { id: "123" },
context: { current_user: user },
operation_name: "GetUser",
only: [:query], # Only allow queries, not mutations
validate: true # Validate before execution
)
# Multiplex execution (multiple queries in one request)
queries = [
{
query: "query GetUser($id: ID!) { user(id: $id) { name } }",
variables: { id: "1" },
operation_name: "GetUser"
},
{
query: "query GetPosts { posts(limit: 5) { title } }",
operation_name: "GetPosts"
}
]
results = MySchema.multiplex(queries, context: { current_user: user })
results.each_with_index do |result, index|
puts "Query #{index + 1}: #{result.to_json}"
end
# Custom lazy resolution
class MySchema < GraphQL::Schema
# Define how to resolve lazy values
lazy_resolve(Promise, :sync)
lazy_resolve(Concurrent::Future, :value!)
end
def lazy_field
# Return a lazy value that will be resolved later
Promise.new { expensive_database_call }
endHandle and customize GraphQL execution errors.
class GraphQL::ExecutionError < GraphQL::Error
def initialize(message, ast_node: nil, options: {})
# Error information
def message
def path
def locations
def ast_node
def extensions
# Extensions for additional error data
def extensions=(new_extensions)
end
# Schema error handling hooks
class GraphQL::Schema
def self.type_error(err, context)
def self.rescue_from(error_class, &block)
def self.unauthorized_object(error)
def self.unauthorized_field(error)
endUsage Examples:
# Raising execution errors in resolvers
def user
user = User.find_by(id: args[:id])
unless user
raise GraphQL::ExecutionError.new(
"User not found",
extensions: {
code: "NOT_FOUND",
timestamp: Time.current.iso8601
}
)
end
user
end
# Schema-level error handling
class MySchema < GraphQL::Schema
# Handle type resolution errors
def self.type_error(err, context)
GraphQL::ExecutionError.new(
"Failed to resolve type: #{err.message}",
extensions: { code: "TYPE_ERROR" }
)
end
# Handle specific exception types
rescue_from(ActiveRecord::RecordNotFound) do |err, obj, args, ctx, field|
GraphQL::ExecutionError.new(
"#{field.owner.name} not found",
extensions: { code: "NOT_FOUND" }
)
end
rescue_from(ActiveRecord::RecordInvalid) do |err, obj, args, ctx, field|
GraphQL::ExecutionError.new(
"Validation failed: #{err.record.errors.full_messages.join(', ')}",
extensions: {
code: "VALIDATION_ERROR",
details: err.record.errors.messages
}
)
end
# Handle authorization errors
def self.unauthorized_object(err)
GraphQL::ExecutionError.new(
"Access denied",
extensions: { code: "UNAUTHORIZED" }
)
end
end
# Collecting multiple errors
def resolve_with_multiple_errors
errors = []
results = []
args[:ids].each do |id|
begin
results << fetch_item(id)
rescue StandardError => e
errors << GraphQL::ExecutionError.new(
e.message,
path: context.path + [results.length],
extensions: { code: "FETCH_ERROR" }
)
results << nil
end
end
errors.each { |error| context.add_error(error) }
results
end