graphql 0.16.1 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/query_complexity.rb +20 -13
  3. data/lib/graphql/analysis/query_depth.rb +2 -2
  4. data/lib/graphql/argument.rb +7 -2
  5. data/lib/graphql/base_type.rb +2 -1
  6. data/lib/graphql/boolean_type.rb +1 -0
  7. data/lib/graphql/define/assign_object_field.rb +19 -6
  8. data/lib/graphql/define/instance_definable.rb +44 -3
  9. data/lib/graphql/directive.rb +2 -6
  10. data/lib/graphql/enum_type.rb +11 -14
  11. data/lib/graphql/field.rb +47 -12
  12. data/lib/graphql/field/resolve.rb +57 -0
  13. data/lib/graphql/float_type.rb +2 -0
  14. data/lib/graphql/id_type.rb +2 -0
  15. data/lib/graphql/input_object_type.rb +5 -1
  16. data/lib/graphql/int_type.rb +2 -0
  17. data/lib/graphql/interface_type.rb +1 -1
  18. data/lib/graphql/internal_representation/node.rb +18 -29
  19. data/lib/graphql/internal_representation/rewrite.rb +7 -4
  20. data/lib/graphql/introspection/field_type.rb +1 -1
  21. data/lib/graphql/introspection/input_value_type.rb +1 -1
  22. data/lib/graphql/language/parser.rb +161 -136
  23. data/lib/graphql/language/parser.y +9 -1
  24. data/lib/graphql/object_type.rb +2 -2
  25. data/lib/graphql/query.rb +4 -4
  26. data/lib/graphql/query/directive_resolution.rb +2 -2
  27. data/lib/graphql/query/serial_execution/execution_context.rb +3 -2
  28. data/lib/graphql/query/serial_execution/field_resolution.rb +2 -5
  29. data/lib/graphql/query/serial_execution/selection_resolution.rb +1 -1
  30. data/lib/graphql/query/serial_execution/value_resolution.rb +2 -2
  31. data/lib/graphql/scalar_type.rb +2 -0
  32. data/lib/graphql/schema/timeout_middleware.rb +1 -0
  33. data/lib/graphql/string_type.rb +2 -0
  34. data/lib/graphql/union_type.rb +1 -1
  35. data/lib/graphql/version.rb +1 -1
  36. data/readme.md +1 -3
  37. data/spec/graphql/analysis/query_complexity_spec.rb +41 -5
  38. data/spec/graphql/define/instance_definable_spec.rb +2 -2
  39. data/spec/graphql/field_spec.rb +18 -0
  40. data/spec/graphql/internal_representation/rewrite_spec.rb +7 -6
  41. data/spec/graphql/language/parser_spec.rb +5 -0
  42. data/spec/graphql/query/serial_execution/execution_context_spec.rb +2 -1
  43. data/spec/graphql/schema/timeout_middleware_spec.rb +29 -28
  44. data/spec/support/dairy_app.rb +12 -10
  45. metadata +3 -3
  46. data/lib/graphql/internal_representation/definition.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7e4d95f2f01d1d24633dc5b6c062a67e6a752379
4
- data.tar.gz: 19634030a6f735243babd83be75d9279c2987795
3
+ metadata.gz: 24203b4225051e3273a336d37502cdddd29c3c21
4
+ data.tar.gz: d3c4de442aa838dfde6db055cd4da9e397a89578
5
5
  SHA512:
6
- metadata.gz: 189c9d0eacdc54969b15f412dbb968c3a1d9aadedd0ccd4e74cc3e9afaf1af33c83c0f4a9b911ed563a3fc9dc8f256c75d74d013832273038087ae38734abeb2
7
- data.tar.gz: 675427c4e5233153a58f6ff3a2abbe754e9216261872d666301ceeeeb1919b8098c4b7bdf2ee8065ccb781d3ad35c6a2bfa7f1e0e5e719486176655831032efb
6
+ metadata.gz: d134105d845e9b416078a504048f9ba263943fba5741a4de5d9f3cf229a193614858794b9d6ef5b857b84cb14ea3f1bb14d1b9ffc6beb4ee9e6fff2d9c9fac1c
7
+ data.tar.gz: e7107d02800a97cf95ed00dc54463056ba7dd0bc38623f5750deb4cc6a0ea736074a84ee77da81859c7c548ffe75b117a9c130e7a98fc13a6339dfdc8a8ba1ea
@@ -38,7 +38,7 @@ module GraphQL
38
38
  else
39
39
  0
40
40
  end
