graphql 0.16.1 → 0.17.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.
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