graph_weaver 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -0
  3. data/CHANGELOG.md +256 -0
  4. data/Gemfile.lock +32 -2
  5. data/Makefile +7 -2
  6. data/NOTES.md +1 -1
  7. data/PLAN.md +41 -1
  8. data/README.md +56 -41
  9. data/docs/cassettes.md +76 -0
  10. data/docs/errors.md +37 -9
  11. data/docs/generated_modules.md +178 -29
  12. data/docs/getting_started.md +136 -0
  13. data/docs/logging.md +33 -0
  14. data/docs/real_world.md +27 -20
  15. data/docs/scalars.md +122 -8
  16. data/docs/testing.md +55 -38
  17. data/docs/transports.md +124 -0
  18. data/graph_weaver.gemspec +7 -1
  19. data/lib/graph_weaver/client.rb +201 -0
  20. data/lib/graph_weaver/codegen/emit.rb +315 -76
  21. data/lib/graph_weaver/codegen/enum_type.rb +149 -0
  22. data/lib/graph_weaver/codegen/nodes.rb +123 -68
  23. data/lib/graph_weaver/codegen/scalar_type.rb +53 -35
  24. data/lib/graph_weaver/codegen.rb +272 -101
  25. data/lib/graph_weaver/errors.rb +37 -25
  26. data/lib/graph_weaver/hints.rb +63 -0
  27. data/lib/graph_weaver/inflect.rb +2 -1
  28. data/lib/graph_weaver/input_struct.rb +59 -0
  29. data/lib/graph_weaver/logging.rb +41 -0
  30. data/lib/graph_weaver/railtie.rb +30 -0
  31. data/lib/graph_weaver/retry.rb +97 -0
  32. data/lib/graph_weaver/rspec.rb +17 -14
  33. data/lib/graph_weaver/schema_loader.rb +156 -20
  34. data/lib/graph_weaver/selection.rb +3 -3
  35. data/lib/graph_weaver/tasks.rb +71 -0
  36. data/lib/graph_weaver/testing/cassette.rb +42 -21
  37. data/lib/graph_weaver/testing/failure.rb +22 -22
  38. data/lib/graph_weaver/testing/{fake_executor.rb → fake_client.rb} +13 -13
  39. data/lib/graph_weaver/testing/values.rb +2 -2
  40. data/lib/graph_weaver/testing.rb +31 -15
  41. data/lib/graph_weaver/transport/faraday.rb +56 -0
  42. data/lib/graph_weaver/transport/http.rb +87 -0
  43. data/lib/graph_weaver/transport.rb +120 -0
  44. data/lib/graph_weaver/version.rb +1 -1
  45. data/lib/graph_weaver.rb +289 -38
  46. metadata +74 -5
  47. data/lib/graph_weaver/faraday_executor.rb +0 -61
  48. data/lib/graph_weaver/http_executor.rb +0 -44
  49. data/lib/graph_weaver/testing/rspec.rb +0 -5
@@ -1,16 +1,27 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require "set"
5
4
  require "sorbet-runtime"
6
5
  require_relative "inflect"
6
+ require_relative "logging"
7
7
 
8
8
  module GraphWeaver
9
9
  # Base for every error GraphWeaver raises — rescue this to catch them
10
10
  # all. #message is the human-friendly side; #to_h is the machine side —
11
11
  # a JSON-ready Hash (string keys) for logging, agents, or surfacing
12
- # structured failures to users. Subclasses merge in their specifics.
12
+ # structured failures to users. One subclass per failure site —
13
+ # {TransportError} (never reached the server), {ServerError} (non-2xx),
14
+ # {QueryError} (GraphQL-level errors), {TypeError} (response wouldn't
15
+ # cast), {ValidationError} (build time) — each merging its specifics
16
+ # into #to_h.
13
17
  class Error < StandardError
18
+ # every GraphWeaver error surfaces on the logger too (see
19
+ # GraphWeaver.logger) — construction here means a raise
20
+ def initialize(*args)
21
+ super
22
+ GraphWeaver.log(:warn) { "#{self.class.name}: #{message}" }
23
+ end
24
+
14
25
  def to_h
15
26
  { "error" => self.class.name, "message" => message }
16
27
  end
@@ -26,14 +37,14 @@ module GraphWeaver
26
37
  end
27
38
 
