Skip to content

Commit

Permalink
Rename Resolver to TypeResolver (#160)
Browse files Browse the repository at this point in the history
  • Loading branch information
gmac authored Sep 15, 2024
1 parent 4310f1e commit e2f8233
Show file tree
Hide file tree
Showing 23 changed files with 157 additions and 157 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ While `Client` is sufficient for most usecases, the library offers several discr

![Merging types](./docs/images/merging.png)

To facilitate this merging of types, stitching must know how to cross-reference and fetch each variant of a type from its source location using [resolver queries](#merged-type-resolver-queries). For those in an Apollo ecosystem, there's also _limited_ support for merging types though a [federation `_entities` protocol](./docs/federation_entities.md).
To facilitate this merging of types, stitching must know how to cross-reference and fetch each variant of a type from its source location using [type resolver queries](#merged-type-resolver-queries). For those in an Apollo ecosystem, there's also _limited_ support for merging types though a [federation `_entities` protocol](./docs/federation_entities.md).

### Merged type resolver queries

Expand Down Expand Up @@ -249,7 +249,7 @@ type Query {
}
```

See [resolver arguments](./docs/resolver.md#arguments) for full documentation on shaping input.
See [resolver arguments](./docs/type_resolver.md#arguments) for full documentation on shaping input.

#### Composite type keys

Expand Down
6 changes: 3 additions & 3 deletions docs/resolver.md → docs/type_resolver.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
## GraphQL::Stitching::Resolver
## GraphQL::Stitching::TypeResolver

A `Resolver` contains all information about a root query used by stitching to fetch location-specific variants of a merged type. Specifically, resolvers manage parsed keys and argument structures.
A `TypeResolver` contains all information about a root query used by stitching to fetch location-specific variants of a merged type. Specifically, resolvers manage parsed keys and argument structures.

### Arguments

Resolvers configure arguments through a template string of [GraphQL argument literal syntax](https://spec.graphql.org/October2021/#sec-Language.Arguments). This allows sending multiple arguments that intermix stitching keys with complex object shapes and other static values.
Type resolvers configure arguments through a template string of [GraphQL argument literal syntax](https://spec.graphql.org/October2021/#sec-Language.Arguments). This allows sending multiple arguments that intermix stitching keys with complex object shapes and other static values.

#### Key insertions

Expand Down
2 changes: 1 addition & 1 deletion lib/graphql/stitching.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ def stitching_directive_names
require_relative "stitching/plan"
require_relative "stitching/planner"
require_relative "stitching/request"
require_relative "stitching/resolver"
require_relative "stitching/type_resolver"
require_relative "stitching/util"
require_relative "stitching/version"
20 changes: 10 additions & 10 deletions lib/graphql/stitching/composer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

require_relative "composer/base_validator"
require_relative "composer/validate_interfaces"
require_relative "composer/validate_resolvers"
require_relative "composer/resolver_config"
require_relative "composer/validate_type_resolvers"
require_relative "composer/type_resolver_config"

module GraphQL
module Stitching
Expand Down Expand Up @@ -31,7 +31,7 @@ class T < GraphQL::Schema::Object
# @api private
COMPOSITION_VALIDATORS = [
ValidateInterfaces,
ValidateResolvers,
ValidateTypeResolvers,
].freeze

# @return [String] name of the Query type in the composed schema.
Expand Down Expand Up @@ -199,8 +199,8 @@ def prepare_locations_input(locations_input)
raise CompositionError, "The schema for `#{location}` location must be a GraphQL::Schema class."
end

@resolver_configs.merge!(ResolverConfig.extract_directive_assignments(schema, location, input[:stitch]))
@resolver_configs.merge!(ResolverConfig.extract_federation_entities(schema, location))
@resolver_configs.merge!(TypeResolverConfig.extract_directive_assignments(schema, location, input[:stitch]))
@resolver_configs.merge!(TypeResolverConfig.extract_federation_entities(schema, location))

schemas[location.to_s] = schema
executables[location.to_s] = input[:executable] || schema
Expand Down Expand Up @@ -546,13 +546,13 @@ def extract_resolvers(type_name, types_by_location)

subgraph_field.directives.each do |directive|
next unless directive.graphql_name == GraphQL::Stitching.stitch_directive
resolver_configs << ResolverConfig.from_kwargs(directive.arguments.keyword_arguments)
resolver_configs << TypeResolverConfig.from_kwargs(directive.arguments.keyword_arguments)
end

resolver_configs.each do |config|
resolver_type_name = if config.type_name
if !resolver_type.kind.abstract?
raise CompositionError, "Resolver config may only specify a type name for abstract resolvers."
raise CompositionError, "Type resolver config may only specify a type name for abstract resolvers."
elsif !resolver_type.possible_types.find { _1.graphql_name == config.type_name }
raise CompositionError, "Type `#{config.type_name}` is not a possible return type for query `#{field_name}`."
end
Expand All @@ -561,7 +561,7 @@ def extract_resolvers(type_name, types_by_location)
resolver_type.graphql_name
end

key = Resolver.parse_key_with_types(
key = TypeResolver.parse_key_with_types(
config.key,
@subgraph_types_by_name_and_location[resolver_type_name],
)
Expand All @@ -581,11 +581,11 @@ def extract_resolvers(type_name, types_by_location)
"#{argument.graphql_name}: $.#{key.primitive_name}"
end

arguments = Resolver.parse_arguments_with_field(arguments_format, subgraph_field)
arguments = TypeResolver.parse_arguments_with_field(arguments_format, subgraph_field)
arguments.each { _1.verify_key(key) }

@resolver_map[resolver_type_name] ||= []
@resolver_map[resolver_type_name] << Resolver.new(
@resolver_map[resolver_type_name] << TypeResolver.new(
location: location,
type_name: resolver_type_name,
field: subgraph_field.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module GraphQL::Stitching
class Composer
class ResolverConfig
class TypeResolverConfig
ENTITY_TYPENAME = "_Entity"
ENTITIES_QUERY = "_entities"

Expand Down Expand Up @@ -30,7 +30,7 @@ def extract_federation_entities(schema, location)
entity_type.directives.each do |directive|
next unless directive.graphql_name == "key"

key = Resolver.parse_key(directive.arguments.keyword_arguments.fetch(:fields))
key = TypeResolver.parse_key(directive.arguments.keyword_arguments.fetch(:fields))
key_fields = key.map { "#{_1.name}: $.#{_1.name}" }
field_path = "#{location}._entities"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module GraphQL::Stitching
class Composer
class ValidateResolvers < BaseValidator
class ValidateTypeResolvers < BaseValidator

def perform(supergraph, composer)
root_types = [
Expand Down
4 changes: 2 additions & 2 deletions lib/graphql/stitching/executor.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# frozen_string_literal: true

require "json"
require_relative "executor/resolver_source"
require_relative "executor/root_source"
require_relative "executor/type_resolver_source"
require_relative "executor/shaper"

module GraphQL
Expand Down Expand Up @@ -66,7 +66,7 @@ def exec!(next_steps = [@after])
.select { next_steps.include?(_1.after) }
.group_by { [_1.location, _1.resolver.nil?] }
.map do |(location, root_source), ops|
source_class = root_source ? RootSource : ResolverSource
source_class = root_source ? RootSource : TypeResolverSource
@dataloader.with(source_class, self, location).request_all(ops)
end

Expand Down
4 changes: 2 additions & 2 deletions lib/graphql/stitching/executor/shaper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def perform!(raw)
def resolve_object_scope(raw_object, parent_type, selections, typename = nil)
return nil if raw_object.nil?

typename ||= raw_object[Resolver::TYPENAME_EXPORT_NODE.alias]
raw_object.reject! { |key, _v| Resolver.export_key?(key) }
typename ||= raw_object[TypeResolver::TYPENAME_EXPORT_NODE.alias]
raw_object.reject! { |key, _v| TypeResolver.export_key?(key) }

selections.each do |node|
case node
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module GraphQL::Stitching
class Executor
class ResolverSource < GraphQL::Dataloader::Source
class TypeResolverSource < GraphQL::Dataloader::Source
def initialize(executor, location)
@executor = executor
@location = location
Expand All @@ -17,7 +17,7 @@ def fetch(ops)

if op.if_type
# operations planned around unused fragment conditions should not trigger requests
origin_set.select! { _1[Resolver::TYPENAME_EXPORT_NODE.alias] == op.if_type }
origin_set.select! { _1[TypeResolver::TYPENAME_EXPORT_NODE.alias] == op.if_type }
end

memo[op] = origin_set if origin_set.any?
Expand Down
10 changes: 5 additions & 5 deletions lib/graphql/stitching/planner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,8 @@ def extract_locale_selections(
input_selections.each do |node|
case node
when GraphQL::Language::Nodes::Field
if node.alias&.start_with?(Resolver::EXPORT_PREFIX)
raise StitchingError, %(Alias "#{node.alias}" is not allowed because "#{Resolver::EXPORT_PREFIX}" is a reserved prefix.)
if node.alias&.start_with?(TypeResolver::EXPORT_PREFIX)
raise StitchingError, %(Alias "#{node.alias}" is not allowed because "#{TypeResolver::EXPORT_PREFIX}" is a reserved prefix.)
elsif node.name == TYPENAME
locale_selections << node
next
Expand Down Expand Up @@ -277,8 +277,8 @@ def extract_locale_selections(

# B.4) Add a `__typename` export to abstracts and types that implement
# fragments so that resolved type information is available during execution.
if requires_typename && !locale_selections.include?(Resolver::TYPENAME_EXPORT_NODE)
locale_selections << Resolver::TYPENAME_EXPORT_NODE
if requires_typename && !locale_selections.include?(TypeResolver::TYPENAME_EXPORT_NODE)
locale_selections << TypeResolver::TYPENAME_EXPORT_NODE
end

if remote_selections
Expand All @@ -294,7 +294,7 @@ def extract_locale_selections(
# E.1) Add the key of each resolver query into the prior location's selection set.
parent_selections.push(*resolver.key.export_nodes) if resolver.key
parent_selections.uniq! do |node|
export_node = node.is_a?(GraphQL::Language::Nodes::Field) && Resolver.export_key?(node.alias)
export_node = node.is_a?(GraphQL::Language::Nodes::Field) && TypeResolver.export_key?(node.alias)
export_node ? node.alias : node.object_id
end

Expand Down
2 changes: 1 addition & 1 deletion lib/graphql/stitching/request/skip_include.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def render_node(parent_node, variables)
end

if filtered_selections.none?
filtered_selections << Resolver::TYPENAME_EXPORT_NODE
filtered_selections << TypeResolver::TYPENAME_EXPORT_NODE
end

if changed
Expand Down
2 changes: 1 addition & 1 deletion lib/graphql/stitching/supergraph.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def route_type_to_locations(type_name, start_location, goal_locations)
if key_count.zero?
# nested root scopes have no resolver keys and just return a location
goal_locations.each_with_object({}) do |goal_location, memo|
memo[goal_location] = [Resolver.new(location: goal_location)]
memo[goal_location] = [TypeResolver.new(location: goal_location)]
end

elsif key_count > 1
Expand Down
6 changes: 3 additions & 3 deletions lib/graphql/stitching/supergraph/to_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def from_definition(schema, executables:)
end

key_definitions = locations_by_key.each_with_object({}) do |(key, locations), memo|
memo[key] = Resolver.parse_key(key, locations)
memo[key] = TypeResolver.parse_key(key, locations)
end

# Collect/build resolver definitions for each type
Expand All @@ -41,13 +41,13 @@ def from_definition(schema, executables:)

kwargs = directive.arguments.keyword_arguments
resolver_map[type_name] ||= []
resolver_map[type_name] << Resolver.new(
resolver_map[type_name] << TypeResolver.new(
location: kwargs[:location],
type_name: kwargs.fetch(:type_name, type_name),
field: kwargs[:field],
list: kwargs[:list] || false,
key: key_definitions[kwargs[:key]],
arguments: Resolver.parse_arguments_with_type_defs(kwargs[:arguments], kwargs[:argument_types]),
arguments: TypeResolver.parse_arguments_with_type_defs(kwargs[:arguments], kwargs[:argument_types]),
)
end

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# frozen_string_literal: true

require_relative "resolver/arguments"
require_relative "resolver/keys"
require_relative "type_resolver/arguments"
require_relative "type_resolver/keys"

module GraphQL
module Stitching
# Defines a type resolver query that provides direct access to an entity type.
class Resolver
class TypeResolver
extend ArgumentsParser
extend KeysParser

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module GraphQL::Stitching
class Resolver
class TypeResolver
# Defines a single resolver argument structure
# @api private
class Argument
Expand Down Expand Up @@ -129,9 +129,9 @@ def key?
end

def verify_key(arg, key)
key_field = value.reduce(Resolver::KeyField.new("", inner: key)) do |field, ns|
key_field = value.reduce(TypeResolver::KeyField.new("", inner: key)) do |field, ns|
if ns == TYPENAME
Resolver::KeyField.new(TYPENAME)
TypeResolver::KeyField.new(TYPENAME)
elsif field
field.inner.find { _1.name == ns }
end
Expand All @@ -146,7 +146,7 @@ def verify_key(arg, key)

def build(origin_obj)
value.each_with_index.reduce(origin_obj) do |obj, (ns, idx)|
obj[idx.zero? ? Resolver.export_key(ns) : ns]
obj[idx.zero? ? TypeResolver.export_key(ns) : ns]
end
end

Expand Down Expand Up @@ -174,7 +174,7 @@ module ArgumentsParser
# Parses an argument template string into resolver arguments via schema casting.
# @param template [String] the template string to parse.
# @param field_def [GraphQL::Schema::FieldDefinition] a field definition providing arguments schema.
# @return [[GraphQL::Stitching::Resolver::Argument]] an array of resolver arguments.
# @return [[GraphQL::Stitching::TypeResolver::Argument]] an array of resolver arguments.
def parse_arguments_with_field(template, field_def)
ast = parse_arg_defs(template)
args = build_argument_set(ast, field_def.arguments)
Expand All @@ -196,7 +196,7 @@ def parse_arguments_with_field(template, field_def)
# Parses an argument template string into resolver arguments via SDL casting.
# @param template [String] the template string to parse.
# @param type_defs [String] the type definition string declaring argument types.
# @return [[GraphQL::Stitching::Resolver::Argument]] an array of resolver arguments.
# @return [[GraphQL::Stitching::TypeResolver::Argument]] an array of resolver arguments.
def parse_arguments_with_type_defs(template, type_defs)
type_map = parse_type_defs(type_defs)
parse_arg_defs(template).map { build_argument(_1, type_struct: type_map[_1.name]) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module GraphQL::Stitching
class Resolver
class TypeResolver
EXPORT_PREFIX = "_export_"

class FieldNode
Expand Down
14 changes: 7 additions & 7 deletions test/graphql/stitching/composer/configuration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,21 @@ def test_perform_with_static_resolver_config

expected_resolvers = {
"Product" => [
GraphQL::Stitching::Resolver.new(
GraphQL::Stitching::TypeResolver.new(
location: "alpha",
type_name: "Product",
list: false,
field: "productA",
key: GraphQL::Stitching::Resolver.parse_key("id"),
arguments: GraphQL::Stitching::Resolver.parse_arguments_with_type_defs("id: $.id", "id: ID"),
key: GraphQL::Stitching::TypeResolver.parse_key("id"),
arguments: GraphQL::Stitching::TypeResolver.parse_arguments_with_type_defs("id: $.id", "id: ID"),
),
GraphQL::Stitching::Resolver.new(
GraphQL::Stitching::TypeResolver.new(
location: "bravo",
type_name: "Product",
list: false,
field: "productB",
key: GraphQL::Stitching::Resolver.parse_key("id"),
arguments: GraphQL::Stitching::Resolver.parse_arguments_with_type_defs("key: $.id", "key: ID"),
key: GraphQL::Stitching::TypeResolver.parse_key("id"),
arguments: GraphQL::Stitching::TypeResolver.parse_arguments_with_type_defs("key: $.id", "key: ID"),
),
]
}
Expand All @@ -75,7 +75,7 @@ def test_perform_federation_schema
type Query { _entities(representations: [_Any!]!): [_Entity]! }
|

configs = GraphQL::Stitching::Composer::ResolverConfig.extract_federation_entities(
configs = GraphQL::Stitching::Composer::TypeResolverConfig.extract_federation_entities(
GraphQL::Schema.from_definition(schema),
"alpha",
)
Expand Down
Loading

0 comments on commit e2f8233

Please sign in to comment.