Skip to content

Commit a004c21

Browse files
committed
feat(Schema) instrumenters can wrap queries
1 parent 9f98ada commit a004c21

File tree

4 files changed

+79
-26
lines changed

4 files changed

+79
-26
lines changed

lib/graphql/query.rb

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,9 @@ def initialize(schema, query_string = nil, document: nil, context: nil, variable
9191
# Get the result for this query, executing it once
9292
def result
9393
@result ||= begin
94-
if !valid?
95-
all_errors = validation_errors + analysis_errors
96-
if all_errors.any?
97-
{ "errors" => all_errors.map(&:to_h) }
98-
else
99-
nil
100-
end
101-
else
102-
Executor.new(self).result
103-
end
94+
instrumenters = @schema.instrumenters[:query]
95+
execution_call = ExecutionCall.new(self, instrumenters)
96+
execution_call.call
10497
end
10598
end
10699

@@ -209,5 +202,37 @@ def find_operation(operations, operation_name)
209202
operations[operation_name]
210203
end
211204
end
205+
206+
class ExecutionCall
207+
def initialize(query, instrumenters)
208+
@query = query
209+
@instrumenters = instrumenters
210+
end
211+
212+
# Check if the query is valid, and if it is,
213+
# execute it, calling instrumenters along the way
214+
# @return [Hash] The GraphQL response
215+
def call
216+
@instrumenters.each { |i| i.before_query(@query) }
217+
result = get_result
218+
@instrumenters.each { |i| i.after_query(@query) }
219+
result
220+
end
221+
222+
private
223+
224+
def get_result
225+
if !@query.valid?
226+
all_errors = @query.validation_errors + @query.analysis_errors
227+
if all_errors.any?
228+
{ "errors" => all_errors.map(&:to_h) }
229+
else
230+
nil
231+
end
232+
else
233+
Executor.new(@query).result
234+
end
235+
end
236+
end
212237
end
213238
end

lib/graphql/query/variables.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ module GraphQL
22
class Query
33
# Read-only access to query variables, applying default values if needed.
44
class Variables
5+
extend Forwardable
6+
57
# @return [Array<GraphQL::Query::VariableValidationError>] Any errors encountered when parsing the provided variables and literal values
68
attr_reader :errors
79

@@ -20,6 +22,8 @@ def [](key)
2022
@storage.fetch(key)
2123
end
2224

25+
def_delegators :@storage, :length
26+
2327
private
2428

2529
# Find the right value for this variable:

lib/graphql/schema.rb

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@ def remove_handler(*args, &block)
110110

111111
def define(**kwargs, &block)
112112
super
113-
types
113+
all_types = orphan_types + [query, mutation, subscription, GraphQL::Introspection::SchemaType]
114+
@types = GraphQL::Schema::ReduceTypes.reduce(all_types.compact)
115+
114116
@instrumented_field_map = InstrumentedFieldMap.new(self)
115117
field_instrumenters = @instrumenters[:field]
116118
types.each do |type_name, type|
@@ -134,13 +136,7 @@ def define(**kwargs, &block)
134136

135137
# @see [GraphQL::Schema::Warden] Restricted access to members of a schema
136138
# @return [GraphQL::Schema::TypeMap] `{ name => type }` pairs of types in this schema
137-
def types
138-
@types ||= begin
139-
ensure_defined
140-
all_types = orphan_types + [query, mutation, subscription, GraphQL::Introspection::SchemaType]
141-
GraphQL::Schema::ReduceTypes.reduce(all_types.compact)
142-
end
143-
end
139+
attr_reader :types
144140

145141
# Execute a query on itself.
146142
# See {Query#initialize} for arguments.

spec/graphql/schema_spec.rb

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -190,18 +190,39 @@ def initialize(multiplier)
190190
end
191191

192192
def instrument(type_defn, field_defn)
193-
prev_proc = field_defn.resolve_proc
194-
new_resolve_proc = ->(obj, args, ctx) {
195-
inner_value = prev_proc.call(obj, args, ctx)
196-
inner_value * @multiplier
197-
}
198-
199-
field_defn.redefine do
200-
resolve(new_resolve_proc)
193+
if type_defn.name == "Query" && field_defn.name == "int"
194+
prev_proc = field_defn.resolve_proc
195+
new_resolve_proc = ->(obj, args, ctx) {
196+
inner_value = prev_proc.call(obj, args, ctx)
197+
inner_value * @multiplier
198+
}
199+
200+
field_defn.redefine do
201+
resolve(new_resolve_proc)
202+
end
203+
else
204+
field_defn
201205
end
202206
end
203207
end
204208

209+
class VariableCountInstrumenter
210+
attr_reader :counts
211+
def initialize
212+
@counts = []
213+
end
214+
215+
def before_query(query)
216+
@counts << query.variables.length
217+
end
218+
219+
def after_query(query)
220+
end
221+
end
222+
223+
let(:variable_counter) {
224+
VariableCountInstrumenter.new
225+
}
205226
let(:query_type) {
206227
GraphQL::ObjectType.define do
207228
name "Query"
@@ -217,12 +238,19 @@ def instrument(type_defn, field_defn)
217238
GraphQL::Schema.define do
218239
query(spec.query_type)
219240
instrument(:field, MultiplyInstrumenter.new(3))
241+
instrument(:query, spec.variable_counter)
220242
end
221243
}
222244

223245
it "can modify field definitions" do
224246
res = schema.execute(" { int(value: 2) } ")
225247
assert_equal 6, res["data"]["int"]
226248
end
249+
250+
it "can wrap query execution" do
251+
schema.execute("query getInt($val: Int = 5){ int(value: $val) } ")
252+
schema.execute("query getInt($val: Int = 5, $val2: Int = 3){ int(value: $val) int2: int(value: $val2) } ")
253+
assert_equal [1, 2], variable_counter.counts
254+
end
227255
end
228256
end

0 commit comments

Comments
 (0)