graphql 1.1.0 → 1.2.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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyze_query.rb +4 -2
  3. data/lib/graphql/analysis/field_usage.rb +4 -4
  4. data/lib/graphql/analysis/query_complexity.rb +16 -21
  5. data/lib/graphql/argument.rb +13 -6
  6. data/lib/graphql/base_type.rb +2 -1
  7. data/lib/graphql/compatibility/execution_specification.rb +76 -0
  8. data/lib/graphql/compatibility/query_parser_specification.rb +16 -2
  9. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +0 -5
  10. data/lib/graphql/compatibility/schema_parser_specification.rb +6 -0
  11. data/lib/graphql/define/assign_argument.rb +8 -2
  12. data/lib/graphql/define/instance_definable.rb +12 -15
  13. data/lib/graphql/directive.rb +2 -1
  14. data/lib/graphql/enum_type.rb +5 -7
  15. data/lib/graphql/field.rb +6 -11
  16. data/lib/graphql/field/resolve.rb +1 -0
  17. data/lib/graphql/input_object_type.rb +9 -9
  18. data/lib/graphql/interface_type.rb +2 -1
  19. data/lib/graphql/internal_representation.rb +1 -0
  20. data/lib/graphql/internal_representation/node.rb +31 -9
  21. data/lib/graphql/internal_representation/rewrite.rb +26 -26
  22. data/lib/graphql/internal_representation/selections.rb +41 -0
  23. data/lib/graphql/introspection/input_value_type.rb +6 -2
  24. data/lib/graphql/language/generation.rb +2 -0
  25. data/lib/graphql/language/lexer.rl +4 -0
  26. data/lib/graphql/language/nodes.rb +3 -0
  27. data/lib/graphql/language/parser.rb +525 -509
  28. data/lib/graphql/language/parser.y +2 -0
  29. data/lib/graphql/object_type.rb +2 -2
  30. data/lib/graphql/query.rb +21 -0
  31. data/lib/graphql/query/context.rb +52 -4
  32. data/lib/graphql/query/serial_execution.rb +3 -4
  33. data/lib/graphql/query/serial_execution/field_resolution.rb +35 -36
  34. data/lib/graphql/query/serial_execution/operation_resolution.rb +9 -15
  35. data/lib/graphql/query/serial_execution/selection_resolution.rb +14 -11
  36. data/lib/graphql/query/serial_execution/value_resolution.rb +18 -17
  37. data/lib/graphql/query/variables.rb +1 -1
  38. data/lib/graphql/relay/mutation.rb +5 -8
  39. data/lib/graphql/scalar_type.rb +1 -2
  40. data/lib/graphql/schema.rb +2 -13
  41. data/lib/graphql/schema/build_from_definition.rb +28 -13
  42. data/lib/graphql/schema/loader.rb +4 -1
  43. data/lib/graphql/schema/printer.rb +10 -3
  44. data/lib/graphql/schema/timeout_middleware.rb +18 -2
  45. data/lib/graphql/schema/unique_within_type.rb +6 -3
  46. data/lib/graphql/static_validation/literal_validator.rb +3 -1
  47. data/lib/graphql/union_type.rb +1 -2
  48. data/lib/graphql/version.rb +1 -1
  49. data/readme.md +1 -0
  50. data/spec/graphql/analysis/analyze_query_spec.rb +6 -8
  51. data/spec/graphql/argument_spec.rb +18 -0
  52. data/spec/graphql/define/assign_argument_spec.rb +48 -0
  53. data/spec/graphql/define/instance_definable_spec.rb +4 -2
  54. data/spec/graphql/execution_error_spec.rb +66 -0
  55. data/spec/graphql/input_object_type_spec.rb +81 -0
  56. data/spec/graphql/internal_representation/rewrite_spec.rb +104 -21
  57. data/spec/graphql/introspection/input_value_type_spec.rb +43 -6
  58. data/spec/graphql/introspection/schema_type_spec.rb +1 -0
  59. data/spec/graphql/introspection/type_type_spec.rb +2 -0
  60. data/spec/graphql/language/generation_spec.rb +3 -2
  61. data/spec/graphql/query/arguments_spec.rb +17 -4
  62. data/spec/graphql/query/context_spec.rb +23 -0
  63. data/spec/graphql/query/variables_spec.rb +15 -1
  64. data/spec/graphql/relay/mutation_spec.rb +42 -2
  65. data/spec/graphql/schema/build_from_definition_spec.rb +4 -2
  66. data/spec/graphql/schema/loader_spec.rb +59 -1
  67. data/spec/graphql/schema/printer_spec.rb +2 -0
  68. data/spec/graphql/schema/reduce_types_spec.rb +1 -1
  69. data/spec/graphql/schema/timeout_middleware_spec.rb +2 -2
  70. data/spec/graphql/schema/unique_within_type_spec.rb +9 -0
  71. data/spec/graphql/schema/validation_spec.rb +15 -3
  72. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +122 -0
  73. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +78 -0
  74. data/spec/support/dairy_app.rb +9 -0
  75. data/spec/support/minimum_input_object.rb +4 -0
  76. data/spec/support/star_wars_schema.rb +1 -1
  77. metadata +5 -5
  78. data/lib/graphql/query/serial_execution/execution_context.rb +0 -37
  79. data/spec/graphql/query/serial_execution/execution_context_spec.rb +0 -54
