graphql 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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