File: base_visitor.rb

package info (click to toggle)
ruby-graphql 2.2.17-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 9,584 kB
  • sloc: ruby: 67,505; ansic: 1,753; yacc: 831; javascript: 331; makefile: 6
file content (200 lines) | stat: -rw-r--r-- 5,954 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
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
199
200
# frozen_string_literal: true
module GraphQL
  module StaticValidation
    class BaseVisitor < GraphQL::Language::StaticVisitor
      def initialize(document, context)
        @path = []
        @object_types = []
        @directives = []
        @field_definitions = []
        @argument_definitions = []
        @directive_definitions = []
        @context = context
        @schema = context.schema
        super(document)
      end

      attr_reader :context

      # @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
      attr_reader :object_types

      # @return [Array<String>] The nesting of the current position in the AST
      def path
        @path.dup
      end

      # Build a class to visit the AST and perform validation,
      # or use a pre-built class if rules is `ALL_RULES` or empty.
      # @param rules [Array<Module, Class>]
      # @return [Class] A class for validating `rules` during visitation
      def self.including_rules(rules)
        if rules.empty?
          # It's not doing _anything?!?_
          BaseVisitor
        elsif rules == ALL_RULES
          InterpreterVisitor
        else
          visitor_class = Class.new(self) do
            include(GraphQL::StaticValidation::DefinitionDependencies)
          end

          rules.reverse_each do |r|
            # If it's a class, it gets attached later.
            if !r.is_a?(Class)
              visitor_class.include(r)
            end
          end

          visitor_class.include(ContextMethods)
          visitor_class
        end
      end

      module ContextMethods
        def on_operation_definition(node, parent)
          object_type = @schema.root_type_for_operation(node.operation_type)
          push_type(object_type)
          @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
          super
          @object_types.pop
          @path.pop
        end

        def on_fragment_definition(node, parent)
          on_fragment_with_type(node) do
            @path.push("fragment #{node.name}")
            super
          end
        end

        def on_inline_fragment(node, parent)
          on_fragment_with_type(node) do
            @path.push("...#{node.type ? " on #{node.type.to_query_string}" : ""}")
            super
          end
        end

        def on_field(node, parent)
          parent_type = @object_types.last
          field_definition = @schema.get_field(parent_type, node.name, @context.query.context)
          @field_definitions.push(field_definition)
          if !field_definition.nil?
            next_object_type = field_definition.type.unwrap
            push_type(next_object_type)
          else
            push_type(nil)
          end
          @path.push(node.alias || node.name)
          super
          @field_definitions.pop
          @object_types.pop
          @path.pop
        end

        def on_directive(node, parent)
          directive_defn = @context.schema_directives[node.name]
          @directive_definitions.push(directive_defn)
          super
          @directive_definitions.pop
        end

        def on_argument(node, parent)
          argument_defn = if (arg = @argument_definitions.last)
            arg_type = arg.type.unwrap
            if arg_type.kind.input_object?
              @context.warden.get_argument(arg_type, node.name)
            else
              nil
            end
          elsif (directive_defn = @directive_definitions.last)
            @context.warden.get_argument(directive_defn, node.name)
          elsif (field_defn = @field_definitions.last)
            @context.warden.get_argument(field_defn, node.name)
          else
            nil
          end

          @argument_definitions.push(argument_defn)
          @path.push(node.name)
          super
          @argument_definitions.pop
          @path.pop
        end

        def on_fragment_spread(node, parent)
          @path.push("... #{node.name}")
          super
          @path.pop
        end

        def on_input_object(node, parent)
          arg_defn = @argument_definitions.last
          if arg_defn && arg_defn.type.list?
            @path.push(parent.children.index(node))
            super
            @path.pop
          else
            super
          end
        end

        # @return [GraphQL::BaseType] The current object type
        def type_definition
          @object_types.last
        end

        # @return [GraphQL::BaseType] The type which the current type came from
        def parent_type_definition
          @object_types[-2]
        end

        # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
        def field_definition
          @field_definitions.last
        end

        # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
        def directive_definition
          @directive_definitions.last
        end

        # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
        def argument_definition
          # Don't get the _last_ one because that's the current one.
          # Get the second-to-last one, which is the parent of the current one.
          @argument_definitions[-2]
        end

        private

        def on_fragment_with_type(node)
          object_type = if node.type
            @context.warden.get_type(node.type.name)
          else
            @object_types.last
          end
          push_type(object_type)
          yield(node)
          @object_types.pop
          @path.pop
        end

        def push_type(t)
          @object_types.push(t)
        end
      end

      private

      def add_error(error, path: nil)
        if @context.too_many_errors?
          throw :too_many_validation_errors
        end
        error.path ||= (path || @path.dup)
        context.errors << error
      end

    end
  end
end