graph_weaver 0.3.0 → 0.4.4

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.
@@ -26,19 +26,151 @@ module GraphWeaver::SchemaLoader
26
26
  raise ArgumentError, "unsupported schema content: #{source.lstrip[0, 80].inspect}"
27
27
  end
28
28
 
29
- GraphQL::Schema.from_definition(source)
29
+ build_sdl(source)
30
30
  else # a file path
31
31
  case File.extname(source)
32
32
  when ".json"
33
33
  GraphQL::Schema.from_introspection(JSON.parse(File.read(source)))
34
34
  when ".graphql", ".gql"
35
- GraphQL::Schema.from_definition(File.read(source))
35
+ build_sdl(File.read(source))
36
36
  else
37
37
  raise ArgumentError, "unsupported schema format: #{source}"
38
38
  end
39
39
  end
40
40
  end
41
41
 
42
+ # Build a schema from SDL, first stripping Apollo Federation composition
43
+ # machinery when the SDL is a composed supergraph — so a supergraph dump
44
+ # (often the only artifact for the merged graph, and what the router
45
+ # actually serves) loads like any schema, with the federation plumbing
46
+ # gone rather than leaked into schema.types. Plain SDL passes through.
47
+ def self.build_sdl(sdl)
48
+ GraphQL::Schema.from_definition(federation_sdl?(sdl) ? strip_federation(sdl) : sdl)
49
+ end
50
+ private_class_method :build_sdl
51
+
52
+ # A composed Fed2 supergraph is marked by @join__* directives (every merged
53
+ # type carries them); a plain schema has none.
54
+ def self.federation_sdl?(sdl)
55
+ sdl.match?(/@join__\w/)
56
+ end
57
+
58
+ FEDERATION_PREFIXES = %w[join__ link__ core__].freeze
59
+ FEDERATION_DIRECTIVES = %w[link core inaccessible].to_set.freeze
60
+
61
+ # Whether a type/directive name belongs to the federation composition layer.
62
+ def self.federation_name?(name)
63
+ !!name && (name.start_with?(*FEDERATION_PREFIXES) || FEDERATION_DIRECTIVES.include?(name))
64
+ end
65
+ private_class_method :federation_name?
66
+
67
+ # Drop the composition machinery from supergraph SDL: the synthetic
68
+ # join__*/link__* type and directive definitions, and every @join__*/@link
69
+ # application on the types that remain. What's left is the merged graph's
70
+ # ordinary type shapes — exactly what codegen reads. Parsing is lenient (it's
71
+ # schema *building* that rejects the join directives), so we parse, filter the
72
+ # AST, and reprint clean SDL for from_definition — no graphql-ruby monkeypatch
73
+ # and no join__* leaking into schema.types.
74
+ def self.strip_federation(sdl)
75
+ doc = GraphQL.parse(sdl)
76
+ defs = remove_inaccessible(doc.definitions)
77
+ .reject { |defn| defn.respond_to?(:name) && federation_name?(defn.name) }
78
+ .map { |defn| strip_federation_directives(defn) }
79
+ GraphQL::Language::Nodes::Document.new(definitions: defs).to_query_string
80
+ end
81
+
82
+ # Derive the API schema by dropping every element marked @inaccessible —
83
+ # present in the federated graph but hidden from the public API the router
84
+ # serves (its common use is safely rolling out a field on a shared type).
85
+ # Cascades: a field/argument/union-member/implements referencing a removed
86
+ # type goes too, and a type left with no fields/values/members is itself
87
+ # removed — repeated to a fixpoint. So codegen matches exactly what clients
88
+ # can query, without the over-permitting a raw supergraph would allow and
89
+ # without Apollo's JS tooling to subtract the API schema.
90
+ def self.remove_inaccessible(definitions)
91
+ removed = definitions.select { |d| type_definition?(d) && inaccessible?(d) }.map(&:name).to_set
92
+ loop do
93
+ survivors = definitions
94
+ .reject { |d| type_definition?(d) && removed.include?(d.name) }
95
+ .map { |d| prune_inaccessible(d, removed) }
96
+ newly = survivors.select { |d| type_definition?(d) && type_emptied?(d) }.map(&:name)
97
+ return survivors if (newly - removed.to_a).empty?
98
+
99
+ removed.merge(newly)
100
+ end
101
+ end
102
+ private_class_method :remove_inaccessible
103
+
104
+ # A type-system type definition (object/interface/union/enum/input/scalar) —
105
+ # not a directive or schema definition.
106
+ def self.type_definition?(node)
107
+ node.respond_to?(:name) &&
108
+ !node.is_a?(GraphQL::Language::Nodes::DirectiveDefinition) &&
109
+ node.class.name.end_with?("TypeDefinition")
110
+ end
111
+ private_class_method :type_definition?
112
+
113
+ def self.inaccessible?(node)
114
+ node.respond_to?(:directives) && node.directives.any? { |d| d.name == "inaccessible" }
115
+ end
116
+ private_class_method :inaccessible?
117
+
118
+ # Whether pruning left the type with nothing the SDL grammar allows to be
119
+ # empty — a fieldless object/interface/input, a valueless enum, a memberless
120
+ # union — so it must be removed and its references cascaded.
121
+ def self.type_emptied?(node)
122
+ (node.respond_to?(:fields) && node.fields && node.fields.empty?) ||
123
+ (node.is_a?(GraphQL::Language::Nodes::EnumTypeDefinition) && node.values.empty?) ||
124
+ (node.is_a?(GraphQL::Language::Nodes::UnionTypeDefinition) && node.types.empty?)
125
+ end
126
+ private_class_method :type_emptied?
127
+
128
+ # the unwrapped (through NON_NULL/LIST) type name a field or argument references
129
+ def self.unwrapped_type_name(node)
130
+ type = node.type
131
+ type = type.of_type while type.respond_to?(:of_type)
132
+ type.name
133
+ end
134
+ private_class_method :unwrapped_type_name
135
+
136
+ # Remove @inaccessible children and children referencing a removed type,
137
+ # from a type's fields (and their arguments), enum values, union members,
138
+ # and implemented interfaces.
139
+ def self.prune_inaccessible(node, removed)
140
+ gone = lambda do |child|
141
+ inaccessible?(child) || (child.respond_to?(:type) && removed.include?(unwrapped_type_name(child)))
142
+ end
143
+
144
+ changes = {}
145
+ if node.respond_to?(:fields) && node.fields
146
+ changes[:fields] = node.fields.reject(&gone).map do |field|
147
+ args = field.respond_to?(:arguments) && field.arguments
148
+ args && args.any? ? field.merge(arguments: args.reject(&gone)) : field
149
+ end
150
+ end
151
+ changes[:values] = node.values.reject(&gone) if node.respond_to?(:values) && node.values
152
+ changes[:types] = node.types.reject { |t| removed.include?(t.name) } if node.respond_to?(:types) && node.types
153
+ if node.respond_to?(:interfaces) && node.interfaces
154
+ changes[:interfaces] = node.interfaces.reject { |i| removed.include?(i.name) }
155
+ end
156
+ changes.empty? ? node : node.merge(changes)
157
+ end
158
+ private_class_method :prune_inaccessible
159
+
160
+ # Recursively remove @join__*/@link applications from a definition and its
161
+ # fields, arguments, and enum values.
162
+ def self.strip_federation_directives(node)
163
+ changes = {}
164
+ if node.respond_to?(:directives) && node.directives
165
+ changes[:directives] = node.directives.reject { |d| federation_name?(d.name) }
166
+ end
167
+ changes[:fields] = node.fields.map { |c| strip_federation_directives(c) } if node.respond_to?(:fields) && node.fields
168
+ changes[:arguments] = node.arguments.map { |c| strip_federation_directives(c) } if node.respond_to?(:arguments) && node.arguments
169
+ changes[:values] = node.values.map { |c| strip_federation_directives(c) } if node.respond_to?(:values) && node.values
170
+ changes.empty? ? node : node.merge(changes)
171
+ end
172
+ private_class_method :strip_federation_directives
173
+
42
174
  # Run the standard introspection query through a transport and build a
