graphql 2.0.27 → 2.1.0
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/execution/interpreter/runtime.rb +23 -21
- data/lib/graphql/language/document_from_schema_definition.rb +6 -16
- data/lib/graphql/language/nodes.rb +1 -1
- data/lib/graphql/language/printer.rb +233 -145
- data/lib/graphql/language/sanitized_printer.rb +14 -21
- data/lib/graphql/language/visitor.rb +18 -81
- data/lib/graphql/pagination/connection.rb +23 -1
- data/lib/graphql/query.rb +2 -19
- data/lib/graphql/rake_task.rb +3 -12
- data/lib/graphql/schema/field/scope_extension.rb +7 -1
- data/lib/graphql/schema/member/scoped.rb +19 -0
- data/lib/graphql/schema/object.rb +8 -0
- data/lib/graphql/schema/printer.rb +8 -7
- data/lib/graphql/schema/subscription.rb +11 -4
- data/lib/graphql/schema/warden.rb +3 -34
- data/lib/graphql/schema.rb +4 -20
- data/lib/graphql/static_validation/validation_context.rb +0 -3
- data/lib/graphql/static_validation.rb +0 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
- data/lib/graphql/subscriptions.rb +11 -6
- data/lib/graphql/types/relay/connection_behaviors.rb +19 -2
- data/lib/graphql/types/relay/edge_behaviors.rb +7 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +0 -1
- metadata +1 -3
- data/lib/graphql/filter.rb +0 -59
- data/lib/graphql/static_validation/type_stack.rb +0 -216
| @@ -31,11 +31,6 @@ module GraphQL | |
| 31 31 | 
             
                #   visitor.count
         | 
| 32 32 | 
             
                #   # => 3
         | 
| 33 33 | 
             
                class Visitor
         | 
| 34 | 
            -
                  # If any hook returns this value, the {Visitor} stops visiting this
         | 
| 35 | 
            -
                  # node right away
         | 
| 36 | 
            -
                  # @deprecated Use `super` to continue the visit; or don't call it to halt.
         | 
| 37 | 
            -
                  SKIP = :_skip
         | 
| 38 | 
            -
             | 
| 39 34 | 
             
                  class DeleteNode; end
         | 
| 40 35 |  | 
| 41 36 | 
             
                  # When this is returned from a visitor method,
         | 
| @@ -44,25 +39,13 @@ module GraphQL | |
| 44 39 |  | 
| 45 40 | 
             
                  def initialize(document)
         | 
| 46 41 | 
             
                    @document = document
         | 
| 47 | 
            -
                    @visitors = {}
         | 
| 48 42 | 
             
                    @result = nil
         | 
| 49 43 | 
             
                  end
         | 
| 50 44 |  | 
| 51 45 | 
             
                  # @return [GraphQL::Language::Nodes::Document] The document with any modifications applied
         | 
| 52 46 | 
             
                  attr_reader :result
         | 
| 53 47 |  | 
| 54 | 
            -
                  #  | 
| 55 | 
            -
                  # @param node_class [Class] The node class that you want to listen to
         | 
| 56 | 
            -
                  # @return [NodeVisitor]
         | 
| 57 | 
            -
                  #
         | 
| 58 | 
            -
                  # @example Run a hook whenever you enter a new Field
         | 
| 59 | 
            -
                  #   visitor[GraphQL::Language::Nodes::Field] << ->(node, parent) { p "Here's a field" }
         | 
| 60 | 
            -
                  # @deprecated see `on_` methods, like {#on_field}
         | 
| 61 | 
            -
                  def [](node_class)
         | 
| 62 | 
            -
                    @visitors[node_class] ||= NodeVisitor.new
         | 
| 63 | 
            -
                  end
         | 
| 64 | 
            -
             | 
| 65 | 
            -
                  # Visit `document` and all children, applying hooks as you go
         | 
| 48 | 
            +
                  # Visit `document` and all children
         | 
| 66 49 | 
             
                  # @return [void]
         | 
| 67 50 | 
             
                  def visit
         | 
| 68 51 | 
             
                    # `@document` may be any kind of node:
         | 
| @@ -88,7 +71,6 @@ module GraphQL | |
| 88 71 | 
             
                      # To customize this hook, override one of its make_visit_methods (or the base method?)
         | 
| 89 72 | 
             
                      # in your subclasses.
         | 
| 90 73 | 
             
                      #
         | 
| 91 | 
            -
                      # For compatibility, it calls hook procs, too.
         | 
| 92 74 | 
             
                      # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
         | 
| 93 75 | 
             
                      # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
         | 
| 94 76 | 
             
                      # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
         | 
| @@ -98,29 +80,24 @@ module GraphQL | |
| 98 80 | 
             
                          # by a user hook, don't want to keep visiting in that case.
         | 
| 99 81 | 
             
                          [node, parent]
         | 
| 100 82 | 
             
                        else
         | 
| 101 | 
            -
                          # Run hooks if there are any
         | 
| 102 83 | 
             
                          new_node = node
         | 
| 103 | 
            -
                           | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 | 
            -
             | 
| 110 | 
            -
                                   | 
| 111 | 
            -
             | 
| 112 | 
            -
                                     | 
| 113 | 
            -
             | 
| 114 | 
            -
             | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 120 | 
            -
                              end
         | 
| 121 | 
            -
                            }
         | 
| 122 | 
            -
                          end
         | 
| 123 | 
            -
                          end_visit(new_node, parent) unless no_hooks
         | 
