graphql 1.10.6 → 1.10.11

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/generators/graphql/object_generator.rb +50 -8
  3. data/lib/graphql.rb +4 -4
  4. data/lib/graphql/analysis/ast/query_complexity.rb +1 -1
  5. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +2 -2
  6. data/lib/graphql/execution/instrumentation.rb +1 -1
  7. data/lib/graphql/execution/interpreter.rb +2 -0
  8. data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
  9. data/lib/graphql/execution/interpreter/arguments.rb +36 -0
  10. data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -7
  11. data/lib/graphql/execution/interpreter/runtime.rb +47 -38
  12. data/lib/graphql/execution/lookahead.rb +3 -1
  13. data/lib/graphql/internal_representation/scope.rb +2 -2
  14. data/lib/graphql/internal_representation/visit.rb +2 -2
  15. data/lib/graphql/language/document_from_schema_definition.rb +42 -23
  16. data/lib/graphql/object_type.rb +1 -1
  17. data/lib/graphql/relay/base_connection.rb +0 -2
  18. data/lib/graphql/schema.rb +28 -13
  19. data/lib/graphql/schema/argument.rb +6 -0
  20. data/lib/graphql/schema/base_64_encoder.rb +2 -0
  21. data/lib/graphql/schema/enum.rb +9 -1
  22. data/lib/graphql/schema/field.rb +30 -21
  23. data/lib/graphql/schema/input_object.rb +9 -6
  24. data/lib/graphql/schema/interface.rb +5 -0
  25. data/lib/graphql/schema/list.rb +7 -1
  26. data/lib/graphql/schema/loader.rb +110 -103
  27. data/lib/graphql/schema/member.rb +1 -0
  28. data/lib/graphql/schema/member/has_arguments.rb +33 -13
  29. data/lib/graphql/schema/member/has_fields.rb +1 -1
  30. data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
  31. data/lib/graphql/schema/non_null.rb +5 -0
  32. data/lib/graphql/schema/object.rb +10 -3
  33. data/lib/graphql/schema/printer.rb +0 -14
  34. data/lib/graphql/schema/resolver.rb +1 -1
  35. data/lib/graphql/schema/union.rb +6 -0
  36. data/lib/graphql/schema/warden.rb +7 -1
  37. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  38. data/lib/graphql/subscriptions/subscription_root.rb +10 -2
  39. data/lib/graphql/tracing.rb +5 -4
  40. data/lib/graphql/tracing/new_relic_tracing.rb +1 -12
  41. data/lib/graphql/tracing/platform_tracing.rb +14 -0
  42. data/lib/graphql/tracing/scout_tracing.rb +11 -0
  43. data/lib/graphql/types/iso_8601_date.rb +3 -3
  44. data/lib/graphql/types/iso_8601_date_time.rb +18 -8
  45. data/lib/graphql/version.rb +1 -1
  46. metadata +6 -3
@@ -31,6 +31,11 @@ module GraphQL
31
31
  nil
32
32
  end
33
33
 
34
+ # Also for implementing introspection
35
+ def description
36
+ nil
37
+ end
38
+
34
39
  def coerce_result(value, ctx)
35
40
  value.map { |i| i.nil? ? nil : of_type.coerce_result(i, ctx) }
36
41
  end
@@ -39,7 +44,8 @@ module GraphQL
39
44
  if value.nil?
40
45
  nil
41
46
  else
42
- ensure_array(value).map { |item| item.nil? ? item : of_type.coerce_input(item, ctx) }
47
+ coerced = ensure_array(value).map { |item| item.nil? ? item : of_type.coerce_input(item, ctx) }
48
+ ctx.schema.after_any_lazies(coerced, &:itself)
43
49
  end
44
50
  end
45
51
 
@@ -5,18 +5,19 @@ module GraphQL
5
5
  # to make a schema. This schema is missing some important details like
6
6
  # `resolve` functions, but it does include the full type system,
7
7
  # so you can use it to validate queries.
8
+ #
9
+ # @see GraphQL::Schema.from_introspection for a public API
8
10
  module Loader
9
11
  extend self
10
12
 
11
13
  # Create schema with the result of an introspection query.
12
14
  # @param introspection_result [Hash] A response from {GraphQL::Introspection::INTROSPECTION_QUERY}
13
- # @return [GraphQL::Schema] the schema described by `input`
14
- # @deprecated Use {GraphQL::Schema.from_introspection} instead
15
+ # @return [Class] the schema described by `input`
15
16
  def load(introspection_result)