43
175
  # schema from the result:
44
176
  #
@@ -1,3 +1,3 @@
1
1
  module GraphWeaver
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.4"
3
3
  end
data/lib/graph_weaver.rb CHANGED
@@ -17,9 +17,6 @@ require_relative "graph_weaver/railtie" if defined?(::Rails::Railtie)
17
17
 
18
18
  # opt-in extras:
19
19
  # require "graph_weaver/transport/faraday" # Faraday transport
20
- # require "graph_weaver/directive_defaults_patch" # fix graphql-ruby
21
- # dropping directive argument defaults when loading SDL (needed for
22
- # Apollo supergraph SDL until rmosolgo/graphql-ruby#5659 ships)
23
20
  module GraphWeaver
24
21
  class << self
25
22
  # A client for one GraphQL server — transport, schema, and scoped
@@ -67,7 +64,7 @@ module GraphWeaver
67
64
  #
68
65
  # The singular accessors read the first entry (the default target
69
66
  # for generate! and the rake tasks); assigning one replaces the list.
70
- attr_writer :queries_paths, :generated_paths, :schema_path
67
+ attr_writer :queries_paths, :generated_paths, :schema_path, :fragments_paths
71
68
 
72
69
  # Entries may be glob patterns — the generated default also matches
73
70
  # per-schema layouts (app/graphql/github/generated). Queries stay
