graphql 1.11.1 → 1.11.2
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/templates/graphql_controller.erb +11 -9
- data/lib/graphql/directive.rb +4 -0
- data/lib/graphql/execution/interpreter/runtime.rb +3 -2
- data/lib/graphql/field.rb +4 -0
- data/lib/graphql/input_object_type.rb +4 -0
- data/lib/graphql/language/nodes.rb +1 -0
- data/lib/graphql/language/visitor.rb +2 -2
- data/lib/graphql/pagination/connection.rb +6 -8
- data/lib/graphql/pagination/connections.rb +12 -0
- data/lib/graphql/schema.rb +7 -16
- data/lib/graphql/schema/argument.rb +5 -0
- data/lib/graphql/schema/enum_value.rb +1 -0
- data/lib/graphql/schema/field/connection_extension.rb +3 -0
- data/lib/graphql/schema/input_object.rb +16 -15
- data/lib/graphql/schema/loader.rb +19 -1
- data/lib/graphql/schema/member/has_arguments.rb +16 -0
- data/lib/graphql/schema/union.rb +29 -0
- data/lib/graphql/schema/warden.rb +6 -0
- data/lib/graphql/static_validation/literal_validator.rb +7 -7
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +2 -2
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -2
- data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +4 -2
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +24 -13
- data/lib/graphql/subscriptions/serialize.rb +22 -4
- data/lib/graphql/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bafdf456b47766daa370e9f8559e2f525b37d24b6f5a253742eba71759428f29
|
4
|
+
data.tar.gz: f2dc55cb9c2777104f23358a27e01328ed4b4df5334ce237e9af1cae731c3ab7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f7120485f538e60577a55b9674cb21e89785d10becfb5545e8412c284547b4c066c3c7abff15b53edca990cb49fd659eeb0df16e84f6bfb511aca89dd41d2834
|
7
|
+
data.tar.gz: a48a2c59655e72c44d711f69cd94c7649a0ada18e1dba50cabadaa8a2748b12021ed7f03a6accca78bfba4aab93de2d37cf0b5e119f3f08a12a7d9784667af15
|
@@ -5,7 +5,7 @@ class GraphqlController < ApplicationController
|
|
5
5
|
# protect_from_forgery with: :null_session
|
6
6
|
|
7
7
|
def execute
|
8
|
-
variables =
|
8
|
+
variables = prepare_variables(params[:variables])
|
9
9
|
query = params[:query]
|
10
10
|
operation_name = params[:operationName]
|
11
11
|
context = {
|
@@ -21,21 +21,23 @@ class GraphqlController < ApplicationController
|
|
21
21
|
|
22
22
|
private
|
23
23
|
|
24
|
-
# Handle form data, JSON body, or a blank value
|
25
|
-
def
|
26
|
-
case
|
24
|
+
# Handle variables in form data, JSON body, or a blank value
|
25
|
+
def prepare_variables(variables_param)
|
26
|
+
case variables_param
|
27
27
|
when String
|
28
|
-
if
|
29
|
-
|
28
|
+
if variables_param.present?
|
29
|
+
JSON.parse(variables_param) || {}
|
30
30
|
else
|
31
31
|
{}
|
32
32
|
end
|
33
|
-
when Hash
|
34
|
-
|
33
|
+
when Hash
|
34
|
+
variables_param
|
35
|
+
when ActionController::Parameters
|
36
|
+
variables_param.to_unsafe_hash # GraphQL-Ruby will validate name and type of incoming variables.
|
35
37
|
when nil
|
36
38
|
{}
|
37
39
|
else
|
38
|
-
raise ArgumentError, "Unexpected parameter: #{
|
40
|
+
raise ArgumentError, "Unexpected parameter: #{variables_param}"
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
data/lib/graphql/directive.rb
CHANGED
@@ -461,8 +461,9 @@ module GraphQL
|
|
461
461
|
end
|
462
462
|
|
463
463
|
def arguments(graphql_object, arg_owner, ast_node)
|
464
|
-
# Don't cache arguments if field extras are requested since
|
465
|
-
if arg_owner.arguments_statically_coercible? &&
|
464
|
+
# Don't cache arguments if field extras or extensions are requested since they can mutate the argument data structure
|
465
|
+
if arg_owner.arguments_statically_coercible? &&
|
466
|
+
(!arg_owner.is_a?(GraphQL::Schema::Field) || (arg_owner.extras.empty? && arg_owner.extensions.empty?))
|
466
467
|
query.arguments_for(ast_node, arg_owner)
|
467
468
|
else
|
468
469
|
# The arguments must be prepared in the context of the given object
|
data/lib/graphql/field.rb
CHANGED
@@ -89,7 +89,7 @@ module GraphQL
|
|
89
89
|
# @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
|
90
90
|
# @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
|
91
91
|
def on_abstract_node(node, parent)
|
92
|
-
if node
|
92
|
+
if node.equal?(DELETE_NODE)
|
93
93
|
# This might be passed to `super(DELETE_NODE, ...)`
|
94
94
|
# by a user hook, don't want to keep visiting in that case.
|
95
95
|
nil
|
@@ -179,7 +179,7 @@ module GraphQL
|
|
179
179
|
# The user-provided hook returned a new node.
|
180
180
|
new_parent = new_parent && new_parent.replace_child(node, new_node)
|
181
181
|
return new_node, new_parent
|
182
|
-
elsif new_node
|
182
|
+
elsif new_node.equal?(DELETE_NODE)
|
183
183
|
# The user-provided hook requested to remove this node
|
184
184
|
new_parent = new_parent && new_parent.delete_child(node)
|
185
185
|
return nil, new_parent
|
@@ -15,11 +15,6 @@ module GraphQL
|
|
15
15
|
class PaginationImplementationMissingError < GraphQL::Error
|
16
16
|
end
|
17
17
|
|
18
|
-
# @return [Class] The class to use for wrapping items as `edges { ... }`. Defaults to `Connection::Edge`
|
19
|
-
def self.edge_class
|
20
|
-
self::Edge
|
21
|
-
end
|
22
|
-
|
23
18
|
# @return [Object] A list object, from the application. This is the unpaginated value passed into the connection.
|
24
19
|
attr_reader :items
|
25
20
|
|
@@ -58,7 +53,7 @@ module GraphQL
|
|
58
53
|
# @param last [Integer, nil] Limit parameter from the client, if provided
|
59
54
|
# @param before [String, nil] A cursor for pagination, if the client provided one.
|
60
55
|
# @param max_page_size [Integer, nil] A configured value to cap the result size. Applied as `first` if neither first or last are given.
|
61
|
-
def initialize(items, parent: nil, context: nil, first: nil, after: nil, max_page_size: :not_given, last: nil, before: nil)
|
56
|
+
def initialize(items, parent: nil, context: nil, first: nil, after: nil, max_page_size: :not_given, last: nil, before: nil, edge_class: nil)
|
62
57
|
@items = items
|
63
58
|
@parent = parent
|
64
59
|
@context = context
|
@@ -66,7 +61,7 @@ module GraphQL
|
|
66
61
|
@after_value = after
|
67
62
|
@last_value = last
|
68
63
|
@before_value = before
|
69
|
-
|
64
|
+
@edge_class = edge_class || self.class::Edge
|
70
65
|
# This is only true if the object was _initialized_ with an override
|
71
66
|
# or if one is assigned later.
|
72
67
|
@has_max_page_size_override = max_page_size != :not_given
|
@@ -117,9 +112,12 @@ module GraphQL
|
|
117
112
|
|
118
113
|
# @return [Array<Edge>] {nodes}, but wrapped with Edge instances
|
119
114
|
def edges
|
120
|
-
@edges ||= nodes.map { |n|
|
115
|
+
@edges ||= nodes.map { |n| @edge_class.new(n, self) }
|
121
116
|
end
|
122
117
|
|
118
|
+
# @return [Class] A wrapper class for edges of this connection
|
119
|
+
attr_accessor :edge_class
|
120
|
+
|
123
121
|
# @return [Array<Object>] A slice of {items}, constrained by {@first_value}/{@after_value}/{@last_value}/{@before_value}
|
124
122
|
def nodes
|
125
123
|
raise PaginationImplementationMissingError, "Implement #{self.class}#nodes to paginate `@items`"
|
@@ -86,9 +86,21 @@ module GraphQL
|
|
86
86
|
after: arguments[:after],
|
87
87
|
last: arguments[:last],
|
88
88
|
before: arguments[:before],
|
89
|
+
edge_class: edge_class_for_field(field),
|
89
90
|
)
|
90
91
|
end
|
91
92
|
|
93
|
+
# use an override if there is one
|
94
|
+
# @api private
|
95
|
+
def edge_class_for_field(field)
|
96
|
+
conn_type = field.type.unwrap
|
97
|
+
conn_type_edge_type = conn_type.respond_to?(:edge_class) && conn_type.edge_class
|
98
|
+
if conn_type_edge_type && conn_type_edge_type != Relay::Edge
|
99
|
+
conn_type_edge_type
|
100
|
+
else
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
end
|
92
104
|
protected
|
93
105
|
|
94
106
|
attr_reader :wrappers
|
data/lib/graphql/schema.rb
CHANGED
@@ -127,7 +127,7 @@ module GraphQL
|
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
130
|
-
# @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered
|
130
|
+
# @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered with {#lazy_resolve}.
|
131
131
|
def lazy_method_name(obj)
|
132
132
|
lazy_methods.get(obj)
|
133
133
|
end
|
@@ -1534,9 +1534,9 @@ module GraphQL
|
|
1534
1534
|
|
1535
1535
|
# Add several directives at once
|
1536
1536
|
# @param new_directives [Class]
|
1537
|
-
def directives(new_directives
|
1538
|
-
if new_directives
|
1539
|
-
new_directives.each { |d| directive(d) }
|
1537
|
+
def directives(*new_directives)
|
1538
|
+
if new_directives.any?
|
1539
|
+
new_directives.flatten.each { |d| directive(d) }
|
1540
1540
|
end
|
1541
1541
|
|
1542
1542
|
find_inherited_value(:directives, default_directives).merge(own_directives)
|
@@ -1550,11 +1550,11 @@ module GraphQL
|
|
1550
1550
|
end
|
1551
1551
|
|
1552
1552
|
def default_directives
|
1553
|
-
{
|
1553
|
+
@default_directives ||= {
|
1554
1554
|
"include" => GraphQL::Schema::Directive::Include,
|
1555
1555
|
"skip" => GraphQL::Schema::Directive::Skip,
|
1556
1556
|
"deprecated" => GraphQL::Schema::Directive::Deprecated,
|
1557
|
-
}
|
1557
|
+
}.freeze
|
1558
1558
|
end
|
1559
1559
|
|
1560
1560
|
def tracer(new_tracer)
|
@@ -1783,16 +1783,7 @@ module GraphQL
|
|
1783
1783
|
if owner.kind.union?
|
1784
1784
|
# It's a union with possible_types
|
1785
1785
|
# Replace the item by class name
|
1786
|
-
owner.
|
1787
|
-
possible_type = tm.object_type
|
1788
|
-
if possible_type.is_a?(String) && (possible_type == type.name)
|
1789
|
-
# This is a match of Ruby class names, not graphql names,
|
1790
|
-
# since strings are used to refer to constants.
|
1791
|
-
tm.object_type = type
|
1792
|
-
elsif possible_type.is_a?(LateBoundType) && possible_type.graphql_name == type.graphql_name
|
1793
|
-
tm.object_type = type
|
1794
|
-
end
|
1795
|
-
}
|
1786
|
+
owner.assign_type_membership_object_type(type)
|
1796
1787
|
own_possible_types[owner.graphql_name] = owner.possible_types
|
1797
1788
|
elsif type.kind.interface? && owner.kind.object?
|
1798
1789
|
new_interfaces = []
|
@@ -61,6 +61,11 @@ module GraphQL
|
|
61
61
|
@from_resolver = from_resolver
|
62
62
|
@method_access = method_access
|
63
63
|
|
64
|
+
if !@null && default_value?
|
65
|
+
raise ArgumentError, "Argument '#{@name}' has conflicting params, " \
|
66
|
+
"either use `required: false` or remove `default_value:`."
|
67
|
+
end
|
68
|
+
|
64
69
|
if definition_block
|
65
70
|
if definition_block.arity == 1
|
66
71
|
instance_exec(self, &definition_block)
|
@@ -41,6 +41,7 @@ module GraphQL
|
|
41
41
|
|
42
42
|
def initialize(graphql_name, desc = nil, owner:, ast_node: nil, description: nil, value: nil, deprecation_reason: nil, &block)
|
43
43
|
@graphql_name = graphql_name.to_s
|
44
|
+
GraphQL::NameValidator.validate!(@graphql_name)
|
44
45
|
@description = desc || description
|
45
46
|
@value = value.nil? ? @graphql_name : value
|
46
47
|
@deprecation_reason = deprecation_reason
|
@@ -39,6 +39,9 @@ module GraphQL
|
|
39
39
|
if field.has_max_page_size? && !value.has_max_page_size_override?
|
40
40
|
value.max_page_size = field.max_page_size
|
41
41
|
end
|
42
|
+
if (custom_t = context.schema.connections.edge_class_for_field(@field))
|
43
|
+
value.edge_class = custom_t
|
44
|
+
end
|
42
45
|
value
|
43
46
|
elsif context.schema.new_connections?
|
44
47
|
wrappers = context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
|
@@ -166,10 +166,7 @@ module GraphQL
|
|
166
166
|
return result
|
167
167
|
end
|
168
168
|
|
169
|
-
|
170
|
-
# using these methods to make sure that the object will
|
171
|
-
# behave like a hash below, when we call `each` on it.
|
172
|
-
begin
|
169
|
+
input = begin
|
173
170
|
input.to_h
|
174
171
|
rescue
|
175
172
|
begin
|
@@ -182,21 +179,25 @@ module GraphQL
|
|
182
179
|
end
|
183
180
|
end
|
184
181
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
if visible_arguments_map[name].nil?
|
190
|
-
result.add_problem("Field is not defined on #{self.graphql_name}", [name])
|
182
|
+
# Inject missing required arguments
|
183
|
+
missing_required_inputs = self.arguments.reduce({}) do |m, (argument_name, argument)|
|
184
|
+
if !input.key?(argument_name) && argument.type.non_null? && warden.get_argument(self, argument_name)
|
185
|
+
m[argument_name] = nil
|
191
186
|
end
|
187
|
+
|
188
|
+
m
|
192
189
|
end
|
193
190
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
result.
|
191
|
+
input.merge(missing_required_inputs).each do |argument_name, value|
|
192
|
+
argument = warden.get_argument(self, argument_name)
|
193
|
+
# Items in the input that are unexpected
|
194
|
+
unless argument
|
195
|
+
result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
|
196
|
+
next
|
199
197
|
end
|
198
|
+
# Items in the input that are expected, but have invalid values
|
199
|
+
argument_result = argument.type.validate_input(value, ctx)
|
200
|
+
result.merge_result!(argument_name, argument_result) unless argument_result.valid?
|
200
201
|
end
|
201
202
|
|
202
203
|
result
|
@@ -25,8 +25,15 @@ module GraphQL
|
|
25
25
|
types[type["name"]] = type_object
|
26
26
|
end
|
27
27
|
|
28
|
+
directives = []
|
29
|
+
schema.fetch("directives", []).each do |directive|
|
30
|
+
next if GraphQL::Schema.default_directives.include?(directive.fetch("name"))
|
31
|
+
directives << define_directive(directive, type_resolver)
|
32
|
+
end
|
33
|
+
|
28
34
|
Class.new(GraphQL::Schema) do
|
29
35
|
orphan_types(types.values)
|
36
|
+
directives(directives)
|
30
37
|
|
31
38
|
def self.resolve_type(*)
|
32
39
|
raise(GraphQL::RequiredImplementationMissingError, "This schema was loaded from string, so it can't resolve types for objects")
|
@@ -98,7 +105,7 @@ module GraphQL
|
|
98
105
|
value(
|
99
106
|
enum_value["name"],
|
100
107
|
description: enum_value["description"],
|
101
|
-
deprecation_reason: enum_value["
|
108
|
+
deprecation_reason: enum_value["deprecationReason"],
|
102
109
|
)
|
103
110
|
end
|
104
111
|
end
|
@@ -147,6 +154,16 @@ module GraphQL
|
|
147
154
|
end
|
148
155
|
end
|
149
156
|
|
157
|
+
def define_directive(directive, type_resolver)
|
158
|
+
loader = self
|
159
|
+
Class.new(GraphQL::Schema::Directive) do
|
160
|
+
graphql_name(directive["name"])
|
161
|
+
description(directive["description"])
|
162
|
+
locations(*directive["locations"].map(&:to_sym))
|
163
|
+
loader.build_arguments(self, directive["args"], type_resolver)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
150
167
|
public
|
151
168
|
|
152
169
|
def build_fields(type_defn, fields, type_resolver)
|
@@ -156,6 +173,7 @@ module GraphQL
|
|
156
173
|
field_hash["name"],
|
157
174
|
type: type_resolver.call(field_hash["type"]),
|
158
175
|
description: field_hash["description"],
|
176
|
+
deprecation_reason: field_hash["deprecationReason"],
|
159
177
|
null: true,
|
160
178
|
camelize: false,
|
161
179
|
) do
|
@@ -58,6 +58,22 @@ module GraphQL
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
+
# @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
|
62
|
+
def get_argument(argument_name)
|
63
|
+
a = own_arguments[argument_name]
|
64
|
+
|
65
|
+
if a || !self.is_a?(Class)
|
66
|
+
a
|
67
|
+
else
|
68
|
+
for ancestor in ancestors
|
69
|
+
if ancestor.respond_to?(:own_arguments) && a = ancestor.own_arguments[argument_name]
|
70
|
+
return a
|
71
|
+
end
|
72
|
+
end
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
61
77
|
# @param new_arg_class [Class] A class to use for building argument definitions
|
62
78
|
def argument_class(new_arg_class = nil)
|
63
79
|
self.class.argument_class(new_arg_class)
|
data/lib/graphql/schema/union.rb
CHANGED
@@ -14,6 +14,7 @@ module GraphQL
|
|
14
14
|
def possible_types(*types, context: GraphQL::Query::NullContext, **options)
|
15
15
|
if types.any?
|
16
16
|
types.each do |t|
|
17
|
+
assert_valid_union_member(t)
|
17
18
|
type_memberships << type_membership_class.new(self, t, **options)
|
18
19
|
end
|
19
20
|
else
|
@@ -55,6 +56,34 @@ module GraphQL
|
|
55
56
|
def type_memberships
|
56
57
|
@type_memberships ||= []
|
57
58
|
end
|
59
|
+
|
60
|
+
# Update a type membership whose `.object_type` is a string or late-bound type
|
61
|
+
# so that the type membership's `.object_type` is the given `object_type`.
|
62
|
+
# (This is used for updating the union after the schema as lazily loaded the union member.)
|
63
|
+
# @api private
|
64
|
+
def assign_type_membership_object_type(object_type)
|
65
|
+
assert_valid_union_member(object_type)
|
66
|
+
type_memberships.each { |tm|
|
67
|
+
possible_type = tm.object_type
|
68
|
+
if possible_type.is_a?(String) && (possible_type == object_type.name)
|
69
|
+
# This is a match of Ruby class names, not graphql names,
|
70
|
+
# since strings are used to refer to constants.
|
71
|
+
tm.object_type = object_type
|
72
|
+
elsif possible_type.is_a?(LateBoundType) && possible_type.graphql_name == object_type.graphql_name
|
73
|
+
tm.object_type = object_type
|
74
|
+
end
|
75
|
+
}
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def assert_valid_union_member(type_defn)
|
82
|
+
if type_defn.is_a?(Module) && !type_defn.is_a?(Class)
|
83
|
+
# it's an interface type, defined as a module
|
84
|
+
raise ArgumentError, "Union possible_types can only be object types (not interface types), remove #{type_defn.graphql_name} (#{type_defn.inspect})"
|
85
|
+
end
|
86
|
+
end
|
58
87
|
end
|
59
88
|
end
|
60
89
|
end
|
@@ -105,6 +105,12 @@ module GraphQL
|
|
105
105
|
@visible_parent_fields[parent_type][field_name]
|
106
106
|
end
|
107
107
|
|
108
|
+
# @return [GraphQL::Argument, nil] The argument named `argument_name` on `parent_type`, if it exists and is visible
|
109
|
+
def get_argument(parent_type, argument_name)
|
110
|
+
argument = parent_type.get_argument(argument_name)
|
111
|
+
return argument if argument && visible_argument?(argument)
|
112
|
+
end
|
113
|
+
|
108
114
|
# @return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`
|
109
115
|
def possible_types(type_defn)
|
110
116
|
@visible_possible_types ||= read_through { |type_defn|
|
@@ -95,16 +95,17 @@ module GraphQL
|
|
95
95
|
def required_input_fields_are_present(type, ast_node)
|
96
96
|
# TODO - would be nice to use these to create an error message so the caller knows
|
97
97
|
# that required fields are missing
|
98
|
-
required_field_names =
|
99
|
-
.select { |
|
98
|
+
required_field_names = type.arguments.each_value
|
99
|
+
.select { |argument| argument.type.kind.non_null? && @warden.get_argument(type, argument.name) }
|
100
100
|
.map(&:name)
|
101
|
+
|
101
102
|
present_field_names = ast_node.arguments.map(&:name)
|
102
103
|
missing_required_field_names = required_field_names - present_field_names
|
103
104
|
if @context.schema.error_bubbling
|
104
105
|
missing_required_field_names.empty? ? @valid_response : @invalid_response
|
105
106
|
else
|
106
107
|
results = missing_required_field_names.map do |name|
|
107
|
-
arg_type = @warden.
|
108
|
+
arg_type = @warden.get_argument(type, name).type
|
108
109
|
recursively_validate(GraphQL::Language::Nodes::NullValue.new(name: name), arg_type)
|
109
110
|
end
|
110
111
|
merge_results(results)
|
@@ -112,13 +113,12 @@ module GraphQL
|
|
112
113
|
end
|
113
114
|
|
114
115
|
def present_input_field_values_are_valid(type, ast_node)
|
115
|
-
field_map = @warden.arguments(type).reduce({}) { |m, f| m[f.name] = f; m}
|
116
116
|
results = ast_node.arguments.map do |value|
|
117
|
-
field =
|
117
|
+
field = @warden.get_argument(type, value.name)
|
118
118
|
# we want to call validate on an argument even if it's an invalid one
|
119
119
|
# so that our raise exception is on it instead of the entire InputObject
|
120
|
-
|
121
|
-
recursively_validate(value.value,
|
120
|
+
field_type = field && field.type
|
121
|
+
recursively_validate(value.value, field_type)
|
122
122
|
end
|
123
123
|
merge_results(results)
|
124
124
|
end
|
@@ -5,7 +5,7 @@ module GraphQL
|
|
5
5
|
def on_argument(node, parent)
|
6
6
|
parent_defn = parent_definition(parent)
|
7
7
|
|
8
|
-
if parent_defn && context.warden.
|
8
|
+
if parent_defn && context.warden.get_argument(parent_defn, node.name)
|
9
9
|
super
|
10
10
|
elsif parent_defn
|
11
11
|
kind_of_node = node_type(parent)
|
@@ -17,8 +17,8 @@ module GraphQL
|
|
17
17
|
|
18
18
|
def assert_required_args(ast_node, defn)
|
19
19
|
present_argument_names = ast_node.arguments.map(&:name)
|
20
|
-
required_argument_names =
|
21
|
-
.select { |a| a.type.kind.non_null? && !a.default_value? }
|
20
|
+
required_argument_names = defn.arguments.each_value
|
21
|
+
.select { |a| a.type.kind.non_null? && !a.default_value? && context.warden.get_argument(defn, a.name) }
|
22
22
|
.map(&:name)
|
23
23
|
|
24
24
|
missing_names = required_argument_names - present_argument_names
|
@@ -26,8 +26,7 @@ module GraphQL
|
|
26
26
|
context.field_definition
|
27
27
|
end
|
28
28
|
|
29
|
-
parent_type = context.warden.
|
30
|
-
.find{|f| f.name == parent_name(parent, defn) }
|
29
|
+
parent_type = context.warden.get_argument(defn, parent_name(parent, defn))
|
31
30
|
parent_type ? parent_type.type.unwrap : nil
|
32
31
|
end
|
33
32
|
|
@@ -126,8 +126,9 @@ module GraphQL
|
|
126
126
|
node_variables
|
127
127
|
.select { |name, usage| usage.declared? && !usage.used? }
|
128
128
|
.each { |var_name, usage|
|
129
|
+
declared_by_error_name = usage.declared_by.name || "anonymous #{usage.declared_by.operation_type}"
|
129
130
|
add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new(
|
130
|
-
"Variable $#{var_name} is declared by #{
|
131
|
+
"Variable $#{var_name} is declared by #{declared_by_error_name} but not used",
|
131
132
|
nodes: usage.declared_by,
|
132
133
|
path: usage.path,
|
133
134
|
name: var_name,
|
@@ -139,8 +140,9 @@ module GraphQL
|
|
139
140
|
node_variables
|
140
141
|
.select { |name, usage| usage.used? && !usage.declared? }
|
141
142
|
.each { |var_name, usage|
|
143
|
+
used_by_error_name = usage.used_by.name || "anonymous #{usage.used_by.operation_type}"
|
142
144
|
add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new(
|
143
|
-
"Variable $#{var_name} is used by #{
|
145
|
+
"Variable $#{var_name} is used by #{used_by_error_name} but not declared",
|
144
146
|
nodes: usage.ast_node,
|
145
147
|
path: usage.path,
|
146
148
|
name: var_name,
|
@@ -165,24 +165,35 @@ module GraphQL
|
|
165
165
|
# Return the query from "storage" (in memory)
|
166
166
|
def read_subscription(subscription_id)
|
167
167
|
query = @subscriptions[subscription_id]
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
168
|
+
if query.nil?
|
169
|
+
# This can happen when a subscription is triggered from an unsubscribed channel,
|
170
|
+
# see https://github.com/rmosolgo/graphql-ruby/issues/2478.
|
171
|
+
# (This `nil` is handled by `#execute_update`)
|
172
|
+
nil
|
173
|
+
else
|
174
|
+
{
|
175
|
+
query_string: query.query_string,
|
176
|
+
variables: query.provided_variables,
|
177
|
+
context: query.context.to_h,
|
178
|
+
operation_name: query.operation_name,
|
179
|
+
}
|
180
|
+
end
|
174
181
|
end
|
175
182
|
|
176
183
|
# The channel was closed, forget about it.
|
177
184
|
def delete_subscription(subscription_id)
|
178
185
|
query = @subscriptions.delete(subscription_id)
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
ev_by_fingerprint
|
186
|
+
# This can be `nil` when `.trigger` happens inside an unsubscribed ActionCable channel,
|
187
|
+
# see https://github.com/rmosolgo/graphql-ruby/issues/2478
|
188
|
+
if query
|
189
|
+
events = query.context.namespace(:subscriptions)[:events]
|
190
|
+
events.each do |event|
|
191
|
+
ev_by_fingerprint = @events[event.topic]
|
192
|
+
ev_for_fingerprint = ev_by_fingerprint[event.fingerprint]
|
193
|
+
ev_for_fingerprint.delete(event)
|
194
|
+
if ev_for_fingerprint.empty?
|
195
|
+
ev_by_fingerprint.delete(event.fingerprint)
|
196
|
+
end
|
186
197
|
end
|
187
198
|
end
|
188
199
|
end
|
@@ -9,6 +9,9 @@ module GraphQL
|
|
9
9
|
GLOBALID_KEY = "__gid__"
|
10
10
|
SYMBOL_KEY = "__sym__"
|
11
11
|
SYMBOL_KEYS_KEY = "__sym_keys__"
|
12
|
+
TIMESTAMP_KEY = "__timestamp__"
|
13
|
+
TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%N%Z" # eg '2020-01-01 23:59:59.123456789+05:00'
|
14
|
+
OPEN_STRUCT_KEY = "__ostruct__"
|
12
15
|
|
13
16
|
module_function
|
14
17
|
|
@@ -55,10 +58,20 @@ module GraphQL
|
|
55
58
|
if value.is_a?(Array)
|
56
59
|
value.map{|item| load_value(item)}
|
57
60
|
elsif value.is_a?(Hash)
|
58
|
-
if value.size == 1
|
59
|
-
|
60
|
-
|
61
|
-
|
61
|
+
if value.size == 1
|
62
|
+
case value.keys.first # there's only 1 key
|
63
|
+
when GLOBALID_KEY
|
64
|
+
GlobalID::Locator.locate(value[GLOBALID_KEY])
|
65
|
+
when SYMBOL_KEY
|
66
|
+
value[SYMBOL_KEY].to_sym
|
67
|
+
when TIMESTAMP_KEY
|
68
|
+
timestamp_class_name, timestamp_s = value[TIMESTAMP_KEY]
|
69
|
+
timestamp_class = Object.const_get(timestamp_class_name)
|
70
|
+
timestamp_class.strptime(timestamp_s, TIMESTAMP_FORMAT)
|
71
|
+
when OPEN_STRUCT_KEY
|
72
|
+
ostruct_values = load_value(value[OPEN_STRUCT_KEY])
|
73
|
+
OpenStruct.new(ostruct_values)
|
74
|
+
end
|
62
75
|
else
|
63
76
|
loaded_h = {}
|
64
77
|
sym_keys = value.fetch(SYMBOL_KEYS_KEY, [])
|
@@ -101,6 +114,11 @@ module GraphQL
|
|
101
114
|
{ SYMBOL_KEY => obj.to_s }
|
102
115
|
elsif obj.respond_to?(:to_gid_param)
|
103
116
|
{GLOBALID_KEY => obj.to_gid_param}
|
117
|
+
elsif obj.is_a?(Date) || obj.is_a?(Time)
|
118
|
+
# DateTime extends Date; for TimeWithZone, call `.utc` first.
|
119
|
+
{ TIMESTAMP_KEY => [obj.class.name, obj.strftime(TIMESTAMP_FORMAT)] }
|
120
|
+
elsif obj.is_a?(OpenStruct)
|
121
|
+
{ OPEN_STRUCT_KEY => dump_value(obj.to_h) }
|
104
122
|
else
|
105
123
|
obj
|
106
124
|
end
|
data/lib/graphql/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.11.
|
4
|
+
version: 1.11.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: benchmark-ips
|