graphql 2.3.5 → 2.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/graphql/analysis/analyzer.rb +89 -0
- data/lib/graphql/analysis/field_usage.rb +82 -0
- data/lib/graphql/analysis/max_query_complexity.rb +20 -0
- data/lib/graphql/analysis/max_query_depth.rb +20 -0
- data/lib/graphql/analysis/query_complexity.rb +183 -0
- data/lib/graphql/analysis/{ast/query_depth.rb → query_depth.rb} +23 -25
- data/lib/graphql/analysis/visitor.rb +282 -0
- data/lib/graphql/analysis.rb +92 -1
- data/lib/graphql/dataloader/async_dataloader.rb +2 -0
- data/lib/graphql/execution/interpreter.rb +1 -1
- data/lib/graphql/query/validation_pipeline.rb +2 -2
- data/lib/graphql/schema/argument.rb +19 -5
- data/lib/graphql/schema/directive.rb +2 -0
- data/lib/graphql/schema/field.rb +8 -0
- data/lib/graphql/schema/resolver.rb +1 -0
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +1 -1
- data/lib/graphql/subscriptions.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- metadata +10 -11
- data/lib/graphql/analysis/ast/analyzer.rb +0 -91
- data/lib/graphql/analysis/ast/field_usage.rb +0 -84
- data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
- data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
- data/lib/graphql/analysis/ast/query_complexity.rb +0 -185
- data/lib/graphql/analysis/ast/visitor.rb +0 -284
- data/lib/graphql/analysis/ast.rb +0 -94
| @@ -0,0 +1,282 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
            module GraphQL
         | 
| 3 | 
            +
              module Analysis
         | 
| 4 | 
            +
                # Depth first traversal through a query AST, calling AST analyzers
         | 
| 5 | 
            +
                # along the way.
         | 
| 6 | 
            +
                #
         | 
| 7 | 
            +
                # The visitor is a special case of GraphQL::Language::StaticVisitor, visiting
         | 
| 8 | 
            +
                # only the selected operation, providing helpers for common use cases such
         | 
| 9 | 
            +
                # as skipped fields and visiting fragment spreads.
         | 
| 10 | 
            +
                #
         | 
| 11 | 
            +
                # @see {GraphQL::Analysis::Analyzer} AST Analyzers for queries
         | 
| 12 | 
            +
                class Visitor < GraphQL::Language::StaticVisitor
         | 
| 13 | 
            +
                  def initialize(query:, analyzers:)
         | 
| 14 | 
            +
                    @analyzers = analyzers
         | 
| 15 | 
            +
                    @path = []
         | 
| 16 | 
            +
                    @object_types = []
         | 
| 17 | 
            +
                    @directives = []
         | 
| 18 | 
            +
                    @field_definitions = []
         | 
| 19 | 
            +
                    @argument_definitions = []
         | 
| 20 | 
            +
                    @directive_definitions = []
         | 
| 21 | 
            +
                    @rescued_errors = []
         | 
| 22 | 
            +
                    @query = query
         | 
| 23 | 
            +
                    @schema = query.schema
         | 
| 24 | 
            +
                    @response_path = []
         | 
| 25 | 
            +
                    @skip_stack = [false]
         | 
| 26 | 
            +
                    super(query.selected_operation)
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  # @return [GraphQL::Query] the query being visited
         | 
| 30 | 
            +
                  attr_reader :query
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  # @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
         | 
| 33 | 
            +
                  attr_reader :object_types
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  # @return [Array<GraphQL::AnalysisError]
         | 
| 36 | 
            +
                  attr_reader :rescued_errors
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def visit
         | 
| 39 | 
            +
                    return unless @document
         | 
| 40 | 
            +
                    super
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  # Visit Helpers
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  # @return [GraphQL::Execution::Interpreter::Arguments] Arguments for this node, merging default values, literal values and query variables
         | 
| 46 | 
            +
                  # @see {GraphQL::Query#arguments_for}
         | 
| 47 | 
            +
                  def arguments_for(ast_node, field_definition)
         | 
| 48 | 
            +
                    @query.arguments_for(ast_node, field_definition)
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  # @return [Boolean] If the visitor is currently inside a fragment definition
         | 
| 52 | 
            +
                  def visiting_fragment_definition?
         | 
| 53 | 
            +
                    @in_fragment_def
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  # @return [Boolean] If the current node should be skipped because of a skip or include directive
         | 
| 57 | 
            +
                  def skipping?
         | 
| 58 | 
            +
                    @skipping
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  # @return [Array<String>] The path to the response key for the current field
         | 
| 62 | 
            +
                  def response_path
         | 
| 63 | 
            +
                    @response_path.dup
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  # Visitor Hooks
         | 