@@ -75,8 +72,14 @@ module GraphWeaver
75
72
  def queries_paths = @queries_paths ||= ["app/graphql/queries"]
76
73
  def generated_paths = @generated_paths ||= ["app/graphql/generated", "app/graphql/*/generated"]
77
74
 
75
+ # Reusable named fragments, defined once and available to every query —
76
+ # each query inlines only the ones it (transitively) spreads, so the sent
77
+ # query stays self-contained.
78
+ def fragments_paths = @fragments_paths ||= ["app/graphql/fragments"]
79
+
78
80
  def queries_path = queries_paths.first
79
81
  def generated_path = generated_paths.first
82
+ def fragments_path = fragments_paths.first
80
83
 
81
84
  def queries_path=(path)
82
85
  @queries_paths = path.nil? ? nil : [path]
@@ -88,26 +91,35 @@ module GraphWeaver
88
91
 
89
92
  def schema_path = @schema_path || "app/graphql/schema.json"
90
93
 
91
- # The shared-inputs module name: set it globally, pass inputs_module:
92
- # per generate!, or let it derive from the output path — the
93
- # directory above generated/ names the schema in multi-schema
94
- # layouts (app/graphql/github/generated => GithubInputs); the
95
- # conventional layout (and anything unrecognizable) stays
96
- # GraphQLInputs.
97
- attr_writer :inputs_module
94
+ # The shared-inputs / shared-unions module names: set them globally, pass
95
+ # inputs_module:/unions_module: per generate!, or let them derive from the
96
+ # output path — the directory above generated/ names the schema in
97
+ # multi-schema layouts (app/graphql/github/generated => GithubInputs /
98
+ # GithubUnions); the conventional layout (and anything unrecognizable)
99
+ # stays GraphQLInputs / GraphQLUnions.
100
+ attr_writer :inputs_module, :unions_module
98
101
 
99
102
  def inputs_module(output = generated_path)
100
- return @inputs_module if @inputs_module
103
+ @inputs_module || derive_module("Inputs", output)
104
+ end
101
105
 
106
+ def unions_module(output = generated_path)
107
+ @unions_module || derive_module("Unions", output)
108
+ end
109
+
110
+ # Name a shared module from the output path: <Schema><suffix> in a
111
+ # multi-schema layout, else GraphQL<suffix>.
112
+ def derive_module(suffix, output)
102
113
  segments = File.expand_path(output.to_s).split(File::SEPARATOR)
103
114
  segments.pop if segments.last == "generated"
104
115
  parent = segments.last.to_s
105
116
  if parent.match?(/\A[a-zA-Z]\w*\z/) && !%w[graphql app lib spec support test].include?(parent)
106
- "#{Inflect.camelize(parent)}Inputs"
117
+ "#{Inflect.camelize(parent)}#{suffix}"
107
118
  else
108
- "GraphQLInputs"
119
+ "GraphQL#{suffix}"
109
120
  end
110
121
  end
122
+ private :derive_module
111
123
 
112
124
  # Generate every .graphql query in a directory into checked-in Ruby
113
125
  # files. Paths default to the conventions above; schema: defaults to
@@ -118,11 +130,12 @@ module GraphWeaver
118
130
  # person.graphql => person_query.rb defining PersonQuery. Returns the
119
131
  # written paths. Pair with a freshness spec (docs/generated_modules.md).
120
132
  def generate!(schema: nil, queries: queries_path, output: generated_path, client: nil,
121
- shared_inputs: true, inputs_module: nil)
133
+ inputs_module: nil, unions_module: nil)
122
134
  schema ||= locate_schema!
123
135
  inputs_module ||= self.inputs_module(output)
136
+ unions_module ||= self.unions_module(output)
124
137
 