16
17
  schema = introspection_result.fetch("data").fetch("__schema")
17
18
 
18
19
  types = {}
19
- type_resolver = ->(type) { -> { resolve_type(types, type) } }
20
+ type_resolver = ->(type) { resolve_type(types, type) }
20
21
 
21
22
  schema.fetch("types").each do |type|
22
23
  next if type.fetch("name").start_with?("__")
@@ -24,18 +25,22 @@ module GraphQL
24
25
  types[type["name"]] = type_object
25
26
  end
26
27
 
27
- kargs = { orphan_types: types.values, resolve_type: NullResolveType }
28
- [:query, :mutation, :subscription].each do |root|
29
- type = schema["#{root}Type"]
30
- kargs[root] = types.fetch(type.fetch("name")) if type
31
- end
28
+ Class.new(GraphQL::Schema) do
29
+ orphan_types(types.values)
32
30
 
33
- Schema.define(**kargs, raise_definition_error: true)
34
- end
31
+ def self.resolve_type(*)
32
+ raise(GraphQL::RequiredImplementationMissingError, "This schema was loaded from string, so it can't resolve types for objects")
33
+ end
35
34
 
36
- NullResolveType = ->(type, obj, ctx) {
37
- raise(GraphQL::RequiredImplementationMissingError, "This schema was loaded from string, so it can't resolve types for objects")
38
- }
35
+ [:query, :mutation, :subscription].each do |root|
36
+ type = schema["#{root}Type"]
37
+ if type
38
+ type_defn = types.fetch(type.fetch("name"))
39
+ self.public_send(root, type_defn)
40
+ end
41
+ end
42
+ end
43
+ end
39
44
 
40
45
  NullScalarCoerce = ->(val, _ctx) { val }
41
46
 
@@ -48,14 +53,14 @@ module GraphQL
48
53
  type_name = type.fetch("name")
49
54
  type = types[type_name] || Schema::BUILT_IN_TYPES[type_name]
50
55
  if type.nil?
51
- raise "Type not found: #{type_name.inspect} among #{types.keys.sort}"
56
+ GraphQL::Schema::LateBoundType.new(type_name)
52
57
  else
53
- type.graphql_definition
58
+ type
54
59
  end
55
60
  when "LIST"
56
- ListType.new(of_type: resolve_type(types, type.fetch("ofType")))
61
+ Schema::List.new(resolve_type(types, type.fetch("ofType")))
57
62
  when "NON_NULL"
58
- NonNullType.new(of_type: resolve_type(types, type.fetch("ofType")))
63
+ Schema::NonNull.new(resolve_type(types, type.fetch("ofType")))
59
64
  else
60
65
  fail GraphQL::RequiredImplementationMissingError, "#{kind} not implemented"
61
66
  end
@@ -83,109 +88,111 @@ module GraphQL
83
88
  end
84
89
 
85
90
  def define_type(type, type_resolver)
91
+ loader = self
86
92
  case type.fetch("kind")
87
93
  when "ENUM"
