Skip to content

Commit 6c2f834

Browse files
committed
coming together.
1 parent cd84552 commit 6c2f834

File tree

8 files changed

+89
-107
lines changed

8 files changed

+89
-107
lines changed

docs/composing_a_supergraph.md

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,7 @@ client = GraphQL::Stitching::Client.new(
4242
mutation_name: "Mutation",
4343
subscription_name: "Subscription",
4444
visibility_profiles: nil, # ["public", "private", ...]
45-
description_merger: ->(values_by_location, info) { values_by_location.values.join("\n") },
46-
deprecation_merger: ->(values_by_location, info) { values_by_location.values.first },
47-
default_value_merger: ->(values_by_location, info) { values_by_location.values.first },
48-
directive_kwarg_merger: ->(values_by_location, info) { values_by_location.values.last },
49-
root_entrypoints: {},
45+
root_entrypoints: { "Query.ping" => "pings" },
5046
},
5147
locations: {
5248
# ...
@@ -62,47 +58,49 @@ client = GraphQL::Stitching::Client.new(
6258

6359
- **`visibility_profiles:`**, an array of [visibility profiles](./visibility.md) that the supergraph responds to.
6460

65-
- **`description_merger:`**, a [value merger function](#value-merger-functions) for merging element description strings from across locations.
66-
67-
- **`deprecation_merger:`**, a [value merger function](#value-merger-functions) for merging element deprecation strings from across locations.
68-
69-
- **`default_value_merger:`**, a [value merger function](#value-merger-functions) for merging argument default values from across locations.
70-
71-
- **`directive_kwarg_merger:`**, a [value merger function](#value-merger-functions) for merging directive keyword arguments from across locations.
72-
7361
- **`root_entrypoints:`**, a hash of root field names mapped to their entrypoint locations, see [overlapping root fields](#overlapping-root-fields) below.
7462

75-
#### Value merger functions
63+
### Value mergers
7664

77-
Static data values such as element descriptions and directive arguments must also merge across locations. By default, the first non-null value encountered for a given element attribute is used. A value merger function may customize this process by selecting a different value or computing a new one:
65+
Static data values such as element descriptions and directive arguments must also merge across locations. By default, the first non-null value encountered for a given element property is used. Value merger methods can be customized by defining them on your own `Client` class:
7866

7967
```ruby
80-
join_values_merger = ->(values_by_location, info) { values_by_location.values.compact.join("\n") }
81-
82-
client = GraphQL::Stitching::Client.new(
83-
composer_options: {
84-
description_merger: join_values_merger,
85-
deprecation_merger: join_values_merger,
86-
default_value_merger: join_values_merger,
87-
directive_kwarg_merger: join_values_merger,
88-
},
89-
)
68+
class MyClient < GraphQL::Stitching::Client
69+
def merge_descriptions(values_by_location, info)
70+
# return a merged element description string from across locations...
71+
values_by_location.each_value.join("\n")
72+
end
73+
74+
def merge_deprecations(values_by_location, info)
75+
# return a merged element deprecation string from across locations...
76+
end
77+
78+
def merge_default_values(values_by_location, info)
79+
# return a merged argument default value from across locations...
80+
end
81+
82+
def merge_kwargs(values_by_location, info)
83+
# return a merged directive keyword argument from across locations...
84+
end
85+
end
86+
87+
client = MyClient.new(locations: ...)
9088
```
9189

92-
A merger function receives `values_by_location` and `info` arguments; these provide possible values keyed by location and info about where in the schema these values were encountered:
90+
All merge functions receive `values_by_location` and `info` arguments; these provide possible values keyed by location and info about where in the schema these values were encountered. For example:
9391

9492
```ruby
9593
values_by_location = {
96-
"users" => "A fabulous data type.",
97-
"products" => "An excellent data type.",
94+
"users" => "A fabulous data type description.",
95+
"products" => "An excellent data type description.",
9896
}
9997

100-
info = {
98+
info = GraphQL::Stitching::Formatter::Info.new(
10199
type_name: "Product",
102100
# field_name: ...,
103101
# argument_name: ...,
104102
# directive_name: ...,
105-
}
103+
)
106104
```
107105

108106
### Cached supergraphs

lib/graphql/stitching/client.rb

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ module Stitching
77
# Client is an out-of-the-box helper that assembles all
88
# stitching components into a workflow that executes requests.
99
class Client
10+
include Formatter
11+
1012
class << self
1113
def from_definition(schema, executables:)
1214
new(supergraph: Supergraph.from_definition(schema, executables: executables))
@@ -30,6 +32,7 @@ def initialize(locations: nil, supergraph: nil, composer_options: {})
3032
elsif supergraph
3133
supergraph
3234
else
35+
composer_options = { formatter: self }.merge!(composer_options)
3336
composer = Composer.new(**composer_options)
3437
composer.perform(locations)
3538
end
@@ -58,29 +61,45 @@ def execute(raw_query = nil, query: nil, variables: nil, operation_name: nil, co
5861
rescue GraphQL::ParseError, GraphQL::ExecutionError => e
5962
error_result(request, [e])
6063
rescue StandardError => e
61-
custom_message = @on_error.call(request, e) if @on_error
62-
error_result(request, [{ "message" => custom_message || "An unexpected error occured." }])
64+
error_result(request, [handle_error(request, e)])
6365
end
6466

6567
def on_cache_read(&block)
68+
Warning.warn("Using `on_cache_read` is deprecated and will be removed. Use `read_cached_plan` instead.")
6669
raise ArgumentError, "A cache read block is required." unless block_given?
6770
@on_cache_read = block
6871
end
6972

7073
def on_cache_write(&block)
74+
Warning.warn("Using `on_cache_write` is deprecated and will be removed. Use `write_cached_plan` instead.")
7175
raise ArgumentError, "A cache write block is required." unless block_given?
7276
@on_cache_write = block
7377
end
7478

7579
def on_error(&block)
80+
Warning.warn("Using `on_error` is deprecated and will be removed. Use `handle_error` instead.")
7681
raise ArgumentError, "An error handler block is required." unless block_given?
7782
@on_error = block
7883
end
7984

85+
def read_cached_plan(request)
86+
@on_cache_read.call(request) if @on_cache_read
87+
end
88+
89+
def write_cached_plan(request, plan)
90+
@on_cache_write.call(request, JSON.generate(plan.as_json)) if @on_cache_write
91+
end
92+
93+
def handle_error(request, error)
94+
message = @on_error.call(request, error) if @on_error
95+
message ||= "An unexpected error occured."
96+
{ "message" => message }
97+
end
98+
8099
private
81100

82101
def load_plan(request)
83-
if @on_cache_read && plan_json = @on_cache_read.call(request)
102+
if (plan_json = read_cached_plan(request))
84103
plan = Plan.from_json(JSON.parse(plan_json))
85104

86105
# only use plans referencing current resolver versions
@@ -89,20 +108,13 @@ def load_plan(request)
89108
end
90109
end
91110

92-
plan = request.plan
93-
94-
if @on_cache_write
95-
@on_cache_write.call(request, JSON.generate(plan.as_json))
111+
request.plan.tap do |plan|
112+
write_cached_plan(request, plan)
96113
end
97-
98-
plan
99114
end
100115

101116
def error_result(request, errors)
102-
public_errors = errors.map! do |e|
103-
e.is_a?(Hash) ? e : e.to_h
104-
end
105-
117+
public_errors = errors.map! { |e| e.is_a?(Hash) ? e : e.to_h }
106118
GraphQL::Query::Result.new(query: request, values: { "errors" => public_errors })
107119
end
108120
end

lib/graphql/stitching/composer.rb

Lines changed: 22 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ module Stitching
1111
# representing various graph locations and merges them into one
1212
# combined Supergraph that is validated for integrity.
1313
class Composer
14+
include Formatter
15+
1416
# @api private
1517
NO_DEFAULT_VALUE = begin
1618
t = Class.new(GraphQL::Schema::Object) do
@@ -20,19 +22,6 @@ class Composer
2022
t.get_field("f").get_argument("a").default_value
2123
end
2224

23-
# @api private
24-
BASIC_VALUE_MERGER = ->(values_by_location, _info) { values_by_location.each_value.find { !_1.nil? } }
25-
26-
# @api private
27-
VISIBILITY_PROFILES_MERGER = begin
28-
f = Class.new(Formatter) do
29-
def merge_kwargs(values_by_location, _info)
30-
values_by_location.each_value.reduce(:&)
31-
end
32-
end
33-
f.new
34-
end
35-
3625
# @api private
3726
COMPOSITION_VALIDATORS = [
3827
ValidateInterfaces,
@@ -59,18 +48,14 @@ def initialize(
5948
mutation_name: "Mutation",
6049
subscription_name: "Subscription",
6150
visibility_profiles: [],
62-
deprecation_merger: nil,
63-
root_field_location_selector: nil,
6451
root_entrypoints: nil,
6552
formatter: nil
6653
)
6754
@query_name = query_name
6855
@mutation_name = mutation_name
6956
@subscription_name = subscription_name
70-
@deprecation_merger = deprecation_merger || BASIC_VALUE_MERGER
71-
@root_field_location_selector = root_field_location_selector
7257
@root_entrypoints = root_entrypoints || {}
73-
@formatter = formatter || Formatter.new
58+
@formatter = formatter || self
7459

7560
@field_map = {}
7661
@resolver_map = {}
@@ -232,7 +217,7 @@ def build_directive(directive_name, directives_by_location)
232217

233218
Class.new(GraphQL::Schema::Directive) do
234219
graphql_name(directive_name)
235-
description(builder.merge_descriptions(directive_name, directives_by_location))
220+
description(builder.merge_member_descriptions(directive_name, directives_by_location))
236221
repeatable(directives_by_location.values.any?(&:repeatable?))
237222
locations(*directives_by_location.values.flat_map(&:locations).tap(&:uniq!))
238223
builder.build_merged_arguments(directive_name, directives_by_location, self, directive_name: directive_name)
@@ -249,7 +234,7 @@ def build_scalar_type(type_name, types_by_location)
249234

250235
Class.new(GraphQL::Stitching::Supergraph::ScalarType) do
251236
graphql_name(type_name)
252-
description(builder.merge_descriptions(type_name, types_by_location))
237+
description(builder.merge_member_descriptions(type_name, types_by_location))
253238
builder.build_merged_directives(type_name, types_by_location, self)
254239
end
255240
end
@@ -276,14 +261,14 @@ def build_enum_type(type_name, types_by_location, enum_usage)
276261

277262
Class.new(GraphQL::Stitching::Supergraph::EnumType) do
278263
graphql_name(type_name)
279-
description(builder.merge_descriptions(type_name, types_by_location))
264+
description(builder.merge_member_descriptions(type_name, types_by_location))
280265
builder.build_merged_directives(type_name, types_by_location, self)
281266

282267
enum_values_by_name_location.each do |value_name, enum_values_by_location|
283268
enum_value = value(value_name,
284269
value: value_name,
285-
description: builder.merge_descriptions(type_name, enum_values_by_location, enum_value: value_name),
286-
deprecation_reason: builder.merge_deprecations(type_name, enum_values_by_location, enum_value: value_name),
270+
description: builder.merge_member_descriptions(type_name, enum_values_by_location, enum_value: value_name),
271+
deprecation_reason: builder.merge_member_deprecations(type_name, enum_values_by_location, enum_value: value_name),
287272
)
288273

289274
builder.build_merged_directives(type_name, enum_values_by_location, enum_value, enum_value: value_name)
@@ -298,7 +283,7 @@ def build_object_type(type_name, types_by_location)
298283

299284
Class.new(GraphQL::Stitching::Supergraph::ObjectType) do
300285
graphql_name(type_name)
301-
description(builder.merge_descriptions(type_name, types_by_location))
286+
description(builder.merge_member_descriptions(type_name, types_by_location))
302287

303288
interface_names = types_by_location.values.flat_map { _1.interfaces.map(&:graphql_name) }
304289
interface_names.tap(&:uniq!).each do |interface_name|
@@ -318,7 +303,7 @@ def build_interface_type(type_name, types_by_location)
318303
Module.new do
319304
include GraphQL::Stitching::Supergraph::InterfaceType
320305
graphql_name(type_name)
321-
description(builder.merge_descriptions(type_name, types_by_location))
306+
description(builder.merge_member_descriptions(type_name, types_by_location))
322307

323308
interface_names = types_by_location.values.flat_map { _1.interfaces.map(&:graphql_name) }
324309
interface_names.tap(&:uniq!).each do |interface_name|
@@ -337,7 +322,7 @@ def build_union_type(type_name, types_by_location)
337322

338323
Class.new(GraphQL::Stitching::Supergraph::UnionType) do
339324
graphql_name(type_name)
340-
description(builder.merge_descriptions(type_name, types_by_location))
325+
description(builder.merge_member_descriptions(type_name, types_by_location))
341326

342327
possible_names = types_by_location.values.flat_map { _1.possible_types.map(&:graphql_name) }.tap(&:uniq!)
343328
possible_types(*possible_names.map { builder.build_type_binding(_1) })
@@ -352,7 +337,7 @@ def build_input_object_type(type_name, types_by_location)
352337

353338
Class.new(GraphQL::Stitching::Supergraph::InputObjectType) do
354339
graphql_name(type_name)
355-
description(builder.merge_descriptions(type_name, types_by_location))
340+
description(builder.merge_member_descriptions(type_name, types_by_location))
356341
builder.build_merged_arguments(type_name, types_by_location, self)
357342
builder.build_merged_directives(type_name, types_by_location, self)
358343
end
@@ -385,8 +370,8 @@ def build_merged_fields(type_name, types_by_location, owner)
385370
type = merge_value_types(type_name, value_types, field_name: field_name)
386371
schema_field = owner.field(
387372
field_name,
388-
description: merge_descriptions(type_name, fields_by_location, field_name: field_name),
389-
deprecation_reason: merge_deprecations(type_name, fields_by_location, field_name: field_name),
373+
description: merge_member_descriptions(type_name, fields_by_location, field_name: field_name),
374+
deprecation_reason: merge_member_deprecations(type_name, fields_by_location, field_name: field_name),
390375
type: Util.unwrap_non_null(type),
391376
null: !type.non_null?,
392377
connection: false,
@@ -438,8 +423,8 @@ def build_merged_arguments(type_name, members_by_location, owner, field_name: ni
438423
type = merge_value_types(type_name, value_types, argument_name: argument_name, field_name: field_name)
439424
schema_argument = owner.argument(
440425
argument_name,
441-
description: merge_descriptions(type_name, arguments_by_location, argument_name: argument_name, field_name: field_name),
442-
deprecation_reason: merge_deprecations(type_name, arguments_by_location, argument_name: argument_name, field_name: field_name),
426+
description: merge_member_descriptions(type_name, arguments_by_location, argument_name: argument_name, field_name: field_name),
427+
deprecation_reason: merge_member_deprecations(type_name, arguments_by_location, argument_name: argument_name, field_name: field_name),
443428
type: Util.unwrap_non_null(type),
444429
required: type.non_null?,
445430
camelize: false,
@@ -484,7 +469,7 @@ def build_merged_directives(type_name, members_by_location, owner, field_name: n
484469

485470
if (profiles = kwarg_values_by_name_location["profiles"])
486471
@visibility_profiles.merge(profiles.each_value.reduce(&:|))
487-
kwarg_formatter = VISIBILITY_PROFILES_MERGER
472+
kwarg_formatter = self
488473
end
489474
end
490475

@@ -538,7 +523,7 @@ def merge_value_types(type_name, subgraph_types, field_name: nil, argument_name:
538523

539524
# @!scope class
540525
# @!visibility private
541-
def merge_descriptions(type_name, members_by_location, field_name: nil, argument_name: nil, enum_value: nil)
526+
def merge_member_descriptions(type_name, members_by_location, field_name: nil, argument_name: nil, enum_value: nil)
542527
strings_by_location = members_by_location.each_with_object({}) { |(l, m), memo| memo[l] = m.description }
543528
@formatter.merge_descriptions(strings_by_location, Formatter::Info.new(
544529
type_name: type_name,
@@ -550,14 +535,14 @@ def merge_descriptions(type_name, members_by_location, field_name: nil, argument
550535

551536
# @!scope class
552537
# @!visibility private
553-
def merge_deprecations(type_name, members_by_location, field_name: nil, argument_name: nil, enum_value: nil)
538+
def merge_member_deprecations(type_name, members_by_location, field_name: nil, argument_name: nil, enum_value: nil)
554539
strings_by_location = members_by_location.each_with_object({}) { |(l, m), memo| memo[l] = m.deprecation_reason }
555-
@deprecation_merger.call(strings_by_location, {
540+
@formatter.merge_deprecations(strings_by_location, Formatter::Info.new(
556541
type_name: type_name,
557542
field_name: field_name,
558543
argument_name: argument_name,
559544
enum_value: enum_value,
560-
}.tap(&:compact!))
545+
))
561546
end
562547

563548
# @!scope class
@@ -632,15 +617,7 @@ def select_root_field_locations(schema)
632617
next unless root_field_locations.length > 1
633618

634619
root_field_path = "#{root_type.graphql_name}.#{root_field_name}"
635-
target_location = if @root_field_location_selector && @root_entrypoints.empty?
636-
Warning.warn("Composer option `root_field_location_selector` is deprecated and will be removed.")
637-
@root_field_location_selector.call(root_field_locations, {
638-
type_name: root_type.graphql_name,
639-
field_name: root_field_name,
640-
})
641-
else
642-
@root_entrypoints[root_field_path] || root_field_locations.last
643-
end
620+
target_location = @root_entrypoints[root_field_path] || root_field_locations.last
644621

645622
unless root_field_locations.include?(target_location)
646623
raise CompositionError, "Invalid `root_entrypoints` configuration: `#{root_field_path}` has no `#{target_location}` location."

lib/graphql/stitching/formatter.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
module GraphQL
77
module Stitching
8-
class Formatter
8+
module Formatter
99
class Info
1010
attr_reader :type_name, :field_name, :argument_name, :enum_value, :directive_name, :kwarg_name
1111

@@ -43,7 +43,11 @@ def merge_default_values(values_by_location, info)
4343
end
4444

4545
def merge_kwargs(values_by_location, info)
46-
merge_values(values_by_location, info)
46+
if info.directive_name == GraphQL::Stitching.visibility_directive
47+
values_by_location.each_value.reduce(:&)
48+
else
49+
merge_values(values_by_location, info)
50+
end
4751
end
4852
end
4953
end

0 commit comments

Comments
 (0)