125
- plan = generation_plan(queries:, schema:, client:, shared_inputs:, inputs_module:)
138
+ plan = generation_plan(queries:, schema:, client:, inputs_module:, unions_module:)
126
139
  written = plan.map do |filename, source|
127
140
  target = File.join(output, filename)
128
141
  FileUtils.mkdir_p(File.dirname(target))
@@ -131,9 +144,9 @@ module GraphWeaver
131
144
  target
132
145
  end
133
146
 
134
- # a type dropped from the schema must not linger as a stale file
135
- # inputs/ is wholly generated, so pruning is safe
136
- (Dir[File.join(output, "inputs", "*.rb")] - written).each do |orphan|
147
+ # a type dropped from the schema (or a union no longer hoisted) must not
148
+ # linger as a stale file — inputs/ and unions.rb are wholly generated
149
+ (shared_artifacts(output) - written).each do |orphan|
137
150
  File.delete(orphan)
138
151
  log(:info) { "pruned #{orphan}" }
139
152
  end
@@ -141,6 +154,13 @@ module GraphWeaver
141
154
  written
142
155
  end
143
156
 
157
+ # The wholly-generated shared-artifact files under output (inputs/*.rb and
158
+ # unions.rb) — safe to prune when regeneration no longer produces them.
159
+ def shared_artifacts(output)
160
+ Dir[File.join(output, "inputs", "*.rb")] + Dir[File.join(output, "unions.rb")]
161
+ end
162
+ private :shared_artifacts
163
+
144
164
  # The freshness guard: raise unless every generated file matches what
145
165
  # the current schema + queries + scalar registrations would produce.
146
166
  # One line in a spec, or `rake graph_weaver:verify` in CI:
@@ -149,16 +169,17 @@ module GraphWeaver
149
169
  # GraphWeaver.verify_generated!
150
170
  # end
151
171
  def verify_generated!(schema: nil, queries: queries_path, output: generated_path, client: nil,
152
- shared_inputs: true, inputs_module: nil)
172
+ inputs_module: nil, unions_module: nil)
153
173
  schema ||= locate_schema!
154
174
  inputs_module ||= self.inputs_module(output)
155
- plan = generation_plan(queries:, schema:, client:, shared_inputs:, inputs_module:)
175
+ unions_module ||= self.unions_module(output)
176
+ plan = generation_plan(queries:, schema:, client:, inputs_module:, unions_module:)
156
177
  stale = plan.filter_map do |filename, source|
157
178
  target = File.join(output, filename)
158
179
  target unless File.exist?(target) && File.read(target) == source
159
180
  end
160
- # strays: a type file the current schema no longer produces
161
- stale += Dir[File.join(output, "inputs", "*.rb")] - plan.map { |f, _| File.join(output, f) }
181
+ # strays: a shared-artifact file the current schema + queries no longer produce
182
+ stale += shared_artifacts(output) - plan.map { |f, _| File.join(output, f) }
162
183
 
163
184
  unless stale.empty?
164
185
  raise Error, "stale generated queries — regenerate (rake graph_weaver:generate): #{stale.join(", ")}"
@@ -192,34 +213,49 @@ module GraphWeaver
192
213
  end
193
214
  private :locate_schema!
194
215
 
195
- # (filename, source) per artifact: shared_inputs (the default) emits
196
- # every variable type once into inputs.rb, with query modules
197
- # aliasing what they use — the difference between hundreds of
198
- # duplicated bool_exp structs and one copy per schema.
199
- def generation_plan(queries:, schema:, client:, shared_inputs:, inputs_module: self.inputs_module)
200
- namespace = shared_inputs ? inputs_module : nil
216
+ # (filename, source) per artifact. Every variable type is emitted once into
217
+ # inputs.rb, and each named shared fragment spread as a whole-union field
218
+ # once into unions.rb, with query modules aliasing what they use — the
219
+ # difference between hundreds of duplicated bool_exp structs (or the same
220
+ # union re-typed per query) and one copy per schema. (Single-query parse
221
+ # inlines both there's no cross-query set to share against.)
222
+ def generation_plan(queries:, schema:, client:, inputs_module: self.inputs_module,
223
+ unions_module: self.unions_module, fragments: fragments_paths)
201
224
  used = { inputs: [], enums: [], mapped: [] }
225
+ used_unions = []
226
+ shared = Codegen.load_fragments(fragments)
202
227
 
203
228
  plan = Dir[File.join(queries, "*.graphql")].sort.map do |path|
204
229
  base = File.basename(path, ".graphql")