88
- EnumType.define(
89
- name: type["name"],
90
- description: type["description"],
91
- values: type["enumValues"].map { |enum|
92
- EnumType::EnumValue.define(
93
- name: enum["name"],
94
- description: enum["description"],
95
- deprecation_reason: enum["deprecationReason"],
96
- value: enum["name"]
94
+ Class.new(GraphQL::Schema::Enum) do
95
+ graphql_name(type["name"])
96
+ description(type["description"])
97
+ type["enumValues"].each do |enum_value|
98
+ value(
99
+ enum_value["name"],
100
+ description: enum_value["description"],
101
+ deprecation_reason: enum_value["deprecation_reason"],
97
102
  )
98
- })
103
+ end
104
+ end
99
105
  when "INTERFACE"
100
- InterfaceType.define(
101
- name: type["name"],
102
- description: type["description"],
103
- fields: Hash[(type["fields"] || []).map { |field|
104
- [field["name"], define_type(field.merge("kind" => "FIELD"), type_resolver)]
105
- }]
106
- )
106
+ Module.new do
107
+ include GraphQL::Schema::Interface
108
+ graphql_name(type["name"])
109
+ description(type["description"])
110
+ loader.build_fields(self, type["fields"] || [], type_resolver)
111
+ end
107
112
  when "INPUT_OBJECT"
108
- InputObjectType.define(
109
- name: type["name"],
110
- description: type["description"],
111
- arguments: Hash[type["inputFields"].map { |arg|
112
- [arg["name"], define_type(arg.merge("kind" => "ARGUMENT"), type_resolver)]
113
- }]
114
- )
115
- when "OBJECT"
116
- ObjectType.define(
117
- name: type["name"],
118
- description: type["description"],
119
- interfaces: (type["interfaces"] || []).map { |interface|
120
- type_resolver.call(interface)
121
- },
122
- fields: Hash[type["fields"].map { |field|
123
- [field["name"], define_type(field.merge("kind" => "FIELD"), type_resolver)]
124
- }]
125
- )
126
- when "FIELD"
127
- defns = {
128
- name: type["name"],
129
- type: type_resolver.call(type["type"]),
130
- description: type["description"],
131
- }
132
-
133
- # Avoid passing an empty hash, which warns on Ruby 2.7
134
- if type["args"].any?
135
- defns[:arguments] = Hash[type["args"].map { |arg|
136
- [arg["name"], define_type(arg.merge("kind" => "ARGUMENT"), type_resolver)]
137
- }]
113
+ Class.new(GraphQL::Schema::InputObject) do
114
+ graphql_name(type["name"])
115
+ description(type["description"])
116
+ loader.build_arguments(self, type["inputFields"], type_resolver)
138
117
  end
139
-
140
- GraphQL::Field.define(**defns)
141
- when "ARGUMENT"
142
- kwargs = {}
143
- if type["defaultValue"]
144
- kwargs[:default_value] = begin
145
- default_value_str = type["defaultValue"]
146
-
147
- dummy_query_str = "query getStuff($var: InputObj = #{default_value_str}) { __typename }"
148
-
149
- # Returns a `GraphQL::Language::Nodes::Document`:
150
- dummy_query_ast = GraphQL.parse(dummy_query_str)
151
-
152
- # Reach into the AST for the default value:
153
- input_value_ast = dummy_query_ast.definitions.first.variables.first.default_value
154
-
155
- extract_default_value(default_value_str, input_value_ast)
118
+ when "OBJECT"
119
+ Class.new(GraphQL::Schema::Object) do
120
+ graphql_name(type["name"])
121
+ description(type["description"])
122
+ if type["interfaces"]
123
+ type["interfaces"].each do |interface_type|
124
+ implements(type_resolver.call(interface_type))
125
+ end
156
126
  end
127
+ loader.build_fields(self, type["fields"], type_resolver)
157
128
  end
158
-
159
- GraphQL::Argument.define(
160
- name: type["name"],
161
- type: type_resolver.call(type["type"]),
162
- description: type["description"],
163
- method_access: false,
164
- **kwargs
165
- )
166
129
  when "SCALAR"
167
130
  type_name = type.fetch("name")
168
- if GraphQL::Schema::BUILT_IN_TYPES[type_name]
169
- GraphQL::Schema::BUILT_IN_TYPES[type_name]
131
+ if (builtin = GraphQL::Schema::BUILT_IN_TYPES[type_name])
132
+ builtin
170
133
  else
171
- ScalarType.define(
172
- name: type["name"],
173
- description: type["description"],
174
- coerce: NullScalarCoerce,
175
- )
134
+ Class.new(GraphQL::Schema::Scalar) do
135
+ graphql_name(type["name"])
136
+ description(type["description"])
137
+ end
176
138
  end
177
139
  when "UNION"
178
- UnionType.define(
179
- name: type["name"],
180
- description: type["description"],
181
- possible_types: type["possibleTypes"].map { |possible_type|
182
- type_resolver.call(possible_type)
183
- }
184
- )
140
+ Class.new(GraphQL::Schema::Union) do
141
+ graphql_name(type["name"])
142
+ description(type["description"])
143
+ possible_types(*(type["possibleTypes"].map { |pt| type_resolver.call(pt) }))
144
+ end
185
145
  else
186
146
  fail GraphQL::RequiredImplementationMissingError, "#{type["kind"]} not implemented"
187
147
  end
188
148
  end
149
+
150
+ public
151
+
152
+ def build_fields(type_defn, fields, type_resolver)
153
+ loader = self
154
+ fields.each do |field_hash|
155
+ type_defn.field(
156
+ field_hash["name"],
157
+ type: type_resolver.call(field_hash["type"]),
158
+ description: field_hash["description"],
159
+ null: true,
160
+ camelize: false,
161
+ ) do
162
+ if field_hash["args"].any?
163
+ loader.build_arguments(self, field_hash["args"], type_resolver)
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ def build_arguments(arg_owner, args, type_resolver)
170
+ args.each do |arg|
171
+ kwargs = {
172
+ type: type_resolver.call(arg["type"]),
173
+ description: arg["description"],
174
+ required: false,
175
+ method_access: false,
176
+ camelize: false,
177
+ }
178
+
179
+ if arg["defaultValue"]
180
+ default_value_str = arg["defaultValue"]
181
+
182
+ dummy_query_str = "query getStuff($var: InputObj = #{default_value_str}) { __typename }"
183
+
184
+ # Returns a `GraphQL::Language::Nodes::Document`:
185
+ dummy_query_ast = GraphQL.parse(dummy_query_str)
186
+
187
+ # Reach into the AST for the default value:
188
+ input_value_ast = dummy_query_ast.definitions.first.variables.first.default_value
189
+
190
+ kwargs[:default_value] = extract_default_value(default_value_str, input_value_ast)
191
+ end
192
+
193
+ arg_owner.argument(arg["name"], **kwargs)
194
+ end
195
+ end
189
196
  end
190
197
  end
191
198
  end
@@ -5,6 +5,7 @@ require 'graphql/schema/member/cached_graphql_definition'
5
5
  require 'graphql/schema/member/graphql_type_names'
6
6
  require 'graphql/schema/member/has_ast_node'
7
7
  require 'graphql/schema/member/has_path'
8
+ require 'graphql/schema/member/has_unresolved_type_error'
8
9
  require 'graphql/schema/member/relay_shortcuts'
9
10
  require 'graphql/schema/member/scoped'
10
11
  require 'graphql/schema/member/type_system_helpers'
@@ -63,10 +63,12 @@ module GraphQL
63
63
  self.class.argument_class(new_arg_class)
64
64
  end
65
65
 
66
+ # @api private
66
67
  # @param values [Hash<String, Object>]
67
68
  # @param context [GraphQL::Query::Context]
68
- # @return Hash<Symbol, Object>
69
+ # @return [Hash<Symbol, Object>, Execution::Lazy<Hash>]
69
70
  def coerce_arguments(parent_object, values, context)
71
+ argument_values = {}
70
72
  kwarg_arguments = {}
71
73
  # Cache this hash to avoid re-merging it
72
74
  arg_defns = self.arguments
@@ -75,6 +77,7 @@ module GraphQL
75
77
  arg_lazies = arg_defns.map do |arg_name, arg_defn|
76
78
  arg_key = arg_defn.keyword
77
79
  has_value = false
80
+ default_used = false
78
81
  if values.key?(arg_name)
79
82
  has_value = true
80
83
  value = values[arg_name]
@@ -84,6 +87,7 @@ module GraphQL
84
87
  elsif arg_defn.default_value?
85
88
  has_value = true
86
89
  value = arg_defn.default_value
90
+ default_used = true
87
91
  end
88
92
 
89
93
  if has_value
@@ -91,36 +95,52 @@ module GraphQL
91
95
  loaded_value = nil
92
96
  if loads && !arg_defn.from_resolver?
93
97
  loaded_value = if arg_defn.type.list?
94
- value.map { |val| load_application_object(arg_defn, loads, val, context) }
98
+ loaded_values = value.map { |val| load_application_object(arg_defn, loads, val, context) }
99
+ context.schema.after_any_lazies(loaded_values) { |result| result }
95
100
  else
96
101
  load_application_object(arg_defn, loads, value, context)
97
102
  end
98
103
  end
99
104
 
100
- context.schema.after_lazy(loaded_value) do |loaded_value|
101
- coerced_value = nil
102
- prepared_value = context.schema.error_handler.with_error_handling(context) do
103
-
104
- coerced_value = if loaded_value
105
- loaded_value
106
- else
107
- arg_defn.type.coerce_input(value, context)
108
- end
105
+ coerced_value = if loaded_value
106
+ loaded_value
107
+ else
108
+ context.schema.error_handler.with_error_handling(context) do
109
+ arg_defn.type.coerce_input(value, context)
110
+ end
111
+ end
109
112
 
113
+ context.schema.after_lazy(coerced_value) do |coerced_value|
114
+ prepared_value = context.schema.error_handler.with_error_handling(context) do
110
115
  arg_defn.prepare_value(parent_object, coerced_value, context: context)
111
116
  end
112
117
 
113
- kwarg_arguments[arg_defn.keyword] = prepared_value
118
+ kwarg_arguments[arg_key] = prepared_value
119
+ # TODO code smell to access such a deeply-nested constant in a distant module
120
+ argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
121
+ value: prepared_value,
122
+ definition: arg_defn,
123
+ default_used: default_used,
124
+ )
114
125
  end