28
39
  class << self
29
- # The exception classes the bundled executors reclassify as
40
+ # The exception classes the bundled transports reclassify as
30
41
  # TransportError — network-level failures where the request never
31
42
  # reached the server. A mutable Set: each transport contributes its own
32
43
  # on load (net/http adds Timeout/SSL, Faraday adds its ConnectionFailed,
33
44
  # …), and you can add more so they get the same handling:
34
45
  #
35
- # GraphWeaver.transport_errors << MyPool::TimeoutError
36
- # GraphWeaver.register_transport_error(Adapter::ResetError)
46
+ # GraphWeaver.transport_errors << MyPool::TimeoutError
47
+ # GraphWeaver.register_transport_error(Adapter::ResetError)
37
48
  #
38
49
  # SystemCallError covers every Errno::* (connection refused/reset, host
39
50
  # unreachable); SocketError covers DNS.
@@ -72,11 +83,12 @@ module GraphWeaver
72
83
  class GraphQLError
73
84
  attr_reader :message, :locations, :path, :extensions
74
85
 
75
- def initialize(message:, locations: [], path: nil, extensions: {})
86
+ def initialize(message:, locations: [], path: nil, extensions: {}, type: nil)
76
87
  @message = message
77
88
  @locations = locations
78
89
  @path = path
79
90
  @extensions = extensions
91
+ @error_type = type
80
92
  end
81
93
 
82
94
  def self.from_h(hash)
@@ -85,13 +97,16 @@ module GraphWeaver
85
97
  locations: hash["locations"] || [],
86
98
  path: hash["path"],
87
99
  extensions: hash["extensions"] || {},
100
+ type: hash["type"],
88
101
  )
89
102
  end
90
103
 
91
104
  # The machine-readable error code, e.g. "THROTTLED" — the thing to
92
- # branch on. nil when the server didn't set extensions.code.
105
+ # branch on. Read from extensions.code (the Apollo/spec-adjacent
106
+ # convention) or a top-level "type" (GitHub's dialect: NOT_FOUND,
107
+ # FORBIDDEN). nil when the server sent neither.
93
108
  def code
94
- extensions["code"]
109
+ extensions["code"] || @error_type
95
110
  end
96
111
 
97
112
  # Message shapes servers use when they reject the *shape* of a query
@@ -166,8 +181,8 @@ module GraphWeaver
166
181
 
167
182
  # True when any error looks like the server rejected the query's
168
183
  # shape — the schema has likely changed since the module was
169
- # generated. Regenerate (bin/generate) and/or refresh the schema
170
- # cache (delete the cache: file, or wait out its ttl).
184
+ # generated. Refresh the schema dump and regenerate (rake
185
+ # graph_weaver:schema:refresh && rake graph_weaver:generate).
171
186
  def schema_stale?
172
187
  errors.any?(&:validation?)
173
188
  end
@@ -175,9 +190,9 @@ module GraphWeaver
175
190
  # Errors grouped by the field they point at (index-stripped dotted
176
191
  # path; nil key for global errors) — iterate with each_error:
177
192
  #
178
- # response.each_error do |field, errors|
179
- # form.add_error(field, errors.map(&:message))
180
- # end
193
+ # response.each_error do |field, errors|
194
+ # form.add_error(field, errors.map(&:message))
195
+ # end
181
196
  def errors_by_field
182
197
  errors.group_by(&:field)
183
198
  end
@@ -213,8 +228,8 @@ module GraphWeaver
213
228
  # actual records that failed inlined — "the 3 in people.3.email is
214
229
  # useless to a user; people.email plus which people is the answer".
215
230
  #
216
- # { "people.email" => { "messages" => [...], "codes" => [...],
217
- # "entity_ids" => ["7", "9"], "errors" => [full to_h...] } }
231
+ # { "people.email" => { "messages" => [...], "codes" => [...],
232
+ # "entity_ids" => ["7", "9"], "errors" => [full to_h...] } }
218
233
  def report
219
234
  errors_by_field.to_h do |field, field_errors|