230
+ source = File.read(path)
205
231
  codegen = Codegen.new(
206
232
  schema:,
207
- query: File.read(path),
233
+ query: Codegen.inline_fragments(source, shared),
208
234
  module_name: "#{Inflect.camelize(base)}Query",
209
235
  client:,
210
- inputs_namespace: namespace,
236
+ inputs_namespace: inputs_module,
237
+ unions_namespace: unions_module,
238
+ hoistable_unions: Codegen.shared_fragment_spreads(source, shared),
211
239
  )
212
- source = codegen.generate
240
+ out = codegen.generate
213
241
  codegen.variable_type_names.each { |kind, names| used[kind] |= names }
214
- ["#{base}_query.rb", source]
242
+ used_unions |= codegen.used_union_names
243
+ ["#{base}_query.rb", out]
215
244
  end
216
245
 
217
- if namespace && used.values.any?(&:any?)
218
- shared = Codegen.generate_inputs(
219
- schema:, module_name: namespace,
246
+ if inputs_module && used.values.any?(&:any?)
247
+ inputs = Codegen.generate_inputs(
248
+ schema:, module_name: inputs_module,
220
249
  input_types: used[:inputs], enum_types: used[:enums] + used[:mapped],
221
250
  )
222
- plan = shared.to_a + plan
251
+ plan = inputs.to_a + plan
252
+ end
253
+
254
+ if unions_module && used_unions.any?
255
+ unions = Codegen.generate_unions(
256
+ schema:, module_name: unions_module, fragments: shared, names: used_unions,
257
+ )
258
+ plan = unions.to_a + plan
223
259
  end
224
260
 
225
261
  plan
@@ -238,6 +274,25 @@ module GraphWeaver
238
274
  # a registration always wins.
239
275
  attr_accessor :auto_coerce
240
276
 
277
+ # Whether generated modules/structs emit `extend T::Sig` (so `sig`
278
+ # resolves standalone). Default (nil) auto-detects: an app that globally
279
+ # injects T::Sig (`class Module; include T::Sig`) makes the per-struct
280
+ # extend redundant — rubocop's Sorbet/RedundantExtendTSig flags it — so
281
+ # generation skips it. Force with true/false. Resolved at generation time.
282
+ #
283
+ # GraphWeaver.extend_t_sig = false # never emit (rely on a global include)
284
+ attr_writer :extend_t_sig
285
+
286
+ # The resolved boolean codegen uses: the explicit setting, else emit
287
+ # unless T::Sig is globally injected into Module.
288
+ def extend_t_sig?
289
+ @extend_t_sig.nil? ? !global_tsig? : @extend_t_sig
290
+ end
291
+
292
+ # Whether the host app has globally injected T::Sig into every module
293
+ # (`class Module; include T::Sig`) — extracted so it's stubbable in tests.
294
+ def global_tsig? = Module.include?(T::Sig)
295
+
241
296
  # Teach the generator how a GraphQL custom scalar deserializes into a
242
297
  # rich Ruby object (and serializes back onto the wire when used as a
243
298
  # variable):
@@ -256,7 +311,15 @@ module GraphWeaver
256
311
  # raw input (e.g. "12.00"), running the latter through the cast before
257
312
  # serializing — it raises on bad input, so some safety survives. Built-in
258
313
  # scalars are pre-registered the same way, so this also overrides them.
259
- # Call before generating.
314
+ #
315
+ # Pass a `Type.field` coordinate instead of a scalar name to override just
316
+ # that one field — so the same scalar can deserialize as different Ruby
317
+ # types across fields (a `Date` for `User.birthday`, a `Time` elsewhere):
318
+ #
319
+ # GraphWeaver.register_scalar("User.birthday", Date)
320
+ #
321
+ # A field-level override wins over the scalar-name registration. Same
322
+ # signature either way. Call before generating.
260
323
  def register_scalar(graphql_name, type, cast: nil, serialize: nil, requires: nil, coerce: nil)
261
324
  Codegen.register_scalar(graphql_name, type, cast:, serialize:, requires:, coerce:)
262
325
  end
@@ -282,13 +345,15 @@ module GraphWeaver
282
345
  end
283
346
 
284
347
  # Include app-owned helper modules into every struct generated from a
285
- # GraphQL type — derived values live as methods next to the honest
286
- # wire data, and srb tc checks them against each query's selection:
348
+ # GraphQL type — derived values live as methods next to the honest wire
349
+ # data, on the struct at runtime (fakes/cassettes included):
287
350
  #
288
351
  # GraphWeaver.extend_type("Pet", PetHelpers)
289
352
  #
290
- # Or build the mixin inline with a block (module_eval'd into an
291
- # auto-named module quick, but invisible to srb tc):
353
+ # A helper that reads a wire field is checked by srb tc in the module's
354
+ # own scope, not the struct's, so the field won't resolve — write it
355
+ # # typed: false or reach it via T.unsafe(self). Or build the mixin inline
356
+ # with a block (module_eval'd into an auto-named module — invisible to srb):
292
357
  #
293
358
  # GraphWeaver.extend_type("Pet") do
294
359
  # def display_name = "#{name} the pet"
@@ -296,8 +361,8 @@ module GraphWeaver
296
361
  #
297
362
  # Additive (repeated and client-scoped registrations stack). Global;
298
363
  # client.extend_type scopes to one client.
299
- def extend_type(graphql_name, *mixins, requires: nil, &block)
300
- Codegen.extend_type(graphql_name, *mixins, requires:, &block)
364
+ def extend_type(graphql_name, *mixins, requires: nil, **kw, &block)
365
+ Codegen.extend_type(graphql_name, *mixins, requires:, **kw, &block)
301
366
  end
302
367
 
303
368
  # Restore the built-in scalars, dropping every custom registration —
@@ -322,11 +387,13 @@ module GraphWeaver
322
387
  # falling back to "Query" for anonymous operations — collisions are
323
388
  # impossible since each parse gets its own container). Pass name: to
324
389
  # override, client: to bake the module's default client/transport.
325
- def parse(schema:, query:, name: nil, client: nil, scalars: nil, enums: nil, types: nil)
390
+ def parse(schema:, query:, name: nil, client: nil, scalars: nil, enums: nil, types: nil,
391
+ fragments: fragments_paths)
326
392
  if query.end_with?(".graphql", ".gql")
327
393
  name ||= "#{Inflect.camelize(File.basename(query, ".*"))}Query"
328
394
  query = File.read(query)
329
395
  end
396
+ query = Codegen.inline_fragments(query, Codegen.load_fragments(fragments))
330
397
 
331
398
  Codegen.parse(schema:, query:, module_name: name, client:, scalars:, enums:, types:)
332
399
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graph_weaver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Pepper
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '2'
18
+ version: 2.6.7
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: '2'
25
+ version: 2.6.7
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: sorbet-runtime
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -253,7 +253,6 @@ files:
253
253
  - lib/graph_weaver/codegen/enum_type.rb
254
254
  - lib/graph_weaver/codegen/nodes.rb
255
255
  - lib/graph_weaver/codegen/scalar_type.rb
256
- - lib/graph_weaver/directive_defaults_patch.rb
257
256
  - lib/graph_weaver/errors.rb
258
257
  - lib/graph_weaver/hints.rb
259
258
  - lib/graph_weaver/inflect.rb
@@ -1,32 +0,0 @@
1
- # typed: false — monkeypatch; `self.class` resolves against the prepended host
2
- # frozen_string_literal: true
3
-
4
- require "graphql"
5
-
6
- # graphql-ruby's SDL builder (BuildFromDefinition#prepare_directives)
7
- # passes only the directive arguments present at the usage site, but
8
- # Directive#initialize validates ALL defined arguments — so a defaulted
9
- # non-null argument (`extension: Boolean! = false`) raises
10
- # InvalidArgumentError when omitted, even though the SDL spec makes it
11
- # optional. Real Apollo supergraph SDL (join v0.3) hits this on every
12
- # @join__type usage.
13
- #
14
- # Fill in the declared defaults before validation. This is what upstream
15
- # should do; present in graphql 2.6.3 (latest at time of writing).
16
- #
17
- # TODO: delete this file (and its requires) once
18
- # https://github.com/rmosolgo/graphql-ruby/pull/5659 lands in a released
19
- # graphql version and the Gemfile picks it up.
20
- module DirectiveDefaultsPatch
21
- def initialize(owner, **arguments)
22
- self.class.all_argument_definitions.each do |arg_defn|
23
- if !arguments.key?(arg_defn.keyword) && arg_defn.default_value?
24
- arguments[arg_defn.keyword] = arg_defn.default_value
25
- end
26
- end
27
-
28
- super(owner, **arguments)
29
- end
30
- end
31
-
32
- GraphQL::Schema::Directive.prepend(DirectiveDefaultsPatch)