Skip to content

Commit 4cb6ae5

Browse files
committed
add tests for merging visibility.
1 parent 1146daa commit 4cb6ae5

File tree

12 files changed

+815
-211
lines changed

12 files changed

+815
-211
lines changed

lib/graphql/stitching/composer.rb

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# frozen_string_literal: true
22

33
require_relative "composer/base_validator"
4-
require_relative "composer/supergraph_directives"
54
require_relative "composer/validate_interfaces"
65
require_relative "composer/validate_type_resolvers"
76
require_relative "composer/type_resolver_config"
@@ -55,6 +54,7 @@ def initialize(
5554
query_name: "Query",
5655
mutation_name: "Mutation",
5756
subscription_name: "Subscription",
57+
visibility_profiles: [],
5858
description_merger: nil,
5959
deprecation_merger: nil,
6060
default_value_merger: nil,
@@ -74,6 +74,7 @@ def initialize(
7474
@resolver_map = {}
7575
@resolver_configs = {}
7676
@mapped_type_names = {}
77+
@visibility_profiles = Set.new(visibility_profiles)
7778
@subgraph_directives_by_name_and_location = nil
7879
@subgraph_types_by_name_and_location = nil
7980
@schema_directives = nil
@@ -88,9 +89,9 @@ def perform(locations_input)
8889

8990
directives_to_omit = [
9091
GraphQL::Stitching.stitch_directive,
91-
KeyDirective.graphql_name,
92-
ResolverDirective.graphql_name,
93-
SourceDirective.graphql_name,
92+
Directives::SupergraphKey.graphql_name,
93+
Directives::SupergraphResolver.graphql_name,
94+
Directives::SupergraphSource.graphql_name,
9495
]
9596

9697
# "directive_name" => "location" => subgraph_directive
@@ -184,6 +185,10 @@ def perform(locations_input)
184185
expand_abstract_resolvers(schema, schemas)
185186
apply_supergraph_directives(schema, @resolver_map, @field_map)
186187

188+
if (visibility_def = schema.directives[GraphQL::Stitching.visibility_directive])
189+
visibility_def.get_argument("profiles").default_value(@visibility_profiles.to_a.sort)
190+
end
191+
187192
supergraph = Supergraph.from_definition(schema, executables: executables)
188193

189194
COMPOSITION_VALIDATORS.each do |validator_class|
@@ -461,14 +466,6 @@ def build_merged_directives(type_name, members_by_location, owner, field_name: n
461466
# handled by deprecation_reason merger...
462467
next if directive_class.graphql_name == "deprecated"
463468

464-
if directive_class.graphql_name == GraphQL::Stitching.visibility_directive
465-
unless GraphQL::Stitching.supports_visibility?
466-
raise CompositionError, "Using `@#{GraphQL::Stitching.visibility_directive}` directive " \
467-
"for schema visibility controls requires GraphQL Ruby v2.4.15 or later."
468-
end
469-
kwarg_merger = VISIBILITY_PROFILES_MERGER
470-
end
471-
472469
kwarg_values_by_name_location = directives_by_location.each_with_object({}) do |(location, directive), memo|
473470
directive.arguments.keyword_arguments.each do |key, value|
474471
key = key.to_s
@@ -479,6 +476,18 @@ def build_merged_directives(type_name, members_by_location, owner, field_name: n
479476
end
480477
end
481478

479+
if directive_class.graphql_name == GraphQL::Stitching.visibility_directive
480+
unless GraphQL::Stitching.supports_visibility?
481+
raise CompositionError, "Using `@#{GraphQL::Stitching.visibility_directive}` directive " \
482+
"for schema visibility controls requires GraphQL Ruby v2.4.15 or later."
483+
end
484+
485+
if (profiles = kwarg_values_by_name_location["profiles"])
486+
@visibility_profiles.merge(profiles.each_value.reduce(&:|))
487+
kwarg_merger = VISIBILITY_PROFILES_MERGER
488+
end
489+
end
490+
482491
kwargs = kwarg_values_by_name_location.each_with_object({}) do |(kwarg_name, kwarg_values_by_location), memo|
483492
memo[kwarg_name.to_sym] = kwarg_merger.call(kwarg_values_by_location, {
484493
type_name: type_name,
@@ -705,8 +714,8 @@ def apply_supergraph_directives(schema, resolvers_by_type_name, locations_by_typ
705714

706715
keys_for_type.each do |key, locations|
707716
locations.each do |location|
708-
schema_directives[KeyDirective.graphql_name] ||= KeyDirective
709-
type.directive(KeyDirective, key: key, location: location)
717+
schema_directives[Directives::SupergraphKey.graphql_name] ||= Directives::SupergraphKey
718+
type.directive(Directives::SupergraphKey, key: key, location: location)
710719
end
711720
end
712721

@@ -722,8 +731,8 @@ def apply_supergraph_directives(schema, resolvers_by_type_name, locations_by_typ
722731
type_name: (resolver.type_name if resolver.type_name != type_name),
723732
}
724733

725-
schema_directives[ResolverDirective.graphql_name] ||= ResolverDirective
726-
type.directive(ResolverDirective, **params.tap(&:compact!))
734+
schema_directives[Directives::SupergraphResolver.graphql_name] ||= Directives::SupergraphResolver
735+
type.directive(Directives::SupergraphResolver, **params.tap(&:compact!))
727736
end
728737
end
729738

@@ -749,8 +758,8 @@ def apply_supergraph_directives(schema, resolvers_by_type_name, locations_by_typ
749758

750759
# Apply source directives to annotate the possible locations of each field
751760
locations_for_field.each do |location|
752-
schema_directives[SourceDirective.graphql_name] ||= SourceDirective
753-
field.directive(SourceDirective, location: location)
761+
schema_directives[Directives::SupergraphSource.graphql_name] ||= Directives::SupergraphSource
762+
field.directive(Directives::SupergraphSource, location: location)
754763
end
755764
end
756765
end

lib/graphql/stitching/composer/supergraph_directives.rb

Lines changed: 0 additions & 33 deletions
This file was deleted.

lib/graphql/stitching/directives.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,40 @@ class Stitch < GraphQL::Schema::Directive
1010
argument :type_name, String, required: false
1111
repeatable true
1212
end
13+
14+
class Visibility < GraphQL::Schema::Directive
15+
graphql_name "visibility"
16+
locations(OBJECT, INTERFACE, UNION, INPUT_OBJECT, ENUM, SCALAR,
17+
FIELD_DEFINITION, ARGUMENT_DEFINITION, INPUT_FIELD_DEFINITION, ENUM_VALUE)
18+
argument :profiles, [String, null: false], required: true
19+
end
20+
21+
class SupergraphKey < GraphQL::Schema::Directive
22+
graphql_name "key"
23+
locations OBJECT, INTERFACE, UNION
24+
argument :key, String, required: true
25+
argument :location, String, required: true
26+
repeatable true
27+
end
28+
29+
class SupergraphResolver < GraphQL::Schema::Directive
30+
graphql_name "resolver"
31+
locations OBJECT, INTERFACE, UNION
32+
argument :location, String, required: true
33+
argument :list, Boolean, required: false
34+
argument :key, String, required: true
35+
argument :field, String, required: true
36+
argument :arguments, String, required: true
37+
argument :argument_types, String, required: true
38+
argument :type_name, String, required: false
39+
repeatable true
40+
end
41+
42+
class SupergraphSource < GraphQL::Schema::Directive
43+
graphql_name "source"
44+
locations FIELD_DEFINITION
45+
argument :location, String, required: true
46+
repeatable true
47+
end
1348
end
1449
end

lib/graphql/stitching/supergraph.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Supergraph
2222
attr_reader :memoized_introspection_types
2323
attr_reader :locations_by_type_and_field
2424

25-
def initialize(schema:, fields: {}, resolvers: {}, executables: {})
25+
def initialize(schema:, fields: {}, resolvers: {}, visibility_profiles: [], executables: {})
2626
@schema = schema
2727
@resolvers = resolvers
2828
@resolvers_by_version = nil
@@ -50,7 +50,12 @@ def initialize(schema:, fields: {}, resolvers: {}, executables: {})
5050
end
5151
end.freeze
5252

53-
@schema.use(GraphQL::Schema::AlwaysVisible)
53+
if visibility_profiles.any?
54+
profiles = visibility_profiles.each_with_object({ nil => {} }) { |p, m| m[p.to_s] = {} }
55+
@schema.use(GraphQL::Schema::Visibility, profiles: profiles)
56+
else
57+
@schema.use(GraphQL::Schema::AlwaysVisible)
58+
end
5459
end
5560

5661
def to_definition

lib/graphql/stitching/supergraph/base_types.rb

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,20 @@ module GraphQL::Stitching
44
class Supergraph
55
module Visibility
66
def visible?(ctx)
7+
# named_class = self.class
8+
# named_class = self.superclass while named_class.name.nil?
9+
# puts "#{named_class}, #{graphql_name}"
10+
711
profile = ctx[:visibility_profile]
812
return true if profile.nil?
913

10-
directive = directives.find { _1.graphql_name == Stitching.visibility_directive }
14+
directive = directives.find { _1.graphql_name == GraphQL::Stitching.visibility_directive }
1115
return true if directive.nil?
1216

13-
directive.arguments.keyword_arguments[:profiles].include?(profile)
17+
profiles = directive.arguments.keyword_arguments[:profiles]
18+
return true if profiles.nil?
19+
20+
profiles.include?(profile)
1421
end
1522
end
1623

@@ -24,18 +31,21 @@ class BaseField < GraphQL::Schema::Field
2431
end
2532

2633
class BaseInputObject < GraphQL::Schema::InputObject
27-
include Visibility
34+
extend Visibility
2835
argument_class(BaseArgument)
2936
end
3037

3138
module BaseInterface
3239
include GraphQL::Schema::Interface
33-
include Visibility
3440
field_class(BaseField)
41+
42+
definition_methods do
43+
include Visibility
44+
end
3545
end
3646

3747
class BaseObject < GraphQL::Schema::Object
38-
include Visibility
48+
extend Visibility
3949
field_class(BaseField)
4050
end
4151

@@ -44,16 +54,16 @@ class BaseEnumValue < GraphQL::Schema::EnumValue
4454
end
4555

4656
class BaseEnum < GraphQL::Schema::Enum
47-
include Visibility
57+
extend Visibility
4858
enum_value_class(BaseEnumValue)
4959
end
5060

5161
class BaseScalar < GraphQL::Schema::Scalar
52-
include Visibility
62+
extend Visibility
5363
end
5464

5565
class BaseUnion < GraphQL::Schema::Union
56-
include Visibility
66+
extend Visibility
5767
end
5868

5969
BASE_TYPES = {

lib/graphql/stitching/supergraph/from_definition.rb

Lines changed: 31 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,22 @@ def from_definition(schema, executables:)
1717
GraphQL::Schema.from_definition(schema)
1818
end
1919
end
20-
20+
2121
field_map = {}
2222
resolver_map = {}
2323
possible_locations = {}
24+
visibility_profiles = if (visibility_def = schema.directives[GraphQL::Stitching.visibility_directive])
25+
visibility_def.get_argument("profiles").default_value
26+
else
27+
[]
28+
end
2429

2530
schema.types.each do |type_name, type|
2631
next if type.introspection?
2732

2833
# Collect/build key definitions for each type
2934
locations_by_key = type.directives.each_with_object({}) do |directive, memo|
30-
next unless directive.graphql_name == Composer::KeyDirective.graphql_name
35+
next unless directive.graphql_name == Directives::SupergraphKey.graphql_name
3136

3237
kwargs = directive.arguments.keyword_arguments
3338
memo[kwargs[:key]] ||= []
@@ -40,45 +45,33 @@ def from_definition(schema, executables:)
4045

4146
# Collect/build resolver definitions for each type
4247
type.directives.each do |d|
43-
case d.graphql_name
44-
when Composer::ResolverDirective.graphql_name
45-
kwargs = d.arguments.keyword_arguments
46-
resolver_map[type_name] ||= []
47-
resolver_map[type_name] << TypeResolver.new(
48-
location: kwargs[:location],
49-
type_name: kwargs.fetch(:type_name, type_name),
50-
field: kwargs[:field],
51-
list: kwargs[:list] || false,
52-
key: key_definitions[kwargs[:key]],
53-
arguments: TypeResolver.parse_arguments_with_type_defs(kwargs[:arguments], kwargs[:argument_types]),
54-
)
55-
when Stitching.visibility_directive
56-
# collect profiles...
57-
end
48+
next unless d.graphql_name == Directives::SupergraphResolver.graphql_name
49+
50+
kwargs = d.arguments.keyword_arguments
51+
resolver_map[type_name] ||= []
52+
resolver_map[type_name] << TypeResolver.new(
53+
location: kwargs[:location],
54+
type_name: kwargs.fetch(:type_name, type_name),
55+
field: kwargs[:field],
56+
list: kwargs[:list] || false,
57+
key: key_definitions[kwargs[:key]],
58+
arguments: TypeResolver.parse_arguments_with_type_defs(kwargs[:arguments], kwargs[:argument_types]),
59+
)
5860
end
5961

60-
if type.kind.fields?
61-
type.fields.each do |field_name, field|
62-
# Collection locations for each field definition
63-
field.directives.each do |d|
64-
case d.graphql_name
65-
when Composer::SourceDirective.graphql_name
66-
location = d.arguments.keyword_arguments[:location]
67-
field_map[type_name] ||= {}
68-
field_map[type_name][field_name] ||= []
69-
field_map[type_name][field_name] << location
70-
possible_locations[location] = true
71-
when Stitching.visibility_directive
72-
# collect profiles...
73-
end
74-
end
62+
next unless type.kind.fields?
7563

76-
collect_visibility_profiles(field.arguments)
64+
type.fields.each do |field_name, field|
65+
# Collection locations for each field definition
66+
field.directives.each do |d|
67+
next unless d.graphql_name == Directives::SupergraphSource.graphql_name
68+
69+
location = d.arguments.keyword_arguments[:location]
70+
field_map[type_name] ||= {}
71+
field_map[type_name][field_name] ||= []
72+
field_map[type_name][field_name] << location
73+
possible_locations[location] = true
7774
end
78-
elsif type.kind.input_object?
79-
collect_visibility_profiles(type.arguments)
80-
elsif type.kind.enum?
81-
collect_visibility_profiles(type.values)
8275
end
8376
end
8477

@@ -93,18 +86,10 @@ def from_definition(schema, executables:)
9386
schema: schema,
9487
fields: field_map,
9588
resolvers: resolver_map,
89+
visibility_profiles: visibility_profiles,
9690
executables: executables,
9791
)
9892
end
99-
100-
def collect_visibility_profiles(elements)
101-
elements.each do |element_name, element|
102-
element.directives.each do |d|
103-
next unless d.graphql_name == Stitching.visibility_directive
104-
# collect profiles...
105-
end
106-
end
107-
end
10893
end
10994
end
11095
end

0 commit comments

Comments
 (0)