graphql 2.0.26 → 2.1.0
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/relay.rb +18 -1
- data/lib/graphql/execution/interpreter/runtime.rb +23 -21
- data/lib/graphql/language/document_from_schema_definition.rb +6 -16
- data/lib/graphql/language/lexer.rb +86 -56
- data/lib/graphql/language/nodes.rb +1 -1
- data/lib/graphql/language/parser.rb +706 -691
- data/lib/graphql/language/parser.y +1 -0
- data/lib/graphql/language/printer.rb +233 -145
- data/lib/graphql/language/sanitized_printer.rb +14 -21
- data/lib/graphql/language/visitor.rb +18 -81
- data/lib/graphql/pagination/connection.rb +23 -1
- data/lib/graphql/query.rb +2 -19
- data/lib/graphql/rake_task.rb +3 -12
- data/lib/graphql/schema/addition.rb +6 -0
- data/lib/graphql/schema/field/scope_extension.rb +7 -1
- data/lib/graphql/schema/field.rb +1 -1
- data/lib/graphql/schema/member/scoped.rb +19 -0
- data/lib/graphql/schema/object.rb +8 -0
- data/lib/graphql/schema/printer.rb +8 -7
- data/lib/graphql/schema/resolver.rb +10 -8
- data/lib/graphql/schema/subscription.rb +11 -4
- data/lib/graphql/schema/validator.rb +1 -1
- data/lib/graphql/schema/warden.rb +3 -34
- data/lib/graphql/schema.rb +4 -20
- data/lib/graphql/static_validation/validation_context.rb +0 -3
- data/lib/graphql/static_validation.rb +0 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
- data/lib/graphql/subscriptions.rb +11 -6
- data/lib/graphql/types/relay/connection_behaviors.rb +19 -2
- data/lib/graphql/types/relay/edge_behaviors.rb +7 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +0 -1
- metadata +2 -4
- data/lib/graphql/filter.rb +0 -59
- data/lib/graphql/static_validation/type_stack.rb +0 -216
@@ -31,11 +31,6 @@ module GraphQL
|
|
31
31
|
# visitor.count
|
32
32
|
# # => 3
|
33
33
|
class Visitor
|
34
|
-
# If any hook returns this value, the {Visitor} stops visiting this
|
35
|
-
# node right away
|
36
|
-
# @deprecated Use `super` to continue the visit; or don't call it to halt.
|
37
|
-
SKIP = :_skip
|
38
|
-
|
39
34
|
class DeleteNode; end
|
40
35
|
|
41
36
|
# When this is returned from a visitor method,
|
@@ -44,25 +39,13 @@ module GraphQL
|
|
44
39
|
|
45
40
|
def initialize(document)
|
46
41
|
@document = document
|
47
|
-
@visitors = {}
|
48
42
|
@result = nil
|
49
43
|
end
|
50
44
|
|
51
45
|
# @return [GraphQL::Language::Nodes::Document] The document with any modifications applied
|
52
46
|
attr_reader :result
|
53
47
|
|
54
|
-
#
|
55
|
-
# @param node_class [Class] The node class that you want to listen to
|
56
|
-
# @return [NodeVisitor]
|
57
|
-
#
|
58
|
-
# @example Run a hook whenever you enter a new Field
|
59
|
-
# visitor[GraphQL::Language::Nodes::Field] << ->(node, parent) { p "Here's a field" }
|
60
|
-
# @deprecated see `on_` methods, like {#on_field}
|
61
|
-
def [](node_class)
|
62
|
-
@visitors[node_class] ||= NodeVisitor.new
|
63
|
-
end
|
64
|
-
|
65
|
-
# Visit `document` and all children, applying hooks as you go
|
48
|
+
# Visit `document` and all children
|
66
49
|
# @return [void]
|
67
50
|
def visit
|
68
51
|
# `@document` may be any kind of node:
|
@@ -88,7 +71,6 @@ module GraphQL
|
|
88
71
|
# To customize this hook, override one of its make_visit_methods (or the base method?)
|
89
72
|
# in your subclasses.
|
90
73
|
#
|
91
|
-
# For compatibility, it calls hook procs, too.
|
92
74
|
# @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited
|
93
75
|
# @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node.
|
94
76
|
# @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`.
|
@@ -98,29 +80,24 @@ module GraphQL
|
|
98
80
|
# by a user hook, don't want to keep visiting in that case.
|
99
81
|
[node, parent]
|
100
82
|
else
|
101
|
-
# Run hooks if there are any
|
102
83
|
new_node = node
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
end
|
121
|
-
}
|
122
|
-
end
|
123
|
-
end_visit(new_node, parent) unless no_hooks
|
84
|
+
#{
|
85
|
+
if method_defined?(child_visit_method)
|
86
|
+
"new_node = #{child_visit_method}(new_node)"
|
87
|
+
elsif children_of_type
|
88
|
+
children_of_type.map do |child_accessor, child_class|
|
89
|
+
"node.#{child_accessor}.each do |child_node|
|
90
|
+
new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node)
|
91
|
+
# Reassign `node` in case the child hook makes a modification
|
92
|
+
if new_child_and_node.is_a?(Array)
|
93
|
+
new_node = new_child_and_node[1]
|
94
|
+
end
|
95
|
+
end"
|
96
|
+
end.join("\n")
|
97
|
+
else
|
98
|
+
""
|
99
|
+
end
|
100
|
+
}
|
124
101
|
|
125
102
|
if new_node.equal?(node)
|
126
103
|
[node, parent]
|
@@ -305,46 +282,6 @@ module GraphQL
|
|
305
282
|
new_node_and_new_parent
|
306
283
|
end
|
307
284
|
end
|
308
|
-
|
309
|
-
def begin_visit(node, parent)
|
310
|
-
node_visitor = self[node.class]
|
311
|
-
self.class.apply_hooks(node_visitor.enter, node, parent)
|
312
|
-
end
|
313
|
-
|
314
|
-
# Should global `leave` visitors come first or last?
|
315
|
-
def end_visit(node, parent)
|
316
|
-
node_visitor = self[node.class]
|
317
|
-
self.class.apply_hooks(node_visitor.leave, node, parent)
|
318
|
-
end
|
319
|
-
|
320
|
-
# If one of the visitors returns SKIP, stop visiting this node
|
321
|
-
def self.apply_hooks(hooks, node, parent)
|
322
|
-
hooks.each do |proc|
|
323
|
-
return false if proc.call(node, parent) == SKIP
|
324
|
-
end
|
325
|
-
true
|
326
|
-
end
|
327
|
-
|
328
|
-
# Collect `enter` and `leave` hooks for classes in {GraphQL::Language::Nodes}
|
329
|
-
#
|
330
|
-
# Access {NodeVisitor}s via {GraphQL::Language::Visitor#[]}
|
331
|
-
class NodeVisitor
|
332
|
-
# @return [Array<Proc>] Hooks to call when entering a node of this type
|
333
|
-
attr_reader :enter
|
334
|
-
# @return [Array<Proc>] Hooks to call when leaving a node of this type
|
335
|
-
attr_reader :leave
|
336
|
-
|
337
|
-
def initialize
|
338
|
-
@enter = []
|
339
|
-
@leave = []
|
340
|
-
end
|
341
|
-
|
342
|
-
# Shorthand to add a hook to the {#enter} array
|
343
|
-
# @param hook [Proc] A hook to add
|
344
|
-
def <<(hook)
|
345
|
-
enter << hook
|
346
|
-
end
|
347
|
-
end
|
348
285
|
end
|
349
286
|
end
|
350
287
|
end
|
@@ -19,7 +19,14 @@ module GraphQL
|
|
19
19
|
attr_reader :items
|
20
20
|
|
21
21
|
# @return [GraphQL::Query::Context]
|
22
|
-
|
22
|
+
attr_reader :context
|
23
|
+
|
24
|
+
def context=(new_ctx)
|
25
|
+
current_runtime_state = Thread.current[:__graphql_runtime_info]
|
26
|
+
query_runtime_state = current_runtime_state[new_ctx.query]
|
27
|
+
@was_authorized_by_scope_items = query_runtime_state.was_authorized_by_scope_items
|
28
|
+
@context = new_ctx
|
29
|
+
end
|
23
30
|
|
24
31
|
# @return [Object] the object this collection belongs to
|
25
32
|
attr_accessor :parent
|
@@ -83,6 +90,17 @@ module GraphQL
|
|
83
90
|
else
|
84
91
|
default_page_size
|
85
92
|
end
|
93
|
+
@was_authorized_by_scope_items = if @context
|
94
|
+
current_runtime_state = Thread.current[:__graphql_runtime_info]
|
95
|
+
query_runtime_state = current_runtime_state[@context.query]
|
96
|
+
query_runtime_state.was_authorized_by_scope_items
|
97
|
+
else
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def was_authorized_by_scope_items?
|
103
|
+
@was_authorized_by_scope_items
|
86
104
|
end
|
87
105
|
|
88
106
|
def max_page_size=(new_value)
|
@@ -247,6 +265,10 @@ module GraphQL
|
|
247
265
|
def cursor
|
248
266
|
@cursor ||= @connection.cursor_for(@node)
|
249
267
|
end
|
268
|
+
|
269
|
+
def was_authorized_by_scope_items?
|
270
|
+
@connection.was_authorized_by_scope_items?
|
271
|
+
end
|
250
272
|
end
|
251
273
|
end
|
252
274
|
end
|
data/lib/graphql/query.rb
CHANGED
@@ -95,15 +95,10 @@ module GraphQL
|
|
95
95
|
# @param root_value [Object] the object used to resolve fields on the root type
|
96
96
|
# @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value)
|
97
97
|
# @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
|
98
|
-
|
99
|
-
# @param only [<#call(schema_member, context)>] If provided, objects will be hidden from the schema when `.call(schema_member, context)` returns false
|
100
|
-
def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, except: nil, only: nil, warden: nil)
|
98
|
+
def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, validate: true, static_validator: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil)
|
101
99
|
# Even if `variables: nil` is passed, use an empty hash for simpler logic
|
102
100
|
variables ||= {}
|
103
101
|
@schema = schema
|
104
|
-
if only || except
|
105
|
-
merge_filters(except: except, only: only)
|
106
|
-
end
|
107
102
|
@context = schema.context_class.new(query: self, object: root_value, values: context)
|
108
103
|
@warden = warden
|
109
104
|
@subscription_topic = subscription_topic
|
@@ -129,7 +124,6 @@ module GraphQL
|
|
129
124
|
raise ArgumentError, "context[:tracers] are not supported without `trace_with(GraphQL::Tracing::CallLegacyTracers)` in the schema configuration, please add it."
|
130
125
|
end
|
131
126
|
|
132
|
-
|
133
127
|
@analysis_errors = []
|
134
128
|
if variables.is_a?(String)
|
135
129
|
raise ArgumentError, "Query variables should be a Hash, not a String. Try JSON.parse to prepare variables."
|
@@ -354,17 +348,6 @@ module GraphQL
|
|
354
348
|
with_prepared_ast { @query }
|
355
349
|
end
|
356
350
|
|
357
|
-
# @return [void]
|
358
|
-
def merge_filters(only: nil, except: nil)
|
359
|
-
if @prepared_ast
|
360
|
-
raise "Can't add filters after preparing the query"
|
361
|
-
else
|
362
|
-
@filter ||= @schema.default_filter
|
363
|
-
@filter = @filter.merge(only: only, except: except)
|
364
|
-
end
|
365
|
-
nil
|
366
|
-
end
|
367
|
-
|
368
351
|
def subscription?
|
369
352
|
with_prepared_ast { @subscription }
|
370
353
|
end
|
@@ -400,7 +383,7 @@ module GraphQL
|
|
400
383
|
|
401
384
|
def prepare_ast
|
402
385
|
@prepared_ast = true
|
403
|
-
@warden ||= @schema.warden_class.new(
|
386
|
+
@warden ||= @schema.warden_class.new(schema: @schema, context: @context)
|
404
387
|
parse_error = nil
|
405
388
|
@document ||= begin
|
406
389
|
if query_string
|
data/lib/graphql/rake_task.rb
CHANGED
@@ -9,8 +9,7 @@ module GraphQL
|
|
9
9
|
# By default, schemas are looked up by name as constants using `schema_name:`.
|
10
10
|
# You can provide a `load_schema` function to return your schema another way.
|
11
11
|
#
|
12
|
-
# `load_context
|
13
|
-
# you can keep an eye on how filters affect your schema.
|
12
|
+
# Use `load_context:` and `visible?` to dump schemas under certain visibility constraints.
|
14
13
|
#
|
15
14
|
# @example Dump a Schema to .graphql + .json files
|
16
15
|
# require "graphql/rake_task"
|
@@ -36,8 +35,6 @@ module GraphQL
|
|
36
35
|
schema_name: nil,
|
37
36
|
load_schema: ->(task) { Object.const_get(task.schema_name) },
|
38
37
|
load_context: ->(task) { {} },
|
39
|
-
only: nil,
|
40
|
-
except: nil,
|
41
38
|
directory: ".",
|
42
39
|
idl_outfile: "schema.graphql",
|
43
40
|
json_outfile: "schema.json",
|
@@ -68,12 +65,6 @@ module GraphQL
|
|
68
65
|
# @return [<#call(task)>] A callable for loading the query context
|
69
66
|
attr_accessor :load_context
|
70
67
|
|
71
|
-
# @return [<#call(member, ctx)>, nil] A filter for this task
|
72
|
-
attr_accessor :only
|
73
|
-
|
74
|
-
# @return [<#call(member, ctx)>, nil] A filter for this task
|
75
|
-
attr_accessor :except
|
76
|
-
|
77
68
|
# @return [String] target for IDL task
|
78
69
|
attr_accessor :idl_outfile
|
79
70
|
|
@@ -117,10 +108,10 @@ module GraphQL
|
|
117
108
|
include_is_repeatable: include_is_repeatable,
|
118
109
|
include_specified_by_url: include_specified_by_url,
|
119
110
|
include_schema_description: include_schema_description,
|
120
|
-
|
111
|
+
context: context
|
121
112
|
)
|
122
113
|
when :to_definition
|
123
|
-
schema.to_definition(
|
114
|
+
schema.to_definition(context: context)
|
124
115
|
else
|
125
116
|
raise ArgumentError, "Unexpected schema dump method: #{method_name.inspect}"
|
126
117
|
end
|
@@ -10,7 +10,13 @@ module GraphQL
|
|
10
10
|
else
|
11
11
|
ret_type = @field.type.unwrap
|
12
12
|
if ret_type.respond_to?(:scope_items)
|
13
|
-
ret_type.scope_items(value, context)
|
13
|
+
scoped_items = ret_type.scope_items(value, context)
|
14
|
+
if !scoped_items.equal?(value) && !ret_type.reauthorize_scoped_objects
|
15
|
+
current_runtime_state = Thread.current[:__graphql_runtime_info]
|
16
|
+
query_runtime_state = current_runtime_state[context.query]
|
17
|
+
query_runtime_state.was_authorized_by_scope_items = true
|
18
|
+
end
|
19
|
+
scoped_items
|
14
20
|
else
|
15
21
|
value
|
16
22
|
end
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -92,7 +92,7 @@ module GraphQL
|
|
92
92
|
# @param resolver [Class] A {GraphQL::Schema::Resolver} class to use for field configuration
|
93
93
|
# @param mutation [Class] A {GraphQL::Schema::Mutation} class to use for field configuration
|
94
94
|
# @param subscription [Class] A {GraphQL::Schema::Subscription} class to use for field configuration
|
95
|
-
# @return [GraphQL::Schema:Field] an instance of `self
|
95
|
+
# @return [GraphQL::Schema:Field] an instance of `self`
|
96
96
|
# @see {.initialize} for other options
|
97
97
|
def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
|
98
98
|
if (resolver_class = resolver || mutation || subscription)
|
@@ -15,6 +15,25 @@ module GraphQL
|
|
15
15
|
def scope_items(items, context)
|
16
16
|
items
|
17
17
|
end
|
18
|
+
|
19
|
+
def reauthorize_scoped_objects(new_value = nil)
|
20
|
+
if new_value.nil?
|
21
|
+
if @reauthorize_scoped_objects != nil
|
22
|
+
@reauthorize_scoped_objects
|
23
|
+
else
|
24
|
+
find_inherited_value(:reauthorize_scoped_objects, nil)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
@reauthorize_scoped_objects = new_value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def inherited(subclass)
|
32
|
+
super
|
33
|
+
subclass.class_eval do
|
34
|
+
@reauthorize_scoped_objects = nil
|
35
|
+
end
|
36
|
+
end
|
18
37
|
end
|
19
38
|
end
|
20
39
|
end
|
@@ -30,6 +30,10 @@ module GraphQL
|
|
30
30
|
# @see authorized_new to make instances
|
31
31
|
protected :new
|
32
32
|
|
33
|
+
def wrap_scoped(object, context)
|
34
|
+
scoped_new(object, context)
|
35
|
+
end
|
36
|
+
|
33
37
|
# This is called by the runtime to return an object to call methods on.
|
34
38
|
def wrap(object, context)
|
35
39
|
authorized_new(object, context)
|
@@ -91,6 +95,10 @@ module GraphQL
|
|
91
95
|
end
|
92
96
|
end
|
93
97
|
end
|
98
|
+
|
99
|
+
def scoped_new(object, context)
|
100
|
+
self.new(object, context)
|
101
|
+
end
|
94
102
|
end
|
95
103
|
|
96
104
|
def initialize(object, context)
|
@@ -36,15 +36,11 @@ module GraphQL
|
|
36
36
|
|
37
37
|
# @param schema [GraphQL::Schema]
|
38
38
|
# @param context [Hash]
|
39
|
-
# @param only [<#call(member, ctx)>]
|
40
|
-
# @param except [<#call(member, ctx)>]
|
41
39
|
# @param introspection [Boolean] Should include the introspection types in the string?
|
42
|
-
def initialize(schema, context: nil,
|
40
|
+
def initialize(schema, context: nil, introspection: false)
|
43
41
|
@document_from_schema = GraphQL::Language::DocumentFromSchemaDefinition.new(
|
44
42
|
schema,
|
45
43
|
context: context,
|
46
|
-
only: only,
|
47
|
-
except: except,
|
48
44
|
include_introspection_types: introspection,
|
49
45
|
)
|
50
46
|
|
@@ -61,7 +57,12 @@ module GraphQL
|
|
61
57
|
false
|
62
58
|
end
|
63
59
|
end
|
64
|
-
schema = Class.new(GraphQL::Schema) {
|
60
|
+
schema = Class.new(GraphQL::Schema) {
|
61
|
+
query(query_root)
|
62
|
+
def self.visible?(member, _ctx)
|
63
|
+
member.graphql_name != "Root"
|
64
|
+
end
|
65
|
+
}
|
65
66
|
|
66
67
|
introspection_schema_ast = GraphQL::Language::DocumentFromSchemaDefinition.new(
|
67
68
|
schema,
|
@@ -94,7 +95,7 @@ module GraphQL
|
|
94
95
|
|
95
96
|
class IntrospectionPrinter < GraphQL::Language::Printer
|
96
97
|
def print_schema_definition(schema)
|
97
|
-
"schema {\n query: Root\n}"
|
98
|
+
print_string("schema {\n query: Root\n}")
|
98
99
|
end
|
99
100
|
end
|
100
101
|
end
|
@@ -65,19 +65,20 @@ module GraphQL
|
|
65
65
|
# @api private
|
66
66
|
def resolve_with_support(**args)
|
67
67
|
# First call the ready? hook which may raise
|
68
|
-
|
68
|
+
raw_ready_val = if args.any?
|
69
69
|
ready?(**args)
|
70
70
|
else
|
71
71
|
ready?
|
72
72
|
end
|
73
|
-
context.query.after_lazy(
|
74
|
-
if
|
73
|
+
context.query.after_lazy(raw_ready_val) do |ready_val|
|
74
|
+
if ready_val.is_a?(Array)
|
75
|
+
is_ready, ready_early_return = ready_val
|
75
76
|
if is_ready != false
|
76
77
|
raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{is_ready.inspect}, #{ready_early_return.inspect}]"
|
77
78
|
else
|
78
79
|
ready_early_return
|
79
80
|
end
|
80
|
-
elsif
|
81
|
+
elsif ready_val
|
81
82
|
# Then call each prepare hook, which may return a different value
|
82
83
|
# for that argument, or may return a lazy object
|
83
84
|
load_arguments_val = load_arguments(args)
|
@@ -85,21 +86,22 @@ module GraphQL
|
|
85
86
|
@prepared_arguments = loaded_args
|
86
87
|
Schema::Validator.validate!(self.class.validators, object, context, loaded_args, as: @field)
|
87
88
|
# Then call `authorized?`, which may raise or may return a lazy object
|
88
|
-
|
89
|
+
raw_authorized_val = if loaded_args.any?
|
89
90
|
authorized?(**loaded_args)
|
90
91
|
else
|
91
92
|
authorized?
|
92
93
|
end
|
93
|
-
context.query.after_lazy(
|
94
|
+
context.query.after_lazy(raw_authorized_val) do |authorized_val|
|
94
95
|
# If the `authorized?` returned two values, `false, early_return`,
|
95
96
|
# then use the early return value instead of continuing
|
96
|
-
if
|
97
|
+
if authorized_val.is_a?(Array)
|
98
|
+
authorized_result, early_return = authorized_val
|
97
99
|
if authorized_result == false
|
98
100
|
early_return
|
99
101
|
else
|
100
102
|
raise "Unexpected result from #authorized? (expected `true`, `false` or `[false, {...}]`): [#{authorized_result.inspect}, #{early_return.inspect}]"
|
101
103
|
end
|
102
|
-
elsif
|
104
|
+
elsif authorized_val
|
103
105
|
# Finally, all the hooks have passed, so resolve it
|
104
106
|
if loaded_args.any?
|
105
107
|
public_send(self.class.resolve_method, **loaded_args)
|
@@ -28,14 +28,19 @@ module GraphQL
|
|
28
28
|
def resolve_with_support(**args)
|
29
29
|
result = nil
|
30
30
|
unsubscribed = true
|
31
|
-
catch :graphql_subscription_unsubscribed do
|
31
|
+
unsubscribed_result = catch :graphql_subscription_unsubscribed do
|
32
32
|
result = super
|
33
33
|
unsubscribed = false
|
34
34
|
end
|
35
35
|
|
36
36
|
|
37
37
|
if unsubscribed
|
38
|
-
|
38
|
+
if unsubscribed_result
|
39
|
+
context.namespace(:subscriptions)[:final_update] = true
|
40
|
+
unsubscribed_result
|
41
|
+
else
|
42
|
+
context.skip
|
43
|
+
end
|
39
44
|
else
|
40
45
|
result
|
41
46
|
end
|
@@ -94,9 +99,11 @@ module GraphQL
|
|
94
99
|
end
|
95
100
|
|
96
101
|
# Call this to halt execution and remove this subscription from the system
|
97
|
-
|
102
|
+
# @param update_value [Object] if present, deliver this update before unsubscribing
|
103
|
+
# @return [void]
|
104
|
+
def unsubscribe(update_value = nil)
|
98
105
|
context.namespace(:subscriptions)[:unsubscribed] = true
|
99
|
-
throw :graphql_subscription_unsubscribed
|
106
|
+
throw :graphql_subscription_unsubscribed, update_value
|
100
107
|
end
|
101
108
|
|
102
109
|
READING_SCOPE = ::Object.new
|
@@ -133,7 +133,7 @@ module GraphQL
|
|
133
133
|
if all_errors.frozen? # It's empty
|
134
134
|
all_errors = []
|
135
135
|
end
|
136
|
-
interpolation_vars = { validated: validated.graphql_name }
|
136
|
+
interpolation_vars = { validated: validated.graphql_name, value: value.inspect }
|
137
137
|
if errors.is_a?(String)
|
138
138
|
all_errors << (errors % interpolation_vars)
|
139
139
|
else
|
@@ -4,37 +4,12 @@ require 'set'
|
|
4
4
|
|
5
5
|
module GraphQL
|
6
6
|
class Schema
|
7
|
-
# Restrict access to a {GraphQL::Schema} with a user-defined
|
7
|
+
# Restrict access to a {GraphQL::Schema} with a user-defined `visible?` implementations.
|
8
8
|
#
|
9
9
|
# When validating and executing a query, all access to schema members
|
10
10
|
# should go through a warden. If you access the schema directly,
|
11
11
|
# you may show a client something that it shouldn't be allowed to see.
|
12
12
|
#
|
13
|
-
# @example Hiding private fields
|
14
|
-
# private_members = -> (member, ctx) { member.metadata[:private] }
|
15
|
-
# result = Schema.execute(query_string, except: private_members)
|
16
|
-
#
|
17
|
-
# @example Custom filter implementation
|
18
|
-
# # It must respond to `#call(member)`.
|
19
|
-
# class MissingRequiredFlags
|
20
|
-
# def initialize(user)
|
21
|
-
# @user = user
|
22
|
-
# end
|
23
|
-
#
|
24
|
-
# # Return `false` if any required flags are missing
|
25
|
-
# def call(member, ctx)
|
26
|
-
# member.metadata[:required_flags].any? do |flag|
|
27
|
-
# !@user.has_flag?(flag)
|
28
|
-
# end
|
29
|
-
# end
|
30
|
-
# end
|
31
|
-
#
|
32
|
-
# # Then, use the custom filter in query:
|
33
|
-
# missing_required_flags = MissingRequiredFlags.new(current_user)
|
34
|
-
#
|
35
|
-
# # This query can only access members which match the user's flags
|
36
|
-
# result = Schema.execute(query_string, except: missing_required_flags)
|
37
|
-
#
|
38
13
|
# @api private
|
39
14
|
class Warden
|
40
15
|
def self.from_context(context)
|
@@ -114,22 +89,16 @@ module GraphQL
|
|
114
89
|
def interfaces(obj_type); obj_type.interfaces; end
|
115
90
|
end
|
116
91
|
|
117
|
-
# @param filter [<#call(member)>] Objects are hidden when `.call(member, ctx)` returns true
|
118
92
|
# @param context [GraphQL::Query::Context]
|
119
93
|
# @param schema [GraphQL::Schema]
|
120
|
-
def initialize(
|
94
|
+
def initialize(context:, schema:)
|
121
95
|
@schema = schema
|
122
96
|
# Cache these to avoid repeated hits to the inheritance chain when one isn't present
|
123
97
|
@query = @schema.query
|
124
98
|
@mutation = @schema.mutation
|
125
99
|
@subscription = @schema.subscription
|
126
100
|
@context = context
|
127
|
-
@visibility_cache =
|
128
|
-
read_through { |m| filter.call(m, context) }
|
129
|
-
else
|
130
|
-
read_through { |m| schema.visible?(m, context) }
|
131
|
-
end
|
132
|
-
|
101
|
+
@visibility_cache = read_through { |m| schema.visible?(m, context) }
|
133
102
|
@visibility_cache.compare_by_identity
|
134
103
|
# Initialize all ivars to improve object shape consistency:
|
135
104
|
@types = @visible_types = @reachable_types = @visible_parent_fields =
|
data/lib/graphql/schema.rb
CHANGED
@@ -222,7 +222,7 @@ module GraphQL
|
|
222
222
|
# @param include_specified_by_url [Boolean] If true, scalar types' `specifiedByUrl:` will be included in the response
|
223
223
|
# @param include_is_one_of [Boolean] If true, `isOneOf: true|false` will be included with input objects
|
224
224
|
# @return [Hash] GraphQL result
|
225
|
-
def as_json(
|
225
|
+
def as_json(context: {}, include_deprecated_args: true, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false)
|
226
226
|
introspection_query = Introspection.query(
|
227
227
|
include_deprecated_args: include_deprecated_args,
|
228
228
|
include_schema_description: include_schema_description,
|
@@ -231,16 +231,14 @@ module GraphQL
|
|
231
231
|
include_specified_by_url: include_specified_by_url,
|
232
232
|
)
|
233
233
|
|
234
|
-
execute(introspection_query,
|
234
|
+
execute(introspection_query, context: context).to_h
|
235
235
|
end
|
236
236
|
|
237
237
|
# Return the GraphQL IDL for the schema
|
238
238
|
# @param context [Hash]
|
239
|
-
# @param only [<#call(member, ctx)>]
|
240
|
-
# @param except [<#call(member, ctx)>]
|
241
239
|
# @return [String]
|
242
|
-
def to_definition(
|
243
|
-
GraphQL::Schema::Printer.print_schema(self,
|
240
|
+
def to_definition(context: {})
|
241
|
+
GraphQL::Schema::Printer.print_schema(self, context: context)
|
244
242
|
end
|
245
243
|
|
246
244
|
# Return the GraphQL::Language::Document IDL AST for the schema
|
@@ -268,20 +266,6 @@ module GraphQL
|
|
268
266
|
@find_cache[path] ||= @finder.find(path)
|
269
267
|
end
|
270
268
|
|
271
|
-
def default_filter
|
272
|
-
GraphQL::Filter.new(except: default_mask)
|
273
|
-
end
|
274
|
-
|
275
|
-
def default_mask(new_mask = nil)
|
276
|
-
if new_mask
|
277
|
-
line = caller(2, 10).find { |l| !l.include?("lib/graphql") }
|
278
|
-
GraphQL::Deprecation.warn("GraphQL::Filter and Schema.mask are deprecated and will be removed in v2.1.0. Implement `visible?` on your schema members instead (https://graphql-ruby.org/authorization/visibility.html).\n #{line}")
|
279
|
-
@own_default_mask = new_mask
|
280
|
-
else
|
281
|
-
@own_default_mask || find_inherited_value(:default_mask, Schema::NullMask)
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
269
|
def static_validator
|
286
270
|
GraphQL::StaticValidation::Validator.new(schema: self)
|
287
271
|
end
|
@@ -8,9 +8,6 @@ module GraphQL
|
|
8
8
|
# It provides access to the schema & fragments which validators may read from.
|
9
9
|
#
|
10
10
|
# It holds a list of errors which each validator may add to.
|
11
|
-
#
|
12
|
-
# It also provides limited access to the {TypeStack} instance,
|
13
|
-
# which tracks state as you climb in and out of different fields.
|
14
11
|
class ValidationContext
|
15
12
|
extend Forwardable
|
16
13
|
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "graphql/static_validation/error"
|
3
3
|
require "graphql/static_validation/definition_dependencies"
|
4
|
-
require "graphql/static_validation/type_stack"
|
5
4
|
require "graphql/static_validation/validator"
|
6
5
|
require "graphql/static_validation/validation_context"
|
7
6
|
require "graphql/static_validation/validation_timeout_error"
|
@@ -124,7 +124,8 @@ module GraphQL
|
|
124
124
|
# This subscription was re-evaluated.
|
125
125
|
# Send it to the specific stream where this client was waiting.
|
126
126
|
def deliver(subscription_id, result)
|
127
|
-
|
127
|
+
has_more = !result.context.namespace(:subscriptions)[:final_update]
|
128
|
+
payload = { result: result.to_h, more: has_more }
|
128
129
|
@action_cable.server.broadcast(stream_subscription_name(subscription_id), payload)
|
129
130
|
end
|
130
131
|
|