115
126
  end
116
127
  end
117
128
 
118
129
  maybe_lazies.concat(arg_lazies)
119
130
  context.schema.after_any_lazies(maybe_lazies) do
120
- kwarg_arguments
131
+ GraphQL::Execution::Interpreter::Arguments.new(
132
+ keyword_arguments: kwarg_arguments,
133
+ argument_values: argument_values,
134
+ )
121
135
  end
122
136
  end
123
137
 
138
+ def arguments_statically_coercible?
139
+ return @arguments_statically_coercible if defined?(@arguments_statically_coercible)
140
+
141
+ @arguments_statically_coercible = arguments.each_value.all?(&:statically_coercible?)
142
+ end
143
+
124
144
  module ArgumentClassAccessor
125
145
  def argument_class(new_arg_class = nil)
126
146
  if new_arg_class
@@ -47,7 +47,7 @@ module GraphQL
47
47
  # A list of GraphQL-Ruby keywords.
48
48
  #
49
49
  # @api private
50
- GRAPHQL_RUBY_KEYWORDS = [:context, :object, :method]
50
+ GRAPHQL_RUBY_KEYWORDS = [:context, :object, :method, :raw_value]
51
51
 
52
52
  # A list of field names that we should advise users to pick a different
53
53
  # resolve method name.
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ # Set up a type-specific error to make debugging & bug tracker integration better
7
+ module HasUnresolvedTypeError
8
+ private
9
+ def add_unresolved_type_error(child_class)
10
+ child_class.const_set(:UnresolvedTypeError, Class.new(GraphQL::UnresolvedTypeError))
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -57,6 +57,11 @@ module GraphQL
57
57
  def coerce_result(value, ctx)