@@ -55,9 +55,9 @@ module GraphQL
55
55
  input_field: GraphQL::Define::AssignArgument,
56
56
  return_field: GraphQL::Define::AssignObjectField,
57
57
  )
58
- lazy_defined_attr_accessor :name, :description
59
- lazy_defined_attr_accessor :fields, :arguments, :return_type
58
+ attr_accessor :name, :description, :fields, :arguments, :return_type
60
59
 
60
+ ensure_defined(:name, :description, :fields, :arguments, :return_type, :resolve=, :field, :result_class, :input_type)
61
61
  # For backwards compat, but do we need this separate API?
62
62
  alias :return_fields :fields
63
63
  alias :input_fields :arguments
@@ -75,13 +75,11 @@ module GraphQL
75
75
  end
76
76
 
77
77
  def resolve=(new_resolve_proc)
78
- ensure_defined
79
78
  @resolve_proc = MutationResolve.new(self, new_resolve_proc, wrap_result: has_generated_return_type?)
80
79
  end
81
80
 
82
81
  def field
83
82
  @field ||= begin
84
- ensure_defined
85
83
  relay_mutation = self
86
84
  field_resolve_proc = @resolve_proc
87
85
  GraphQL::Field.define do
@@ -95,7 +93,6 @@ module GraphQL
95
93
  end
96
94
 
97
95
  def return_type
98
- ensure_defined
99
96
  @return_type ||= begin
100
97
  @has_generated_return_type = true
101
98
  relay_mutation = self
@@ -113,14 +110,15 @@ module GraphQL
113
110
 
114
111
  def input_type
115
112
  @input_type ||= begin
116
- ensure_defined
117
113
  relay_mutation = self
118
114
  GraphQL::InputObjectType.define do
119
115
  name("#{relay_mutation.name}Input")
120
116
  description("Autogenerated input type of #{relay_mutation.name}")
121
117
  input_field :clientMutationId, types.String, "A unique identifier for the client performing the mutation."
122
118
  relay_mutation.input_fields.each do |input_field_name, field_obj|
123
- input_field input_field_name, field_obj.type, field_obj.description, default_value: field_obj.default_value
119
+ kwargs = {}
120
+ kwargs[:default_value] = field_obj.default_value if field_obj.default_value?
121
+ input_field input_field_name, field_obj.type, field_obj.description, **kwargs
124
122
  end
125
123
  mutation(relay_mutation)
126
124
  end
@@ -129,7 +127,6 @@ module GraphQL
129
127
 
130
128
  def result_class
131
129
  @result_class ||= begin
132
- ensure_defined
133
130
  Result.define_subclass(self)
134
131
  end
135
132
  end
@@ -35,6 +35,7 @@ module GraphQL
35
35
  #
36
36
  class ScalarType < GraphQL::BaseType
37
37
  accepts_definitions :coerce, :coerce_input, :coerce_result
38
+ ensure_defined :coerce_non_null_input, :coerce_result
38
39
 