| 67 | 
            +
                  [
         | 
| 68 | 
            +
                    :operation_definition, :fragment_definition,
         | 
| 69 | 
            +
                    :inline_fragment, :field, :directive, :argument, :fragment_spread
         | 
| 70 | 
            +
                  ].each do |node_type|
         | 
| 71 | 
            +
                    module_eval <<-RUBY, __FILE__, __LINE__
         | 
| 72 | 
            +
                    def call_on_enter_#{node_type}(node, parent)
         | 
| 73 | 
            +
                      @analyzers.each do |a|
         | 
| 74 | 
            +
                        begin
         | 
| 75 | 
            +
                          a.on_enter_#{node_type}(node, parent, self)
         | 
| 76 | 
            +
                        rescue AnalysisError => err
         | 
| 77 | 
            +
                          @rescued_errors << err
         | 
| 78 | 
            +
                        end
         | 
| 79 | 
            +
                      end
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    def call_on_leave_#{node_type}(node, parent)
         | 
| 83 | 
            +
                      @analyzers.each do |a|
         | 
| 84 | 
            +
                        begin
         | 
| 85 | 
            +
                          a.on_leave_#{node_type}(node, parent, self)
         | 
| 86 | 
            +
                        rescue AnalysisError => err
         | 
| 87 | 
            +
                          @rescued_errors << err
         | 
| 88 | 
            +
                        end
         | 
| 89 | 
            +
                      end
         | 
| 90 | 
            +
                    end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                    RUBY
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                  def on_operation_definition(node, parent)
         | 
| 96 | 
            +
                    object_type = @schema.root_type_for_operation(node.operation_type)
         | 
| 97 | 
            +
                    @object_types.push(object_type)
         | 
| 98 | 
            +
                    @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
         | 
| 99 | 
            +
                    call_on_enter_operation_definition(node, parent)
         | 
| 100 | 
            +
                    super
         | 
| 101 | 
            +
                    call_on_leave_operation_definition(node, parent)
         | 
| 102 | 
            +
                    @object_types.pop
         | 
| 103 | 
            +
                    @path.pop
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                  def on_fragment_definition(node, parent)
         | 
| 107 | 
            +
                    on_fragment_with_type(node) do
         | 
| 108 | 
            +
                      @path.push("fragment #{node.name}")
         | 
| 109 | 
            +
                      @in_fragment_def = false
         | 
| 110 | 
            +
                      call_on_enter_fragment_definition(node, parent)
         | 
| 111 | 
            +
                      super
         | 
| 112 | 
            +
                      @in_fragment_def = false
         | 
| 113 | 
            +
                      call_on_leave_fragment_definition(node, parent)
         | 
| 114 | 
            +
                    end
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                  def on_inline_fragment(node, parent)
         | 
| 118 | 
            +
                    on_fragment_with_type(node) do
         | 