41
- memo[:complexities_on_type].last.merge(irep_node.on_types, own_complexity)
41
+ memo[:complexities_on_type].last.merge(irep_node.definitions, own_complexity)
42
42
  end
43
43
  end
44
44
  memo
@@ -56,17 +56,24 @@ module GraphQL
56
56
  # Get a complexity value for a field,
57
57
  # by getting the number or calling its proc
58
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}")
59
+ max_possible_complexity = 0
60
+ irep_node.definitions.each do |type_defn, field_defn|
61
+ defined_complexity = field_defn.complexity
62
+ type_cpx = case defined_complexity
63
+ when Proc
64
+ args = query.arguments_for(irep_node, field_defn)
65
+ defined_complexity.call(query.context, args, child_complexity)
66
+ when Numeric
67
+ defined_complexity + (child_complexity || 0)
68
+ else
69
+ raise("Invalid complexity: #{defined_complexity.inspect} on #{field_defn.name}")
70
+ end
71
+
72
+ if type_cpx > max_possible_complexity
73
+ max_possible_complexity = type_cpx
74
+ end
69
75
  end
76
+ max_possible_complexity
70
77
  end
71
78
 
72
79
  # Selections on an object may apply differently depending on what is _actually_ returned by the resolve function.
@@ -97,8 +104,8 @@ module GraphQL
97
104
  end
98
105
 
99
106
  # Store the complexity score for each of `types`
100
- def merge(types, complexity)
101
- types.each { |t| @types[t] += complexity }
107
+ def merge(definitions, complexity)
108
+ definitions.each { |type_defn, field_defn| @types[type_defn] += complexity }
102
109
  end
103
110
 
104
111
  private
@@ -24,7 +24,7 @@ module GraphQL
24
24
  def call(memo, visit_type, irep_node)
25
25
  if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field)
26
26
  if visit_type == :enter
27
- if GraphQL::Schema::DYNAMIC_FIELDS.include?(irep_node.definition.name)
27
+ if GraphQL::Schema::DYNAMIC_FIELDS.include?(irep_node.definition_name)
28
28
  # Don't validate introspection fields
29
29
  memo[:skip_current_scope] = true
30
30
  elsif memo[:skip_current_scope]
@@ -33,7 +33,7 @@ module GraphQL
33
33
  memo[:current_depth] += 1
34
34
  end
35
35
  else
36
- if GraphQL::Schema::DYNAMIC_FIELDS.include?(irep_node.definition.name)
36
+ if GraphQL::Schema::DYNAMIC_FIELDS.include?(irep_node.definition_name)
37
37
  memo[:skip_current_scope] = false
38
38
  elsif GraphQL::Query::DirectiveResolution.include_node?(irep_node, memo[:query])
39
39
  if memo[:max_depth] < memo[:current_depth]
@@ -17,9 +17,14 @@ module GraphQL
17
17
  class Argument
18
18
  include GraphQL::Define::InstanceDefinable
19
19
  accepts_definitions :name, :type, :description, :default_value
20
- attr_accessor :type, :description, :default_value
20
+ lazy_defined_attr_accessor :type, :description, :default_value
21
21
 
22
22
  # @return [String] The name of this argument on its {GraphQL::Field} or {GraphQL::InputObjectType}
23
- attr_accessor :name
23
+ def name
24
+ ensure_defined
25
+ @name
26
+ end
27
+
28
+ attr_writer :name
24
29
  end
25
30
  end
@@ -3,8 +3,8 @@ module GraphQL
3
3
  class BaseType
4
4
  include GraphQL::Define::NonNullWithBang
5
5
  include GraphQL::Define::InstanceDefinable
6
- attr_accessor :name, :description
7
6
  accepts_definitions :name, :description
7
+ lazy_defined_attr_accessor :name, :description
8
8
 
9
9
  # @param other [GraphQL::BaseType] compare to this object
10
10
  # @return [Boolean] are these types equivalent? (incl. non-null, list)
@@ -53,6 +53,7 @@ module GraphQL
53
53
  # @param ctx [GraphQL::Query::Context]
54
54
  # @return [GraphQL::ObjectType] the type which should expose `object`
55
55
  def resolve_type(object, ctx)
56
+ ensure_defined
56
57
  instance_exec(object, ctx, &(@resolve_type_proc || DEFAULT_RESOLVE_TYPE))
57
58
  end
58
59
 
@@ -3,6 +3,7 @@ GraphQL::BOOLEAN_TYPE = GraphQL::ScalarType.define do
3
3
  ALLOWED_INPUTS = [true, false]
