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 class T < GraphQL::Schema::Object field(:f, String) do argument(:a, String) end 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? } }
- BASIC_ROOT_FIELD_LOCATION_SELECTOR =
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.
->(locations, _info) { locations.last }
- 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
-
#initialize(query_name: "Query", mutation_name: "Mutation", subscription_name: "Subscription", description_merger: nil, deprecation_merger: nil, default_value_merger: nil, directive_kwarg_merger: nil, root_field_location_selector: nil) ⇒ Composer
constructor
A new instance of Composer.
- #perform(locations_input) ⇒ Object
Constructor Details
#initialize(query_name: "Query", mutation_name: "Mutation", subscription_name: "Subscription", description_merger: nil, deprecation_merger: nil, default_value_merger: nil, directive_kwarg_merger: nil, root_field_location_selector: nil) ⇒ Composer
Returns a new instance of Composer.
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 |
# File 'lib/graphql/stitching/composer.rb', line 52 def initialize( query_name: "Query", mutation_name: "Mutation", subscription_name: "Subscription", description_merger: nil, deprecation_merger: nil, default_value_merger: nil, directive_kwarg_merger: nil, root_field_location_selector: 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 || BASIC_ROOT_FIELD_LOCATION_SELECTOR @field_map = {} @resolver_map = {} @resolver_configs = {} @mapped_type_names = {} @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.
41 42 43 |
# File 'lib/graphql/stitching/composer.rb', line 41 def mutation_name @mutation_name end |
#query_name ⇒ String (readonly)
Returns name of the Query type in the composed schema.
38 39 40 |
# File 'lib/graphql/stitching/composer.rb', line 38 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.
50 51 52 |
# File 'lib/graphql/stitching/composer.rb', line 50 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.
47 48 49 |
# File 'lib/graphql/stitching/composer.rb', line 47 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.
44 45 46 |
# File 'lib/graphql/stitching/composer.rb', line 44 def subscription_name @subscription_name end |
Instance Method Details
#perform(locations_input) ⇒ Object
80 81 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 |
# File 'lib/graphql/stitching/composer.rb', line 80 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) # "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 - GraphQL::Stitching.stitching_directive_names).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) introspection_types = schema.introspection_system.types.keys schema.types.each do |type_name, subgraph_type| next if introspection_types.include?(type_name) 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 add_type_and_traverse(schema_types.values, root: false) orphan_types(schema_types.values.select { |t| t.respond_to?(:kind) && t.kind.object? }) query schema_types[builder.query_name] mutation schema_types[builder.mutation_name] subscription schema_types[builder.subscription_name] directives builder.schema_directives.values own_orphan_types.clear end select_root_field_locations(schema) (schema, schemas) supergraph = Supergraph.new( schema: schema, fields: @field_map, resolvers: @resolver_map, executables: executables, ) COMPOSITION_VALIDATORS.each do |validator_class| validator_class.new.perform(supergraph, self) end supergraph end |