39
40
  def coerce=(proc)
40
41
  self.coerce_input = proc
@@ -50,7 +51,6 @@ module GraphQL
50
51
  end
51
52
 
52
53
  def coerce_non_null_input(value)
53
- ensure_defined
54
54
  @coerce_input_proc.call(value)
55
55
  end
56
56
 
@@ -61,7 +61,6 @@ module GraphQL
61
61
  end
62
62
 
63
63
  def coerce_result(value)
64
- ensure_defined
65
64
  @coerce_result_proc ? @coerce_result_proc.call(value) : value
66
65
  end
67
66
 
@@ -61,7 +61,7 @@ module GraphQL
61
61
  middleware: ->(schema, middleware) { schema.middleware << middleware },
62
62
  rescue_from: ->(schema, err_class, &block) { schema.rescue_from(err_class, &block)}
63
63
 
64
- lazy_defined_attr_accessor \
64
+ attr_accessor \
65
65
  :query, :mutation, :subscription,
66
66
  :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
67
67
  :max_depth, :max_complexity,
@@ -99,17 +99,16 @@ module GraphQL
99
99
  end
100
100
 
101
101
  def rescue_from(*args, &block)
102
- ensure_defined
103
102
  rescue_middleware.rescue_from(*args, &block)
104
103
  end
105
104
 
106
105
  def remove_handler(*args, &block)
107
- ensure_defined
108
106
  rescue_middleware.remove_handler(*args, &block)
109
107
  end
110
108
 
111
109
  def define(**kwargs, &block)
112
110
  super
111
+ ensure_defined
113
112
  all_types = orphan_types + [query, mutation, subscription, GraphQL::Introspection::SchemaType]
114
113
  @types = GraphQL::Schema::ReduceTypes.reduce(all_types.compact)
115
114
 
@@ -151,7 +150,6 @@ module GraphQL
151
150
  # @see [GraphQL::Schema::Warden] Restricted access to members of a schema
152
151
  # @return [GraphQL::Field, nil] The field named `field_name` on `parent_type`
153
152
  def get_field(parent_type, field_name)
154
- ensure_defined
155
153
  defined_field = @instrumented_field_map.get(parent_type.name, field_name)
156
154
  if defined_field
157
155
  defined_field
@@ -167,7 +165,6 @@ module GraphQL
167
165
  end
168
166
 
169
167
  def type_from_ast(ast_node)
170
- ensure_defined
171
168
  GraphQL::Schema::TypeExpression.build_type(self.types, ast_node)
172
169
  end
173
170
 
@@ -175,7 +172,6 @@ module GraphQL
175
172
  # @param type_defn [GraphQL::InterfaceType, GraphQL::UnionType] the type whose members you want to retrieve
176
173
  # @return [Array<GraphQL::ObjectType>] types which belong to `type_defn` in this schema
177
174
  def possible_types(type_defn)
178
- ensure_defined
179
175
  @possible_types ||= GraphQL::Schema::PossibleTypes.new(self)
180
176
  @possible_types.possible_types(type_defn)
181
177
  end
@@ -213,8 +209,6 @@ module GraphQL
213
209
  # @param ctx [GraphQL::Query::Context] The context for the current query
214
210
  # @return [GraphQL::ObjectType] The type for exposing `object` in GraphQL
215
211
  def resolve_type(object, ctx)
216
- ensure_defined
217
-
218
212
  if @resolve_type_proc.nil?
219
213
  raise(NotImplementedError, "Can't determine GraphQL type for: #{object.inspect}, define `resolve_type (obj, ctx) -> { ... }` inside `Schema.define`.")
220
214
  end
@@ -231,7 +225,6 @@ module GraphQL
231
225
  end
232
226
 
233
227
  def resolve_type=(new_resolve_type_proc)
234
- ensure_defined
235
228
  @resolve_type_proc = new_resolve_type_proc
236
229
  end
237
230
 
@@ -240,7 +233,6 @@ module GraphQL
240
233
  # @param ctx [GraphQL::Query::Context] The context for the current query
241
234
  # @return [Any] The application object identified by `id`
242
235
  def object_from_id(id, ctx)