220
235
  [field, {
@@ -264,7 +279,7 @@ module GraphWeaver
264
279
  def summary
265
280
  first = errors.first
266
281
  more = errors.size > 1 ? " (and #{errors.size - 1} more)" : ""
267
- drift = schema_stale? ? " — the server rejected the query shape: the schema may have changed since generation; regenerate modules (bin/generate) and/or refresh the schema cache" : ""
282
+ drift = schema_stale? ? " — the server rejected the query shape: the schema may have changed since generation; refresh the schema dump and regenerate (rake graph_weaver:schema:refresh && rake graph_weaver:generate)" : ""
268
283
  "GraphQL query failed: #{first}#{more}#{drift}"
269
284
  end
270
285
  end
@@ -288,10 +303,11 @@ module GraphWeaver
288
303
  end
289
304
  end
290
305
 
291
- # Build-time: the query didn't validate against the schema. Kept an
292
- # ArgumentError for source compatibility, but carries the structured
293
- # validation errors (message + line/column) rather than a joined string.
294
- class ValidationError < ArgumentError
306
+ # Build-time: the query didn't validate against the schema. Carries the
307
+ # structured validation errors (message + line/column) rather than a
308
+ # joined string. Under the Error umbrella like everything else raised
309
+ # here (through 0.1.0 it was an ArgumentError instead).
310
+ class ValidationError < Error
295
311
  attr_reader :errors
296
312
 
297
313
  def initialize(errors)
@@ -300,11 +316,7 @@ module GraphWeaver
300
316
  end
301
317
 
302
318
  def to_h
303
- {
304
- "error" => self.class.name,
305
- "message" => message,
306
- "errors" => errors.map { |e| e.transform_keys(&:to_s) },
307
- }
319
+ super.merge("errors" => errors.map { |e| e.transform_keys(&:to_s) })
308
320
  end
309
321
  end
310
322
  end
@@ -0,0 +1,63 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require_relative "inflect"
7
+
8
+ module GraphWeaver
9
+ # Included in generated response structs. GraphQL's camelCase fields
10
+ # become snake_case props, and reaching for the wire name is a classic
11
+ # stumble — result.nameWithOwner instead of result.name_with_owner.
12
+ # Catch the miss and point at the prop that does exist ("use ..." when
13
+ # the mapping is exact, "did you mean ...?" for a near-miss typo).
14
+ # (Typed call sites get this hint earlier, from srb tc.)
15
+ module Hints
16
+ include Kernel # for sorbet: hosts are Objects
17
+
18
+ # Guard for generated input-struct .coerce: a hash key that matches
19
+ # no prop raises (spellchecked) instead of silently dropping — a
20
+ # typo'd filter key must not become "match everything" on the wire.
21
+ def self.validate_keys!(struct, hash)
22
+ known = struct.props.keys.map(&:to_s)
23
+ unknown = hash.keys.map(&:to_s) - known
24
+ return if unknown.empty?
25
+
26
+ hints = unknown.map do |key|
27
+ prop = GraphWeaver::Inflect.underscore(key)
28
+ suggestion = if known.include?(prop)
29
+ prop # a wire-cased key — the exact snake_case prop exists
30
+ elsif defined?(DidYouMean::SpellChecker)
31
+ DidYouMean::SpellChecker.new(dictionary: known).correct(prop).first
32
+ end
33
+ suggestion ? "#{key} (did you mean '#{suggestion}'?)" : key
34
+ end
35
+ raise ArgumentError, "unknown key(s) for #{struct}: #{hints.join(", ")}"
36
+ end
37
+
38
+ def method_missing(name, *args, &block)
39
+ if args.empty? && (hint = prop_hint(name.to_s))
40
+ raise NoMethodError, "undefined method '#{name}' for #{self.class} — #{hint}"
41
+ end
42
+
43
+ super
44
+ end
45
+
46
+ private
47
+
48
+ def prop_hint(name)
49
+ prop = GraphWeaver::Inflect.underscore(name)
50
+ if prop != name && respond_to?(prop)
51
+ return "GraphQL fields generate snake_case props; use '#{prop}'"
52
+ end
53
+
54
+ return unless defined?(DidYouMean::SpellChecker)
55
+
56
+ # a guess, not a mapping — spellcheck the (underscored) miss
57
+ # against the props that exist, so typos in either casing land
58
+ props = T.unsafe(self.class).props.keys.map(&:to_s)
59
+ suggestion = DidYouMean::SpellChecker.new(dictionary: props).correct(prop).first
60
+ "did you mean '#{suggestion}'?" if suggestion
61
+ end
62
+ end
63
+ end
@@ -12,7 +12,8 @@ module GraphWeaver
12
12
  end
13
13
 
14
14
  def camelize(name)
15
- name.split("_").map { |part| "#{part[0].upcase}#{part[1..]}" }.join
15
+ # reject: leading underscores split into empty parts (_Service, _and)
16
+ name.split("_").reject(&:empty?).map { |part| "#{part[0].upcase}#{part[1..]}" }.join
16
17
  end
17
18
  end
18
19
  end
@@ -0,0 +1,59 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ require_relative "hints"
7
+
8
+ module GraphWeaver
9
+ # Runtime for generated input structs. Each struct declares its typed
10
+ # consts plus a compact FIELDS table — (prop, wire name, requiredness,
11
+ # serializer, coercer) per field, with the conversions emitted as
12
+ # lambdas — and this module is the loop that drives it. One copy here
13
+ # instead of unrolled methods in every struct, which is the difference
14
+ # between ~2 lines and ~6 lines per field when a Hasura bool_exp pulls
15
+ # hundreds of input types into one module.
16
+ module InputStruct
17
+ include Kernel # for sorbet: hosts are T::Structs
18
+
19
+ # serializer/coercer are code-as-data from the generated file; nil
20
+ # means identity (the wire value passes through untouched)
21
+ Field = Struct.new(:prop, :wire, :required, :serializer, :coercer)
22
+
23
+ def self.included(base)
24
+ base.extend(ClassMethods)
25
+ end
26
+
27
+ # the wire hash — optional fields left nil stay off the wire
28
+ def serialize
29
+ self.class.const_get(:FIELDS).each_with_object({}) do |field, wire|
30
+ value = public_send(field.prop)
31
+ next if value.nil? && !field.required
32
+
33
+ wire[field.wire] = field.serializer && !value.nil? ? field.serializer.call(value) : value
34
+ end
35
+ end
36
+ alias_method :to_h, :serialize
37
+
38
+ module ClassMethods
39
+ include Kernel
40
+
41
+ # Build from a plain hash (underscored keys, Symbol or String):
42
+ # enums accept their wire values, nested inputs accept hashes; the
43
+ # struct's types are enforced on construction, and unknown keys
44
+ # raise with a spellchecked hint.
45
+ def coerce(value)
46
+ return value if value.is_a?(self)
47
+
48
+ # a typo'd key must not silently drop off the wire
49
+ GraphWeaver::Hints.validate_keys!(self, value)
50
+
51
+ fields = T.unsafe(self).const_get(:FIELDS)
52
+ T.unsafe(self).new(**fields.to_h do |field|
53
+ raw = value.key?(field.prop) ? value[field.prop] : value[field.prop.to_s]
54
+ [field.prop, raw.nil? || field.coercer.nil? ? raw : field.coercer.call(raw)]
55
+ end)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,41 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module GraphWeaver
5
+ class << self
6
+ # Where GraphWeaver narrates what it's doing — anything
7
+ # stdlib-Logger-compatible (Logger, Rails.logger, semantic_logger...).
8
+ # Silent by default; Rails apps get Rails.logger wired by the railtie.
9
+ #
10
+ # GraphWeaver.logger = Logger.new($stdout, level: Logger::INFO)
11
+ #
12
+ # What logs where:
13
+ # debug — full queries + variables on the wire, responses
14
+ # (status/bytes/ms), connection lifecycle, parsed modules
15
+ # info — schema introspection and cache decisions, generated files
16
+ # written, query modules loaded
17
+ # warn — every GraphWeaver error raised
18
+ #
19
+ # Queries, variables, and responses appear at debug ONLY — they can
20
+ # carry PII. Auth headers never log.
21
+ attr_accessor :logger
22
+
23
+ # Internal: level-gated and lazy — the block only runs when a logger
24
+ # is listening. Messages carry "graph_weaver" as progname.
25
+ def log(level, &block)
26
+ logger&.public_send(level, "graph_weaver", &block)
27
+ end
28
+
29
+ # Internal: run the block, logging "<label> (Nms)" at level — timing
30
+ # skipped entirely when no logger is set. Returns the block's value.
31
+ def log_timed(level, label)
32
+ return yield unless logger
33
+
34
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
35
+ result = yield
36
+ ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round
37
+ log(level) { "#{label} (#{ms}ms)" }
38
+ result
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ # typed: ignore — Rails::Railtie DSL
2
+ # frozen_string_literal: true
3
+
4
+ # Rails wiring, so the conventional layout needs no ceremony:
5
+ #
6
+ # - rake tasks: Rails.application.load_tasks collects every Railtie's
7
+ # rake_tasks block, so graph_weaver:* tasks appear with no Rakefile
8
+ # edit. (Outside Rails there is no task-discovery hook — add
9
+ # `require "graph_weaver/tasks"` to your Rakefile.)
10
+ # - generated modules: required at boot when generated_path exists,
11
+ # after config/initializers (registrations and GraphWeaver.client=
12
+ # run first — block-built type helpers must exist before the files
13
+ # that include them load). load_generated! stays idempotent, so
14
+ # calling it yourself too is harmless.
15
+ class GraphWeaver::Railtie < Rails::Railtie
16
+ rake_tasks do
17
+ require "graph_weaver/tasks"
18
+ end
19
+
20
+ # Rails.logger, unless the app already chose one (set
21
+ # GraphWeaver.logger = nil in an initializer to silence)
22
+ initializer "graph_weaver.logger" do
23
+ GraphWeaver.logger = Rails.logger if GraphWeaver.logger.nil?
24
+ end
25
+
26
+ initializer "graph_weaver.load_generated", after: :load_config_initializers do
27
+ # entries may be globs, so Dir[] rather than Dir.exist?
28
+ GraphWeaver.load_generated! if GraphWeaver.generated_paths.any? { |path| Dir[path].any? }
29
+ end
30
+ end
@@ -0,0 +1,97 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "errors"
5
+
6
+ # Wraps any client/transport with configurable retries — it satisfies
7
+ # the same execute contract, so it layers over HTTP, Faraday, or
8
+ # anything else:
9
+ #
10
+ # client = GraphWeaver::Retry.new(
11
+ # GraphWeaver::Transport::HTTP.new(url),
12
+ # tries: 5, # total attempts, first included
13
+ # on: [GraphWeaver::TransportError, GraphWeaver::ServerError],
14
+ # backoff: :exponential, # or :linear, or ->(attempt) { seconds }
15
+ # base: 0.5, max: 30, # seconds; delays clamp at max:
16
+ # jitter: true, # randomize each delay by 50-100%
17
+ # retry_codes: ["THROTTLED"], # also retry GraphQL errors by code
18
+ # )
19
+ #
20
+ # What retries, by default:
21
+ # - TransportError: always (the request never arrived)
22
+ # - ServerError: only 5xx — a 4xx is a bug in the request, retrying
23
+ # won't fix it. Override with retry_if: ->(error) { ... }
24
+ # - responses whose GraphQL error codes intersect retry_codes: (off by
25
+ # default — pass the codes your API uses for transient failures)
26
+ #
27
+ # Exhausting tries re-raises the last error (or returns the last
28
+ # code-matched response).
29
+ class GraphWeaver::Retry
30
+ BACKOFFS = {
31
+ exponential: ->(base, attempt) { base * (2**(attempt - 1)) },
32
+ linear: ->(base, attempt) { base * attempt },
33
+ }.freeze
34
+
35
+ # retry 5xx, not 4xx; everything else listed in on: retries
36
+ DEFAULT_RETRY_IF = lambda do |error|
37
+ !error.is_a?(GraphWeaver::ServerError) || error.status >= 500
38
+ end
39
+
40
+ def initialize(client, tries: 3, on: [GraphWeaver::TransportError, GraphWeaver::ServerError],
41
+ backoff: :exponential, base: 0.5, max: 30, jitter: true, retry_if: DEFAULT_RETRY_IF,
42
+ retry_codes: [], sleeper: nil)
43
+ raise ArgumentError, "tries: must be >= 1" unless tries >= 1
44
+
45
+ @client = client
46
+ @tries = tries
47
+ @on = on
48
+ @backoff = if backoff.is_a?(Proc)
49
+ ->(_base, attempt) { backoff.call(attempt) } # custom: ->(attempt) { seconds }
50
+ else
51
+ BACKOFFS.fetch(backoff) {
52
+ raise ArgumentError, "backoff: must be :exponential, :linear, or a Proc, got #{backoff.inspect}"
53
+ }
54
+ end
55
+ @base = base
56
+ @max = max
57
+ @jitter = jitter
58
+ @retry_if = retry_if
59
+ @retry_codes = retry_codes
60
+ @sleeper = sleeper || ->(seconds) { sleep(seconds) }
61
+ end
62
+
63
+ # surface the wrapped transport's endpoint (schema-dump provenance)
64
+ def url
65
+ @client.url if @client.respond_to?(:url)
66
+ end
67
+
68
+ def execute(query, variables: {})
69
+ attempt = 0
70
+
71
+ loop do
72
+ attempt += 1
73
+ begin
74
+ response = @client.execute(query, variables:)
75
+ return response unless attempt < @tries && retryable_response?(response)
76
+ rescue *@on => e
77
+ raise if attempt >= @tries || !@retry_if.call(e)
78
+ end
79
+
80
+ @sleeper.call(delay(attempt))
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def retryable_response?(response)
87
+ return false if @retry_codes.empty?
88
+
89
+ codes = (response.to_h["errors"] || []).filter_map { |error| error.dig("extensions", "code") }
90
+ codes.intersect?(@retry_codes)
91
+ end
92
+
93
+ def delay(attempt)
94
+ seconds = [@backoff.call(@base, attempt), @max].min.to_f
95
+ @jitter ? seconds * (0.5 + rand * 0.5) : seconds
96
+ end
97
+ end
@@ -6,23 +6,26 @@ require_relative "testing"
6
6
  # RSpec integration — require from your spec helper instead of
7
7
  # "graph_weaver/testing":
8
8
  #
9
- # require "graph_weaver/rspec"
9
+ # require "graph_weaver/rspec"
10
10
  #
11
- # GraphWeaver::Testing.configure do |config|
12
- # config.schema = MySchema
13
- # config.auto_fake = true # every example runs against a FakeExecutor
14
- # end
11
+ # Then opt in to per-example fakes (explicit on purpose — silently
12
+ # swapping every example onto a fake is too surprising to be a default):
13
+ #
14
+ # GraphWeaver::Testing.configure do |config|
15
+ # config.auto_fake = true # every example runs against a fake
16
+ # # config.schema = MySchema # optional: the committed dump auto-locates
17
+ # end
15
18
  #
16
19
  # What it wires up:
17
20
  # - seed: defaults to rspec's --seed, so `rspec --seed 1234` reproduces
18
21
  # fake data along with test order
19
- # - auto_fake: when on (and schema is set), each example gets a fresh
20
- # seeded FakeExecutor installed as GraphWeaver.executor generated
21
- # modules run in test mode with zero per-test setup. The prior
22
- # executor is restored after each example.
22
+ # - auto_fake: when on (and a schema resolves), each example gets a
23
+ # fresh seeded FakeClient installed as GraphWeaver.client
24
+ # generated modules run in test mode with zero per-test setup. The
25
+ # prior client is restored after each example.
23
26
  #
24
- # note: modules generated with a baked-in executor: constant don't consult
25
- # GraphWeaver.executor — generate without executor: to make them fakeable.
27
+ # note: modules generated with a baked-in client: constant don't consult
28
+ # GraphWeaver.client — generate without client: to make them fakeable.
26
29
  module GraphWeaver
27
30
  module Testing
28
31
  module RSpecIntegration
@@ -36,15 +39,15 @@ module GraphWeaver
36
39
  config = GraphWeaver::Testing.config
37
40
  next unless config.auto_fake && config.schema
38
41
 
39
- @__graph_weaver_prior_executor = GraphWeaver.instance_variable_get(:@executor)
40
- GraphWeaver.executor = FakeExecutor.new(schema: config.schema)
42
+ @__graph_weaver_prior_client = GraphWeaver.client
43
+ GraphWeaver.client = FakeClient.new(schema: config.schema)
41
44
  end
42
45
 
43
46
  rspec_config.after(:each) do
44
47
  config = GraphWeaver::Testing.config
45
48
  next unless config.auto_fake && config.schema
46
49
 
47
- GraphWeaver.executor = @__graph_weaver_prior_executor
50
+ GraphWeaver.client = @__graph_weaver_prior_client
48
51
  end
49
52
  end
50
53
  end