| 84 | 
            +
                          #{
         | 
| 85 | 
            +
                            if method_defined?(child_visit_method)
         | 
| 86 | 
            +
                              "new_node = #{child_visit_method}(new_node)"
         | 
| 87 | 
            +
                            elsif children_of_type
         | 
| 88 | 
            +
                              children_of_type.map do |child_accessor, child_class|
         | 
| 89 | 
            +
                                "node.#{child_accessor}.each do |child_node|
         | 
| 90 | 
            +
                                  new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
         | 
| 91 | 
            +
                                  # Reassign `node` in case the child hook makes a modification
         | 
| 92 | 
            +
                                  if new_child_and_node.is_a?(Array)
         | 
| 93 | 
            +
                                    new_node = new_child_and_node[1]
         | 
| 94 | 
            +
                                  end
         | 
| 95 | 
            +
                                end"
         | 
| 96 | 
            +
                              end.join("\n")
         | 
| 97 | 
            +
                            else
         | 
| 98 | 
            +
                              ""
         | 
| 99 | 
            +
                            end
         | 
| 100 | 
            +
                          }
         | 
| 124 101 |  | 
| 125 102 | 
             
                          if new_node.equal?(node)
         | 
| 126 103 | 
             
                            [node, parent]
         | 
| @@ -305,46 +282,6 @@ module GraphQL | |
| 305 282 | 
             
                      new_node_and_new_parent
         | 
| 306 283 | 
             
                    end
         | 
| 307 284 | 
             
                  end
         | 
| 308 | 
            -
             | 
| 309 | 
            -
                  def begin_visit(node, parent)
         | 
| 310 | 
            -
                    node_visitor = self[node.class]
         | 
| 311 | 
            -
                    self.class.apply_hooks(node_visitor.enter, node, parent)
         | 
| 312 | 
            -
                  end
         | 
| 313 | 
            -
             | 
| 314 | 
            -
                  # Should global `leave` visitors come first or last?
         | 
| 315 | 
            -
                  def end_visit(node, parent)
         | 
| 316 | 
            -
                    node_visitor = self[node.class]
         | 
| 317 | 
            -
                    self.class.apply_hooks(node_visitor.leave, node, parent)
         | 
| 318 | 
            -
                  end
         | 
| 319 | 
            -
             | 
| 320 | 
            -
                  # If one of the visitors returns SKIP, stop visiting this node
         | 
| 321 | 
            -
                  def self.apply_hooks(hooks, node, parent)
         | 
| 322 | 
            -
                    hooks.each do |proc|
         | 
| 323 | 
            -
                      return false if proc.call(node, parent) == SKIP
         | 
| 324 | 
            -
                    end
         | 
| 325 | 
            -
                    true
         | 
| 326 | 
            -
                  end
         | 
| 327 | 
            -
             | 
| 328 | 
            -
                  # Collect `enter` and `leave` hooks for classes in {GraphQL::Language::Nodes}
         | 
| 329 | 
            -
                  #
         | 
| 330 | 
            -
                  # Access {NodeVisitor}s via {GraphQL::Language::Visitor#[]}
         | 
| 331 | 
            -
                  class NodeVisitor
         | 
| 332 | 
            -
                    # @return [Array<Proc>] Hooks to call when entering a node of this type
         | 
| 333 | 
            -
                    attr_reader :enter
         | 
| 334 | 
            -
                    # @return [Array<Proc>] Hooks to call when leaving a node of this type
         | 
| 335 | 
            -
                    attr_reader :leave
         | 
| 336 | 
            -
             | 
| 337 | 
            -
                    def initialize
         | 
| 338 | 
            -
                      @enter = []
         | 
| 339 | 
            -
                      @leave = []
         | 
| 340 | 
            -
                    end
         | 
| 341 | 
            -
             | 
| 342 | 
            -
                    # Shorthand to add a hook to the {#enter} array
         | 
| 343 | 
            -
                    # @param hook [Proc] A hook to add
         | 
| 344 | 
            -
                    def <<(hook)
         | 
| 345 | 
            -
                      enter << hook
         | 
| 346 | 
            -
                    end
         | 
| 347 | 
            -
                  end
         | 
| 348 285 | 
             
                end
         | 
| 349 286 | 
             
              end
         | 
| 350 287 | 
             
            end
         | 
| @@ -19,7 +19,14 @@ module GraphQL | |
| 19 19 | 
             
                  attr_reader :items
         | 
| 20 20 |  | 
| 21 21 | 
             
                  # @return [GraphQL::Query::Context]
         | 
| 22 | 
            -
                   | 
| 22 | 
            +
                  attr_reader :context
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def context=(new_ctx)
         | 
| 25 | 
            +
                    current_runtime_state = Thread.current[:__graphql_runtime_info]
         | 
| 26 | 
            +
                    query_runtime_state = current_runtime_state[new_ctx.query]
         | 
| 27 | 
            +
                    @was_authorized_by_scope_items = query_runtime_state.was_authorized_by_scope_items
         | 
| 28 | 
            +
                    @context = new_ctx
         | 
| 29 | 
            +
                  end
         | 
| 23 30 |  | 
| 24 31 | 
             
                  # @return [Object] the object this collection belongs to
         | 
| 25 32 | 
             
                  attr_accessor :parent
         | 
| @@ -83,6 +90,17 @@ module GraphQL | |
| 83 90 | 
             
                    else
         | 
