Class: GraphQL::Stitching::Composer
- Inherits:
-
Object
- Object
- GraphQL::Stitching::Composer
- Defined in:
- lib/graphql/stitching/composer.rb,
lib/graphql/stitching/composer/base_validator.rb,
lib/graphql/stitching/composer/validate_interfaces.rb,
lib/graphql/stitching/composer/type_resolver_config.rb,
lib/graphql/stitching/composer/validate_type_resolvers.rb
Overview
Composer receives many individual GraphQL::Schema
instances
representing various graph locations and merges them into one
combined Supergraph that is validated for integrity.
Defined Under Namespace
Classes: BaseValidator, TypeResolverConfig, ValidateInterfaces, ValidateTypeResolvers
Constant Summary collapse
- NO_DEFAULT_VALUE =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
begin t = Class.new(GraphQL::Schema::Object) do field(:f, String) { _1.argument(:a, String) } end t.get_field("f").get_argument("a").default_value end
- BASIC_VALUE_MERGER =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
->(values_by_location, _info) { values_by_location.values.find { !_1.nil? } }
- VISIBILITY_PROFILES_MERGER =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
->(values_by_location, _info) { values_by_location.values.reduce(:&) }
- COMPOSITION_VALIDATORS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
[ ValidateInterfaces, ValidateTypeResolvers, ].freeze
Instance Attribute Summary collapse
-
#mutation_name ⇒ String
readonly
Name of the Mutation type in the composed schema.
-
#query_name ⇒ String
readonly
Name of the Query type in the composed schema.
- #schema_directives ⇒ Object readonly private
- #subgraph_types_by_name_and_location ⇒ Object readonly private
-
#subscription_name ⇒ String
readonly
Name of the Subscription type in the composed schema.
Instance Method Summary collapse
- #apply_supergraph_directives(schema, resolvers_by_type_name, locations_by_type_and_field) ⇒ Object
-
#initialize(query_name: "Query", mutation_name: "Mutation", subscription_name: "Subscription", visibility_profiles: [], description_merger: nil, deprecation_merger: nil, default_value_merger: nil, directive_kwarg_merger: nil, root_field_location_selector: nil, root_entrypoints: nil) ⇒ Composer
constructor
A new instance of Composer.
- #perform(locations_input) ⇒ Object
Constructor Details
#initialize(query_name: "Query", mutation_name: "Mutation", subscription_name: "Subscription", visibility_profiles: [], description_merger: nil, deprecation_merger: nil, default_value_merger: nil, directive_kwarg_merger: nil, root_field_location_selector: nil, root_entrypoints: nil) ⇒ Composer
Returns a new instance of Composer.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/graphql/stitching/composer.rb', line 50 def initialize( query_name: "Query", mutation_name: "Mutation", subscription_name: "Subscription", visibility_profiles: [], description_merger: nil, deprecation_merger: nil, default_value_merger: nil, directive_kwarg_merger: nil, root_field_location_selector: nil, root_entrypoints: nil ) @query_name = query_name @mutation_name = mutation_name @subscription_name = subscription_name @description_merger = description_merger || BASIC_VALUE_MERGER @deprecation_merger = deprecation_merger || BASIC_VALUE_MERGER @default_value_merger = default_value_merger || BASIC_VALUE_MERGER @directive_kwarg_merger = directive_kwarg_merger || BASIC_VALUE_MERGER @root_field_location_selector = root_field_location_selector @root_entrypoints = root_entrypoints || {} @field_map = {} @resolver_map = {} @resolver_configs = {} @mapped_type_names = {} @visibility_profiles = Set.new(visibility_profiles) @subgraph_directives_by_name_and_location = nil @subgraph_types_by_name_and_location = nil @schema_directives = nil end |
Instance Attribute Details
#mutation_name ⇒ String (readonly)
Returns name of the Mutation type in the composed schema.
39 40 41 |
# File 'lib/graphql/stitching/composer.rb', line 39 def mutation_name @mutation_name end |
#query_name ⇒ String (readonly)
Returns name of the Query type in the composed schema.
36 37 38 |
# File 'lib/graphql/stitching/composer.rb', line 36 def query_name @query_name end |
#schema_directives ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
48 49 50 |
# File 'lib/graphql/stitching/composer.rb', line 48 def schema_directives @schema_directives end |
#subgraph_types_by_name_and_location ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
45 46 47 |
# File 'lib/graphql/stitching/composer.rb', line 45 def subgraph_types_by_name_and_location @subgraph_types_by_name_and_location end |
#subscription_name ⇒ String (readonly)
Returns name of the Subscription type in the composed schema.
42 43 44 |
# File 'lib/graphql/stitching/composer.rb', line 42 def subscription_name @subscription_name end |
Instance Method Details
#apply_supergraph_directives(schema, resolvers_by_type_name, locations_by_type_and_field) ⇒ Object
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 |
# File 'lib/graphql/stitching/composer.rb', line 712 def apply_supergraph_directives(schema, resolvers_by_type_name, locations_by_type_and_field) schema_directives = {} schema.types.each do |type_name, type| if resolvers_for_type = resolvers_by_type_name.dig(type_name) # Apply key directives for each unique type/key/location # (this allows keys to be composite selections and/or omitted from the supergraph schema) keys_for_type = resolvers_for_type.each_with_object({}) do |resolver, memo| memo[resolver.key.to_definition] ||= Set.new memo[resolver.key.to_definition].merge(resolver.key.locations) end keys_for_type.each do |key, locations| locations.each do |location| schema_directives[Directives::SupergraphKey.graphql_name] ||= Directives::SupergraphKey type.directive(Directives::SupergraphKey, key: key, location: location) end end # Apply resolver directives for each unique query resolver resolvers_for_type.each do |resolver| params = { location: resolver.location, field: resolver.field, list: resolver.list? || nil, key: resolver.key.to_definition, arguments: resolver.arguments.map(&:to_definition).join(", "), argument_types: resolver.arguments.map(&:to_type_definition).join(", "), type_name: (resolver.type_name if resolver.type_name != type_name), } schema_directives[Directives::SupergraphResolver.graphql_name] ||= Directives::SupergraphResolver type.directive(Directives::SupergraphResolver, **params.tap(&:compact!)) end end next unless type.kind.fields? && !type.introspection? type.fields.each do |field_name, field| if field.owner != type # make a local copy of fields inherited from an interface # to assure that source attributions reflect the object, not the interface. field = type.field( field.graphql_name, description: field.description, deprecation_reason: field.deprecation_reason, type: Util.unwrap_non_null(field.type), null: !field.type.non_null?, connection: false, camelize: false, ) end locations_for_field = locations_by_type_and_field.dig(type_name, field_name) next if locations_for_field.nil? # Apply source directives to annotate the possible locations of each field locations_for_field.each do |location| schema_directives[Directives::SupergraphSource.graphql_name] ||= Directives::SupergraphSource field.directive(Directives::SupergraphSource, location: location) end end end schema_directives.each_value { |directive_class| schema.directive(directive_class) } end |
#perform(locations_input) ⇒ Object
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/graphql/stitching/composer.rb', line 82 def perform(locations_input) if @subgraph_types_by_name_and_location raise CompositionError, "Composer may only perform once per instance." end schemas, executables = prepare_locations_input(locations_input) directives_to_omit = [ GraphQL::Stitching.stitch_directive, Directives::SupergraphKey.graphql_name, Directives::SupergraphResolver.graphql_name, Directives::SupergraphSource.graphql_name, ] # "directive_name" => "location" => subgraph_directive @subgraph_directives_by_name_and_location = schemas.each_with_object({}) do |(location, schema), memo| (schema.directives.keys - schema.default_directives.keys - directives_to_omit).each do |directive_name| memo[directive_name] ||= {} memo[directive_name][location] = schema.directives[directive_name] end end # "directive_name" => merged_directive @schema_directives = @subgraph_directives_by_name_and_location.each_with_object({}) do |(directive_name, directives_by_location), memo| memo[directive_name] = build_directive(directive_name, directives_by_location) end @schema_directives.merge!(GraphQL::Schema.default_directives) # "Typename" => "location" => subgraph_type @subgraph_types_by_name_and_location = schemas.each_with_object({}) do |(location, schema), memo| raise CompositionError, "Location keys must be strings" unless location.is_a?(String) schema.types.each do |type_name, subgraph_type| next if subgraph_type.introspection? if type_name == @query_name && subgraph_type != schema.query raise CompositionError, "Query name \"#{@query_name}\" is used by non-query type in #{location} schema." elsif type_name == @mutation_name && subgraph_type != schema.mutation raise CompositionError, "Mutation name \"#{@mutation_name}\" is used by non-mutation type in #{location} schema." elsif type_name == @subscription_name && subgraph_type != schema.subscription raise CompositionError, "Subscription name \"#{@subscription_name}\" is used by non-subscription type in #{location} schema." end type_name = @query_name if subgraph_type == schema.query type_name = @mutation_name if subgraph_type == schema.mutation type_name = @subscription_name if subgraph_type == schema.subscription @mapped_type_names[subgraph_type.graphql_name] = type_name if subgraph_type.graphql_name != type_name memo[type_name] ||= {} memo[type_name][location] = subgraph_type end end enum_usage = build_enum_usage_map(schemas.values) # "Typename" => merged_type schema_types = @subgraph_types_by_name_and_location.each_with_object({}) do |(type_name, types_by_location), memo| kinds = types_by_location.values.map { _1.kind.name }.tap(&:uniq!) if kinds.length > 1 raise CompositionError, "Cannot merge different kinds for `#{type_name}`. Found: #{kinds.join(", ")}." end extract_resolvers(type_name, types_by_location) if type_name == @query_name memo[type_name] = case kinds.first when "SCALAR" build_scalar_type(type_name, types_by_location) when "ENUM" build_enum_type(type_name, types_by_location, enum_usage) when "OBJECT" build_object_type(type_name, types_by_location) when "INTERFACE" build_interface_type(type_name, types_by_location) when "UNION" build_union_type(type_name, types_by_location) when "INPUT_OBJECT" build_input_object_type(type_name, types_by_location) else raise CompositionError, "Unexpected kind encountered for `#{type_name}`. Found: #{kinds.first}." end end builder = self schema = Class.new(GraphQL::Schema) do object_types = schema_types.values.select { |t| t.respond_to?(:kind) && t.kind.object? } add_type_and_traverse(schema_types.values, root: false) orphan_types(object_types) query schema_types[builder.query_name] mutation schema_types[builder.mutation_name] subscription schema_types[builder.subscription_name] directives builder.schema_directives.values object_types.each do |t| t.interfaces.each { _1.orphan_types(t) } end own_orphan_types.clear end select_root_field_locations(schema) (schema, schemas) apply_supergraph_directives(schema, @resolver_map, @field_map) if (visibility_def = schema.directives[GraphQL::Stitching.visibility_directive]) visibility_def.get_argument("profiles").default_value(@visibility_profiles.to_a.sort) end supergraph = Supergraph.from_definition(schema, executables: executables) COMPOSITION_VALIDATORS.each do |validator_class| validator_class.new.perform(supergraph, self) end supergraph end |