graphql 1.10.6 → 1.10.11

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/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