4
4
 
5
5
  name "Boolean"
6
+ description "Represents `true` or `false` values."
6
7
 
7
8
  coerce_input -> (value) { ALLOWED_INPUTS.include?(value) ? value : nil }
8
9
  coerce_result -> (value) { !!value }
@@ -2,15 +2,28 @@ 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, complexity: nil, &block)
6
- if block_given?
5
+ def self.call(fields_type, name, type_or_field = nil, desc = nil, field: nil, deprecation_reason: nil, property: nil, complexity: nil, hash_key: nil, &block)
6
+ if type_or_field.is_a?(GraphQL::Field)
7
+ field = type_or_field
8
+ elsif block_given?
7
9
  field = GraphQL::Field.define(&block)
8
- else
9
- field ||= GraphQL::Field.new
10
+ elsif field.nil?
11
+ field = GraphQL::Field.new
10
12
  end
11
- type && field.type = type
13
+
14
+ if !type_or_field.nil? && !type_or_field.is_a?(GraphQL::Field)
15
+ field.type = type_or_field
16
+ end
17
+
12
18
  desc && field.description = desc
13
- property && field.property = property
19
+
20
+ # If the field's resolve proc was defined in the config block,
21
+ # don't override it with `property` or `hash_key`
22
+ if field.resolve_proc.is_a?(GraphQL::Field::Resolve::BuiltInResolve)
23
+ property && field.property = property
24
+ hash_key && field.hash_key = hash_key
25
+ end
26
+
14
27
  complexity && field.complexity = complexity
15
28
  deprecation_reason && field.deprecation_reason = deprecation_reason
16
29
  field.name ||= name.to_s
@@ -44,12 +44,36 @@ module GraphQL
44
44
  base.extend(ClassMethods)
45
45
  end
46
46
 
47
+ # Set the definition block for this instance.
48
+ # It can be run later with {#ensure_defined}
49
+ def definition_proc=(defn_block)
50
+ @definition_proc = defn_block
51
+ end
52
+
53
+ private
54
+
55
+ # Run the definition block if it hasn't been run yet.
56
+ # This can only be run once: the block is deleted after it's used.
57
+ # You have to call this before using any value which could
58
+ # come from the definition block.
59
+ # @return [void]
60
+ def ensure_defined
61
+ if @definition_proc
62
+ defn_proc = @definition_proc
63
+ @definition_proc = nil
64
+ proxy = DefinedObjectProxy.new(self, self.class.dictionary)
65
+ proxy.instance_eval(&defn_proc)
66
+ end
67
+ nil
68
+ end
69
+
47
70
  module ClassMethods
48
- # Define an instance of this class using its {.definitions}.
71
+ # Prepare the defintion for an instance of this class using its {.definitions}.
72
+ # Note that the block is not called right away -- instead, it's deferred until
73
+ # one of the defined fields is needed.
49
74
  def define(&block)
50
75
  instance = self.new
51
- proxy = DefinedObjectProxy.new(instance, dictionary)
52
- block && proxy.instance_eval(&block)
76
+ instance.definition_proc = block
53
77
  instance
54
78
  end
55
79
 
@@ -60,6 +84,23 @@ module GraphQL
60
84
  @own_dictionary = own_dictionary.merge(AssignmentDictionary.create(*accepts))
61
85
  end
62
86
 
87
+ # Define a reader and writer for each of `attr_names` which
88
+ # ensures that the definition block was called before accessing it.
89
+ def lazy_defined_attr_accessor(*attr_names)
90
+ attr_names.each do |attr_name|
91
+ ivar_name = :"@#{attr_name}"
92
+ define_method(attr_name) do
93
+ ensure_defined
94
+ instance_variable_get(ivar_name)
95
+ end
96
+
97
+ define_method("#{attr_name}=") do |new_value|
98
+ ensure_defined
99
+ instance_variable_set(ivar_name, new_value)
100
+ end
101
+ end
102
+ end
103
+
63
104
  # @return [Hash] combined definitions for self and ancestors
64
105
  def dictionary
65
106
  if superclass.respond_to?(:dictionary)
@@ -3,7 +3,7 @@ module GraphQL
3
3
  include GraphQL::Define::InstanceDefinable
4
4
  accepts_definitions :locations, :name, :description, :include_proc, argument: GraphQL::Define::AssignArgument
5
5
 
6
- attr_accessor :locations, :arguments, :name, :description
6
+ lazy_defined_attr_accessor :locations, :arguments, :name, :description, :include_proc
7
7
 