243
- ensure_defined
244
236
  if @object_from_id_proc.nil?
245
237
  raise(NotImplementedError, "Can't fetch an object for id \"#{id}\" because the schema's `object_from_id (id, ctx) -> { ... }` function is not defined")
246
238
  else
@@ -250,7 +242,6 @@ module GraphQL
250
242
 
251
243
  # @param new_proc [#call] A new callable for fetching objects by ID
252
244
  def object_from_id=(new_proc)
253
- ensure_defined
254
245
  @object_from_id_proc = new_proc
255
246
  end
256
247
 
@@ -260,7 +251,6 @@ module GraphQL
260
251
  # @param ctx [GraphQL::Query::Context] the context for the current query
261
252
  # @return [String] a unique identifier for `object` which clients can use to refetch it
262
253
  def id_from_object(object, type, ctx)
263
- ensure_defined
264
254
  if @id_from_object_proc.nil?
265
255
  raise(NotImplementedError, "Can't generate an ID for #{object.inspect} of type #{type}, schema's `id_from_object` must be defined")
266
256
  else
@@ -270,7 +260,6 @@ module GraphQL
270
260
 
271
261
  # @param new_proc [#call] A new callable for generating unique IDs
272
262
  def id_from_object=(new_proc)
273
- ensure_defined
274
263
  @id_from_object_proc = new_proc
275
264
  end
276
265
 
@@ -150,13 +150,23 @@ module GraphQL
150
150
  )
151
151
  end
152
152
 
153
+ def build_default_value(default_value)
154
+ case default_value
155
+ when GraphQL::Language::Nodes::Enum
156
+ default_value.name
157
+ when GraphQL::Language::Nodes::NullValue
158
+ nil
159
+ else
160
+ default_value
161
+ end
162
+ end
163
+
153
164
  def build_input_arguments(input_object_type_definition, type_resolver)
154
165
  input_object_type_definition.fields.map do |input_argument|
155
- default_value = case input_argument.default_value
156
- when GraphQL::Language::Nodes::Enum
157
- input_argument.default_value.name
158
- else
159
- input_argument.default_value
166
+ kwargs = {}
167
+
168
+ if !input_argument.default_value.nil?
169
+ kwargs[:default_value] = build_default_value(input_argument.default_value)
160
170
  end
161
171
 
162
172
  [
@@ -165,7 +175,7 @@ module GraphQL
165
175
  name: input_argument.name,
166
176
  type: type_resolver.call(input_argument.type),
167
177
  description: input_argument.description,
168
- default_value: default_value,
178
+ **kwargs,
169
179
  )
170
180
  ]
171
181
  end
@@ -182,13 +192,19 @@ module GraphQL
182
192
 
183
193
  def build_directive_arguments(directive_definition, type_resolver)
184
194
  directive_definition.arguments.map do |directive_argument|
195
+ kwargs = {}
196
+
197
+ if !directive_argument.default_value.nil?
198
+ kwargs[:default_value] = build_default_value(directive_argument.default_value)
199
+ end
200
+
185
201
  [
186
202
  directive_argument.name,
187
203
  GraphQL::Argument.define(
188
204
  name: directive_argument.name,
189
205
  type: type_resolver.call(directive_argument.type),
190
206
  description: directive_argument.description,
191
- default_value: directive_argument.default_value,
207
+ **kwargs,
192
208
  )
193
209
  ]
194
210
  end
@@ -205,18 +221,17 @@ module GraphQL
205
221
  def build_fields(field_definitions, type_resolver)
206
222
  field_definitions.map do |field_definition|
207
223
  field_arguments = Hash[field_definition.arguments.map do |argument|
208
- default_value = case argument.default_value
209
- when GraphQL::Language::Nodes::Enum
210
- argument.default_value.name
211
- else
212
- argument.default_value
224
+ kwargs = {}
225
+
226
+ if !argument.default_value.nil?
227
+ kwargs[:default_value] = build_default_value(argument.default_value)
213
228
  end
214
229
 
215
230
  arg = GraphQL::Argument.define(
216
231
  name: argument.name,
217
232
  description: argument.description,
218
233
  type: type_resolver.call(argument.type),
219
- default_value: default_value,
234
+ **kwargs,
220
235
  )
221
236
 
222
237
  [argument.name, arg]
@@ -103,11 +103,14 @@ module GraphQL
103
103
  }]