| 84 91 | 
             
                      default_page_size
         | 
| 85 92 | 
             
                    end
         | 
| 93 | 
            +
                    @was_authorized_by_scope_items = if @context
         | 
| 94 | 
            +
                      current_runtime_state = Thread.current[:__graphql_runtime_info]
         | 
| 95 | 
            +
                      query_runtime_state = current_runtime_state[@context.query]
         | 
| 96 | 
            +
                      query_runtime_state.was_authorized_by_scope_items
         | 
| 97 | 
            +
                    else
         | 
| 98 | 
            +
                      nil
         | 
| 99 | 
            +
                    end
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                  def was_authorized_by_scope_items?
         | 
| 103 | 
            +
                    @was_authorized_by_scope_items
         | 
| 86 104 | 
             
                  end
         | 
| 87 105 |  | 
| 88 106 | 
             
                  def max_page_size=(new_value)
         | 
| @@ -247,6 +265,10 @@ module GraphQL | |
| 247 265 | 
             
                    def cursor
         | 
| 248 266 | 
             
                      @cursor ||= @connection.cursor_for(@node)
         | 
| 249 267 | 
             
                    end
         | 
| 268 | 
            +
             | 
| 269 | 
            +
                    def was_authorized_by_scope_items?
         | 
| 270 | 
            +
                      @connection.was_authorized_by_scope_items?
         | 
| 271 | 
            +
                    end
         | 
| 250 272 | 
             
                  end
         | 
| 251 273 | 
             
                end
         | 
| 252 274 | 
             
              end
         | 
    
        data/lib/graphql/query.rb
    CHANGED
    
    | @@ -95,15 +95,10 @@ module GraphQL | |
| 95 95 | 
             
                # @param root_value [Object] the object used to resolve fields on the root type
         | 
| 96 96 | 
             
                # @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value)
         | 
| 97 97 | 
             
                # @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
         | 
| 98 | 
            -
                 | 
| 99 | 
            -
                # @param only [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns false
         | 
| 100 | 
            -
                def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, except: nil, only: nil, warden: nil)
         | 
| 98 | 
            +
                def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil)
         | 
| 101 99 | 
             
                  # Even if `variables: nil` is passed, use an empty hash for simpler logic
         | 
| 102 100 | 
             
                  variables ||= {}
         | 
| 103 101 | 
             
                  @schema = schema
         | 
| 104 | 
            -
                  if only || except
         | 
| 105 | 
            -
                    merge_filters(except: except, only: only)
         | 
| 106 | 
            -
                  end
         | 
| 107 102 | 
             
                  @context = schema.context_class.new(query: self, object: root_value, values: context)
         | 
| 108 103 | 
             
                  @warden = warden
         | 
| 109 104 | 
             
                  @subscription_topic = subscription_topic
         | 
| @@ -129,7 +124,6 @@ module GraphQL | |
| 129 124 | 
             
                    raise ArgumentError, "context[:tracers] are not supported without `trace_with(GraphQL::Tracing::CallLegacyTracers)` in the schema configuration, please add it."
         | 
| 130 125 | 
             
                  end
         | 
| 131 126 |  | 
| 132 | 
            -
             | 
| 133 127 | 
             
                  @analysis_errors = []
         | 
| 134 128 | 
             
                  if variables.is_a?(String)
         | 
| 135 129 | 
             
                    raise ArgumentError, "Query variables should be a Hash, not a String. Try JSON.parse to prepare variables."
         | 
| @@ -354,17 +348,6 @@ module GraphQL | |
| 354 348 | 
             
                  with_prepared_ast { @query }
         | 
| 355 349 | 
             
                end
         | 
| 356 350 |  | 
| 357 | 
            -
                # @return [void]
         | 
| 358 | 
            -
                def merge_filters(only: nil, except: nil)
         | 
| 359 | 
            -
                  if @prepared_ast
         | 
| 360 | 
            -
                    raise "Can't add filters after preparing the query"
         | 
| 361 | 
            -
                  else
         | 
| 362 | 
            -
                    @filter ||= @schema.default_filter
         | 
| 363 | 
            -
                    @filter = @filter.merge(only: only, except: except)
         | 
| 364 | 
            -
                  end
         | 
| 365 | 
            -
                  nil
         | 
| 366 | 
            -
                end
         | 
| 367 | 
            -
             | 
| 368 351 | 
             
                def subscription?
         | 
| 369 352 | 
             
                  with_prepared_ast { @subscription }
         | 
| 370 353 | 
             
                end
         | 
| @@ -400,7 +383,7 @@ module GraphQL | |
| 400 383 |  | 
| 401 384 | 
             
                def prepare_ast
         | 
| 402 385 | 
             
                  @prepared_ast = true
         | 
