graphql 0.15.3 → 0.16.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.
- 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
|