| 119 | 
            +
                      @path.push("...#{node.type ? " on #{node.type.name}" : ""}")
         | 
| 120 | 
            +
                      @skipping = @skip_stack.last || skip?(node)
         | 
| 121 | 
            +
                      @skip_stack << @skipping
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                      call_on_enter_inline_fragment(node, parent)
         | 
| 124 | 
            +
                      super
         | 
| 125 | 
            +
                      @skipping = @skip_stack.pop
         | 
| 126 | 
            +
                      call_on_leave_inline_fragment(node, parent)
         | 
| 127 | 
            +
                    end
         | 
| 128 | 
            +
                  end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                  def on_field(node, parent)
         | 
| 131 | 
            +
                    @response_path.push(node.alias || node.name)
         | 
| 132 | 
            +
                    parent_type = @object_types.last
         | 
| 133 | 
            +
                    # This could be nil if the previous field wasn't found:
         | 
| 134 | 
            +
                    field_definition = parent_type && @schema.get_field(parent_type, node.name, @query.context)
         | 
| 135 | 
            +
                    @field_definitions.push(field_definition)
         | 
| 136 | 
            +
                    if !field_definition.nil?
         | 
| 137 | 
            +
                      next_object_type = field_definition.type.unwrap
         | 
| 138 | 
            +
                      @object_types.push(next_object_type)
         | 
| 139 | 
            +
                    else
         | 
| 140 | 
            +
                      @object_types.push(nil)
         | 
| 141 | 
            +
                    end
         | 
| 142 | 
            +
                    @path.push(node.alias || node.name)
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                    @skipping = @skip_stack.last || skip?(node)
         | 
| 145 | 
            +
                    @skip_stack << @skipping
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                    call_on_enter_field(node, parent)
         | 
| 148 | 
            +
                    super
         | 
| 149 | 
            +
                    @skipping = @skip_stack.pop
         | 
| 150 | 
            +
                    call_on_leave_field(node, parent)
         | 
| 151 | 
            +
                    @response_path.pop
         | 
| 152 | 
            +
                    @field_definitions.pop
         | 
| 153 | 
            +
                    @object_types.pop
         | 
| 154 | 
            +
                    @path.pop
         | 
| 155 | 
            +
                  end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                  def on_directive(node, parent)
         | 
| 158 | 
            +
                    directive_defn = @schema.directives[node.name]
         | 
| 159 | 
            +
                    @directive_definitions.push(directive_defn)
         | 
| 160 | 
            +
                    call_on_enter_directive(node, parent)
         | 
| 161 | 
            +
                    super
         | 
| 162 | 
            +
                    call_on_leave_directive(node, parent)
         | 
| 163 | 
            +
                    @directive_definitions.pop
         | 
| 164 | 
            +
                  end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                  def on_argument(node, parent)
         | 
| 167 | 
            +
                    argument_defn = if (arg = @argument_definitions.last)
         | 
| 168 | 
            +
                      arg_type = arg.type.unwrap
         | 
| 169 | 
            +
                      if arg_type.kind.input_object?
         | 
| 170 | 
            +
                        arg_type.get_argument(node.name, @query.context)
         | 
| 171 | 
            +
                      else
         | 
| 172 | 
            +
                        nil
         | 
| 173 | 
            +
                      end
         | 
| 174 | 
            +
                    elsif (directive_defn = @directive_definitions.last)
         | 
| 175 | 
            +
                      directive_defn.get_argument(node.name, @query.context)
         | 
| 176 | 
            +
                    elsif (field_defn = @field_definitions.last)
         | 
| 177 | 
            +
                      field_defn.get_argument(node.name, @query.context)
         | 
| 178 | 
            +
                    else
         | 
| 179 | 
            +
                      nil
         | 
| 180 | 
            +
                    end
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                    @argument_definitions.push(argument_defn)
         | 
| 183 | 
            +
                    @path.push(node.name)
         | 
| 184 | 
            +
                    call_on_enter_argument(node, parent)
         | 
| 185 | 
            +
                    super
         | 
| 186 | 
            +
                    call_on_leave_argument(node, parent)
         | 
| 187 | 
            +
                    @argument_definitions.pop
         | 
| 188 | 
            +
                    @path.pop
         | 
| 189 | 
            +
                  end
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                  def on_fragment_spread(node, parent)
         | 
| 192 | 
            +
                    @path.push("... #{node.name}")
         | 
| 193 | 
            +
                    @skipping = @skip_stack.last || skip?(node)
         | 
| 194 | 
            +
                    @skip_stack << @skipping
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                    call_on_enter_fragment_spread(node, parent)
         | 
| 197 | 
            +
                    enter_fragment_spread_inline(node)
         | 
| 198 | 
            +
                    super
         | 
| 199 | 
            +
                    @skipping = @skip_stack.pop
         | 
| 200 | 
            +
                    leave_fragment_spread_inline(node)
         | 
| 201 | 
            +
                    call_on_leave_fragment_spread(node, parent)
         | 
| 202 | 
            +
                    @path.pop
         | 
| 203 | 
            +
                  end
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                  # @return [GraphQL::BaseType] The current object type
         | 
| 206 | 
            +
                  def type_definition
         | 
| 207 | 
            +
                    @object_types.last
         | 
| 208 | 
            +
                  end
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                  # @return [GraphQL::BaseType] The type which the current type came from
         | 
| 211 | 
            +
                  def parent_type_definition
         | 
| 212 | 
            +
                    @object_types[-2]
         | 
| 213 | 
            +
                  end
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                  # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
         | 
| 216 | 
            +
                  def field_definition
         | 
| 217 | 
            +
                    @field_definitions.last
         | 
| 218 | 
            +
                  end
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                  # @return [GraphQL::Field, nil] The GraphQL field which returned the object that the current field belongs to
         | 
| 221 | 
            +
                  def previous_field_definition
         | 
| 222 | 
            +
                    @field_definitions[-2]
         | 
| 223 | 
            +
                  end
         | 
| 224 | 
            +
             | 
| 225 | 
            +
                  # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
         | 
| 226 | 
            +
                  def directive_definition
         | 
| 227 | 
            +
                    @directive_definitions.last
         | 
| 228 | 
            +
                  end
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                  # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
         | 
| 231 | 
            +
                  def argument_definition
         | 
| 232 | 
            +
                    @argument_definitions.last
         | 
| 233 | 
            +
                  end
         | 
| 234 | 
            +
             | 
| 235 | 
            +
                  # @return [GraphQL::Argument, nil] The previous GraphQL argument
         | 
| 236 | 
            +
                  def previous_argument_definition
         | 
| 237 | 
            +
                    @argument_definitions[-2]
         | 
| 238 | 
            +
                  end
         | 
| 239 | 
            +
             | 
| 240 | 
            +
                  private
         | 
| 241 | 
            +
             | 
| 242 | 
            +
                  # Visit a fragment spread inline instead of visiting the definition
         | 
| 243 | 
            +
                  # by itself.
         | 
| 244 | 
            +
                  def enter_fragment_spread_inline(fragment_spread)
         | 
| 245 | 
            +
                    fragment_def = query.fragments[fragment_spread.name]
         | 
| 246 | 
            +
             | 
| 247 | 
            +
                    object_type = if fragment_def.type
         | 
| 248 | 
            +
                      @query.warden.get_type(fragment_def.type.name)
         | 
| 249 | 
            +
                    else
         | 
| 250 | 
            +
                      object_types.last
         | 
| 251 | 
            +
                    end
         | 
| 252 | 
            +
             | 
| 253 | 
            +
                    object_types << object_type
         | 
| 254 | 
            +
             | 
| 255 | 
            +
                    on_fragment_definition_children(fragment_def)
         | 
| 256 | 
            +
                  end
         | 
| 257 | 
            +
             | 
| 258 | 
            +
                  # Visit a fragment spread inline instead of visiting the definition
         | 
| 259 | 
            +
                  # by itself.
         | 
| 260 | 
            +
                  def leave_fragment_spread_inline(_fragment_spread)
         | 
| 261 | 
            +
                    object_types.pop
         | 
| 262 | 
            +
                  end
         | 
| 263 | 
            +
             | 
| 264 | 
            +
                  def skip?(ast_node)
         | 
| 265 | 
            +
                    dir = ast_node.directives
         | 
| 266 | 
            +
                    dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
         | 
| 267 | 
            +
                  end
         | 
| 268 | 
            +
             | 
| 269 | 
            +
                  def on_fragment_with_type(node)
         | 
| 270 | 
            +
                    object_type = if node.type
         | 
| 271 | 
            +
                      @query.warden.get_type(node.type.name)
         | 
| 272 | 
            +
                    else
         | 
| 273 | 
            +
                      @object_types.last
         | 
| 274 | 
            +
                    end
         | 
| 275 | 
            +
                    @object_types.push(object_type)
         | 
| 276 | 
            +
                    yield(node)
         | 
| 277 | 
            +
                    @object_types.pop
         | 
| 278 | 
            +
                    @path.pop
         | 
| 279 | 
            +
                  end
         | 
| 280 | 
            +
                end
         | 
| 281 | 
            +
              end
         | 
| 282 | 
            +
            end
         | 
    
        data/lib/graphql/analysis.rb
    CHANGED
    
    | @@ -1,2 +1,93 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 | 
            -
            require "graphql/analysis/ | 
| 2 | 
            +
            require "graphql/analysis/visitor"
         | 
| 3 | 
            +
            require "graphql/analysis/analyzer"
         | 
| 4 | 
            +
            require "graphql/analysis/field_usage"
         | 
| 5 | 
            +
            require "graphql/analysis/query_complexity"
         | 
| 6 | 
            +
            require "graphql/analysis/max_query_complexity"
         | 
| 7 | 
            +
            require "graphql/analysis/query_depth"
         | 
| 8 | 
            +
            require "graphql/analysis/max_query_depth"
         | 
| 9 | 
            +
            require "timeout"
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            module GraphQL
         | 
| 12 | 
            +
              module Analysis
         | 
| 13 | 
            +
                AST = self
         | 
| 14 | 
            +
                module_function
         | 
| 15 | 
            +
                # Analyze a multiplex, and all queries within.
         | 
| 16 | 
            +
                # Multiplex analyzers are ran for all queries, keeping state.
         | 
| 17 | 
            +
                # Query analyzers are ran per query, without carrying state between queries.
         | 
| 18 | 
            +
                #
         | 
| 19 | 
            +
                # @param multiplex [GraphQL::Execution::Multiplex]
         | 
| 20 | 
            +
                # @param analyzers [Array<GraphQL::Analysis::Analyzer>]
         | 
| 21 | 
            +
                # @return [Array<Any>] Results from multiplex analyzers
         | 
| 22 | 
            +
                def analyze_multiplex(multiplex, analyzers)
         | 
| 23 | 
            +
                  multiplex_analyzers = analyzers.map { |analyzer| analyzer.new(multiplex) }
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  multiplex.current_trace.analyze_multiplex(multiplex: multiplex) do
         | 
| 26 | 
            +
                    query_results = multiplex.queries.map do |query|
         | 
| 27 | 
            +
                      if query.valid?
         | 
| 28 | 
            +
                        analyze_query(
         | 
| 29 | 
            +
                          query,
         | 
| 30 | 
            +
                          query.analyzers,
         | 
| 31 | 
            +
                          multiplex_analyzers: multiplex_analyzers
         | 
| 32 | 
            +
                        )
         | 
| 33 | 
            +
                      else
         | 
| 34 | 
            +
                        []
         | 
| 35 | 
            +
                      end
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    multiplex_results = multiplex_analyzers.map(&:result)
         | 
| 39 | 
            +
                    multiplex_errors = analysis_errors(multiplex_results)
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    multiplex.queries.each_with_index do |query, idx|
         | 
| 42 | 
            +
                      query.analysis_errors = multiplex_errors + analysis_errors(query_results[idx])
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
                    multiplex_results
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                # @param query [GraphQL::Query]
         | 
| 49 | 
            +
                # @param analyzers [Array<GraphQL::Analysis::Analyzer>]
         | 
| 50 | 
            +
                # @return [Array<Any>] Results from those analyzers
         | 
| 51 | 
            +
                def analyze_query(query, analyzers, multiplex_analyzers: [])
         | 
| 52 | 
            +
                  query.current_trace.analyze_query(query: query) do
         | 
| 53 | 
            +
                    query_analyzers = analyzers
         | 
| 54 | 
            +
                      .map { |analyzer| analyzer.new(query) }
         | 
| 55 | 
            +
                      .tap { _1.select!(&:analyze?) }
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                    analyzers_to_run = query_analyzers + multiplex_analyzers
         | 
| 58 | 
            +
                    if analyzers_to_run.any?
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                      analyzers_to_run.select!(&:visit?)
         | 
| 61 | 
            +
                      if analyzers_to_run.any?
         | 
| 62 | 
            +
                        visitor = GraphQL::Analysis::Visitor.new(
         | 
| 63 | 
            +
                          query: query,
         | 
| 64 | 
            +
                          analyzers: analyzers_to_run
         | 
| 65 | 
            +
                        )
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                        # `nil` or `0` causes no timeout
         | 
| 68 | 
            +
                        Timeout::timeout(query.validate_timeout_remaining) do
         | 
| 69 | 
            +
                          visitor.visit
         | 
| 70 | 
            +
                        end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                        if visitor.rescued_errors.any?
         | 
| 73 | 
            +
                          return visitor.rescued_errors
         | 
| 74 | 
            +
                        end
         | 
| 75 | 
            +
                      end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                      query_analyzers.map(&:result)
         | 
| 78 | 
            +
                    else
         | 
| 79 | 
            +
                      []
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
                rescue Timeout::Error
         | 
| 83 | 
            +
                  [GraphQL::AnalysisError.new("Timeout on validation of query")]
         | 
| 84 | 
            +
                rescue GraphQL::UnauthorizedError
         | 
| 85 | 
            +
                  # This error was raised during analysis and will be returned the client before execution
         | 
| 86 | 
            +
                  []
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                def analysis_errors(results)
         | 
| 90 | 
            +
                  results.flatten.tap { _1.select! { |r| r.is_a?(GraphQL::AnalysisError) } }
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
              end
         | 
| 93 | 
            +
            end
         | 
| @@ -21,6 +21,7 @@ module GraphQL | |
| 21 21 | 
             
                    manager = spawn_fiber do
         | 
| 22 22 | 
             
                      while first_pass || job_fibers.any?
         | 
| 23 23 | 
             
                        first_pass = false
         | 
| 24 | 
            +
                        fiber_vars = get_fiber_variables
         | 
| 24 25 |  | 
| 25 26 | 
             
                        while (f = (job_fibers.shift || spawn_job_fiber))
         | 
| 26 27 | 
             
                          if f.alive?
         | 
| @@ -34,6 +35,7 @@ module GraphQL | |
| 34 35 | 
             
                        next_job_fibers.clear
         | 
| 35 36 |  | 
| 36 37 | 
             
                        Sync do |root_task|
         | 
| 38 | 
            +
                          set_fiber_variables(fiber_vars)
         | 
| 37 39 | 
             
                          while source_tasks.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
         | 
| 38 40 | 
             
                            while (task = source_tasks.shift || spawn_source_task(root_task, sources_condition))
         | 
| 39 41 | 
             
                              if task.alive?
         | 
| @@ -40,7 +40,7 @@ module GraphQL | |
| 40 40 | 
             
                        lazies_at_depth = Hash.new { |h, k| h[k] = [] }
         | 
| 41 41 | 
             
                        multiplex_analyzers = schema.multiplex_analyzers
         | 
| 42 42 | 
             
                        if multiplex.max_complexity
         | 
| 43 | 
            -
                          multiplex_analyzers += [GraphQL::Analysis:: | 
| 43 | 
            +
                          multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
         | 
| 44 44 | 
             
                        end
         | 
| 45 45 |  | 
| 46 46 | 
             
                        schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
         | 
| @@ -100,10 +100,10 @@ module GraphQL | |
| 100 100 | 
             
                      # Depending on the analysis engine, we must use different analyzers
         | 
| 101 101 | 
             
                      # remove this once everything has switched over to AST analyzers
         | 
| 102 102 | 
             
                      if max_depth
         | 
| 103 | 
            -
                        qa << GraphQL::Analysis:: | 
| 103 | 
            +
                        qa << GraphQL::Analysis::MaxQueryDepth
         | 
| 104 104 | 
             
                      end
         | 
| 105 105 | 
             
                      if max_complexity
         | 
| 106 | 
            -
                        qa << GraphQL::Analysis:: | 
| 106 | 
            +
                        qa << GraphQL::Analysis::MaxQueryComplexity
         | 
| 107 107 | 
             
                      end
         | 
| 108 108 | 
             
                      qa
         | 
| 109 109 | 
             
                    else
         | 
| @@ -312,10 +312,15 @@ module GraphQL | |
| 312 312 | 
             
                      context.query.after_lazy(custom_loaded_value) do |custom_value|
         | 
| 313 313 | 
             
                        if loads
         | 
| 314 314 | 
             
                          if type.list?
         | 
| 315 | 
            -
                            loaded_values =  | 
| 316 | 
            -
             | 
| 317 | 
            -
                               | 
| 318 | 
            -
             | 
| 315 | 
            +
                            loaded_values = []
         | 
| 316 | 
            +
                            context.dataloader.run_isolated do
         | 
| 317 | 
            +
                              custom_value.each_with_index.map { |custom_val, idx|
         | 
| 318 | 
            +
                                id = coerced_value[idx]
         | 
| 319 | 
            +
                                context.dataloader.append_job do
         | 
| 320 | 
            +
                                  loaded_values[idx] = load_method_owner.authorize_application_object(self, id, context, custom_val)
         | 
| 321 | 
            +
                                end
         | 
| 322 | 
            +
                              }
         | 
| 323 | 
            +
                            end
         | 
| 319 324 | 
             
                            context.schema.after_any_lazies(loaded_values, &:itself)
         | 
| 320 325 | 
             
                          else
         | 
| 321 326 | 
             
                            load_method_owner.authorize_application_object(self, coerced_value, context, custom_loaded_value)
         | 
| @@ -326,7 +331,16 @@ module GraphQL | |
| 326 331 | 
             
                      end
         | 
| 327 332 | 
             
                    elsif loads
         | 
| 328 333 | 
             
                      if type.list?
         | 
| 329 | 
            -
                        loaded_values =  | 
| 334 | 
            +
                        loaded_values = []
         | 
| 335 | 
            +
                        # We want to run these list items all together,
         | 
| 336 | 
            +
                        # but we also need to wait for the result so we can return it :S
         | 
| 337 | 
            +
                        context.dataloader.run_isolated do
         | 
| 338 | 
            +
                          coerced_value.each_with_index { |val, idx|
         | 
| 339 | 
            +
                            context.dataloader.append_job do
         | 
| 340 | 
            +
                              loaded_values[idx] = load_method_owner.load_and_authorize_application_object(self, val, context)
         | 
| 341 | 
            +
                            end
         | 
| 342 | 
            +
                          }
         | 
| 343 | 
            +
                        end
         | 
| 330 344 | 
             
                        context.schema.after_any_lazies(loaded_values, &:itself)
         | 
| 331 345 | 
             
                      else
         | 
| 332 346 | 
             
                        load_method_owner.load_and_authorize_application_object(self, coerced_value, context)
         | 
| @@ -188,6 +188,8 @@ module GraphQL | |
| 188 188 | 
             
                        assert_has_location(SCALAR)
         | 
| 189 189 | 
             
                      elsif @owner < GraphQL::Schema
         | 
| 190 190 | 
             
                        assert_has_location(SCHEMA)
         | 
| 191 | 
            +
                      elsif @owner < GraphQL::Schema::Resolver
         | 
| 192 | 
            +
                        assert_has_location(FIELD_DEFINITION)
         | 
| 191 193 | 
             
                      else
         | 
| 192 194 | 
             
                        raise "Unexpected directive owner class: #{@owner}"
         | 
| 193 195 | 
             
                      end
         | 
    
        data/lib/graphql/schema/field.rb
    CHANGED
    
    
| @@ -25,6 +25,7 @@ module GraphQL | |
| 25 25 | 
             
                  extend GraphQL::Schema::Member::HasValidators
         | 
| 26 26 | 
             
                  include Schema::Member::HasPath
         | 
| 27 27 | 
             
                  extend Schema::Member::HasPath
         | 
| 28 | 
            +
                  extend Schema::Member::HasDirectives
         | 
| 28 29 |  | 
| 29 30 | 
             
                  # @param object [Object] The application object that this field is being resolved on
         | 
| 30 31 | 
             
                  # @param context [GraphQL::Query::Context]
         | 
| @@ -9,7 +9,7 @@ module GraphQL | |
| 9 9 | 
             
                # Assign the result to `context.namespace(:subscriptions)[:subscription_broadcastable]`
         | 
| 10 10 | 
             
                # @api private
         | 
| 11 11 | 
             
                # @see Subscriptions#broadcastable? for a public API
         | 
| 12 | 
            -
                class BroadcastAnalyzer < GraphQL::Analysis:: | 
| 12 | 
            +
                class BroadcastAnalyzer < GraphQL::Analysis::Analyzer
         | 
| 13 13 | 
             
                  def initialize(subject)
         | 
| 14 14 | 
             
                    super
         | 
| 15 15 | 
             
                    @default_broadcastable = subject.schema.subscriptions.default_broadcastable
         | 
| @@ -235,7 +235,7 @@ module GraphQL | |
| 235 235 | 
             
                  if !query.valid?
         | 
| 236 236 | 
             
                    raise "Invalid query: #{query.validation_errors.map(&:to_h).inspect}"
         | 
| 237 237 | 
             
                  end
         | 
| 238 | 
            -
                  GraphQL::Analysis | 
| 238 | 
            +
                  GraphQL::Analysis.analyze_query(query, @schema.query_analyzers)
         | 
| 239 239 | 
             
                  query.context.namespace(:subscriptions)[:subscription_broadcastable]
         | 
| 240 240 | 
             
                end
         | 
| 241 241 |  | 
    
        data/lib/graphql/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: graphql
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 2.3. | 
| 4 | 
            +
              version: 2.3.6
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Robert Mosolgo
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024-06- | 
| 11 | 
            +
            date: 2024-06-25 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: base64
         | 
| @@ -306,14 +306,13 @@ files: | |
| 306 306 | 
             
            - lib/generators/graphql/union_generator.rb
         | 
| 307 307 | 
             
            - lib/graphql.rb
         | 
| 308 308 | 
             
            - lib/graphql/analysis.rb
         | 
| 309 | 
            -
            - lib/graphql/analysis/ | 
| 310 | 
            -
            - lib/graphql/analysis/ | 
| 311 | 
            -
            - lib/graphql/analysis/ | 
| 312 | 
            -
            - lib/graphql/analysis/ | 
| 313 | 
            -
            - lib/graphql/analysis/ | 
| 314 | 
            -
            - lib/graphql/analysis/ | 
| 315 | 
            -
            - lib/graphql/analysis/ | 
| 316 | 
            -
            - lib/graphql/analysis/ast/visitor.rb
         | 
| 309 | 
            +
            - lib/graphql/analysis/analyzer.rb
         | 
| 310 | 
            +
            - lib/graphql/analysis/field_usage.rb
         | 
| 311 | 
            +
            - lib/graphql/analysis/max_query_complexity.rb
         | 
| 312 | 
            +
            - lib/graphql/analysis/max_query_depth.rb
         | 
| 313 | 
            +
            - lib/graphql/analysis/query_complexity.rb
         | 
| 314 | 
            +
            - lib/graphql/analysis/query_depth.rb
         | 
| 315 | 
            +
            - lib/graphql/analysis/visitor.rb
         | 
| 317 316 | 
             
            - lib/graphql/analysis_error.rb
         | 
| 318 317 | 
             
            - lib/graphql/backtrace.rb
         | 
| 319 318 | 
             
            - lib/graphql/backtrace/inspect_result.rb
         | 
| @@ -645,7 +644,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 645 644 | 
             
                - !ruby/object:Gem::Version
         | 
| 646 645 | 
             
                  version: '0'
         | 
| 647 646 | 
             
            requirements: []
         | 
| 648 | 
            -
            rubygems_version: 3.5. | 
| 647 | 
            +
            rubygems_version: 3.5.12
         | 
| 649 648 | 
             
            signing_key:
         | 
| 650 649 | 
             
            specification_version: 4
         | 
| 651 650 | 
             
            summary: A GraphQL language and runtime for Ruby
         | 
| @@ -1,91 +0,0 @@ | |
| 1 | 
            -
            # frozen_string_literal: true
         | 
| 2 | 
            -
            module GraphQL
         | 
| 3 | 
            -
              module Analysis
         | 
| 4 | 
            -
                module AST
         | 
| 5 | 
            -
                  # Query analyzer for query ASTs. Query analyzers respond to visitor style methods
         | 
| 6 | 
            -
                  # but are prefixed by `enter` and `leave`.
         | 
| 7 | 
            -
                  #
         | 
| 8 | 
            -
                  # When an analyzer is initialized with a Multiplex, you can always get the current query from
         | 
| 9 | 
            -
                  # `visitor.query` in the visit methods.
         | 
| 10 | 
            -
                  #
         | 
| 11 | 
            -
                  # @param [GraphQL::Query, GraphQL::Execution::Multiplex] The query or multiplex to analyze
         | 
| 12 | 
            -
                  class Analyzer
         | 
| 13 | 
            -
                    def initialize(subject)
         | 
| 14 | 
            -
                      @subject = subject
         | 
| 15 | 
            -
             | 
| 16 | 
            -
                      if subject.is_a?(GraphQL::Query)
         | 
| 17 | 
            -
                        @query = subject
         | 
| 18 | 
            -
                        @multiplex = nil
         | 
| 19 | 
            -
                      else
         | 
| 20 | 
            -
                        @multiplex = subject
         | 
| 21 | 
            -
                        @query = nil
         | 
| 22 | 
            -
                      end
         | 
| 23 | 
            -
                    end
         | 
| 24 | 
            -
             | 
| 25 | 
            -
                    # Analyzer hook to decide at analysis time whether a query should
         | 
| 26 | 
            -
                    # be analyzed or not.
         | 
| 27 | 
            -
                    # @return [Boolean] If the query should be analyzed or not
         | 
| 28 | 
            -
                    def analyze?
         | 
| 29 | 
            -
                      true
         | 
| 30 | 
            -
                    end
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                    # Analyzer hook to decide at analysis time whether analysis
         | 
| 33 | 
            -
                    # requires a visitor pass; can be disabled for precomputed results.
         | 
| 34 | 
            -
                    # @return [Boolean] If analysis requires visitation or not
         | 
| 35 | 
            -
                    def visit?
         | 
| 36 | 
            -
                      true
         | 
| 37 | 
            -
                    end
         | 
| 38 | 
            -
             | 
| 39 | 
            -
                    # The result for this analyzer. Returning {GraphQL::AnalysisError} results
         | 
| 40 | 
            -
                    # in a query error.
         | 
| 41 | 
            -
                    # @return [Any] The analyzer result
         | 
| 42 | 
            -
                    def result
         | 
| 43 | 
            -
                      raise GraphQL::RequiredImplementationMissingError
         | 
| 44 | 
            -
                    end
         | 
| 45 | 
            -
             | 
| 46 | 
            -
                    class << self
         | 
| 47 | 
            -
                      private
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                      def build_visitor_hooks(member_name)
         | 
| 50 | 
            -
                        class_eval(<<-EOS, __FILE__, __LINE__ + 1)
         | 
| 51 | 
            -
                          def on_enter_#{member_name}(node, parent, visitor)
         | 
| 52 | 
            -
                          end
         | 
| 53 | 
            -
             | 
| 54 | 
            -
                          def on_leave_#{member_name}(node, parent, visitor)
         | 
| 55 | 
            -
                          end
         | 
| 56 | 
            -
                        EOS
         | 
| 57 | 
            -
                      end
         | 
| 58 | 
            -
                    end
         | 
| 59 | 
            -
             | 
| 60 | 
            -
                    build_visitor_hooks :argument
         | 
| 61 | 
            -
                    build_visitor_hooks :directive
         | 
| 62 | 
            -
                    build_visitor_hooks :document
         | 
| 63 | 
            -
                    build_visitor_hooks :enum
         | 
| 64 | 
            -
                    build_visitor_hooks :field
         | 
| 65 | 
            -
                    build_visitor_hooks :fragment_spread
         | 
| 66 | 
            -
                    build_visitor_hooks :inline_fragment
         | 
| 67 | 
            -
                    build_visitor_hooks :input_object
         | 
| 68 | 
            -
                    build_visitor_hooks :list_type
         | 
| 69 | 
            -
                    build_visitor_hooks :non_null_type
         | 
| 70 | 
            -
                    build_visitor_hooks :null_value
         | 
| 71 | 
            -
                    build_visitor_hooks :operation_definition
         | 
| 72 | 
            -
                    build_visitor_hooks :type_name
         | 
| 73 | 
            -
                    build_visitor_hooks :variable_definition
         | 
| 74 | 
            -
                    build_visitor_hooks :variable_identifier
         | 
| 75 | 
            -
                    build_visitor_hooks :abstract_node
         | 
| 76 | 
            -
             | 
| 77 | 
            -
                    protected
         | 
| 78 | 
            -
             | 
| 79 | 
            -
                    # @return [GraphQL::Query, GraphQL::Execution::Multiplex] Whatever this analyzer is analyzing
         | 
| 80 | 
            -
                    attr_reader :subject
         | 
| 81 | 
            -
             | 
| 82 | 
            -
                    # @return [GraphQL::Query, nil] `nil` if this analyzer is visiting a multiplex
         | 
| 83 | 
            -
                    #  (When this is `nil`, use `visitor.query` inside visit methods to get the current query)
         | 
| 84 | 
            -
                    attr_reader :query
         | 
| 85 | 
            -
             | 
| 86 | 
            -
                    # @return [GraphQL::Execution::Multiplex, nil] `nil` if this analyzer is visiting a query
         | 
| 87 | 
            -
                    attr_reader :multiplex
         | 
| 88 | 
            -
                  end
         | 
| 89 | 
            -
                end
         | 
| 90 | 
            -
              end
         | 
| 91 | 
            -
            end
         |