graphql 1.13.2 → 1.13.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/ast/field_usage.rb +6 -2
- data/lib/graphql/date_encoding_error.rb +16 -0
- data/lib/graphql/execution/interpreter/arguments_cache.rb +4 -2
- data/lib/graphql/execution/interpreter/runtime.rb +33 -17
- data/lib/graphql/introspection/directive_location_enum.rb +2 -2
- data/lib/graphql/introspection/directive_type.rb +2 -0
- data/lib/graphql/introspection/schema_type.rb +5 -0
- data/lib/graphql/introspection/type_type.rb +9 -3
- data/lib/graphql/introspection.rb +3 -0
- data/lib/graphql/language/document_from_schema_definition.rb +8 -3
- data/lib/graphql/language/lexer.rb +50 -25
- data/lib/graphql/language/lexer.rl +2 -0
- data/lib/graphql/language/nodes.rb +2 -2
- data/lib/graphql/language/parser.rb +829 -816
- data/lib/graphql/language/parser.y +8 -2
- data/lib/graphql/language/printer.rb +4 -0
- data/lib/graphql/pagination/active_record_relation_connection.rb +43 -6
- data/lib/graphql/pagination/relation_connection.rb +59 -29
- data/lib/graphql/schema/argument.rb +6 -10
- data/lib/graphql/schema/build_from_definition.rb +1 -0
- data/lib/graphql/schema/directive.rb +15 -0
- data/lib/graphql/schema/field.rb +103 -39
- data/lib/graphql/schema/field_extension.rb +37 -0
- data/lib/graphql/schema/input_object.rb +15 -0
- data/lib/graphql/schema/loader.rb +3 -0
- data/lib/graphql/schema/non_null.rb +4 -0
- data/lib/graphql/schema/scalar.rb +12 -0
- data/lib/graphql/schema/validator/required_validator.rb +29 -15
- data/lib/graphql/schema.rb +16 -1
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/base_visitor.rb +1 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +1 -1
- data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
- data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +6 -0
- data/lib/graphql/static_validation/validation_context.rb +4 -0
- data/lib/graphql/subscriptions/serialize.rb +22 -2
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
- data/lib/graphql/tracing/data_dog_tracing.rb +6 -1
- data/lib/graphql/tracing/notifications_tracing.rb +59 -0
- data/lib/graphql/tracing/platform_tracing.rb +11 -6
- data/lib/graphql/types/iso_8601_date.rb +13 -5
- data/lib/graphql/types/relay/node_field.rb +2 -3
- data/lib/graphql/types/relay/nodes_field.rb +19 -3
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +1 -0
- metadata +10 -6
| @@ -147,6 +147,7 @@ rule | |
| 147 147 | 
             
              name_without_on:
         | 
| 148 148 | 
             
                  IDENTIFIER
         | 
| 149 149 | 
             
                | FRAGMENT
         | 
| 150 | 
            +
                | REPEATABLE
         | 
| 150 151 | 
             
                | TRUE
         | 
| 151 152 | 
             
                | FALSE
         | 
| 152 153 | 
             
                | operation_type
         | 
| @@ -155,6 +156,7 @@ rule | |
| 155 156 | 
             
              enum_name: /* any identifier, but not "true", "false" or "null" */
         | 
| 156 157 | 
             
                  IDENTIFIER
         | 
| 157 158 | 
             
                | FRAGMENT
         | 
| 159 | 
            +
                | REPEATABLE
         | 
| 158 160 | 
             
                | ON
         | 
| 159 161 | 
             
                | operation_type
         | 
| 160 162 | 
             
                | schema_keyword
         | 
| @@ -422,10 +424,14 @@ rule | |
| 422 424 | 
             
                  }
         | 
| 423 425 |  | 
| 424 426 | 
             
              directive_definition:
         | 
