Class: GraphQL::Schema::Visibility::Visit

Inherits:
Object
  • Object
show all
Defined in:
lib/graphql/schema/visibility/visit.rb

Instance Method Summary collapse

Constructor Details

#initialize(schema, &visit_block) ⇒ Visit

Returns a new instance of Visit.

[View source]

6
7
8
9
10
11
12
13
14
# File 'lib/graphql/schema/visibility/visit.rb', line 6

def initialize(schema, &visit_block)
  @schema = schema
  @late_bound_types = nil
  @unvisited_types = nil
  # These accumulate between calls to prevent re-visiting the same types
  @visited_types = Set.new.compare_by_identity
  @visited_directives = Set.new.compare_by_identity
  @visit_block = visit_block
end

Instance Method Details

#entry_point_directivesObject

[View source]

28
29
30
# File 'lib/graphql/schema/visibility/visit.rb', line 28

def entry_point_directives
  @schema.directives.values
end

#entry_point_typesObject

[View source]

16
17
18
19
20
21
22
23
24
25
26
# File 'lib/graphql/schema/visibility/visit.rb', line 16

def entry_point_types
  ept = [
    @schema.query,
    @schema.mutation,
    @schema.subscription,
    *@schema.introspection_system.types.values,
    *@schema.orphan_types,
  ]
  ept.compact!
  ept
end

#visit_each(types: entry_point_types, directives: entry_point_directives) ⇒ Object

[View source]

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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
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
# File 'lib/graphql/schema/visibility/visit.rb', line 32

def visit_each(types: entry_point_types, directives: entry_point_directives)
  @unvisited_types && raise("Can't re-enter `visit_each` on this Visit (another visit is already in progress)")
  @unvisited_types = types
  @late_bound_types = []
  directives_to_visit = directives

  while !@unvisited_types.empty? || !@late_bound_types.empty?
    while (type = @unvisited_types.pop)
      if @visited_types.add?(type) && @visit_block.call(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 @visit_block.call(field)
              directives_to_visit.concat(field.directives)
              append_unvisited_type(type, field.type.unwrap)
              field.all_argument_definitions.each do |argument|
                if @visit_block.call(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 @visit_block.call(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 @visit_block.call(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.is_a?(Class) ? dir : dir.class
      if @visited_directives.add?(dir_class) && @visit_block.call(dir_class)
        dir_class.all_argument_definitions.each do |arg_defn|
          if @visit_block.call(arg_defn)
            directives_to_visit.concat(arg_defn.directives)
            append_unvisited_type(dir_class, arg_defn.type.unwrap)
          end
        end
      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

  @unvisited_types = nil
  nil
end