104
104
  )
105
105
  when "ARGUMENT"
106
+ kwargs = {}
107
+ kwargs[:default_value] = JSON.parse(type["defaultValue"], quirks_mode: true) if type["defaultValue"]
108
+
106
109
  Argument.define(
107
110
  name: type["name"],
108
111
  type: type_resolver.call(type["type"]),
109
112
  description: type["description"],
110
- default_value: type["defaultValue"] ? JSON.parse(type["defaultValue"], quirks_mode: true) : nil
113
+ **kwargs
111
114
  )
112
115
  when "SCALAR"
113
116
  type_name = type.fetch("name")
@@ -120,10 +120,10 @@ module GraphQL
120
120
  end
121
121
 
122
122
  def print_input_value(arg)
123
- if arg.default_value.nil?
124
- default_string = nil
125
- else
123
+ if arg.default_value?
126
124
  default_string = " = #{print_value(arg.default_value, arg.type)}"
125
+ else
126
+ default_string = nil
127
127
  end
128
128
 
129
129
  "#{arg.name}: #{arg.type.to_s}#{default_string}"
@@ -132,16 +132,22 @@ module GraphQL
132
132
  def print_value(value, type)
133
133
  case type
134
134
  when FLOAT_TYPE
135
+ return 'null' if value.nil?
135
136
  value.to_f.inspect
136
137
  when INT_TYPE
138
+ return 'null' if value.nil?
137
139
  value.to_i.inspect
138
140
  when BOOLEAN_TYPE
141
+ return 'null' if value.nil?
139
142
  (!!value).inspect
140
143
  when ScalarType, ID_TYPE, STRING_TYPE
144
+ return 'null' if value.nil?
141
145
  value.to_s.inspect
142
146
  when EnumType
147
+ return 'null' if value.nil?
143
148
  type.coerce_result(value)
144
149
  when InputObjectType