| 425 | 
            -
                  description_opt DIRECTIVE DIR_SIGN name arguments_definitions_opt ON directive_locations {
         | 
| 426 | 
            -
                    result = make_node(:DirectiveDefinition, name: val[3], arguments: val[4], locations: val[ | 
| 427 | 
            +
                  description_opt DIRECTIVE DIR_SIGN name arguments_definitions_opt directive_repeatable_opt ON directive_locations {
         | 
| 428 | 
            +
                    result = make_node(:DirectiveDefinition, name: val[3], arguments: val[4], locations: val[7], repeatable: !!val[5], description: val[0] || get_description(val[1]), definition_line: val[1].line, position_source: val[0] || val[1])
         | 
| 427 429 | 
             
                  }
         | 
| 428 430 |  | 
| 431 | 
            +
              directive_repeatable_opt:
         | 
| 432 | 
            +
                /* nothing */
         | 
| 433 | 
            +
                | REPEATABLE
         | 
| 434 | 
            +
             | 
| 429 435 | 
             
              directive_locations:
         | 
| 430 436 | 
             
                  name                          { result = [make_node(:DirectiveLocation, name: val[0].to_s, position_source: val[0])] }
         | 
| 431 437 | 
             
                | directive_locations PIPE name { val[0] << make_node(:DirectiveLocation, name: val[2].to_s, position_source: val[2]) }
         | 
| @@ -7,13 +7,18 @@ module GraphQL | |
| 7 7 | 
             
                class ActiveRecordRelationConnection < Pagination::RelationConnection
         | 
| 8 8 | 
             
                  private
         | 
| 9 9 |  | 
| 10 | 
            -
                  def relation_larger_than(relation, size)
         | 
| 11 | 
            -
                     | 
| 12 | 
            -
             | 
| 10 | 
            +
                  def relation_larger_than(relation, initial_offset, size)
         | 
| 11 | 
            +
                    if already_loaded?(relation)
         | 
| 12 | 
            +
                      (relation.size + initial_offset) > size
         | 
| 13 | 
            +
                    else
         | 
| 14 | 
            +
                      set_offset(sliced_nodes, initial_offset + size).exists?
         | 
| 15 | 
            +
                    end
         | 
| 13 16 | 
             
                  end
         | 
| 14 17 |  | 
| 15 18 | 
             
                  def relation_count(relation)
         | 
| 16 | 
            -
                    int_or_hash = if  | 
| 19 | 
            +
                    int_or_hash = if already_loaded?(relation)
         | 
| 20 | 
            +
                      relation.size
         | 
| 21 | 
            +
                    elsif relation.respond_to?(:unscope)
         | 
| 17 22 | 
             
                      relation.unscope(:order).count(:all)
         | 
| 18 23 | 
             
                    else
         | 
| 19 24 | 
             
                      # Rails 3
         | 
| @@ -28,11 +33,19 @@ module GraphQL | |
| 28 33 | 
             
                  end
         | 
| 29 34 |  | 
| 30 35 | 
             
                  def relation_limit(relation)
         | 
| 31 | 
            -
                    relation. | 
| 36 | 
            +
                    if relation.is_a?(Array)
         | 
| 37 | 
            +
                      nil
         | 
| 38 | 
            +
                    else
         | 
| 39 | 
            +
                      relation.limit_value
         | 
| 40 | 
            +
                    end
         | 
| 32 41 | 
             
                  end
         | 
| 33 42 |  | 
| 34 43 | 
             
                  def relation_offset(relation)
         | 
| 35 | 
            -
                    relation. | 
| 44 | 
            +
                    if relation.is_a?(Array)
         | 
| 45 | 
            +
                      nil
         | 
| 46 | 
            +
                    else
         | 
| 47 | 
            +
                      relation.offset_value
         | 
| 48 | 
            +
                    end
         | 
| 36 49 | 
             
                  end
         | 
| 37 50 |  | 
| 38 51 | 
             
                  def null_relation(relation)
         | 
| @@ -43,6 +56,30 @@ module GraphQL | |
| 43 56 | 
             
                      relation.where("1=2")
         | 
| 44 57 | 
             
                    end
         | 
| 45 58 | 
             
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  def set_limit(nodes, limit)
         | 
| 61 | 
            +
                    if already_loaded?(nodes)
         | 
| 62 | 
            +
                      nodes.take(limit)
         | 
| 63 | 
            +
                    else
         | 
| 64 | 
            +
                      super
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def set_offset(nodes, offset)
         | 
| 69 | 
            +
                    if already_loaded?(nodes)
         | 
| 70 | 
            +
                      # If the client sent a bogus cursor beyond the size of the relation,
         | 
| 71 | 
            +
                      # it might get `nil` from `#[...]`, so return an empty array in that case
         | 
| 72 | 
            +
                      nodes[offset..-1] || []
         | 
| 73 | 
            +
                    else
         | 
| 74 | 
            +
                      super
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  private
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  def already_loaded?(relation)
         | 
| 81 | 
            +
                    relation.is_a?(Array) || relation.loaded?
         | 
| 82 | 
            +
                  end
         | 
| 46 83 | 
             
                end
         | 
| 47 84 | 
             
              end
         | 
| 48 85 | 
             
            end
         | 
| @@ -35,7 +35,7 @@ module GraphQL | |
| 35 35 | 
             
                        if @nodes && @nodes.count < first
         | 
| 36 36 | 
             
                          false
         | 
| 37 37 | 
             
                        else
         | 
| 38 | 
            -
                          relation_larger_than(sliced_nodes, first)
         | 
| 38 | 
            +
                          relation_larger_than(sliced_nodes, @sliced_nodes_offset, first)
         | 
| 39 39 | 
             
                        end
         | 
| 40 40 | 
             
                      else
         | 
| 41 41 | 
             
                        false
         | 
| @@ -47,16 +47,17 @@ module GraphQL | |
| 47 47 | 
             
                  def cursor_for(item)
         | 
| 48 48 | 
             
                    load_nodes
         | 
| 49 49 | 
             
                    # index in nodes + existing offset + 1 (because it's offset, not index)
         | 
| 50 | 
            -
                    offset = nodes.index(item) + 1 + (@paged_nodes_offset || 0)  | 
| 50 | 
            +
                    offset = nodes.index(item) + 1 + (@paged_nodes_offset || 0) - (relation_offset(items) || 0)
         | 
| 51 51 | 
             
                    encode(offset.to_s)
         | 
| 52 52 | 
             
                  end
         | 
| 53 53 |  | 
| 54 54 | 
             
                  private
         | 
| 55 55 |  | 
| 56 56 | 
             
                  # @param relation [Object] A database query object
         | 
| 57 | 
            +
                  # @param _initial_offset [Integer] The number of items already excluded from the relation
         | 
| 57 58 | 
             
                  # @param size [Integer] The value against which we check the relation size
         | 
| 58 59 | 
             
                  # @return [Boolean] True if the number of items in this relation is larger than `size`
         | 
| 59 | 
            -
                  def relation_larger_than(relation, size)
         | 
| 60 | 
            +
                  def relation_larger_than(relation, _initial_offset, size)
         | 
| 60 61 | 
             
                    relation_count(set_limit(relation, size + 1)) == size + 1
         | 
| 61 62 | 
             
                  end
         | 
| 62 63 |  | 
| @@ -111,30 +112,51 @@ module GraphQL | |
| 111 112 | 
             
                    end
         | 
| 112 113 | 
             
                  end
         | 
| 113 114 |  | 
| 114 | 
            -
                   | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
                     | 
| 118 | 
            -
                       | 
| 119 | 
            -
             | 
| 115 | 
            +
                  def calculate_sliced_nodes_parameters
         | 
| 116 | 
            +
                    if defined?(@sliced_nodes_limit)
         | 
| 117 | 
            +
                      return
         | 
| 118 | 
            +
                    else
         | 
| 119 | 
            +
                      next_offset = relation_offset(items) || 0
         | 
| 120 120 | 
             
                      if after_offset
         | 
| 121 | 
            -
                         | 
| 122 | 
            -
                        paginated_nodes = set_offset(paginated_nodes, previous_offset + after_offset)
         | 
| 121 | 
            +
                        next_offset += after_offset
         | 
| 123 122 | 
             
                      end
         | 
| 124 123 |  | 
| 125 124 | 
             
                      if before_offset && after_offset
         | 
| 126 125 | 
             
                        if after_offset < before_offset
         | 
| 127 126 | 
             
                          # Get the number of items between the two cursors
         | 
| 128 127 | 
             
                          space_between = before_offset - after_offset - 1
         | 
| 129 | 
            -
                           | 
| 128 | 
            +
                          relation_limit = space_between
         | 
| 130 129 | 
             
                        else
         | 
| 131 | 
            -
                          # TODO I think this is untested
         | 
| 132 130 | 
             
                          # The cursors overextend one another to an empty set
         | 
| 133 | 
            -
                           | 
| 131 | 
            +
                          @sliced_nodes_null_relation = true
         | 
| 134 132 | 
             
                        end
         | 
| 135 133 | 
             
                      elsif before_offset
         | 
| 136 134 | 
             
                        # Use limit to cut off the tail of the relation
         | 
| 137 | 
            -
                         | 
| 135 | 
            +
                        relation_limit = before_offset - 1
         | 
| 136 | 
            +
                      end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                      @sliced_nodes_limit = relation_limit
         | 
| 139 | 
            +
                      @sliced_nodes_offset = next_offset
         | 
| 140 | 
            +
                    end
         | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                  # Apply `before` and `after` to the underlying `items`,
         | 
| 144 | 
            +
                  # returning a new relation.
         | 
| 145 | 
            +
                  def sliced_nodes
         | 
| 146 | 
            +
                    @sliced_nodes ||= begin
         | 
| 147 | 
            +
                      calculate_sliced_nodes_parameters
         | 
| 148 | 
            +
                      paginated_nodes = items
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                      if @sliced_nodes_null_relation
         | 
| 151 | 
            +
                        paginated_nodes = null_relation(paginated_nodes)
         | 
| 152 | 
            +
                      else
         | 
| 153 | 
            +
                        if @sliced_nodes_limit
         | 
| 154 | 
            +
                          paginated_nodes = set_limit(paginated_nodes, @sliced_nodes_limit)
         | 
| 155 | 
            +
                        end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                        if @sliced_nodes_offset
         | 
| 158 | 
            +
                          paginated_nodes = set_offset(paginated_nodes, @sliced_nodes_offset)
         | 
| 159 | 
            +
                        end
         | 
| 138 160 | 
             
                      end
         | 
| 139 161 |  | 
| 140 162 | 
             
                      paginated_nodes
         | 
| @@ -155,32 +177,40 @@ module GraphQL | |
| 155 177 | 
             
                  # returning a new relation
         | 
| 156 178 | 
             
                  def limited_nodes
         | 
| 157 179 | 
             
                    @limited_nodes ||= begin
         | 
| 158 | 
            -
                       | 
| 159 | 
            -
                       | 
| 180 | 
            +
                      calculate_sliced_nodes_parameters
         | 
| 181 | 
            +
                      if @sliced_nodes_null_relation
         | 
| 182 | 
            +
                        # it's an empty set
         | 
| 183 | 
            +
                        return sliced_nodes
         | 
| 184 | 
            +
                      end
         | 
| 185 | 
            +
                      relation_limit = @sliced_nodes_limit
         | 
| 186 | 
            +
                      relation_offset = @sliced_nodes_offset
         | 
| 160 187 |  | 
| 161 | 
            -
                      if first && ( | 
| 188 | 
            +
                      if first && (relation_limit.nil? || relation_limit > first)
         | 
| 162 189 | 
             
                        # `first` would create a stricter limit that the one already applied, so add it
         | 
| 163 | 
            -
                         | 
| 190 | 
            +
                        relation_limit = first
         | 
| 164 191 | 
             
                      end
         | 
| 165 192 |  | 
| 166 193 | 
             
                      if last
         | 
| 167 | 
            -
                        if  | 
| 168 | 
            -
                          if last <=  | 
| 194 | 
            +
                        if relation_limit
         | 
| 195 | 
            +
                          if last <= relation_limit
         | 
| 169 196 | 
             
                            # `last` is a smaller slice than the current limit, so apply it
         | 
| 170 | 
            -
                             | 
| 171 | 
            -
                             | 
| 172 | 
            -
                            paginated_nodes = set_limit(paginated_nodes, last)
         | 
| 197 | 
            +
                            relation_offset += (relation_limit - last)
         | 
| 198 | 
            +
                            relation_limit = last
         | 
| 173 199 | 
             
                          end
         | 
| 174 200 | 
             
                        else
         | 
| 175 201 | 
             
                          # No limit, so get the last items
         | 
| 176 | 
            -
                          sliced_nodes_count = relation_count( | 
| 177 | 
            -
                           | 
| 178 | 
            -
                           | 
| 179 | 
            -
                          paginated_nodes = set_limit(paginated_nodes, last)
         | 
| 202 | 
            +
                          sliced_nodes_count = relation_count(sliced_nodes)
         | 
| 203 | 
            +
                          relation_offset += (sliced_nodes_count - [last, sliced_nodes_count].min)
         | 
| 204 | 
            +
                          relation_limit = last
         | 
| 180 205 | 
             
                        end
         | 
| 181 206 | 
             
                      end
         | 
| 182 207 |  | 
| 183 | 
            -
                      @paged_nodes_offset = relation_offset | 
| 208 | 
            +
                      @paged_nodes_offset = relation_offset
         | 
| 209 | 
            +
                      paginated_nodes = items
         | 
| 210 | 
            +
                      paginated_nodes = set_offset(paginated_nodes, relation_offset)
         | 
| 211 | 
            +
                      if relation_limit
         | 
| 212 | 
            +
                        paginated_nodes = set_limit(paginated_nodes, relation_limit)
         | 
| 213 | 
            +
                      end
         | 
| 184 214 | 
             
                      paginated_nodes
         | 
| 185 215 | 
             
                    end
         | 
| 186 216 | 
             
                  end
         | 
| @@ -37,7 +37,7 @@ module GraphQL | |
| 37 37 | 
             
                  # @param arg_name [Symbol]
         | 
| 38 38 | 
             
                  # @param type_expr
         | 
| 39 39 | 
             
                  # @param desc [String]
         | 
| 40 | 
            -
                  # @param required [Boolean] if true, this argument is non-null; if false, this argument is nullable
         | 
| 40 | 
            +
                  # @param required [Boolean, :nullable] if true, this argument is non-null; if false, this argument is nullable. If `:nullable`, then the argument must be provided, though it may be `null`.
         | 
| 41 41 | 
             
                  # @param description [String]
         | 
| 42 42 | 
             
                  # @param default_value [Object]
         | 
| 43 43 | 
             
                  # @param as [Symbol] Override the keyword name when passed to a method
         | 
| @@ -53,7 +53,7 @@ module GraphQL | |
| 53 53 | 
             
                    @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
         | 
| 54 54 | 
             
                    @type_expr = type_expr || type
         | 
| 55 55 | 
             
                    @description = desc || description
         | 
| 56 | 
            -
                    @null =  | 
| 56 | 
            +
                    @null = required != true
         | 
| 57 57 | 
             
                    @default_value = default_value
         | 
| 58 58 | 
             
                    @owner = owner
         | 
| 59 59 | 
             
                    @as = as
         | 
| @@ -72,6 +72,9 @@ module GraphQL | |
| 72 72 | 
             
                    end
         | 
| 73 73 |  | 
| 74 74 | 
             
                    self.validates(validates)
         | 
| 75 | 
            +
                    if required == :nullable
         | 
| 76 | 
            +
                      self.owner.validates(required: { argument: arg_name })
         | 
| 77 | 
            +
                    end
         | 
| 75 78 |  | 
| 76 79 | 
             
                    if definition_block
         | 
| 77 80 | 
             
                      if definition_block.arity == 1
         | 
| @@ -147,14 +150,7 @@ module GraphQL | |
| 147 150 | 
             
                        end
         | 
| 148 151 | 
             
                      end
         | 
| 149 152 | 
             
                    elsif as_type.kind.input_object?
         | 
| 150 | 
            -
                      as_type. | 
| 151 | 
            -
                        input_obj_arg = input_obj_arg.type_class
         | 
| 152 | 
            -
                        # TODO: this skips input objects whose values were alread replaced with application objects.
         | 
| 153 | 
            -
                        # See: https://github.com/rmosolgo/graphql-ruby/issues/2633
         | 
| 154 | 
            -
                        if value.is_a?(InputObject) && value.key?(input_obj_arg.keyword) && !input_obj_arg.authorized?(obj, value[input_obj_arg.keyword], ctx)
         | 
| 155 | 
            -
                          return false
         | 
| 156 | 
            -
                        end
         | 
| 157 | 
            -
                      end
         | 
| 153 | 
            +
                      return as_type.authorized?(obj, value, ctx)
         | 
| 158 154 | 
             
                    end
         | 
| 159 155 | 
             
                    # None of the early-return conditions were activated,
         | 
| 160 156 | 
             
                    # so this is authorized.
         | 
| @@ -377,6 +377,7 @@ module GraphQL | |
| 377 377 | 
             
                      Class.new(GraphQL::Schema::Directive) do
         | 
| 378 378 | 
             
                        graphql_name(directive_definition.name)
         | 
| 379 379 | 
             
                        description(directive_definition.description)
         | 
| 380 | 
            +
                        repeatable(directive_definition.repeatable)
         | 
| 380 381 | 
             
                        locations(*directive_definition.locations.map { |location| location.name.to_sym })
         | 
| 381 382 | 
             
                        ast_node(directive_definition)
         | 
| 382 383 | 
             
                        builder.build_arguments(self, directive_definition.arguments, type_resolver)
         | 
| @@ -90,6 +90,11 @@ module GraphQL | |
| 90 90 | 
             
                      yield
         | 
| 91 91 | 
             
                    end
         | 
| 92 92 |  | 
| 93 | 
            +
                    # Continuing is passed as a block, yield to continue.
         | 
| 94 | 
            +
                    def resolve_each(object, arguments, context)
         | 
| 95 | 
            +
                      yield
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
             | 
| 93 98 | 
             
                    def on_field?
         | 
| 94 99 | 
             
                      locations.include?(FIELD)
         | 
| 95 100 | 
             
                    end
         | 
| @@ -101,6 +106,14 @@ module GraphQL | |
| 101 106 | 
             
                    def on_operation?
         | 
| 102 107 | 
             
                      locations.include?(QUERY) && locations.include?(MUTATION) && locations.include?(SUBSCRIPTION)
         | 
| 103 108 | 
             
                    end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                    def repeatable?
         | 
| 111 | 
            +
                      !!@repeatable
         | 
| 112 | 
            +
                    end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                    def repeatable(new_value)
         | 
| 115 | 
            +
                      @repeatable = new_value
         | 
| 116 | 
            +
                    end
         | 
| 104 117 | 
             
                  end
         | 
| 105 118 |  | 
| 106 119 | 
             
                  # @return [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class, Module]
         | 
| @@ -139,6 +152,7 @@ module GraphQL | |
| 139 152 | 
             
                    ENUM_VALUE =             :ENUM_VALUE,
         | 
| 140 153 | 
             
                    INPUT_OBJECT =           :INPUT_OBJECT,
         | 
| 141 154 | 
             
                    INPUT_FIELD_DEFINITION = :INPUT_FIELD_DEFINITION,
         | 
| 155 | 
            +
                    VARIABLE_DEFINITION =    :VARIABLE_DEFINITION,
         | 
| 142 156 | 
             
                  ]
         | 
| 143 157 |  | 
| 144 158 | 
             
                  DEFAULT_DEPRECATION_REASON = 'No longer supported'
         | 
| @@ -161,6 +175,7 @@ module GraphQL | |
| 161 175 | 
             
                    ENUM_VALUE:               'Location adjacent to an enum value definition.',
         | 
| 162 176 | 
             
                    INPUT_OBJECT:             'Location adjacent to an input object type definition.',
         | 
| 163 177 | 
             
                    INPUT_FIELD_DEFINITION:   'Location adjacent to an input object field definition.',
         | 
| 178 | 
            +
                    VARIABLE_DEFINITION:      'Location adjacent to a variable definition.',
         | 
| 164 179 | 
             
                  }
         | 
| 165 180 |  | 
| 166 181 | 
             
                  private
         | 
    
        data/lib/graphql/schema/field.rb
    CHANGED
    
    | @@ -16,6 +16,8 @@ module GraphQL | |
| 16 16 | 
             
                  include GraphQL::Schema::Member::HasDirectives
         | 
| 17 17 | 
             
                  include GraphQL::Schema::Member::HasDeprecationReason
         | 
| 18 18 |  | 
| 19 | 
            +
                  class FieldImplementationFailed < GraphQL::Error; end
         | 
| 20 | 
            +
             | 
| 19 21 | 
             
                  # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided
         | 
| 20 22 | 
             
                  attr_reader :name
         | 
| 21 23 | 
             
                  alias :graphql_name :name
         | 
| @@ -792,51 +794,103 @@ module GraphQL | |
| 792 794 |  | 
| 793 795 | 
             
                  def public_send_field(unextended_obj, unextended_ruby_kwargs, query_ctx)
         | 
| 794 796 | 
             
                    with_extensions(unextended_obj, unextended_ruby_kwargs, query_ctx) do |obj, ruby_kwargs|
         | 
| 795 | 
            -
                       | 
| 796 | 
            -
                         | 
| 797 | 
            -
             | 
| 798 | 
            -
                         | 
| 799 | 
            -
             | 
| 800 | 
            -
             | 
| 801 | 
            -
             | 
| 802 | 
            -
             | 
| 803 | 
            -
                      #
         | 
| 804 | 
            -
                      # - A method on the type instance;
         | 
| 805 | 
            -
                      # - Hash keys, if the wrapped object is a hash;
         | 
| 806 | 
            -
                      # - A method on the wrapped object;
         | 
| 807 | 
            -
                      # - Or, raise not implemented.
         | 
| 808 | 
            -
                      #
         | 
| 809 | 
            -
                      if obj.respond_to?(@resolver_method)
         | 
| 810 | 
            -
                        # Call the method with kwargs, if there are any
         | 
| 811 | 
            -
                        if ruby_kwargs.any?
         | 
| 812 | 
            -
                          obj.public_send(@resolver_method, **ruby_kwargs)
         | 
| 813 | 
            -
                        else
         | 
| 814 | 
            -
                          obj.public_send(@resolver_method)
         | 
| 797 | 
            +
                      begin
         | 
| 798 | 
            +
                        method_receiver = nil
         | 
| 799 | 
            +
                        method_to_call = nil
         | 
| 800 | 
            +
                        if @resolver_class
         | 
| 801 | 
            +
                          if obj.is_a?(GraphQL::Schema::Object)
         | 
| 802 | 
            +
                            obj = obj.object
         | 
| 803 | 
            +
                          end
         | 
| 804 | 
            +
                          obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
         | 
| 815 805 | 
             
                        end
         | 
| 816 | 
            -
             | 
| 817 | 
            -
                         | 
| 818 | 
            -
                         | 
| 819 | 
            -
             | 
| 806 | 
            +
             | 
| 807 | 
            +
                        # Find a way to resolve this field, checking:
         | 
| 808 | 
            +
                        #
         | 
| 809 | 
            +
                        # - A method on the type instance;
         | 
| 810 | 
            +
                        # - Hash keys, if the wrapped object is a hash;
         | 
| 811 | 
            +
                        # - A method on the wrapped object;
         | 
| 812 | 
            +
                        # - Or, raise not implemented.
         | 
| 813 | 
            +
                        #
         | 
| 814 | 
            +
                        if obj.respond_to?(@resolver_method)
         | 
| 815 | 
            +
                          method_to_call = @resolver_method
         | 
| 816 | 
            +
                          method_receiver = obj
         | 
| 817 | 
            +
                          # Call the method with kwargs, if there are any
         | 
| 818 | 
            +
                          if ruby_kwargs.any?
         | 
| 819 | 
            +
                            obj.public_send(@resolver_method, **ruby_kwargs)
         | 
| 820 | 
            +
                          else
         | 
| 821 | 
            +
                            obj.public_send(@resolver_method)
         | 
| 822 | 
            +
                          end
         | 
| 823 | 
            +
                        elsif obj.object.is_a?(Hash)
         | 
| 824 | 
            +
                          inner_object = obj.object
         | 
| 825 | 
            +
                          if inner_object.key?(@method_sym)
         | 
| 826 | 
            +
                            inner_object[@method_sym]
         | 
| 827 | 
            +
                          else
         | 
| 828 | 
            +
                            inner_object[@method_str]
         | 
| 829 | 
            +
                          end
         | 
| 830 | 
            +
                        elsif obj.object.respond_to?(@method_sym)
         | 
| 831 | 
            +
                          method_to_call = @method_sym
         | 
| 832 | 
            +
                          method_receiver = obj.object
         | 
| 833 | 
            +
                          if ruby_kwargs.any?
         | 
| 834 | 
            +
                            obj.object.public_send(@method_sym, **ruby_kwargs)
         | 
| 835 | 
            +
                          else
         | 
| 836 | 
            +
                            obj.object.public_send(@method_sym)
         | 
| 837 | 
            +
                          end
         | 
| 820 838 | 
             
                        else
         | 
| 821 | 
            -
                           | 
| 839 | 
            +
                          raise <<-ERR
         | 
| 840 | 
            +
                        Failed to implement #{@owner.graphql_name}.#{@name}, tried:
         | 
| 841 | 
            +
             | 
| 842 | 
            +
                        - `#{obj.class}##{@resolver_method}`, which did not exist
         | 
| 843 | 
            +
                        - `#{obj.object.class}##{@method_sym}`, which did not exist
         | 
| 844 | 
            +
                        - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
         | 
| 845 | 
            +
             | 
| 846 | 
            +
                        To implement this field, define one of the methods above (and check for typos)
         | 
| 847 | 
            +
                        ERR
         | 
| 822 848 | 
             
                        end
         | 
| 823 | 
            -
                       | 
| 824 | 
            -
                         | 
| 825 | 
            -
             | 
| 849 | 
            +
                      rescue ArgumentError
         | 
| 850 | 
            +
                        assert_satisfactory_implementation(method_receiver, method_to_call, ruby_kwargs)
         | 
| 851 | 
            +
                        # if the line above doesn't raise, re-raise
         | 
| 852 | 
            +
                        raise
         | 
| 853 | 
            +
                      end
         | 
| 854 | 
            +
                    end
         | 
| 855 | 
            +
                  end
         | 
| 856 | 
            +
             | 
| 857 | 
            +
                  def assert_satisfactory_implementation(receiver, method_name, ruby_kwargs)
         | 
| 858 | 
            +
                    method_defn = receiver.method(method_name)
         | 
| 859 | 
            +
                    unsatisfied_ruby_kwargs = ruby_kwargs.dup
         | 
| 860 | 
            +
                    unsatisfied_method_params = []
         | 
| 861 | 
            +
                    encountered_keyrest = false
         | 
| 862 | 
            +
                    method_defn.parameters.each do |(param_type, param_name)|
         | 
| 863 | 
            +
                      case param_type
         | 
| 864 | 
            +
                      when :key
         | 
| 865 | 
            +
                        unsatisfied_ruby_kwargs.delete(param_name)
         | 
| 866 | 
            +
                      when :keyreq
         | 
| 867 | 
            +
                        if unsatisfied_ruby_kwargs.key?(param_name)
         | 
| 868 | 
            +
                          unsatisfied_ruby_kwargs.delete(param_name)
         | 
| 826 869 | 
             
                        else
         | 
| 827 | 
            -
                           | 
| 870 | 
            +
                          unsatisfied_method_params << "- `#{param_name}:` is required by Ruby, but not by GraphQL. Consider `#{param_name}: nil` instead, or making this argument required in GraphQL."
         | 
| 828 871 | 
             
                        end
         | 
| 829 | 
            -
                       | 
| 830 | 
            -
                         | 
| 831 | 
            -
                       | 
| 872 | 
            +
                      when :keyrest
         | 
| 873 | 
            +
                        encountered_keyrest = true
         | 
| 874 | 
            +
                      when :req
         | 
| 875 | 
            +
                        unsatisfied_method_params << "- `#{param_name}` is required by Ruby, but GraphQL doesn't pass positional arguments. If it's meant to be a GraphQL argument, use `#{param_name}:` instead. Otherwise, remove it."
         | 
| 876 | 
            +
                      when :opt, :rest
         | 
| 877 | 
            +
                        # This is fine, although it will never be present
         | 
| 878 | 
            +
                      end
         | 
| 879 | 
            +
                    end
         | 
| 832 880 |  | 
| 833 | 
            -
             | 
| 834 | 
            -
                       | 
| 835 | 
            -
             | 
| 881 | 
            +
                    if encountered_keyrest
         | 
| 882 | 
            +
                      unsatisfied_ruby_kwargs.clear
         | 
| 883 | 
            +
                    end
         | 
| 836 884 |  | 
| 837 | 
            -
             | 
| 838 | 
            -
                      ERR
         | 
| 839 | 
            -
             | 
| 885 | 
            +
                    if unsatisfied_ruby_kwargs.any? || unsatisfied_method_params.any?
         | 
| 886 | 
            +
                      raise FieldImplementationFailed.new, <<-ERR
         | 
| 887 | 
            +
            Failed to call #{method_name} on #{receiver.inspect} because the Ruby method params were incompatible with the GraphQL arguments:
         | 
| 888 | 
            +
             | 
| 889 | 
            +
            #{ unsatisfied_ruby_kwargs
         | 
| 890 | 
            +
                .map { |key, value| "- `#{key}: #{value}` was given by GraphQL but not defined in the Ruby method. Add `#{key}:` to the method parameters." }
         | 
| 891 | 
            +
                .concat(unsatisfied_method_params)
         | 
| 892 | 
            +
                .join("\n") }
         | 
| 893 | 
            +
            ERR
         | 
| 840 894 | 
             
                    end
         | 
| 841 895 | 
             
                  end
         | 
| 842 896 |  | 
| @@ -850,8 +904,12 @@ module GraphQL | |
| 850 904 | 
             
                      # This is a hack to get the _last_ value for extended obj and args,
         | 
| 851 905 | 
             
                      # in case one of the extensions doesn't `yield`.
         | 
| 852 906 | 
             
                      # (There's another implementation that uses multiple-return, but I'm wary of the perf cost of the extra arrays)
         | 
| 853 | 
            -
                      extended = { args: args, obj: obj, memos: nil }
         | 
| 907 | 
            +
                      extended = { args: args, obj: obj, memos: nil, added_extras: nil }
         | 
| 854 908 | 
             
                      value = run_extensions_before_resolve(obj, args, ctx, extended) do |obj, args|
         | 
| 909 | 
            +
                        if (added_extras = extended[:added_extras])
         | 
| 910 | 
            +
                          args = args.dup
         | 
| 911 | 
            +
                          added_extras.each { |e| args.delete(e) }
         | 
| 912 | 
            +
                        end
         | 
| 855 913 | 
             
                        yield(obj, args)
         | 
| 856 914 | 
             
                      end
         | 
| 857 915 |  | 
| @@ -880,6 +938,12 @@ module GraphQL | |
| 880 938 | 
             
                          memos = extended[:memos] ||= {}
         | 
| 881 939 | 
             
                          memos[idx] = memo
         | 
| 882 940 | 
             
                        end
         | 
| 941 | 
            +
             | 
| 942 | 
            +
                        if (extras = extension.added_extras)
         | 
| 943 | 
            +
                          ae = extended[:added_extras] ||= []
         | 
| 944 | 
            +
                          ae.concat(extras)
         | 
| 945 | 
            +
                        end
         | 
| 946 | 
            +
             | 
| 883 947 | 
             
                        extended[:obj] = extended_obj
         | 
| 884 948 | 
             
                        extended[:args] = extended_args
         | 
| 885 949 | 
             
                        run_extensions_before_resolve(extended_obj, extended_args, ctx, extended, idx: idx + 1) { |o, a| yield(o, a) }
         | 
| @@ -49,8 +49,36 @@ module GraphQL | |
| 49 49 | 
             
                      configs = @own_default_argument_configurations ||= []
         | 
| 50 50 | 
             
                      configs << [argument_args, argument_kwargs]
         | 
| 51 51 | 
             
                    end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    # If configured, these `extras` will be added to the field if they aren't already present,
         | 
| 54 | 
            +
                    # but removed by from `arguments` before the field's `resolve` is called.
         | 
| 55 | 
            +
                    # (The extras _will_ be present for other extensions, though.)
         | 
| 56 | 
            +
                    #
         | 
| 57 | 
            +
                    # @param new_extras [Array<Symbol>] If provided, assign extras used by this extension
         | 
| 58 | 
            +
                    # @return [Array<Symbol>] any extras assigned to this extension
         | 
| 59 | 
            +
                    def extras(new_extras = nil)
         | 
| 60 | 
            +
                      if new_extras
         | 
| 61 | 
            +
                        @own_extras = new_extras
         | 
| 62 | 
            +
                      end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                      inherited_extras = self.superclass.respond_to?(:extras) ? superclass.extras : nil
         | 
| 65 | 
            +
                      if @own_extras
         | 
| 66 | 
            +
                        if inherited_extras
         | 
| 67 | 
            +
                          inherited_extras + @own_extras
         | 
| 68 | 
            +
                        else
         | 
| 69 | 
            +
                          @own_extras
         | 
| 70 | 
            +
                        end
         | 
| 71 | 
            +
                      elsif inherited_extras
         | 
| 72 | 
            +
                        inherited_extras
         | 
| 73 | 
            +
                      else
         | 
| 74 | 
            +
                        NO_EXTRAS
         | 
| 75 | 
            +
                      end
         | 
| 76 | 
            +
                    end
         | 
| 52 77 | 
             
                  end
         | 
| 53 78 |  | 
| 79 | 
            +
                  NO_EXTRAS = [].freeze
         | 
| 80 | 
            +
                  private_constant :NO_EXTRAS
         | 
| 81 | 
            +
             | 
| 54 82 | 
             
                  # Called when this extension is attached to a field.
         | 
| 55 83 | 
             
                  # The field definition may be extended during this method.
         | 
| 56 84 | 
             
                  # @return [void]
         | 
| @@ -79,9 +107,18 @@ module GraphQL | |
| 79 107 | 
             
                        end
         | 
| 80 108 | 
             
                      end
         | 
| 81 109 | 
             
                    end
         | 
| 110 | 
            +
                    if (extras = self.class.extras).any?
         | 
| 111 | 
            +
                      @added_extras = extras - field.extras
         | 
| 112 | 
            +
                      field.extras(@added_extras)
         | 
| 113 | 
            +
                    else
         | 
| 114 | 
            +
                      @added_extras = nil
         | 
| 115 | 
            +
                    end
         | 
| 82 116 | 
             
                    freeze
         | 
| 83 117 | 
             
                  end
         | 
| 84 118 |  | 
| 119 | 
            +
                  # @api private
         | 
| 120 | 
            +
                  attr_reader :added_extras
         | 
| 121 | 
            +
             | 
| 85 122 | 
             
                  # Called before resolving {#field}. It should either:
         | 
| 86 123 | 
             
                  #
         | 
| 87 124 | 
             
                  # - `yield` values to continue execution; OR
         | 
| @@ -79,6 +79,21 @@ module GraphQL | |
| 79 79 | 
             
                    end
         | 
| 80 80 | 
             
                  end
         | 
| 81 81 |  | 
| 82 | 
            +
                  def self.authorized?(obj, value, ctx)
         | 
| 83 | 
            +
                    # Authorize each argument (but this doesn't apply if `prepare` is implemented):
         | 
| 84 | 
            +
                    if value.is_a?(InputObject)
         | 
| 85 | 
            +
                      arguments(ctx).each do |_name, input_obj_arg|
         | 
| 86 | 
            +
                        input_obj_arg = input_obj_arg.type_class
         | 
| 87 | 
            +
                        if value.key?(input_obj_arg.keyword) &&
         | 
| 88 | 
            +
                          !input_obj_arg.authorized?(obj, value[input_obj_arg.keyword], ctx)
         | 
| 89 | 
            +
                          return false
         | 
| 90 | 
            +
                        end
         | 
| 91 | 
            +
                      end
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
                    # It didn't early-return false:
         | 
| 94 | 
            +
                    true
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
             | 
| 82 97 | 
             
                  def unwrap_value(value)
         | 
| 83 98 | 
             
                    case value
         | 
| 84 99 | 
             
                    when Array
         | 
| @@ -34,6 +34,7 @@ module GraphQL | |
| 34 34 | 
             
                    Class.new(GraphQL::Schema) do
         | 
| 35 35 | 
             
                      orphan_types(types.values)
         | 
| 36 36 | 
             
                      directives(directives)
         | 
| 37 | 
            +
                      description(schema["description"])
         | 
| 37 38 |  | 
| 38 39 | 
             
                      def self.resolve_type(*)
         | 
| 39 40 | 
             
                        raise(GraphQL::RequiredImplementationMissingError, "This schema was loaded from string, so it can't resolve types for objects")
         | 
| @@ -141,6 +142,7 @@ module GraphQL | |
| 141 142 | 
             
                          Class.new(GraphQL::Schema::Scalar) do
         | 
| 142 143 | 
             
                            graphql_name(type["name"])
         | 
| 143 144 | 
             
                            description(type["description"])
         | 
| 145 | 
            +
                            specified_by_url(type["specifiedByUrl"])
         | 
| 144 146 | 
             
                          end
         | 
| 145 147 | 
             
                        end
         | 
| 146 148 | 
             
                      when "UNION"
         | 
| @@ -160,6 +162,7 @@ module GraphQL | |
| 160 162 | 
             
                        graphql_name(directive["name"])
         | 
| 161 163 | 
             
                        description(directive["description"])
         | 
| 162 164 | 
             
                        locations(*directive["locations"].map(&:to_sym))
         | 
| 165 | 
            +
                        repeatable(directive["isRepeatable"])
         | 
| 163 166 | 
             
                        loader.build_arguments(self, directive["args"], type_resolver)
         | 
| 164 167 | 
             
                      end
         | 
| 165 168 | 
             
                    end
         | 
| @@ -53,6 +53,10 @@ module GraphQL | |
| 53 53 | 
             
                  end
         | 
| 54 54 |  | 
| 55 55 | 
             
                  def coerce_input(value, ctx)
         | 
| 56 | 
            +
                    # `.validate_input` above is used for variables, but this method is used for arguments
         | 
| 57 | 
            +
                    if value.nil?
         | 
| 58 | 
            +
                      raise GraphQL::ExecutionError, "`null` is not a valid input for `#{to_type_signature}`, please provide a value for this argument."
         | 
| 59 | 
            +
                    end
         | 
| 56 60 | 
             
                    of_type.coerce_input(value, ctx)
         | 
| 57 61 | 
             
                  end
         | 
| 58 62 |  | 
| @@ -32,6 +32,18 @@ module GraphQL | |
| 32 32 | 
             
                      GraphQL::TypeKinds::SCALAR
         | 
| 33 33 | 
             
                    end
         | 
| 34 34 |  | 
| 35 | 
            +
                    def specified_by_url(new_url = nil)
         | 
| 36 | 
            +
                      if new_url
         | 
| 37 | 
            +
                        @specified_by_url = new_url
         | 
| 38 | 
            +
                      elsif defined?(@specified_by_url)
         | 
| 39 | 
            +
                        @specified_by_url
         | 
| 40 | 
            +
                      elsif superclass.respond_to?(:specified_by_url)
         | 
| 41 | 
            +
                        superclass.specified_by_url
         | 
| 42 | 
            +
                      else
         | 
| 43 | 
            +
                        nil
         | 
| 44 | 
            +
                      end
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
             | 
| 35 47 | 
             
                    def default_scalar(is_default = nil)
         | 
| 36 48 | 
             
                      if !is_default.nil?
         | 
| 37 49 | 
             
                        @default_scalar = is_default
         |