|
1 | 1 | # frozen_string_literal: true
|
2 | 2 |
|
3 | 3 | require_relative "composer/base_validator"
|
| 4 | +require_relative "composer/supergraph_directives" |
4 | 5 | require_relative "composer/validate_interfaces"
|
5 | 6 | require_relative "composer/validate_type_resolvers"
|
6 | 7 | require_relative "composer/type_resolver_config"
|
@@ -82,9 +83,16 @@ def perform(locations_input)
|
82 | 83 |
|
83 | 84 | schemas, executables = prepare_locations_input(locations_input)
|
84 | 85 |
|
| 86 | + directives_to_omit = [ |
| 87 | + GraphQL::Stitching.stitch_directive, |
| 88 | + KeyDirective.graphql_name, |
| 89 | + ResolverDirective.graphql_name, |
| 90 | + SourceDirective.graphql_name, |
| 91 | + ] |
| 92 | + |
85 | 93 | # "directive_name" => "location" => subgraph_directive
|
86 | 94 | @subgraph_directives_by_name_and_location = schemas.each_with_object({}) do |(location, schema), memo|
|
87 |
| - (schema.directives.keys - schema.default_directives.keys - GraphQL::Stitching.stitching_directive_names).each do |directive_name| |
| 95 | + (schema.directives.keys - schema.default_directives.keys - directives_to_omit).each do |directive_name| |
88 | 96 | memo[directive_name] ||= {}
|
89 | 97 | memo[directive_name][location] = schema.directives[directive_name]
|
90 | 98 | end
|
@@ -154,25 +162,26 @@ def perform(locations_input)
|
154 | 162 |
|
155 | 163 | builder = self
|
156 | 164 | schema = Class.new(GraphQL::Schema) do
|
| 165 | + object_types = schema_types.values.select { |t| t.respond_to?(:kind) && t.kind.object? } |
157 | 166 | add_type_and_traverse(schema_types.values, root: false)
|
158 |
| - orphan_types(schema_types.values.select { |t| t.respond_to?(:kind) && t.kind.object? }) |
| 167 | + orphan_types(object_types) |
159 | 168 | query schema_types[builder.query_name]
|
160 | 169 | mutation schema_types[builder.mutation_name]
|
161 | 170 | subscription schema_types[builder.subscription_name]
|
162 | 171 | directives builder.schema_directives.values
|
163 | 172 |
|
| 173 | + object_types.each do |t| |
| 174 | + t.interfaces.each { _1.orphan_types(t) } |
| 175 | + end |
| 176 | + |
164 | 177 | own_orphan_types.clear
|
165 | 178 | end
|
166 | 179 |
|
167 | 180 | select_root_field_locations(schema)
|
168 | 181 | expand_abstract_resolvers(schema, schemas)
|
| 182 | + apply_supergraph_directives(schema, @resolver_map, @field_map) |
169 | 183 |
|
170 |
| - supergraph = Supergraph.new( |
171 |
| - schema: schema, |
172 |
| - fields: @field_map, |
173 |
| - resolvers: @resolver_map, |
174 |
| - executables: executables, |
175 |
| - ) |
| 184 | + supergraph = Supergraph.from_definition(schema, executables: executables) |
176 | 185 |
|
177 | 186 | COMPOSITION_VALIDATORS.each do |validator_class|
|
178 | 187 | validator_class.new.perform(supergraph, self)
|
@@ -670,6 +679,72 @@ def build_enum_usage_map(schemas)
|
670 | 679 | memo[enum_name] << :write
|
671 | 680 | end
|
672 | 681 | end
|
| 682 | + |
| 683 | + def apply_supergraph_directives(schema, resolvers_by_type_name, locations_by_type_and_field) |
| 684 | + schema_directives = {} |
| 685 | + schema.types.each do |type_name, type| |
| 686 | + if resolvers_for_type = resolvers_by_type_name.dig(type_name) |
| 687 | + # Apply key directives for each unique type/key/location |
| 688 | + # (this allows keys to be composite selections and/or omitted from the supergraph schema) |
| 689 | + keys_for_type = resolvers_for_type.each_with_object({}) do |resolver, memo| |
| 690 | + memo[resolver.key.to_definition] ||= Set.new |
| 691 | + memo[resolver.key.to_definition].merge(resolver.key.locations) |
| 692 | + end |
| 693 | + |
| 694 | + keys_for_type.each do |key, locations| |
| 695 | + locations.each do |location| |
| 696 | + schema_directives[KeyDirective.graphql_name] ||= KeyDirective |
| 697 | + type.directive(KeyDirective, key: key, location: location) |
| 698 | + end |
| 699 | + end |
| 700 | + |
| 701 | + # Apply resolver directives for each unique query resolver |
| 702 | + resolvers_for_type.each do |resolver| |
| 703 | + params = { |
| 704 | + location: resolver.location, |
| 705 | + field: resolver.field, |
| 706 | + list: resolver.list? || nil, |
| 707 | + key: resolver.key.to_definition, |
| 708 | + arguments: resolver.arguments.map(&:to_definition).join(", "), |
| 709 | + argument_types: resolver.arguments.map(&:to_type_definition).join(", "), |
| 710 | + type_name: (resolver.type_name if resolver.type_name != type_name), |
| 711 | + } |
| 712 | + |
| 713 | + schema_directives[ResolverDirective.graphql_name] ||= ResolverDirective |
| 714 | + type.directive(ResolverDirective, **params.tap(&:compact!)) |
| 715 | + end |
| 716 | + end |
| 717 | + |
| 718 | + next unless type.kind.fields? && !type.introspection? |
| 719 | + |
| 720 | + type.fields.each do |field_name, field| |
| 721 | + if field.owner != type |
| 722 | + # make a local copy of fields inherited from an interface |
| 723 | + # to assure that source attributions reflect the object, not the interface. |
| 724 | + field = type.field( |
| 725 | + field.graphql_name, |
| 726 | + description: field.description, |
| 727 | + deprecation_reason: field.deprecation_reason, |
| 728 | + type: Util.unwrap_non_null(field.type), |
| 729 | + null: !field.type.non_null?, |
| 730 | + connection: false, |
| 731 | + camelize: false, |
| 732 | + ) |
| 733 | + end |
| 734 | + |
| 735 | + locations_for_field = locations_by_type_and_field.dig(type_name, field_name) |
| 736 | + next if locations_for_field.nil? |
| 737 | + |
| 738 | + # Apply source directives to annotate the possible locations of each field |
| 739 | + locations_for_field.each do |location| |
| 740 | + schema_directives[SourceDirective.graphql_name] ||= SourceDirective |
| 741 | + field.directive(SourceDirective, location: location) |
| 742 | + end |
| 743 | + end |
| 744 | + end |
| 745 | + |
| 746 | + schema_directives.each_value { |directive_class| schema.directive(directive_class) } |
| 747 | + end |
673 | 748 | end
|
674 | 749 | end
|
675 | 750 | end
|
0 commit comments