| 403 | 
            -
                  @warden ||= @schema.warden_class.new( | 
| 386 | 
            +
                  @warden ||= @schema.warden_class.new(schema: @schema, context: @context)
         | 
| 404 387 | 
             
                  parse_error = nil
         | 
| 405 388 | 
             
                  @document ||= begin
         | 
| 406 389 | 
             
                    if query_string
         | 
    
        data/lib/graphql/rake_task.rb
    CHANGED
    
    | @@ -9,8 +9,7 @@ module GraphQL | |
| 9 9 | 
             
              # By default, schemas are looked up by name as constants using `schema_name:`.
         | 
| 10 10 | 
             
              # You can provide a `load_schema` function to return your schema another way.
         | 
| 11 11 | 
             
              #
         | 
| 12 | 
            -
              # `load_context | 
| 13 | 
            -
              # you can keep an eye on how filters affect your schema.
         | 
| 12 | 
            +
              # Use `load_context:` and `visible?` to dump schemas under certain visibility constraints.
         | 
| 14 13 | 
             
              #
         | 
| 15 14 | 
             
              # @example Dump a Schema to .graphql + .json files
         | 
| 16 15 | 
             
              #   require "graphql/rake_task"
         | 
| @@ -36,8 +35,6 @@ module GraphQL | |
| 36 35 | 
             
                  schema_name: nil,
         | 
| 37 36 | 
             
                  load_schema: ->(task) { Object.const_get(task.schema_name) },
         | 
| 38 37 | 
             
                  load_context: ->(task) { {} },
         | 
| 39 | 
            -
                  only: nil,
         | 
| 40 | 
            -
                  except: nil,
         | 
| 41 38 | 
             
                  directory: ".",
         | 
| 42 39 | 
             
                  idl_outfile: "schema.graphql",
         | 
| 43 40 | 
             
                  json_outfile: "schema.json",
         | 
| @@ -68,12 +65,6 @@ module GraphQL | |
| 68 65 | 
             
                # @return [<#call(task)>] A callable for loading the query context
         | 
| 69 66 | 
             
                attr_accessor :load_context
         | 
| 70 67 |  | 
| 71 | 
            -
                # @return [<#call(member, ctx)>, nil] A filter for this task
         | 
| 72 | 
            -
                attr_accessor :only
         | 
| 73 | 
            -
             | 
| 74 | 
            -
                # @return [<#call(member, ctx)>, nil] A filter for this task
         | 
| 75 | 
            -
                attr_accessor :except
         | 
| 76 | 
            -
             | 
| 77 68 | 
             
                # @return [String] target for IDL task
         | 
| 78 69 | 
             
                attr_accessor :idl_outfile
         | 
| 79 70 |  | 
| @@ -117,10 +108,10 @@ module GraphQL | |
| 117 108 | 
             
                      include_is_repeatable: include_is_repeatable,
         | 
| 118 109 | 
             
                      include_specified_by_url: include_specified_by_url,
         | 
| 119 110 | 
             
                      include_schema_description: include_schema_description,
         | 
| 120 | 
            -
                       | 
| 111 | 
            +
                      context: context
         | 
| 121 112 | 
             
                    )
         | 
| 122 113 | 
             
                  when :to_definition
         | 