8
8
  LOCATIONS = [
9
9
  QUERY = :QUERY,
@@ -20,11 +20,7 @@ module GraphQL
20
20
  end
21
21
 
22
22
  def include?(arguments)
23
- @include_proc.call(arguments)
24
- end
25
-
26
- def include_proc=(include_proc)
27
- @include_proc = include_proc
23
+ include_proc.call(arguments)
28
24
  end
29
25
 
30
26
  def to_s
@@ -19,10 +19,10 @@ module GraphQL
19
19
  @values_by_value = {}
20
20
  end
21
21
 
22
- def values=(values)
22
+ def values=(new_values)
23
23
  @values_by_name = {}
24
24
  @values_by_value = {}
25
- values.each { |enum_value| add_value(enum_value) }
25
+ new_values.each { |enum_value| add_value(enum_value) }
26
26
  end
27
27
 
28
28
  def add_value(enum_value)
@@ -31,24 +31,16 @@ module GraphQL
31
31
  end
32
32
 
33
33
  def values
34
+ ensure_defined
34
35
  @values_by_name
35
36
  end
36
37
 
37
- # Define a value within this enum
38
- # @deprecated use {.define} API instead
39
- # @param name [String] the string representation of this value
40
- # @param description [String]
41
- # @param deprecation_reason [String] if provided, `deprecated?` will be true
42
- # @param value [Object] the underlying value for this enum value
43
- def value(name, description=nil, deprecation_reason: nil, value: name)
44
- values[name] = EnumValue.new(name: name, description: description, deprecation_reason: deprecation_reason, value: value)
45
- end
46
-
47
38
  def kind
48
39
  GraphQL::TypeKinds::ENUM
49
40
  end
50
41
 
51
42
  def validate_non_null_input(value_name)
43
+ ensure_defined
52
44
  result = GraphQL::Query::InputValidationResult.new
53
45
 
54
46
  if !@values_by_name.key?(value_name)
@@ -67,11 +59,16 @@ module GraphQL
67
59
  # @param value_name [String] the string representation of this enum value
68
60
  # @return [Object] the underlying value for this enum value
69
61
  def coerce_non_null_input(value_name)
70
- return nil unless @values_by_name.key?(value_name)
71
- @values_by_name.fetch(value_name).value
62
+ ensure_defined
63
+ if @values_by_name.key?(value_name)
64
+ @values_by_name.fetch(value_name).value
65
+ else
66
+ nil
67
+ end
72
68
  end
73
69
 
74
70
  def coerce_result(value)
71
+ ensure_defined
75
72
  @values_by_value.fetch(value).name
76
73
  end
77
74
 
data/lib/graphql/field.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require "graphql/field/resolve"
2
+
1
3
  module GraphQL
2
4
  # {Field}s belong to {ObjectType}s and {InterfaceType}s.
3
5
  #
@@ -57,20 +59,35 @@ module GraphQL
57
59
  #
58
60
  class Field
59
61
  include GraphQL::Define::InstanceDefinable
60
- accepts_definitions :name, :description, :resolve, :type, :property, :deprecation_reason, :complexity, argument: GraphQL::Define::AssignArgument
62
+ accepts_definitions :name, :description, :resolve, :type, :property, :deprecation_reason, :complexity, :hash_key, argument: GraphQL::Define::AssignArgument
61
63
 
62
- attr_accessor :deprecation_reason, :name, :description, :property
64
+ lazy_defined_attr_accessor :deprecation_reason, :description, :property, :hash_key
63
65
 
64
66
  attr_reader :resolve_proc
65
67
 
66
68
  # @return [String] The name of this field on its {GraphQL::ObjectType} (or {GraphQL::InterfaceType})
67
- attr_reader :name
69
+ def name
70
+ ensure_defined
71
+ @name
72
+ end
73
+
74
+ attr_writer :name
68
75
 
69
76
  # @return [Hash<String => GraphQL::Argument>] Map String argument names to their {GraphQL::Argument} implementations
70
- attr_accessor :arguments
77
+ def arguments
78
+ ensure_defined
79
+ @arguments
80
+ end
81
+
82
+ attr_writer :arguments
71
83
 
72
84
  # @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
85
+ def complexity
86
+ ensure_defined
87
+ @complexity
88
+ end
89
+
90
+ attr_writer :complexity
74
91
 
75
92
  def initialize
76
93
  @complexity = 1
@@ -86,21 +103,27 @@ module GraphQL
86
103
  # @param arguments [Hash] Arguments declared in the query
87
104
  # @param context [GraphQL::Query::Context]
88
105
  def resolve(object, arguments, context)
89
- @resolve_proc.call(object, arguments, context)
106
+ ensure_defined
107
+ resolve_proc.call(object, arguments, context)
90
108
  end
91
109
 
92
110
  def resolve=(resolve_proc)
111
+ ensure_defined
93
112
  @resolve_proc = resolve_proc || build_default_resolver
94
113
  end
95
114
 
96
115
  def type=(new_return_type)
116
+ ensure_defined
97
117
  @clean_type = nil
98
118
  @dirty_type = new_return_type
99
119
  end
100
120
 
101
121
  # Get the return type for this field.
102
122
  def type
103
- @clean_type ||= GraphQL::BaseType.resolve_related_type(@dirty_type)
123
+ @clean_type ||= begin
124
+ ensure_defined
125
+ GraphQL::BaseType.resolve_related_type(@dirty_type)
126
+ end
104
127
  end
105
128
 
106
129
  # You can only set a field's name _once_ -- this to prevent
@@ -108,13 +131,28 @@ module GraphQL
108
131
  #
109
132
  # This is important because {#name} may be used by {#resolve}.
110
133
  def name=(new_name)
134
+ ensure_defined
111
135
  if @name.nil?
112
136
  @name = new_name
113
- else
137
+ elsif @name != new_name
114
138
  raise("Can't rename an already-named field. (Tried to rename \"#{@name}\" to \"#{new_name}\".) If you're passing a field with the `field:` argument, make sure it's an unused instance of GraphQL::Field.")
115
139
  end
116
140
  end
117
141
 
142
+ # @param new_property [Symbol] A method to call to resolve this field. Overrides the existing resolve proc.
143
+ def property=(new_property)
144
+ ensure_defined
145
+ @property = new_property
146
+ self.resolve = nil # reset resolve proc
147
+ end
148
+
149
+ # @param new_hash_key [Symbol] A key to access with `#[key]` to resolve this field. Overrides the existing resolve proc.
150
+ def hash_key=(new_hash_key)
151
+ ensure_defined
152
+ @hash_key = new_hash_key
153
+ self.resolve = nil # reset resolve proc
154
+ end
155
+
118
156
  def to_s
119
157
  "<Field: #{name || "not-named"}>"
120
158
  end
@@ -122,10 +160,7 @@ module GraphQL
122
160
  private
123
161
 
124
162
  def build_default_resolver
125
- -> (obj, args, ctx) do
126
- resolve_method = self.property || self.name
127
- obj.public_send(resolve_method)
128
- end
163
+ GraphQL::Field::Resolve.create_proc(self)
129
164
  end
130
165
  end
131
166
  end
@@ -0,0 +1,57 @@
1
+ module GraphQL
2
+ class Field
3
+ # Create resolve procs ahead of time based on a {GraphQL::Field}'s `name`, `property`, and `hash_key` configuration.
4
+ module Resolve
5
+ module_function
6
+
7
+ # @param [GraphQL::Field] A field that needs a resolve proc
8
+ # @return [Proc] A resolver for this field, based on its config
9
+ def create_proc(field)
10
+ if field.property
11
+ MethodResolve.new(field.property.to_sym)
12
+ elsif !field.hash_key.nil?
13
+ HashKeyResolve.new(field.hash_key)
14
+ else
15
+ NameResolve.new(field)
16
+ end
17
+ end
18
+
19
+ class BuiltInResolve
20
+ end
21
+
22
+ # Resolve the field by `public_send`ing `@method_name`
23
+ class MethodResolve < BuiltInResolve
24
+ def initialize(method_name)
25
+ @method_name = method_name
26
+ end
27
+
28
+ def call(obj, args, ctx)
29
+ obj.public_send(@method_name)
30
+ end
31
+ end
32
+
33
+ # Resolve the field by looking up `@hash_key` with `#[]`
34
+ class HashKeyResolve < BuiltInResolve
35
+ def initialize(hash_key)
36
+ @hash_key = hash_key
37
+ end
38
+
39
+ def call(obj, args, ctx)
40
+ obj[@hash_key]
41
+ end
42
+ end
43
+
44
+ # Call the field's name at query-time since
45
+ # it might have changed
46
+ class NameResolve < BuiltInResolve
47
+ def initialize(field)
48
+ @field = field
49
+ end
50
+
51
+ def call(obj, args, ctx)
52
+ obj.public_send(@field.name)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end