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