58
58
  of_type.coerce_result(value, ctx)
59
59
  end
60
+
61
+ # This is for implementing introspection
62
+ def description
63
+ nil
64
+ end
60
65
  end
61
66
  end
62
67
  end
@@ -71,6 +71,13 @@ module GraphQL
71
71
  end
72
72
 
73
73
  class << self
74
+ # Set up a type-specific invalid null error to use when this object's non-null fields wrongly return `nil`.
75
+ # It should help with debugging and bug tracker integrations.
76
+ def inherited(child_class)
77
+ child_class.const_set(:InvalidNullError, Class.new(GraphQL::InvalidNullError))
78
+ super
79
+ end
80
+
74
81
  def implements(*new_interfaces, **options)
75
82
  new_memberships = []
76
83
  new_interfaces.each do |int|
@@ -79,14 +86,14 @@ module GraphQL
79
86
  raise "#{int} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules."
80
87
  end
81
88
 
82
- new_memberships << int.type_membership_class.new(int, self, options)
89
+ new_memberships << int.type_membership_class.new(int, self, **options)
83
90
 
84
91
  # Include the methods here,
85
92
  # `.fields` will use the inheritance chain
86
93
  # to find inherited fields
87
94
  include(int)
88
95
  elsif int.is_a?(GraphQL::InterfaceType)
89
- new_memberships << int.type_membership_class.new(int, self, options)
96
+ new_memberships << int.type_membership_class.new(int, self, **options)
90
97
  elsif int.is_a?(String) || int.is_a?(GraphQL::Schema::LateBoundType)
91
98
  if options.any?
92
99
  raise ArgumentError, "`implements(...)` doesn't support options with late-loaded types yet. Remove #{options} and open an issue to request this feature."
@@ -162,7 +169,7 @@ module GraphQL
162
169
  obj_type = GraphQL::ObjectType.new
163
170
  obj_type.name = graphql_name
164
171
  obj_type.description = description
165
- obj_type.structural_interface_type_memberships = own_interface_type_memberships
172
+ obj_type.structural_interface_type_memberships = interface_type_memberships
166
173
  obj_type.introspection = introspection
167
174
  obj_type.mutation = mutation
168
175
  obj_type.ast_node = ast_node