150
+ return 'null' if value.nil?
145
151
  fields = value.to_h.map{ |field_name, field_value|
146
152
  field_type = type.input_fields.fetch(field_name.to_s).type
147
153
  "#{field_name}: #{print_value(field_value, field_type)}"
@@ -150,6 +156,7 @@ module GraphQL
150
156
  when NonNullType
151
157
  print_value(value, type.of_type)
152
158
  when ListType
159
+ return 'null' if value.nil?
153
160
  "[#{value.to_a.map{ |v| print_value(v, type.of_type) }.join(", ")}]"
154
161
  else
155
162
  raise NotImplementedError, "Unexpected value type #{type.inspect}"
@@ -1,3 +1,5 @@
1
+ require "delegate"
2
+
1
3
  module GraphQL
2
4
  class Schema
3
5
  # This middleware will stop resolving new fields after `max_seconds` have elapsed.
@@ -43,14 +45,28 @@ module GraphQL
43
45
 
44
46
  # This is called when a field _would_ be resolved, except that we're over the time limit.
45
47
  # @return [GraphQL::Schema::TimeoutMiddleware::TimeoutError] An error whose message will be added to the `errors` key
46
- def on_timeout(parent_type, parent_object, field_definition, field_args, query_context)
48
+ def on_timeout(parent_type, parent_object, field_definition, field_args, field_context)
47
49
  err = GraphQL::Schema::TimeoutMiddleware::TimeoutError.new(parent_type, field_definition)
48
50
  if @error_handler
49
- @error_handler.call(err, query_context.query)
51
+ query_proxy = TimeoutQueryProxy.new(field_context.query, field_context)
52
+ @error_handler.call(err, query_proxy)
50
53
  end
51
54
  err
52
55
  end
53
56
 
57
+ # This behaves like {GraphQL::Query} but {#context} returns
58
+ # the _field-level_ context, not the query-level context.
59
+ # This means you can reliably get the `irep_node` and `path`
60
+ # from it after the fact.
61
+ class TimeoutQueryProxy < SimpleDelegator
62
+ def initialize(query, ctx)
63
+ @context = ctx
64
+ super(query)
65
+ end
66
+
67
+ attr_reader :context
68
+ end
69
+
54
70
  # This error is raised when a query exceeds `max_seconds`.
55
71
  # Since it's a child of {GraphQL::ExecutionError},
56
72
  # its message will be added to the response's `errors` key.
@@ -1,14 +1,17 @@
1
1
  module GraphQL
2
2
  class Schema
3
3
  module UniqueWithinType
4
- DEFAULT_SEPARATOR = "-"
4
+ class << self
5
+ attr_accessor :default_id_separator
6
+ end
7
+ self.default_id_separator = "-"
5
8
 
6
9
  module_function
7
10
 
8
11
  # @param type_name [String]
9
12
  # @param object_value [Any]
10
13
  # @return [String] a unique, opaque ID generated as a function of the two inputs
11
- def encode(type_name, object_value, separator: DEFAULT_SEPARATOR)
14
+ def encode(type_name, object_value, separator: self.default_id_separator)
12
15
  object_value_str = object_value.to_s
13
16
 
14
17
  if type_name.include?(separator) || object_value_str.include?(separator)
@@ -20,7 +23,7 @@ module GraphQL
20
23
 
21
24
  # @param node_id [String] A unique ID generated by {.encode}
22
25
  # @return [Array<(String, String)>] The type name & value passed to {.encode}
23
- def decode(node_id, separator: DEFAULT_SEPARATOR)
26
+ def decode(node_id, separator: self.default_id_separator)
24
27
  Base64.decode64(node_id).split(separator)
25
28
  end
26
29
  end
@@ -7,7 +7,9 @@ module GraphQL
7
7
  end
8
8
 
9
9
  def validate(ast_value, type)
10
- if type.kind.non_null?
10
+ if ast_value.is_a?(GraphQL::Language::Nodes::NullValue)
11
+ !type.kind.non_null?
12
+ elsif type.kind.non_null?
11
13
  (!ast_value.nil?) && validate(ast_value, type.of_type)
12
14
  elsif type.kind.list?
13
15
  item_type = type.of_type
@@ -24,6 +24,7 @@ module GraphQL
24
24
  #
25
25
  class UnionType < GraphQL::BaseType
26
26
  accepts_definitions :possible_types, :resolve_type
27
+ ensure_defined :possible_types
27
28
 
28
29
  def kind
29
30
  GraphQL::TypeKinds::UNION
@@ -40,8 +41,6 @@ module GraphQL
40
41
 
41
42
  def possible_types
42
43
  @clean_possible_types ||= begin
43
- ensure_defined
44
-
45
44
  if @dirty_possible_types.respond_to?(:map)
46
45
  @dirty_possible_types.map { |type| GraphQL::BaseType.resolve_related_type(type) }
47
46
  else
@@ -1,3 +1,3 @@
1
1
  module GraphQL
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
data/readme.md CHANGED
@@ -68,3 +68,4 @@ See "Getting Started" on the [website](https://rmosolgo.github.io/graphql-ruby/)
68
68
  - Renaming fragments from local names to unique names
69
69
  - Document encrypted & versioned cursors
70
70
  - Make it faster
71
+ - Is it possible to merge typed branches before resolving them? It's hard because even the branches will have branches.
@@ -82,14 +82,12 @@ describe GraphQL::Analysis do
82
82
  memo ||= Hash.new { |h,k| h[k] = 0 }
83
83
  if visit_type == :enter
84
84
  if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field)
85
- irep_node.definitions.each do |type_defn, field_defn|
86
- if field_defn.resolve_proc.is_a?(GraphQL::Relay::ConnectionResolve)
87
- memo[:connection] ||= 0
88
- memo[:connection] += 1
89
- else
90
- memo[:field] ||= 0
91
- memo[:field] += 1
92
- end
85
+ if irep_node.definition.resolve_proc.is_a?(GraphQL::Relay::ConnectionResolve)
86
+ memo[:connection] ||= 0
87
+ memo[:connection] += 1
88
+ else
89
+ memo[:field] ||= 0
90
+ memo[:field] += 1
93
91
  end
94
92
  end
95
93
  end