Skip to content

Commit

Permalink
Move Visibility::Visit into its own file, use it for Visibility::Profile
Browse files Browse the repository at this point in the history
  • Loading branch information
rmosolgo committed Nov 13, 2024
1 parent 50ed69a commit 6221d73
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 337 deletions.
188 changes: 2 additions & 186 deletions lib/graphql/schema/visibility.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require "graphql/schema/visibility/profile"
require "graphql/schema/visibility/migration"
require "graphql/schema/visibility/visit"

module GraphQL
class Schema
Expand Down Expand Up @@ -67,7 +68,7 @@ def get_type(type_name)
def load_all
# TODO thread-safe
@loaded_all ||= begin
visit = Visit.new(@schema)
visit = Visibility::Visit.new(@schema)

visit.entry_point_types.each do |t|
@references[t] << true
Expand Down Expand Up @@ -124,191 +125,6 @@ def load_all
end
end

class Visit
def initialize(schema)
@schema = schema
@late_bound_types = nil
@unvisited_types = nil
end

def entry_point_types
ept = [
@schema.query,
@schema.mutation,
@schema.subscription,
*@schema.introspection_system.types.values,
*@schema.introspection_system.entry_points.map { |ep| ep.type.unwrap },
*@schema.orphan_types,
]
ept.compact!
ept
end

def visit_each
@unvisited_types && raise("Can't call #visit_each twice on this Visit object")
@unvisited_types = entry_point_types
@late_bound_types = []
visited_types = Set.new.compare_by_identity
visited_directives = Set.new.compare_by_identity

directives_to_visit = []

@schema.directives.each_value { |dir_class|
if visited_directives.add?(dir_class)
yield(dir_class)
dir_class.all_argument_definitions.each do |arg_defn|
if yield(arg_defn)
directives_to_visit.concat(arg_defn.directives)
append_unvisited_type(dir_class, arg_defn.type.unwrap)
end
end
end
}

while @unvisited_types.any? || @late_bound_types.any?
while (type = @unvisited_types.pop)
if visited_types.add?(type) && yield(type)
directives_to_visit.concat(type.directives)
case type.kind.name
when "OBJECT", "INTERFACE"
type.interface_type_memberships.each do |itm|
append_unvisited_type(type, itm.abstract_type)
end
if type.kind.interface?
type.orphan_types.each do |orphan_type|
append_unvisited_type(type, orphan_type)
end
end

type.all_field_definitions.each do |field|
field.ensure_loaded
if yield(field)
directives_to_visit.concat(field.directives)
append_unvisited_type(type, field.type.unwrap)
field.all_argument_definitions.each do |argument|
if yield(argument)
directives_to_visit.concat(argument.directives)
append_unvisited_type(field, argument.type.unwrap)
end
end
end
end
when "INPUT_OBJECT"
type.all_argument_definitions.each do |argument|
if yield(argument)
directives_to_visit.concat(argument.directives)
append_unvisited_type(type, argument.type.unwrap)
end
end
when "UNION"
type.type_memberships.each do |tm|
append_unvisited_type(type, tm.object_type)
end
when "ENUM"
type.all_enum_value_definitions.each do |val|
if yield(val)
directives_to_visit.concat(val.directives)
end
end
when "SCALAR"
# pass -- nothing else to visit
else
raise "Invariant: unhandled type kind: #{type.kind.inspect}"
end
end
end

directives_to_visit.each do |dir|
dir_class = dir.class
if visited_directives.add?(dir_class)
yield(dir_class)
end
end

missed_late_types_streak = 0
while (owner, late_type = @late_bound_types.shift)
if (late_type.is_a?(String) && (type = Member::BuildType.constantize(type))) ||
(late_type.is_a?(LateBoundType) && (type = visited_types.find { |t| t.graphql_name == late_type.graphql_name }))
missed_late_types_streak = 0 # might succeed next round
update_type_owner(owner, type)
append_unvisited_type(owner, type)
else
# Didn't find it -- keep trying
missed_late_types_streak += 1
@late_bound_types << [owner, late_type]
if missed_late_types_streak == @late_bound_types.size
raise UnresolvedLateBoundTypeError.new(type: late_type)
end
end
end
end
nil
end

private

def append_unvisited_type(owner, type)
if type.is_a?(LateBoundType) || type.is_a?(String)
@late_bound_types << [owner, type]
else
@unvisited_types << type
end
end

def update_type_owner(owner, type)
case owner
when Module
if owner.kind.union?
owner.assign_type_membership_object_type(type)
elsif type.kind.interface?
new_interfaces = []
owner.interfaces.each do |int_t|
if int_t.is_a?(String) && int_t == type.graphql_name
new_interfaces << type
elsif int_t.is_a?(LateBoundType) && int_t.graphql_name == type.graphql_name
new_interfaces << type
else
# Don't re-add proper interface definitions,
# they were probably already added, maybe with options.
end
end
owner.implements(*new_interfaces)
new_interfaces.each do |int|
pt = @possible_types[int] ||= []
if !pt.include?(owner) && owner.is_a?(Class)
pt << owner
end
int.interfaces.each do |indirect_int|
if indirect_int.is_a?(LateBoundType) && (indirect_int_type = get_type(indirect_int.graphql_name))
update_type_owner(owner, indirect_int_type)
end
end
end
end
when GraphQL::Schema::Argument, GraphQL::Schema::Field
orig_type = owner.type
# Apply list/non-null wrapper as needed
if orig_type.respond_to?(:of_type)
transforms = []
while (orig_type.respond_to?(:of_type))
if orig_type.kind.non_null?
transforms << :to_non_null_type
elsif orig_type.kind.list?
transforms << :to_list_type
else
raise "Invariant: :of_type isn't non-null or list"
end
orig_type = orig_type.of_type
end
transforms.reverse_each { |t| type = type.public_send(t) }
end
owner.type = type
else
raise "Unexpected update: #{owner.inspect} #{type.inspect}"
end
end
end

def preload
# Traverse the schema now (and in the *_configured hooks below)
# To make sure things are loaded during boot
Expand Down
Loading

0 comments on commit 6221d73

Please sign in to comment.