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.
- checksums.yaml +4 -4
- data/lib/generators/graphql/object_generator.rb +50 -8
- data/lib/graphql.rb +4 -4
- data/lib/graphql/analysis/ast/query_complexity.rb +1 -1
- data/lib/graphql/compatibility/execution_specification/specification_schema.rb +2 -2
- data/lib/graphql/execution/instrumentation.rb +1 -1
- data/lib/graphql/execution/interpreter.rb +2 -0
- data/lib/graphql/execution/interpreter/argument_value.rb +28 -0
- data/lib/graphql/execution/interpreter/arguments.rb +36 -0
- data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -7
- data/lib/graphql/execution/interpreter/runtime.rb +47 -38
- data/lib/graphql/execution/lookahead.rb +3 -1
- data/lib/graphql/internal_representation/scope.rb +2 -2
- data/lib/graphql/internal_representation/visit.rb +2 -2
- data/lib/graphql/language/document_from_schema_definition.rb +42 -23
- data/lib/graphql/object_type.rb +1 -1
- data/lib/graphql/relay/base_connection.rb +0 -2
- data/lib/graphql/schema.rb +28 -13
- data/lib/graphql/schema/argument.rb +6 -0
- data/lib/graphql/schema/base_64_encoder.rb +2 -0
- data/lib/graphql/schema/enum.rb +9 -1
- data/lib/graphql/schema/field.rb +30 -21
- data/lib/graphql/schema/input_object.rb +9 -6
- data/lib/graphql/schema/interface.rb +5 -0
- data/lib/graphql/schema/list.rb +7 -1
- data/lib/graphql/schema/loader.rb +110 -103
- data/lib/graphql/schema/member.rb +1 -0
- data/lib/graphql/schema/member/has_arguments.rb +33 -13
- data/lib/graphql/schema/member/has_fields.rb +1 -1
- data/lib/graphql/schema/member/has_unresolved_type_error.rb +15 -0
- data/lib/graphql/schema/non_null.rb +5 -0
- data/lib/graphql/schema/object.rb +10 -3
- data/lib/graphql/schema/printer.rb +0 -14
- data/lib/graphql/schema/resolver.rb +1 -1
- data/lib/graphql/schema/union.rb +6 -0
- data/lib/graphql/schema/warden.rb +7 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
- data/lib/graphql/subscriptions/subscription_root.rb +10 -2
- data/lib/graphql/tracing.rb +5 -4
- data/lib/graphql/tracing/new_relic_tracing.rb +1 -12
- data/lib/graphql/tracing/platform_tracing.rb +14 -0
- data/lib/graphql/tracing/scout_tracing.rb +11 -0
- data/lib/graphql/types/iso_8601_date.rb +3 -3
- data/lib/graphql/types/iso_8601_date_time.rb +18 -8
- data/lib/graphql/version.rb +1 -1
- metadata +6 -3
data/lib/graphql/schema/list.rb
CHANGED
@@ -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 [
|
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) {
|
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
|
-
|
28
|
-
|
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
|
-
|
34
|
-
|
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
|
-
|
37
|
-
|
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
|
-
|
56
|
+
GraphQL::Schema::LateBoundType.new(type_name)
|
52
57
|
else
|
53
|
-
type
|
58
|
+
type
|
54
59
|
end
|
55
60
|
when "LIST"
|
56
|
-
|
61
|
+
Schema::List.new(resolve_type(types, type.fetch("ofType")))
|
57
62
|
when "NON_NULL"
|
58
|
-
|
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
|
-
|
89
|
-
|
90
|
-
description
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
description:
|
95
|
-
deprecation_reason:
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
109
|
-
|
110
|
-
description
|
111
|
-
|
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::
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
-
|
131
|
+
if (builtin = GraphQL::Schema::BUILT_IN_TYPES[type_name])
|
132
|
+
builtin
|
170
133
|
else
|
171
|
-
|
172
|
-
|
173
|
-
description
|
174
|
-
|
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
|
-
|
179
|
-
|
180
|
-
description
|
181
|
-
possible_types
|
182
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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[
|
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
|
-
|
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
|
@@ -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 =
|
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
|