graphql 0.15.3 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql.rb +4 -1
- data/lib/graphql/analysis.rb +5 -0
- data/lib/graphql/analysis/analyze_query.rb +73 -0
- data/lib/graphql/analysis/max_query_complexity.rb +25 -0
- data/lib/graphql/analysis/max_query_depth.rb +25 -0
- data/lib/graphql/analysis/query_complexity.rb +122 -0
- data/lib/graphql/analysis/query_depth.rb +54 -0
- data/lib/graphql/analysis_error.rb +4 -0
- data/lib/graphql/base_type.rb +7 -0
- data/lib/graphql/define/assign_object_field.rb +2 -1
- data/lib/graphql/field.rb +25 -3
- data/lib/graphql/input_object_type.rb +1 -1
- data/lib/graphql/internal_representation.rb +2 -0
- data/lib/graphql/internal_representation/node.rb +81 -0
- data/lib/graphql/internal_representation/rewrite.rb +177 -0
- data/lib/graphql/language/visitor.rb +15 -9
- data/lib/graphql/object_type.rb +1 -1
- data/lib/graphql/query.rb +66 -7
- data/lib/graphql/query/context.rb +10 -3
- data/lib/graphql/query/directive_resolution.rb +5 -5
- data/lib/graphql/query/serial_execution.rb +5 -3
- data/lib/graphql/query/serial_execution/field_resolution.rb +22 -15
- data/lib/graphql/query/serial_execution/operation_resolution.rb +7 -5
- data/lib/graphql/query/serial_execution/selection_resolution.rb +20 -105
- data/lib/graphql/query/serial_execution/value_resolution.rb +15 -12
- data/lib/graphql/schema.rb +7 -2
- data/lib/graphql/schema/timeout_middleware.rb +67 -0
- data/lib/graphql/static_validation/all_rules.rb +0 -1
- data/lib/graphql/static_validation/type_stack.rb +7 -11
- data/lib/graphql/static_validation/validation_context.rb +11 -1
- data/lib/graphql/static_validation/validator.rb +14 -4
- data/lib/graphql/version.rb +1 -1
- data/readme.md +10 -9
- data/spec/graphql/analysis/analyze_query_spec.rb +50 -0
- data/spec/graphql/analysis/max_query_complexity_spec.rb +62 -0
- data/spec/graphql/{static_validation/rules/document_does_not_exceed_max_depth_spec.rb → analysis/max_query_depth_spec.rb} +20 -21
- data/spec/graphql/analysis/query_complexity_spec.rb +235 -0
- data/spec/graphql/analysis/query_depth_spec.rb +80 -0
- data/spec/graphql/directive_spec.rb +1 -0
- data/spec/graphql/internal_representation/rewrite_spec.rb +120 -0
- data/spec/graphql/introspection/schema_type_spec.rb +1 -0
- data/spec/graphql/language/visitor_spec.rb +14 -4
- data/spec/graphql/non_null_type_spec.rb +31 -0
- data/spec/graphql/query/context_spec.rb +24 -1
- data/spec/graphql/query_spec.rb +6 -2
- data/spec/graphql/schema/timeout_middleware_spec.rb +180 -0
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +1 -1
- data/spec/graphql/static_validation/validator_spec.rb +1 -1
- data/spec/support/dairy_app.rb +22 -1
- metadata +29 -5
- data/lib/graphql/static_validation/rules/document_does_not_exceed_max_depth.rb +0 -79
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8d3d315c998e0d44c6ebbcd186825a1d7c86639
|
4
|
+
data.tar.gz: adfa16f977635d988505202bee3ea8fe54ede003
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 34bad6ba6d2ff0e811b2d9abbddf3e834df69e8fb8d6a1b06581f446b8bffee325717df2731874bcec57581cac8d7808a9c97937d43e1d45386edf41f8824a4f
|
7
|
+
data.tar.gz: e11a5741e64138f68cac3ad51d14243bfa47c8f56b3b03144b685b1d2d71d4fea657221bcdffcfdaef5edd485b7df6262eb018b38ac4dcb0e01b22648c55a080
|
data/lib/graphql.rb
CHANGED
@@ -31,6 +31,7 @@ end
|
|
31
31
|
|
32
32
|
# Order matters for these:
|
33
33
|
|
34
|
+
require "graphql/execution_error"
|
34
35
|
require "graphql/define"
|
35
36
|
require "graphql/base_type"
|
36
37
|
require "graphql/object_type"
|
@@ -56,13 +57,15 @@ require "graphql/directive"
|
|
56
57
|
|
57
58
|
require "graphql/introspection"
|
58
59
|
require "graphql/language"
|
60
|
+
require "graphql/analysis"
|
59
61
|
require "graphql/schema"
|
60
62
|
require "graphql/schema/printer"
|
61
63
|
|
62
64
|
# Order does not matter for these:
|
63
65
|
|
64
|
-
require "graphql/
|
66
|
+
require "graphql/analysis_error"
|
65
67
|
require "graphql/invalid_null_error"
|
66
68
|
require "graphql/query"
|
69
|
+
require "graphql/internal_representation"
|
67
70
|
require "graphql/static_validation"
|
68
71
|
require "graphql/version"
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Analysis
|
3
|
+
module_function
|
4
|
+
# Visit `query`'s internal representation, calling `analyzers` along the way.
|
5
|
+
#
|
6
|
+
# - First, query analyzers are initialized by calling `.initial_value(query)`, if they respond to that method.
|
7
|
+
# - Then, they receive `.call(memo, visit_type, irep_node)`, where visit type is `:enter` or `:leave`.
|
8
|
+
# - Last, they receive `.final_value(memo)`, if they respond to that method.
|
9
|
+
#
|
10
|
+
# It returns an array of final `memo` values in the order that `analyzers` were passed in.
|
11
|
+
#
|
12
|
+
# @param query [GraphQL::Query]
|
13
|
+
# @param analyzers [Array<#call>] Objects that respond to `#call(memo, visit_type, irep_node)`
|
14
|
+
# @return [Array<Any>] Results from those analyzers
|
15
|
+
def analyze_query(query, analyzers)
|
16
|
+
analyzers_and_values = analyzers.map { |r| initialize_reducer(r, query) }
|
17
|
+
|
18
|
+
irep = query.internal_representation
|
19
|
+
|
20
|
+
irep.each do |name, op_node|
|
21
|
+
reduce_node(op_node, analyzers_and_values)
|
22
|
+
end
|
23
|
+
|
24
|
+
analyzers_and_values.map { |(r, value)| finalize_reducer(r, value) }
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
module_function
|
30
|
+
|
31
|
+
# Enter the node, visit its children, then leave the node.
|
32
|
+
def reduce_node(irep_node, analyzers_and_values)
|
33
|
+
visit_analyzers(:enter, irep_node, analyzers_and_values)
|
34
|
+
|
35
|
+
irep_node.children.each do |name, child_irep_node|
|
36
|
+
reduce_node(child_irep_node, analyzers_and_values)
|
37
|
+
end
|
38
|
+
|
39
|
+
visit_analyzers(:leave, irep_node, analyzers_and_values)
|
40
|
+
end
|
41
|
+
|
42
|
+
def visit_analyzers(visit_type, irep_node, analyzers_and_values)
|
43
|
+
analyzers_and_values.each do |reducer_and_value|
|
44
|
+
reducer = reducer_and_value[0]
|
45
|
+
memo = reducer_and_value[1]
|
46
|
+
next_memo = reducer.call(memo, visit_type, irep_node)
|
47
|
+
reducer_and_value[1] = next_memo
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# If the reducer has an `initial_value` method, call it and store
|
52
|
+
# the result as `memo`. Otherwise, use `nil` as memo.
|
53
|
+
# @return [Array<(#call, Any)>] reducer-memo pairs
|
54
|
+
def initialize_reducer(reducer, query)
|
55
|
+
if reducer.respond_to?(:initial_value)
|
56
|
+
[reducer, reducer.initial_value(query)]
|
57
|
+
else
|
58
|
+
[reducer, nil]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# If the reducer accepts `final_value`, send it the last memo value.
|
63
|
+
# Otherwise, use the last value from the traversal.
|
64
|
+
# @return [Any] final memo value
|
65
|
+
def finalize_reducer(reducer, reduced_value)
|
66
|
+
if reducer.respond_to?(:final_value)
|
67
|
+
reducer.final_value(reduced_value)
|
68
|
+
else
|
69
|
+
reduced_value
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative "./query_complexity"
|
2
|
+
module GraphQL
|
3
|
+
module Analysis
|
4
|
+
# Used under the hood to implement complexity validation,
|
5
|
+
# see {Schema#max_complexity} and {Query#max_complexity}
|
6
|
+
#
|
7
|
+
# @example Assert max complexity of 10
|
8
|
+
# # DON'T actually do this, graphql-ruby
|
9
|
+
# # Does this for you based on your `max_complexity` setting
|
10
|
+
# MySchema.query_analyzers << GraphQL::Analysis::MaxQueryComplexity.new(10)
|
11
|
+
#
|
12
|
+
class MaxQueryComplexity < GraphQL::Analysis::QueryComplexity
|
13
|
+
def initialize(max_complexity)
|
14
|
+
disallow_excessive_complexity = -> (query, complexity) {
|
15
|
+
if complexity > max_complexity
|
16
|
+
GraphQL::AnalysisError.new("Query has complexity of #{complexity}, which exceeds max complexity of #{max_complexity}")
|
17
|
+
else
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
}
|
21
|
+
super(&disallow_excessive_complexity)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative "./query_depth"
|
2
|
+
module GraphQL
|
3
|
+
module Analysis
|
4
|
+
# Used under the hood to implement depth validation,
|
5
|
+
# see {Schema#max_depth} and {Query#max_depth}
|
6
|
+
#
|
7
|
+
# @example Assert max depth of 10
|
8
|
+
# # DON'T actually do this, graphql-ruby
|
9
|
+
# # Does this for you based on your `max_depth` setting
|
10
|
+
# MySchema.query_analyzers << GraphQL::Analysis::MaxQueryDepth.new(10)
|
11
|
+
#
|
12
|
+
class MaxQueryDepth < GraphQL::Analysis::QueryDepth
|
13
|
+
def initialize(max_depth)
|
14
|
+
disallow_excessive_depth = -> (query, depth) {
|
15
|
+
if depth > max_depth
|
16
|
+
GraphQL::AnalysisError.new("Query has depth of #{depth}, which exceeds max depth of #{max_depth}")
|
17
|
+
else
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
}
|
21
|
+
super(&disallow_excessive_depth)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Analysis
|
3
|
+
# Calculate the complexity of a query, using {Field#complexity} values.
|
4
|
+
#
|
5
|
+
# @example Log the complexity of incoming queries
|
6
|
+
# MySchema.query_analyzers << GraphQL::AnalysisQueryComplexity.new do |query, complexity|
|
7
|
+
# Rails.logger.info("Complexity: #{complexity}")
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
class QueryComplexity
|
11
|
+
# @yield [query, complexity] Called for each query analyzed by the schema, before executing it
|
12
|
+
# @yieldparam query [GraphQL::Query] The query that was analyzed
|
13
|
+
# @yieldparam complexity [Numeric] The complexity for this query
|
14
|
+
def initialize(&block)
|
15
|
+
@complexity_handler = block
|
16
|
+
end
|
17
|
+
|
18
|
+
# State for the query complexity calcuation:
|
19
|
+
# - `query` is needed for variables, then passed to handler
|
20
|
+
# - `complexities_on_type` holds complexity scores for each type in an IRep node
|
21
|
+
def initial_value(query)
|
22
|
+
{
|
23
|
+
query: query,
|
24
|
+
complexities_on_type: [TypeComplexity.new],
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Implement the query analyzer API
|
29
|
+
def call(memo, visit_type, irep_node)
|
30
|
+
if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field)
|
31
|
+
if visit_type == :enter
|
32
|
+
memo[:complexities_on_type].push(TypeComplexity.new)
|
33
|
+
else
|
34
|
+
type_complexities = memo[:complexities_on_type].pop
|
35
|
+
own_complexity = if GraphQL::Query::DirectiveResolution.include_node?(irep_node, memo[:query])
|
36
|
+
child_complexity = type_complexities.max_possible_complexity
|
37
|
+
get_complexity(irep_node, memo[:query], child_complexity)
|
38
|
+
else
|
39
|
+
0
|
40
|
+
end
|
41
|
+
memo[:complexities_on_type].last.merge(irep_node.on_types, own_complexity)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
memo
|
45
|
+
end
|
46
|
+
|
47
|
+
# Send the query and complexity to the block
|
48
|
+
# @return [Object, GraphQL::AnalysisError] Whatever the handler returns
|
49
|
+
def final_value(reduced_value)
|
50
|
+
total_complexity = reduced_value[:complexities_on_type].pop.max_possible_complexity
|
51
|
+
@complexity_handler.call(reduced_value[:query], total_complexity)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Get a complexity value for a field,
|
57
|
+
# by getting the number or calling its proc
|
58
|
+
def get_complexity(irep_node, query, child_complexity)
|
59
|
+
field_defn = irep_node.definition
|
60
|
+
defined_complexity = field_defn.complexity
|
61
|
+
case defined_complexity
|
62
|
+
when Proc
|
63
|
+
args = query.arguments_for(irep_node)
|
64
|
+
defined_complexity.call(query.context, args, child_complexity)
|
65
|
+
when Numeric
|
66
|
+
defined_complexity + (child_complexity || 0)
|
67
|
+
else
|
68
|
+
raise("Invalid complexity: #{defined_complexity.inspect} on #{field_defn.name}")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Selections on an object may apply differently depending on what is _actually_ returned by the resolve function.
|
73
|
+
# Find the maximum possible complexity among those combinations.
|
74
|
+
class TypeComplexity
|
75
|
+
def initialize
|
76
|
+
@types = Hash.new { |h, k| h[k] = 0 }
|
77
|
+
end
|
78
|
+
|
79
|
+
# Return the max possible complexity for types in this selection
|
80
|
+
def max_possible_complexity
|
81
|
+
max_complexity = 0
|
82
|
+
|
83
|
+
@types.each do |type_defn, own_complexity|
|
84
|
+
type_complexity = @types.reduce(0) do |memo, (other_type, other_complexity)|
|
85
|
+
if types_overlap?(type_defn, other_type)
|
86
|
+
memo + other_complexity
|
87
|
+
else
|
88
|
+
memo
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
if type_complexity > max_complexity
|
93
|
+
max_complexity = type_complexity
|
94
|
+
end
|
95
|
+
end
|
96
|
+
max_complexity
|
97
|
+
end
|
98
|
+
|
99
|
+
# Store the complexity score for each of `types`
|
100
|
+
def merge(types, complexity)
|
101
|
+
types.each { |t| @types[t] += complexity }
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
# True if:
|
106
|
+
# - type_1 is type_2
|
107
|
+
# - type_1 is a member of type_2's possible types
|
108
|
+
def types_overlap?(type_1, type_2)
|
109
|
+
if type_1 == type_2
|
110
|
+
true
|
111
|
+
elsif type_2.kind.union?
|
112
|
+
type_2.include?(type_1)
|
113
|
+
elsif type_1.kind.object? && type_2.kind.interface?
|
114
|
+
type_1.interfaces.include?(type_2)
|
115
|
+
else
|
116
|
+
false
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Analysis
|
3
|
+
# A query reducer for measuring the depth of a given query.
|
4
|
+
#
|
5
|
+
# @example Logging the depth of a query
|
6
|
+
# Schema.query_analyzers << GraphQL::Analysis::QueryDepth.new { |depth| puts "GraphQL query depth: #{depth}" }
|
7
|
+
# Schema.execute(query_str)
|
8
|
+
# # GraphQL query depth: 8
|
9
|
+
#
|
10
|
+
class QueryDepth
|
11
|
+
def initialize(&block)
|
12
|
+
@depth_handler = block
|
13
|
+
end
|
14
|
+
|
15
|
+
def initial_value(query)
|
16
|
+
{
|
17
|
+
max_depth: 0,
|
18
|
+
current_depth: 0,
|
19
|
+
skip_current_scope: false,
|
20
|
+
query: query,
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(memo, visit_type, irep_node)
|
25
|
+
if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field)
|
26
|
+
if visit_type == :enter
|
27
|
+
if GraphQL::Schema::DYNAMIC_FIELDS.include?(irep_node.definition.name)
|
28
|
+
# Don't validate introspection fields
|
29
|
+
memo[:skip_current_scope] = true
|
30
|
+
elsif memo[:skip_current_scope]
|
31
|
+
# we're inside an introspection query
|
32
|
+
elsif GraphQL::Query::DirectiveResolution.include_node?(irep_node, memo[:query])
|
33
|
+
memo[:current_depth] += 1
|
34
|
+
end
|
35
|
+
else
|
36
|
+
if GraphQL::Schema::DYNAMIC_FIELDS.include?(irep_node.definition.name)
|
37
|
+
memo[:skip_current_scope] = false
|
38
|
+
elsif GraphQL::Query::DirectiveResolution.include_node?(irep_node, memo[:query])
|
39
|
+
if memo[:max_depth] < memo[:current_depth]
|
40
|
+
memo[:max_depth] = memo[:current_depth]
|
41
|
+
end
|
42
|
+
memo[:current_depth] -= 1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
memo
|
47
|
+
end
|
48
|
+
|
49
|
+
def final_value(memo)
|
50
|
+
@depth_handler.call(memo[:query], memo[:max_depth])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/graphql/base_type.rb
CHANGED
@@ -89,6 +89,13 @@ module GraphQL
|
|
89
89
|
coerce_non_null_input(value)
|
90
90
|
end
|
91
91
|
|
92
|
+
# Types with fields may override this
|
93
|
+
# @param name [String] field name to lookup for this type
|
94
|
+
# @return [GraphQL::Field, nil]
|
95
|
+
def get_field(name)
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
|
92
99
|
# During schema definition, types can be defined inside procs or as strings.
|
93
100
|
# This function converts it to a type instance
|
94
101
|
# @return [GraphQL::BaseType]
|
@@ -2,7 +2,7 @@ module GraphQL
|
|
2
2
|
module Define
|
3
3
|
# Turn field configs into a {GraphQL::Field} and attach it to a {GraphQL::ObjectType} or {GraphQL::InterfaceType}
|
4
4
|
module AssignObjectField
|
5
|
-
def self.call(fields_type, name, type = nil, desc = nil, field: nil, deprecation_reason: nil, property: nil, &block)
|
5
|
+
def self.call(fields_type, name, type = nil, desc = nil, field: nil, deprecation_reason: nil, property: nil, complexity: nil, &block)
|
6
6
|
if block_given?
|
7
7
|
field = GraphQL::Field.define(&block)
|
8
8
|
else
|
@@ -11,6 +11,7 @@ module GraphQL
|
|
11
11
|
type && field.type = type
|
12
12
|
desc && field.description = desc
|
13
13
|
property && field.property = property
|
14
|
+
complexity && field.complexity = complexity
|
14
15
|
deprecation_reason && field.deprecation_reason = deprecation_reason
|
15
16
|
field.name ||= name.to_s
|
16
17
|
fields_type.fields[name.to_s] = field
|
data/lib/graphql/field.rb
CHANGED
@@ -3,7 +3,6 @@ module GraphQL
|
|
3
3
|
#
|
4
4
|
# They're usually created with the `field` helper. If you create it by hand, make sure {#name} is a String.
|
5
5
|
#
|
6
|
-
#
|
7
6
|
# @example creating a field
|
8
7
|
# GraphQL::ObjectType.define do
|
9
8
|
# field :name, types.String, "The name of this thing "
|
@@ -38,20 +37,43 @@ module GraphQL
|
|
38
37
|
# field :name, field: name_field
|
39
38
|
# end
|
40
39
|
#
|
40
|
+
# @example Custom complexity values
|
41
|
+
# # Complexity can be a number or a proc.
|
42
|
+
#
|
43
|
+
# # Complexity can be defined with a keyword:
|
44
|
+
# field :expensive_calculation, !types.Int, complexity: 10
|
45
|
+
#
|
46
|
+
# # Or inside the block:
|
47
|
+
# field :expensive_calculation_2, !types.Int do
|
48
|
+
# complexity -> (ctx, args, child_complexity) { ctx[:current_user].staff? ? 0 : 10 }
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# @example Calculating the complexity of a list field
|
52
|
+
# field :items, types[ItemType] do
|
53
|
+
# argument :limit, !types.Int
|
54
|
+
# # Mulitply the child complexity by the possible items on the list
|
55
|
+
# complexity -> (ctx, args, child_complexity) { child_complexity * args[:limit] }
|
56
|
+
# end
|
57
|
+
#
|
41
58
|
class Field
|
42
59
|
include GraphQL::Define::InstanceDefinable
|
43
|
-
accepts_definitions :name, :description, :resolve, :type, :property, :deprecation_reason, argument: GraphQL::Define::AssignArgument
|
60
|
+
accepts_definitions :name, :description, :resolve, :type, :property, :deprecation_reason, :complexity, argument: GraphQL::Define::AssignArgument
|
44
61
|
|
45
62
|
attr_accessor :deprecation_reason, :name, :description, :property
|
63
|
+
|
46
64
|
attr_reader :resolve_proc
|
47
65
|
|
48
66
|
# @return [String] The name of this field on its {GraphQL::ObjectType} (or {GraphQL::InterfaceType})
|
49
67
|
attr_reader :name
|
50
68
|
|
51
|
-
# @return [Hash<String
|
69
|
+
# @return [Hash<String => GraphQL::Argument>] Map String argument names to their {GraphQL::Argument} implementations
|
52
70
|
attr_accessor :arguments
|
53
71
|
|
72
|
+
# @return [Numeric, Proc] The complexity for this field (default: 1), as a constant or a proc like `-> (query_ctx, args, child_complexity) { } # Numeric`
|
73
|
+
attr_accessor :complexity
|
74
|
+
|
54
75
|
def initialize
|
76
|
+
@complexity = 1
|
55
77
|
@arguments = {}
|
56
78
|
@resolve_proc = build_default_resolver
|
57
79
|
end
|
@@ -14,7 +14,7 @@ module GraphQL
|
|
14
14
|
argument: GraphQL::Define::AssignArgument
|
15
15
|
)
|
16
16
|
|
17
|
-
# @return [Hash<String
|
17
|
+
# @return [Hash<String => GraphQL::Argument>] Map String argument names to their {GraphQL::Argument} implementations
|
18
18
|
attr_accessor :arguments
|
19
19
|
|
20
20
|
alias :input_fields :arguments
|