| 123 | 
            -
                    schema.to_definition( | 
| 114 | 
            +
                    schema.to_definition(context: context)
         | 
| 124 115 | 
             
                  else
         | 
| 125 116 | 
             
                    raise ArgumentError, "Unexpected schema dump method: #{method_name.inspect}"
         | 
| 126 117 | 
             
                  end
         | 
| @@ -10,7 +10,13 @@ module GraphQL | |
| 10 10 | 
             
                      else
         | 
| 11 11 | 
             
                        ret_type = @field.type.unwrap
         | 
| 12 12 | 
             
                        if ret_type.respond_to?(:scope_items)
         | 
| 13 | 
            -
                          ret_type.scope_items(value, context)
         | 
| 13 | 
            +
                          scoped_items = ret_type.scope_items(value, context)
         | 
| 14 | 
            +
                          if !scoped_items.equal?(value) && !ret_type.reauthorize_scoped_objects
         | 
| 15 | 
            +
                            current_runtime_state = Thread.current[:__graphql_runtime_info]
         | 
| 16 | 
            +
                            query_runtime_state = current_runtime_state[context.query]
         | 
| 17 | 
            +
                            query_runtime_state.was_authorized_by_scope_items = true
         | 
| 18 | 
            +
                          end
         | 
| 19 | 
            +
                          scoped_items
         | 
| 14 20 | 
             
                        else
         | 
| 15 21 | 
             
                          value
         | 
| 16 22 | 
             
                        end
         | 
| @@ -15,6 +15,25 @@ module GraphQL | |
| 15 15 | 
             
                    def scope_items(items, context)
         | 
| 16 16 | 
             
                      items
         | 
| 17 17 | 
             
                    end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    def reauthorize_scoped_objects(new_value = nil)
         | 
| 20 | 
            +
                      if new_value.nil?
         | 
| 21 | 
            +
                        if @reauthorize_scoped_objects != nil
         | 
| 22 | 
            +
                          @reauthorize_scoped_objects
         | 
| 23 | 
            +
                        else
         | 
| 24 | 
            +
                          find_inherited_value(:reauthorize_scoped_objects, nil)
         | 
| 25 | 
            +
                        end
         | 
| 26 | 
            +
                      else
         | 
| 27 | 
            +
                        @reauthorize_scoped_objects = new_value
         | 
| 28 | 
            +
                      end
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    def inherited(subclass)
         | 
| 32 | 
            +
                      super
         | 
| 33 | 
            +
                      subclass.class_eval do
         | 
| 34 | 
            +
                        @reauthorize_scoped_objects = nil
         | 
| 35 | 
            +
                      end
         | 
| 36 | 
            +
                    end
         | 
| 18 37 | 
             
                  end
         | 
| 19 38 | 
             
                end
         | 
| 20 39 | 
             
              end
         | 
| @@ -30,6 +30,10 @@ module GraphQL | |
| 30 30 | 
             
                    # @see authorized_new to make instances
         | 
| 31 31 | 
             
                    protected :new
         | 
| 32 32 |  | 
| 33 | 
            +
                    def wrap_scoped(object, context)
         | 
| 34 | 
            +
                      scoped_new(object, context)
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
             | 
| 33 37 | 
             
                    # This is called by the runtime to return an object to call methods on.
         | 
| 34 38 | 
             
                    def wrap(object, context)
         | 
| 35 39 | 
             
                      authorized_new(object, context)
         | 
| @@ -91,6 +95,10 @@ module GraphQL | |
| 91 95 | 
             
                        end
         | 
| 92 96 | 
             
                      end
         | 
| 93 97 | 
             
                    end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                    def scoped_new(object, context)
         | 
| 100 | 
            +
                      self.new(object, context)
         | 
| 101 | 
            +
                    end
         | 
| 94 102 | 
             
                  end
         | 
| 95 103 |  | 
| 96 104 | 
             
                  def initialize(object, context)
         | 
| @@ -36,15 +36,11 @@ module GraphQL | |
| 36 36 |  | 
| 37 37 | 
             
                  # @param schema [GraphQL::Schema]
         | 
| 38 38 | 
             
                  # @param context [Hash]
         | 
| 39 | 
            -
                  # @param only [<#call(member, ctx)>]
         | 
| 40 | 
            -
                  # @param except [<#call(member, ctx)>]
         | 
| 41 39 | 
             
                  # @param introspection [Boolean] Should include the introspection types in the string?
         | 
| 42 | 
            -
                  def initialize(schema, context: nil,  | 
| 40 | 
            +
                  def initialize(schema, context: nil, introspection: false)
         | 
| 43 41 | 
             
                    @document_from_schema = GraphQL::Language::DocumentFromSchemaDefinition.new(
         | 
| 44 42 | 
             
                      schema,
         | 
| 45 43 | 
             
                      context: context,
         | 
| 46 | 
            -
                      only: only,
         | 
| 47 | 
            -
                      except: except,
         | 
| 48 44 | 
             
                      include_introspection_types: introspection,
         | 
| 49 45 | 
             
                    )
         | 
| 50 46 |  | 
| @@ -61,7 +57,12 @@ module GraphQL | |
| 61 57 | 
             
                        false
         | 
| 62 58 | 
             
                      end
         | 
| 63 59 | 
             
                    end
         | 
| 64 | 
            -
                    schema = Class.new(GraphQL::Schema) { | 
| 60 | 
            +
                    schema = Class.new(GraphQL::Schema) {
         | 
| 61 | 
            +
                      query(query_root)
         | 
| 62 | 
            +
                      def self.visible?(member, _ctx)
         | 
| 63 | 
            +
                        member.graphql_name != "Root"
         | 
| 64 | 
            +
                      end
         | 
| 65 | 
            +
                    }
         | 
| 65 66 |  | 
| 66 67 | 
             
                    introspection_schema_ast = GraphQL::Language::DocumentFromSchemaDefinition.new(
         | 
| 67 68 | 
             
                      schema,
         | 
| @@ -94,7 +95,7 @@ module GraphQL | |
| 94 95 |  | 
| 95 96 | 
             
                  class IntrospectionPrinter < GraphQL::Language::Printer
         | 
| 96 97 | 
             
                    def print_schema_definition(schema)
         | 
| 97 | 
            -
                      "schema {\n  query: Root\n}"
         | 
| 98 | 
            +
                      print_string("schema {\n  query: Root\n}")
         | 
| 98 99 | 
             
                    end
         | 
| 99 100 | 
             
                  end
         | 
| 100 101 | 
             
                end
         | 
| @@ -28,14 +28,19 @@ module GraphQL | |
| 28 28 | 
             
                  def resolve_with_support(**args)
         | 
| 29 29 | 
             
                    result = nil
         | 
| 30 30 | 
             
                    unsubscribed = true
         | 
| 31 | 
            -
                    catch :graphql_subscription_unsubscribed do
         | 
| 31 | 
            +
                    unsubscribed_result = catch :graphql_subscription_unsubscribed do
         | 
| 32 32 | 
             
                      result = super
         | 
| 33 33 | 
             
                      unsubscribed = false
         | 
| 34 34 | 
             
                    end
         | 
| 35 35 |  | 
| 36 36 |  | 
| 37 37 | 
             
                    if unsubscribed
         | 
| 38 | 
            -
                       | 
| 38 | 
            +
                      if unsubscribed_result
         | 
| 39 | 
            +
                        context.namespace(:subscriptions)[:final_update] = true
         | 
| 40 | 
            +
                        unsubscribed_result
         | 
| 41 | 
            +
                      else
         | 
| 42 | 
            +
                        context.skip
         | 
| 43 | 
            +
                      end
         | 
| 39 44 | 
             
                    else
         | 
| 40 45 | 
             
                      result
         | 
| 41 46 | 
             
                    end
         | 
| @@ -94,9 +99,11 @@ module GraphQL | |
| 94 99 | 
             
                  end
         | 
| 95 100 |  | 
| 96 101 | 
             
                  # Call this to halt execution and remove this subscription from the system
         | 
| 97 | 
            -
                   | 
| 102 | 
            +
                  # @param update_value [Object] if present, deliver this update before unsubscribing
         | 
| 103 | 
            +
                  # @return [void]
         | 
| 104 | 
            +
                  def unsubscribe(update_value = nil)
         | 
| 98 105 | 
             
                    context.namespace(:subscriptions)[:unsubscribed] = true
         | 
| 99 | 
            -
                    throw :graphql_subscription_unsubscribed
         | 
| 106 | 
            +
                    throw :graphql_subscription_unsubscribed, update_value
         | 
| 100 107 | 
             
                  end
         | 
| 101 108 |  | 
| 102 109 | 
             
                  READING_SCOPE = ::Object.new
         | 
| @@ -4,37 +4,12 @@ require 'set' | |
| 4 4 |  | 
| 5 5 | 
             
            module GraphQL
         | 
| 6 6 | 
             
              class Schema
         | 
| 7 | 
            -
                # Restrict access to a {GraphQL::Schema} with a user-defined  | 
| 7 | 
            +
                # Restrict access to a {GraphQL::Schema} with a user-defined `visible?` implementations.
         | 
| 8 8 | 
             
                #
         | 
| 9 9 | 
             
                # When validating and executing a query, all access to schema members
         | 
| 10 10 | 
             
                # should go through a warden. If you access the schema directly,
         | 
| 11 11 | 
             
                # you may show a client something that it shouldn't be allowed to see.
         | 
| 12 12 | 
             
                #
         | 
| 13 | 
            -
                # @example Hiding private fields
         | 
| 14 | 
            -
                #   private_members = -> (member, ctx) { member.metadata[:private] }
         | 
| 15 | 
            -
                #   result = Schema.execute(query_string, except: private_members)
         | 
| 16 | 
            -
                #
         | 
| 17 | 
            -
                # @example Custom filter implementation
         | 
| 18 | 
            -
                #   # It must respond to `#call(member)`.
         | 
| 19 | 
            -
                #   class MissingRequiredFlags
         | 
| 20 | 
            -
                #     def initialize(user)
         | 
| 21 | 
            -
                #       @user = user
         | 
| 22 | 
            -
                #     end
         | 
| 23 | 
            -
                #
         | 
| 24 | 
            -
                #     # Return `false` if any required flags are missing
         | 
| 25 | 
            -
                #     def call(member, ctx)
         | 
| 26 | 
            -
                #       member.metadata[:required_flags].any? do |flag|
         | 
| 27 | 
            -
                #         !@user.has_flag?(flag)
         | 
| 28 | 
            -
                #       end
         | 
| 29 | 
            -
                #     end
         | 
| 30 | 
            -
                #   end
         | 
| 31 | 
            -
                #
         | 
| 32 | 
            -
                #   # Then, use the custom filter in query:
         | 
| 33 | 
            -
                #   missing_required_flags = MissingRequiredFlags.new(current_user)
         | 
| 34 | 
            -
                #
         | 
| 35 | 
            -
                #   # This query can only access members which match the user's flags
         | 
| 36 | 
            -
                #   result = Schema.execute(query_string, except: missing_required_flags)
         | 
| 37 | 
            -
                #
         | 
| 38 13 | 
             
                # @api private
         | 
| 39 14 | 
             
                class Warden
         | 
| 40 15 | 
             
                  def self.from_context(context)
         | 
| @@ -114,22 +89,16 @@ module GraphQL | |
| 114 89 | 
             
                    def interfaces(obj_type); obj_type.interfaces; end
         | 
| 115 90 | 
             
                  end
         | 
| 116 91 |  | 
| 117 | 
            -
                  # @param filter [<#call(member)>] Objects are hidden when `.call(member, ctx)` returns true
         | 
| 118 92 | 
             
                  # @param context [GraphQL::Query::Context]
         | 
| 119 93 | 
             
                  # @param schema [GraphQL::Schema]
         | 
| 120 | 
            -
                  def initialize( | 
| 94 | 
            +
                  def initialize(context:, schema:)
         | 
| 121 95 | 
             
                    @schema = schema
         | 
| 122 96 | 
             
                    # Cache these to avoid repeated hits to the inheritance chain when one isn't present
         | 
| 123 97 | 
             
                    @query = @schema.query
         | 
| 124 98 | 
             
                    @mutation = @schema.mutation
         | 
| 125 99 | 
             
                    @subscription = @schema.subscription
         | 
| 126 100 | 
             
                    @context = context
         | 
| 127 | 
            -
                    @visibility_cache =  | 
| 128 | 
            -
                      read_through { |m| filter.call(m, context) }
         | 
| 129 | 
            -
                    else
         | 
| 130 | 
            -
                      read_through { |m| schema.visible?(m, context) }
         | 
| 131 | 
            -
                    end
         | 
| 132 | 
            -
             | 
| 101 | 
            +
                    @visibility_cache = read_through { |m| schema.visible?(m, context) }
         | 
| 133 102 | 
             
                    @visibility_cache.compare_by_identity
         | 
| 134 103 | 
             
                    # Initialize all ivars to improve object shape consistency:
         | 
| 135 104 | 
             
                    @types = @visible_types = @reachable_types = @visible_parent_fields =
         | 
    
        data/lib/graphql/schema.rb
    CHANGED
    
    | @@ -222,7 +222,7 @@ module GraphQL | |
| 222 222 | 
             
                  # @param include_specified_by_url [Boolean] If true, scalar types' `specifiedByUrl:` will be included in the response
         | 
| 223 223 | 
             
                  # @param include_is_one_of [Boolean] If true, `isOneOf: true|false` will be included with input objects
         | 
| 224 224 | 
             
                  # @return [Hash] GraphQL result
         | 
| 225 | 
            -
                  def as_json( | 
| 225 | 
            +
                  def as_json(context: {}, include_deprecated_args: true, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false)
         | 
| 226 226 | 
             
                    introspection_query = Introspection.query(
         | 
| 227 227 | 
             
                      include_deprecated_args: include_deprecated_args,
         | 
| 228 228 | 
             
                      include_schema_description: include_schema_description,
         | 
| @@ -231,16 +231,14 @@ module GraphQL | |
| 231 231 | 
             
                      include_specified_by_url: include_specified_by_url,
         | 
| 232 232 | 
             
                    )
         | 
| 233 233 |  | 
| 234 | 
            -
                    execute(introspection_query,  | 
| 234 | 
            +
                    execute(introspection_query, context: context).to_h
         | 
| 235 235 | 
             
                  end
         | 
| 236 236 |  | 
| 237 237 | 
             
                  # Return the GraphQL IDL for the schema
         | 
| 238 238 | 
             
                  # @param context [Hash]
         | 
| 239 | 
            -
                  # @param only [<#call(member, ctx)>]
         | 
| 240 | 
            -
                  # @param except [<#call(member, ctx)>]
         | 
| 241 239 | 
             
                  # @return [String]
         | 
| 242 | 
            -
                  def to_definition( | 
| 243 | 
            -
                    GraphQL::Schema::Printer.print_schema(self,  | 
| 240 | 
            +
                  def to_definition(context: {})
         | 
| 241 | 
            +
                    GraphQL::Schema::Printer.print_schema(self, context: context)
         | 
| 244 242 | 
             
                  end
         | 
| 245 243 |  | 
| 246 244 | 
             
                  # Return the GraphQL::Language::Document IDL AST for the schema
         | 
| @@ -268,20 +266,6 @@ module GraphQL | |
| 268 266 | 
             
                    @find_cache[path] ||= @finder.find(path)
         | 
| 269 267 | 
             
                  end
         | 
| 270 268 |  | 
| 271 | 
            -
                  def default_filter
         | 
| 272 | 
            -
                    GraphQL::Filter.new(except: default_mask)
         | 
| 273 | 
            -
                  end
         | 
| 274 | 
            -
             | 
| 275 | 
            -
                  def default_mask(new_mask = nil)
         | 
| 276 | 
            -
                    if new_mask
         | 
| 277 | 
            -
                      line = caller(2, 10).find { |l| !l.include?("lib/graphql") }
         | 
| 278 | 
            -
                      GraphQL::Deprecation.warn("GraphQL::Filter and Schema.mask are deprecated and will be removed in v2.1.0. Implement `visible?` on your schema members instead (https://graphql-ruby.org/authorization/visibility.html).\n  #{line}")
         | 
| 279 | 
            -
                      @own_default_mask = new_mask
         | 
| 280 | 
            -
                    else
         | 
| 281 | 
            -
                      @own_default_mask || find_inherited_value(:default_mask, Schema::NullMask)
         | 
| 282 | 
            -
                    end
         | 
| 283 | 
            -
                  end
         | 
| 284 | 
            -
             | 
| 285 269 | 
             
                  def static_validator
         | 
| 286 270 | 
             
                    GraphQL::StaticValidation::Validator.new(schema: self)
         | 
| 287 271 | 
             
                  end
         | 
| @@ -8,9 +8,6 @@ module GraphQL | |
| 8 8 | 
             
                # It provides access to the schema & fragments which validators may read from.
         | 
| 9 9 | 
             
                #
         | 
| 10 10 | 
             
                # It holds a list of errors which each validator may add to.
         | 
| 11 | 
            -
                #
         | 
| 12 | 
            -
                # It also provides limited access to the {TypeStack} instance,
         | 
| 13 | 
            -
                # which tracks state as you climb in and out of different fields.
         | 
| 14 11 | 
             
                class ValidationContext
         | 
| 15 12 | 
             
                  extend Forwardable
         | 
| 16 13 |  | 
| @@ -1,7 +1,6 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 | 
             
            require "graphql/static_validation/error"
         | 
| 3 3 | 
             
            require "graphql/static_validation/definition_dependencies"
         | 
| 4 | 
            -
            require "graphql/static_validation/type_stack"
         | 
| 5 4 | 
             
            require "graphql/static_validation/validator"
         | 
| 6 5 | 
             
            require "graphql/static_validation/validation_context"
         | 
| 7 6 | 
             
            require "graphql/static_validation/validation_timeout_error"
         | 
| @@ -124,7 +124,8 @@ module GraphQL | |
| 124 124 | 
             
                  # This subscription was re-evaluated.
         | 
| 125 125 | 
             
                  # Send it to the specific stream where this client was waiting.
         | 
| 126 126 | 
             
                  def deliver(subscription_id, result)
         | 
| 127 | 
            -
                     | 
| 127 | 
            +
                    has_more = !result.context.namespace(:subscriptions)[:final_update]
         | 
| 128 | 
            +
                    payload = { result: result.to_h, more: has_more }
         | 
| 128 129 | 
             
                    @action_cable.server.broadcast(stream_subscription_name(subscription_id), payload)
         | 
| 129 130 | 
             
                  end
         | 
| 130 131 |  | 
| @@ -125,10 +125,10 @@ module GraphQL | |
| 125 125 | 
             
                    variables: variables,
         | 
| 126 126 | 
             
                    root_value: object,
         | 
| 127 127 | 
             
                  }
         | 
| 128 | 
            -
             | 
| 128 | 
            +
             | 
| 129 129 | 
             
                   # merge event's and query's context together
         | 
| 130 130 | 
             
                  context.merge!(event.context) unless event.context.nil? || context.nil?
         | 
| 131 | 
            -
             | 
| 131 | 
            +
             | 
| 132 132 | 
             
                  execute_options[:validate] = validate_update?(**execute_options)
         | 
| 133 133 | 
             
                  result = @schema.execute(**execute_options)
         | 
| 134 134 | 
             
                  subscriptions_context = result.context.namespace(:subscriptions)
         | 
| @@ -136,11 +136,9 @@ module GraphQL | |
| 136 136 | 
             
                    result = nil
         | 
| 137 137 | 
             
                  end
         | 
| 138 138 |  | 
| 139 | 
            -
                  unsubscribed  | 
| 140 | 
            -
             | 
| 141 | 
            -
                  if unsubscribed
         | 
| 139 | 
            +
                  if subscriptions_context[:unsubscribed] && !subscriptions_context[:final_update]
         | 
| 142 140 | 
             
                    # `unsubscribe` was called, clean up on our side
         | 
| 143 | 
            -
                    #  | 
| 141 | 
            +
                    # The transport should also send `{more: false}` to client
         | 
| 144 142 | 
             
                    delete_subscription(subscription_id)
         | 
| 145 143 | 
             
                    result = nil
         | 
| 146 144 | 
             
                  end
         | 
| @@ -164,7 +162,14 @@ module GraphQL | |
| 164 162 | 
             
                  res = execute_update(subscription_id, event, object)
         | 
| 165 163 | 
             
                  if !res.nil?
         | 
| 166 164 | 
             
                    deliver(subscription_id, res)
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                    if res.context.namespace(:subscriptions)[:unsubscribed]
         | 
| 167 | 
            +
                      # `unsubscribe` was called, clean up on our side
         | 
| 168 | 
            +
                      # The transport should also send `{more: false}` to client
         | 
| 169 | 
            +
                      delete_subscription(subscription_id)
         | 
| 170 | 
            +
                    end
         | 
| 167 171 | 
             
                  end
         | 
| 172 | 
            +
             | 
| 168 173 | 
             
                end
         | 
| 169 174 |  | 
| 170 175 | 
             
                # Event `event` occurred on `object`,
         | 
| @@ -67,9 +67,8 @@ module GraphQL | |
| 67 67 | 
             
                          type: [edge_type_class, null: edge_nullable],
         | 
| 68 68 | 
             
                          null: edges_nullable,
         | 
| 69 69 | 
             
                          description: "A list of edges.",
         | 
| 70 | 
            +
                          scope: false, # Assume that the connection was already scoped.
         | 
| 70 71 | 
             
                          connection: false,
         | 
| 71 | 
            -
                          # Assume that the connection was scoped before this step:
         | 
| 72 | 
            -
                          scope: false,
         | 
| 73 72 | 
             
                        }
         | 
| 74 73 |  | 
| 75 74 | 
             
                        if field_options
         | 
| @@ -170,6 +169,24 @@ module GraphQL | |
| 170 169 | 
             
                        obj_type.field :page_info, GraphQL::Types::Relay::PageInfo, null: false, description: "Information to aid in pagination."
         | 
| 171 170 | 
             
                      end
         | 
| 172 171 | 
             
                    end
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                    def edges
         | 
| 174 | 
            +
                      # Assume that whatever authorization needed to happen
         | 
| 175 | 
            +
                      # already happened at the connection level.
         | 
| 176 | 
            +
                      current_runtime_state = Thread.current[:__graphql_runtime_info]
         | 
| 177 | 
            +
                      query_runtime_state = current_runtime_state[context.query]
         | 
| 178 | 
            +
                      query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items?
         | 
| 179 | 
            +
                      @object.edges
         | 
| 180 | 
            +
                    end
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                    def nodes
         | 
| 183 | 
            +
                      # Assume that whatever authorization needed to happen
         | 
| 184 | 
            +
                      # already happened at the connection level.
         | 
| 185 | 
            +
                      current_runtime_state = Thread.current[:__graphql_runtime_info]
         | 
| 186 | 
            +
                      query_runtime_state = current_runtime_state[context.query]
         | 
| 187 | 
            +
                      query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items?
         | 
| 188 | 
            +
                      @object.nodes
         | 
| 189 | 
            +
                    end
         | 
| 173 190 | 
             
                  end
         | 
| 174 191 | 
             
                end
         | 
| 175 192 | 
             
              end
         | 
| @@ -12,6 +12,13 @@ module GraphQL | |
| 12 12 | 
             
                      child_class.node_nullable(true)
         | 
| 13 13 | 
             
                    end
         | 
| 14 14 |  | 
| 15 | 
            +
                    def node
         | 
| 16 | 
            +
                      current_runtime_state = Thread.current[:__graphql_runtime_info]
         | 
| 17 | 
            +
                      query_runtime_state = current_runtime_state[context.query]
         | 
| 18 | 
            +
                      query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items?
         | 
| 19 | 
            +
                      @object.node
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
             | 
| 15 22 | 
             
                    module ClassMethods
         | 
| 16 23 | 
             
                      def inherited(child_class)
         | 
| 17 24 | 
             
                        super
         | 
    
        data/lib/graphql/version.rb
    CHANGED