graphql 1.12.20 → 1.13.1
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/core.rb +3 -1
- data/lib/generators/graphql/install_generator.rb +9 -2
- data/lib/generators/graphql/mutation_generator.rb +1 -1
- data/lib/generators/graphql/object_generator.rb +2 -1
- data/lib/generators/graphql/templates/schema.erb +2 -2
- data/lib/generators/graphql/type_generator.rb +0 -1
- data/lib/graphql/analysis/ast/field_usage.rb +2 -2
- data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
- data/lib/graphql/analysis/ast/visitor.rb +4 -4
- data/lib/graphql/backtrace/table.rb +1 -1
- data/lib/graphql/base_type.rb +4 -2
- data/lib/graphql/boolean_type.rb +1 -1
- data/lib/graphql/dataloader.rb +55 -22
- data/lib/graphql/directive/deprecated_directive.rb +1 -1
- data/lib/graphql/directive/include_directive.rb +1 -1
- data/lib/graphql/directive/skip_directive.rb +1 -1
- data/lib/graphql/directive.rb +0 -4
- data/lib/graphql/enum_type.rb +5 -1
- data/lib/graphql/execution/errors.rb +1 -0
- data/lib/graphql/execution/interpreter/arguments.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -2
- data/lib/graphql/execution/interpreter/runtime.rb +30 -18
- data/lib/graphql/execution/lookahead.rb +2 -2
- data/lib/graphql/execution/multiplex.rb +4 -1
- data/lib/graphql/float_type.rb +1 -1
- data/lib/graphql/id_type.rb +1 -1
- data/lib/graphql/int_type.rb +1 -1
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/enum_value_type.rb +2 -2
- data/lib/graphql/introspection/field_type.rb +2 -2
- data/lib/graphql/introspection/input_value_type.rb +4 -4
- data/lib/graphql/introspection/schema_type.rb +2 -2
- data/lib/graphql/introspection/type_type.rb +10 -10
- data/lib/graphql/language/block_string.rb +2 -6
- data/lib/graphql/language/document_from_schema_definition.rb +4 -2
- data/lib/graphql/language/lexer.rb +0 -3
- data/lib/graphql/language/lexer.rl +0 -4
- data/lib/graphql/language/nodes.rb +12 -2
- data/lib/graphql/language/parser.rb +442 -434
- data/lib/graphql/language/parser.y +5 -4
- data/lib/graphql/language/printer.rb +6 -1
- data/lib/graphql/language/sanitized_printer.rb +5 -5
- data/lib/graphql/language/token.rb +0 -4
- data/lib/graphql/name_validator.rb +0 -4
- data/lib/graphql/query/arguments.rb +1 -1
- data/lib/graphql/query/arguments_cache.rb +1 -1
- data/lib/graphql/query/context.rb +15 -2
- data/lib/graphql/query/literal_input.rb +1 -1
- data/lib/graphql/query/null_context.rb +12 -7
- data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
- data/lib/graphql/query/variables.rb +5 -1
- data/lib/graphql/relay/edges_instrumentation.rb +0 -1
- data/lib/graphql/relay/global_id_resolve.rb +1 -1
- data/lib/graphql/relay/page_info.rb +1 -1
- data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
- data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
- data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
- data/lib/graphql/rubocop.rb +4 -0
- data/lib/graphql/schema/addition.rb +37 -28
- data/lib/graphql/schema/argument.rb +8 -6
- data/lib/graphql/schema/build_from_definition.rb +5 -5
- data/lib/graphql/schema/directive/feature.rb +1 -1
- data/lib/graphql/schema/directive/flagged.rb +2 -2
- data/lib/graphql/schema/directive/include.rb +1 -1
- data/lib/graphql/schema/directive/skip.rb +1 -1
- data/lib/graphql/schema/directive/transform.rb +1 -1
- data/lib/graphql/schema/directive.rb +7 -3
- data/lib/graphql/schema/enum.rb +60 -10
- data/lib/graphql/schema/enum_value.rb +6 -0
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +130 -39
- data/lib/graphql/schema/field_extension.rb +52 -2
- data/lib/graphql/schema/find_inherited_value.rb +1 -0
- data/lib/graphql/schema/finder.rb +5 -5
- data/lib/graphql/schema/input_object.rb +8 -5
- data/lib/graphql/schema/interface.rb +11 -20
- data/lib/graphql/schema/introspection_system.rb +1 -1
- data/lib/graphql/schema/list.rb +3 -1
- data/lib/graphql/schema/member/accepts_definition.rb +15 -3
- data/lib/graphql/schema/member/build_type.rb +0 -4
- data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
- data/lib/graphql/schema/member/has_arguments.rb +55 -13
- data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
- data/lib/graphql/schema/member/has_fields.rb +76 -18
- data/lib/graphql/schema/member/has_interfaces.rb +90 -0
- data/lib/graphql/schema/member.rb +1 -0
- data/lib/graphql/schema/non_null.rb +3 -1
- data/lib/graphql/schema/object.rb +10 -75
- data/lib/graphql/schema/printer.rb +1 -1
- data/lib/graphql/schema/relay_classic_mutation.rb +29 -3
- data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
- data/lib/graphql/schema/resolver.rb +19 -5
- data/lib/graphql/schema/scalar.rb +2 -0
- data/lib/graphql/schema/subscription.rb +11 -1
- data/lib/graphql/schema/traversal.rb +1 -1
- data/lib/graphql/schema/type_expression.rb +1 -1
- data/lib/graphql/schema/type_membership.rb +18 -4
- data/lib/graphql/schema/union.rb +8 -1
- data/lib/graphql/schema/validator/format_validator.rb +3 -5
- data/lib/graphql/schema/validator/numericality_validator.rb +1 -0
- data/lib/graphql/schema/validator.rb +4 -7
- data/lib/graphql/schema/warden.rb +116 -52
- data/lib/graphql/schema.rb +106 -22
- data/lib/graphql/static_validation/base_visitor.rb +5 -5
- data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
- data/lib/graphql/static_validation/literal_validator.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +15 -8
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +7 -7
- data/lib/graphql/string_type.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -4
- data/lib/graphql/subscriptions/event.rb +20 -12
- data/lib/graphql/subscriptions.rb +17 -19
- data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
- data/lib/graphql/types/relay/default_relay.rb +5 -1
- data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
- data/lib/graphql/types/relay/has_node_field.rb +1 -1
- data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +10 -32
- metadata +10 -5
@@ -56,7 +56,7 @@ module GraphQL
|
|
56
56
|
def self.print_introspection_schema
|
57
57
|
query_root = Class.new(GraphQL::Schema::Object) do
|
58
58
|
graphql_name "Root"
|
59
|
-
field :throwaway_field, String
|
59
|
+
field :throwaway_field, String
|
60
60
|
end
|
61
61
|
schema = Class.new(GraphQL::Schema) { query(query_root) }
|
62
62
|
|
@@ -22,7 +22,7 @@ module GraphQL
|
|
22
22
|
#
|
23
23
|
class RelayClassicMutation < GraphQL::Schema::Mutation
|
24
24
|
# The payload should always include this field
|
25
|
-
field(:client_mutation_id, String, "A unique identifier for the client performing the mutation."
|
25
|
+
field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.")
|
26
26
|
# Relay classic default:
|
27
27
|
null(true)
|
28
28
|
|
@@ -81,6 +81,31 @@ module GraphQL
|
|
81
81
|
end
|
82
82
|
|
83
83
|
class << self
|
84
|
+
|
85
|
+
# Also apply this argument to the input type:
|
86
|
+
def argument(*args, **kwargs, &block)
|
87
|
+
it = input_type # make sure any inherited arguments are already added to it
|
88
|
+
arg = super
|
89
|
+
|
90
|
+
# This definition might be overriding something inherited;
|
91
|
+
# if it is, remove the inherited definition so it's not confused at runtime as having multiple definitions
|
92
|
+
prev_args = it.own_arguments[arg.graphql_name]
|
93
|
+
case prev_args
|
94
|
+
when GraphQL::Schema::Argument
|
95
|
+
if prev_args.owner != self
|
96
|
+
it.own_arguments.delete(arg.graphql_name)
|
97
|
+
end
|
98
|
+
when Array
|
99
|
+
prev_args.reject! { |a| a.owner != self }
|
100
|
+
if prev_args.empty?
|
101
|
+
it.own_arguments.delete(arg.graphql_name)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
it.add_argument(arg)
|
106
|
+
arg
|
107
|
+
end
|
108
|
+
|
84
109
|
# The base class for generated input object types
|
85
110
|
# @param new_class [Class] The base class to use for generating input object definitions
|
86
111
|
# @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject})
|
@@ -115,14 +140,15 @@ module GraphQL
|
|
115
140
|
# To customize how input objects are generated, override this method
|
116
141
|
# @return [Class] a subclass of {.input_object_class}
|
117
142
|
def generate_input_type
|
118
|
-
mutation_args =
|
143
|
+
mutation_args = all_argument_definitions
|
119
144
|
mutation_name = graphql_name
|
120
145
|
mutation_class = self
|
121
146
|
Class.new(input_object_class) do
|
122
147
|
graphql_name("#{mutation_name}Input")
|
123
148
|
description("Autogenerated input type of #{mutation_name}")
|
124
149
|
mutation(mutation_class)
|
125
|
-
|
150
|
+
# these might be inherited:
|
151
|
+
mutation_args.each do |arg|
|
126
152
|
add_argument(arg)
|
127
153
|
end
|
128
154
|
argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
|
@@ -38,6 +38,9 @@ module GraphQL
|
|
38
38
|
# @return [Class]
|
39
39
|
def object_class(new_class = nil)
|
40
40
|
if new_class
|
41
|
+
if defined?(@payload_type)
|
42
|
+
raise "Can't configure `object_class(...)` after the payload type has already been initialized. Move this configuration higher up the class definition."
|
43
|
+
end
|
41
44
|
@object_class = new_class
|
42
45
|
else
|
43
46
|
@object_class || find_inherited_value(:object_class, GraphQL::Schema::Object)
|
@@ -46,6 +49,28 @@ module GraphQL
|
|
46
49
|
|
47
50
|
NO_INTERFACES = [].freeze
|
48
51
|
|
52
|
+
def field(*args, **kwargs, &block)
|
53
|
+
pt = payload_type # make sure it's initialized with any inherited fields
|
54
|
+
field_defn = super
|
55
|
+
|
56
|
+
# Remove any inherited fields to avoid false conflicts at runtime
|
57
|
+
prev_fields = pt.own_fields[field_defn.graphql_name]
|
58
|
+
case prev_fields
|
59
|
+
when GraphQL::Schema::Field
|
60
|
+
if prev_fields.owner != self
|
61
|
+
pt.own_fields.delete(field_defn.graphql_name)
|
62
|
+
end
|
63
|
+
when Array
|
64
|
+
prev_fields.reject! { |f| f.owner != self }
|
65
|
+
if prev_fields.empty?
|
66
|
+
pt.own_fields.delete(field_defn.graphql_name)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
pt.add_field(field_defn, method_conflict_warning: false)
|
71
|
+
field_defn
|
72
|
+
end
|
73
|
+
|
49
74
|
private
|
50
75
|
|
51
76
|
# Build a subclass of {.object_class} based on `self`.
|
@@ -53,11 +78,11 @@ module GraphQL
|
|
53
78
|
# Override this hook to customize return type generation.
|
54
79
|
def generate_payload_type
|
55
80
|
resolver_name = graphql_name
|
56
|
-
resolver_fields =
|
81
|
+
resolver_fields = all_field_definitions
|
57
82
|
Class.new(object_class) do
|
58
83
|
graphql_name("#{resolver_name}Payload")
|
59
84
|
description("Autogenerated return type of #{resolver_name}")
|
60
|
-
resolver_fields.each do |
|
85
|
+
resolver_fields.each do |f|
|
61
86
|
# Reattach the already-defined field here
|
62
87
|
# (The field's `.owner` will still point to the mutation, not the object type, I think)
|
63
88
|
# Don't re-warn about a method conflict. Since this type is generated, it should be fixed in the resolver instead.
|
@@ -37,7 +37,7 @@ module GraphQL
|
|
37
37
|
@field = field
|
38
38
|
# Since this hash is constantly rebuilt, cache it for this call
|
39
39
|
@arguments_by_keyword = {}
|
40
|
-
self.class.arguments.each do |name, arg|
|
40
|
+
self.class.arguments(context).each do |name, arg|
|
41
41
|
@arguments_by_keyword[arg.keyword] = arg
|
42
42
|
end
|
43
43
|
@prepared_arguments = nil
|
@@ -145,7 +145,7 @@ module GraphQL
|
|
145
145
|
# @raise [GraphQL::UnauthorizedError] To signal an authorization failure
|
146
146
|
# @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
|
147
147
|
def authorized?(**inputs)
|
148
|
-
self.class.arguments.each_value do |argument|
|
148
|
+
self.class.arguments(context).each_value do |argument|
|
149
149
|
arg_keyword = argument.keyword
|
150
150
|
if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
|
151
151
|
arg_auth, err = argument.authorized?(self, arg_value, context)
|
@@ -199,8 +199,8 @@ module GraphQL
|
|
199
199
|
end
|
200
200
|
end
|
201
201
|
|
202
|
-
def get_argument(name)
|
203
|
-
self.class.get_argument(name)
|
202
|
+
def get_argument(name, context = GraphQL::Query::NullContext)
|
203
|
+
self.class.get_argument(name, context)
|
204
204
|
end
|
205
205
|
|
206
206
|
class << self
|
@@ -305,13 +305,27 @@ module GraphQL
|
|
305
305
|
end
|
306
306
|
|
307
307
|
def field_options
|
308
|
+
|
309
|
+
all_args = {}
|
310
|
+
all_argument_definitions.each do |arg|
|
311
|
+
if (prev_entry = all_args[arg.graphql_name])
|
312
|
+
if prev_entry.is_a?(Array)
|
313
|
+
prev_entry << arg
|
314
|
+
else
|
315
|
+
all_args[arg.graphql_name] = [prev_entry, arg]
|
316
|
+
end
|
317
|
+
else
|
318
|
+
all_args[arg.graphql_name] = arg
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
308
322
|
field_opts = {
|
309
323
|
type: type_expr,
|
310
324
|
description: description,
|
311
325
|
extras: extras,
|
312
326
|
resolver_method: :resolve_with_support,
|
313
327
|
resolver_class: self,
|
314
|
-
arguments:
|
328
|
+
arguments: all_args,
|
315
329
|
null: null,
|
316
330
|
complexity: complexity,
|
317
331
|
broadcastable: broadcastable?,
|
@@ -103,10 +103,12 @@ module GraphQL
|
|
103
103
|
# Call this method to provide a new subscription_scope; OR
|
104
104
|
# call it without an argument to get the subscription_scope
|
105
105
|
# @param new_scope [Symbol]
|
106
|
+
# @param optional [Boolean] If true, then don't require `scope:` to be provided to updates to this subscription.
|
106
107
|
# @return [Symbol]
|
107
|
-
def self.subscription_scope(new_scope = READING_SCOPE)
|
108
|
+
def self.subscription_scope(new_scope = READING_SCOPE, optional: false)
|
108
109
|
if new_scope != READING_SCOPE
|
109
110
|
@subscription_scope = new_scope
|
111
|
+
@subscription_scope_optional = optional
|
110
112
|
elsif defined?(@subscription_scope)
|
111
113
|
@subscription_scope
|
112
114
|
else
|
@@ -114,6 +116,14 @@ module GraphQL
|
|
114
116
|
end
|
115
117
|
end
|
116
118
|
|
119
|
+
def self.subscription_scope_optional?
|
120
|
+
if defined?(@subscription_scope_optional)
|
121
|
+
@subscription_scope_optional
|
122
|
+
else
|
123
|
+
find_inherited_value(:subscription_scope_optional, false)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
117
127
|
# This is called during initial subscription to get a "name" for this subscription.
|
118
128
|
# Later, when `.trigger` is called, this will be called again to build another "name".
|
119
129
|
# Any subscribers with matching topic will begin the update flow.
|
@@ -173,7 +173,7 @@ Some late-bound types couldn't be resolved:
|
|
173
173
|
end
|
174
174
|
when Class
|
175
175
|
if member.respond_to?(:graphql_definition)
|
176
|
-
graphql_member = member.graphql_definition
|
176
|
+
graphql_member = member.graphql_definition(silence_deprecation_warning: true)
|
177
177
|
visit(schema, graphql_member, context_description)
|
178
178
|
else
|
179
179
|
raise GraphQL::Schema::InvalidTypeError.new("Unexpected traversal member: #{member} (#{member.class.name})")
|
@@ -11,7 +11,7 @@ module GraphQL
|
|
11
11
|
def self.build_type(type_owner, ast_node)
|
12
12
|
case ast_node
|
13
13
|
when GraphQL::Language::Nodes::TypeName
|
14
|
-
type_owner.get_type(ast_node.name)
|
14
|
+
type_owner.get_type(ast_node.name) # rubocop:disable Development/ContextIsPassedCop -- this is a `context` or `warden`, it's already query-aware
|
15
15
|
when GraphQL::Language::Nodes::NonNullType
|
16
16
|
ast_inner_type = ast_node.of_type
|
17
17
|
inner_type = build_type(type_owner, ast_inner_type)
|
@@ -4,8 +4,6 @@ module GraphQL
|
|
4
4
|
class Schema
|
5
5
|
# This class joins an object type to an abstract type (interface or union) of which
|
6
6
|
# it is a member.
|
7
|
-
#
|
8
|
-
# TODO: Not yet implemented for interfaces.
|
9
7
|
class TypeMembership
|
10
8
|
# @return [Class<GraphQL::Schema::Object>]
|
11
9
|
attr_accessor :object_type
|
@@ -26,9 +24,25 @@ module GraphQL
|
|
26
24
|
end
|
27
25
|
|
28
26
|
# @return [Boolean] if false, {#object_type} will be treated as _not_ a member of {#abstract_type}
|
29
|
-
def visible?(
|
30
|
-
|
27
|
+
def visible?(ctx)
|
28
|
+
warden = Warden.from_context(ctx)
|
29
|
+
(@object_type.respond_to?(:visible?) ? warden.visible_type?(@object_type, ctx) : true) &&
|
30
|
+
(@abstract_type.respond_to?(:visible?) ? warden.visible_type?(@abstract_type, ctx) : true)
|
31
31
|
end
|
32
|
+
|
33
|
+
def graphql_name
|
34
|
+
"#{@object_type.graphql_name}.#{@abstract_type.kind.interface? ? "implements" : "belongsTo" }.#{@abstract_type.graphql_name}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def path
|
38
|
+
graphql_name
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect
|
42
|
+
"#<#{self.class} #{@object_type.inspect} => #{@abstract_type.inspect}>"
|
43
|
+
end
|
44
|
+
|
45
|
+
alias :type_class :itself
|
32
46
|
end
|
33
47
|
end
|
34
48
|
end
|
data/lib/graphql/schema/union.rb
CHANGED
@@ -19,8 +19,9 @@ module GraphQL
|
|
19
19
|
end
|
20
20
|
else
|
21
21
|
visible_types = []
|
22
|
+
warden = Warden.from_context(context)
|
22
23
|
type_memberships.each do |type_membership|
|
23
|
-
if
|
24
|
+
if warden.visible_type_membership?(type_membership, context)
|
24
25
|
visible_types << type_membership.object_type
|
25
26
|
end
|
26
27
|
end
|
@@ -28,6 +29,12 @@ module GraphQL
|
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
32
|
+
def all_possible_types
|
33
|
+
type_memberships.map(&:object_type)
|
34
|
+
end
|
35
|
+
|
36
|
+
prepend GraphQL::Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
|
37
|
+
|
31
38
|
def to_graphql
|
32
39
|
type_defn = GraphQL::UnionType.new
|
33
40
|
type_defn.name = graphql_name
|
@@ -18,10 +18,6 @@ module GraphQL
|
|
18
18
|
# # It's pretty hard to come up with a legitimate use case for `without:`
|
19
19
|
#
|
20
20
|
class FormatValidator < Validator
|
21
|
-
if !String.method_defined?(:match?)
|
22
|
-
using GraphQL::StringMatchBackport
|
23
|
-
end
|
24
|
-
|
25
21
|
# @param with [RegExp, nil]
|
26
22
|
# @param without [Regexp, nil]
|
27
23
|
# @param message [String]
|
@@ -38,7 +34,9 @@ module GraphQL
|
|
38
34
|
end
|
39
35
|
|
40
36
|
def validate(_object, _context, value)
|
41
|
-
if value
|
37
|
+
if permitted_empty_value?(value)
|
38
|
+
# Do nothing
|
39
|
+
elsif value.nil? ||
|
42
40
|
(@with_pattern && !value.match?(@with_pattern)) ||
|
43
41
|
(@without_pattern && value.match?(@without_pattern))
|
44
42
|
@message
|
@@ -48,16 +48,13 @@ module GraphQL
|
|
48
48
|
EMPTY_ARRAY
|
49
49
|
else
|
50
50
|
validates_hash = validates_hash.dup
|
51
|
-
allow_null = validates_hash.delete(:allow_null)
|
52
|
-
allow_blank = validates_hash.delete(:allow_blank)
|
53
51
|
|
54
|
-
# This could be {...}.compact on Ruby 2.4+
|
55
52
|
default_options = {}
|
56
|
-
if
|
57
|
-
default_options[:allow_null] = allow_null
|
53
|
+
if validates_hash[:allow_null]
|
54
|
+
default_options[:allow_null] = validates_hash.delete(:allow_null)
|
58
55
|
end
|
59
|
-
if
|
60
|
-
default_options[:allow_blank] = allow_blank
|
56
|
+
if validates_hash[:allow_blank]
|
57
|
+
default_options[:allow_blank] = validates_hash.delete(:allow_blank)
|
61
58
|
end
|
62
59
|
|
63
60
|
# allow_nil or allow_blank are the _only_ validations:
|
@@ -37,6 +37,50 @@ module GraphQL
|
|
37
37
|
#
|
38
38
|
# @api private
|
39
39
|
class Warden
|
40
|
+
def self.from_context(context)
|
41
|
+
(context.respond_to?(:warden) && context.warden) || PassThruWarden
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param visibility_method [Symbol] a Warden method to call for this entry
|
45
|
+
# @param entry [Object, Array<Object>] One or more definitions for a given name in a GraphQL Schema
|
46
|
+
# @param context [GraphQL::Query::Context]
|
47
|
+
# @param warden [Warden]
|
48
|
+
# @return [Object] `entry` or one of `entry`'s items if exactly one of them is visible for this context
|
49
|
+
# @return [nil] If neither `entry` nor any of `entry`'s items are visible for this context
|
50
|
+
def self.visible_entry?(visibility_method, entry, context, warden = Warden.from_context(context))
|
51
|
+
if entry.is_a?(Array)
|
52
|
+
visible_item = nil
|
53
|
+
entry.each do |item|
|
54
|
+
if warden.public_send(visibility_method, item, context)
|
55
|
+
if visible_item.nil?
|
56
|
+
visible_item = item
|
57
|
+
else
|
58
|
+
raise Schema::DuplicateNamesError, "Found two visible definitions for `#{item.path}`: #{visible_item.inspect}, #{item.inspect}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
visible_item
|
63
|
+
elsif warden.public_send(visibility_method, entry, context)
|
64
|
+
entry
|
65
|
+
else
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# This is used when a caller provides a Hash for context.
|
71
|
+
# We want to call the schema's hooks, but we don't have a full-blown warden.
|
72
|
+
# The `context` arguments to these methods exist purely to simplify the code that
|
73
|
+
# calls methods on this object, so it will have everything it needs.
|
74
|
+
class PassThruWarden
|
75
|
+
class << self
|
76
|
+
def visible_field?(field, ctx); field.visible?(ctx); end
|
77
|
+
def visible_argument?(arg, ctx); arg.visible?(ctx); end
|
78
|
+
def visible_type?(type, ctx); type.visible?(ctx); end
|
79
|
+
def visible_enum_value?(ev, ctx); ev.visible?(ctx); end
|
80
|
+
def visible_type_membership?(tm, ctx); tm.visible?(ctx); end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
40
84
|
# @param filter [<#call(member)>] Objects are hidden when `.call(member, ctx)` returns true
|
41
85
|
# @param context [GraphQL::Query::Context]
|
42
86
|
# @param schema [GraphQL::Schema]
|
@@ -54,8 +98,8 @@ module GraphQL
|
|
54
98
|
def types
|
55
99
|
@types ||= begin
|
56
100
|
vis_types = {}
|
57
|
-
@schema.types.each do |n, t|
|
58
|
-
if
|
101
|
+
@schema.types(@context).each do |n, t|
|
102
|
+
if visible_and_reachable_type?(t)
|
59
103
|
vis_types[n] = t
|
60
104
|
end
|
61
105
|
end
|
@@ -66,8 +110,8 @@ module GraphQL
|
|
66
110
|
# @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)
|
67
111
|
def get_type(type_name)
|
68
112
|
@visible_types ||= read_through do |name|
|
69
|
-
type_defn = @schema.get_type(name)
|
70
|
-
if type_defn &&
|
113
|
+
type_defn = @schema.get_type(name, @context)
|
114
|
+
if type_defn && visible_and_reachable_type?(type_defn)
|
71
115
|
type_defn
|
72
116
|
else
|
73
117
|
nil
|
@@ -84,7 +128,7 @@ module GraphQL
|
|
84
128
|
|
85
129
|
# @return Boolean True if the type is visible and reachable in the schema
|
86
130
|
def reachable_type?(type_name)
|
87
|
-
type = get_type(type_name)
|
131
|
+
type = get_type(type_name) # rubocop:disable Development/ContextIsPassedCop -- `self` is query-aware
|
88
132
|
type && reachable_type_set.include?(type)
|
89
133
|
end
|
90
134
|
|
@@ -92,8 +136,8 @@ module GraphQL
|
|
92
136
|
def get_field(parent_type, field_name)
|
93
137
|
@visible_parent_fields ||= read_through do |type|
|
94
138
|
read_through do |f_name|
|
95
|
-
field_defn = @schema.get_field(type, f_name)
|
96
|
-
if field_defn && visible_field?(
|
139
|
+
field_defn = @schema.get_field(type, f_name, @context)
|
140
|
+
if field_defn && visible_field?(field_defn, nil, type)
|
97
141
|
field_defn
|
98
142
|
else
|
99
143
|
nil
|
@@ -106,15 +150,15 @@ module GraphQL
|
|
106
150
|
|
107
151
|
# @return [GraphQL::Argument, nil] The argument named `argument_name` on `parent_type`, if it exists and is visible
|
108
152
|
def get_argument(parent_type, argument_name)
|
109
|
-
argument = parent_type.get_argument(argument_name)
|
110
|
-
return argument if argument && visible_argument?(argument)
|
153
|
+
argument = parent_type.get_argument(argument_name, @context)
|
154
|
+
return argument if argument && visible_argument?(argument, @context)
|
111
155
|
end
|
112
156
|
|
113
157
|
# @return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`
|
114
158
|
def possible_types(type_defn)
|
115
159
|
@visible_possible_types ||= read_through { |type_defn|
|
116
160
|
pt = @schema.possible_types(type_defn, @context)
|
117
|
-
pt.select { |t|
|
161
|
+
pt.select { |t| visible_and_reachable_type?(t) }
|
118
162
|
}
|
119
163
|
@visible_possible_types[type_defn]
|
120
164
|
end
|
@@ -122,26 +166,31 @@ module GraphQL
|
|
122
166
|
# @param type_defn [GraphQL::ObjectType, GraphQL::InterfaceType]
|
123
167
|
# @return [Array<GraphQL::Field>] Fields on `type_defn`
|
124
168
|
def fields(type_defn)
|
125
|
-
@visible_fields ||= read_through { |t| @schema.get_fields(t
|
169
|
+
@visible_fields ||= read_through { |t| @schema.get_fields(t, @context).values }
|
126
170
|
@visible_fields[type_defn]
|
127
171
|
end
|
128
172
|
|
129
173
|
# @param argument_owner [GraphQL::Field, GraphQL::InputObjectType]
|
130
174
|
# @return [Array<GraphQL::Argument>] Visible arguments on `argument_owner`
|
131
175
|
def arguments(argument_owner)
|
132
|
-
@visible_arguments ||= read_through { |o| o.arguments.each_value.select { |a| visible_argument?(a) } }
|
176
|
+
@visible_arguments ||= read_through { |o| o.arguments(@context).each_value.select { |a| visible_argument?(a) } }
|
133
177
|
@visible_arguments[argument_owner]
|
134
178
|
end
|
135
179
|
|
136
180
|
# @return [Array<GraphQL::EnumType::EnumValue>] Visible members of `enum_defn`
|
137
181
|
def enum_values(enum_defn)
|
138
|
-
@
|
139
|
-
@
|
182
|
+
@visible_enum_arrays ||= read_through { |e| e.enum_values(@context) }
|
183
|
+
@visible_enum_arrays[enum_defn]
|
184
|
+
end
|
185
|
+
|
186
|
+
def visible_enum_value?(enum_value, _ctx = nil)
|
187
|
+
@visible_enum_values ||= read_through { |ev| visible?(ev) }
|
188
|
+
@visible_enum_values[enum_value]
|
140
189
|
end
|
141
190
|
|
142
191
|
# @return [Array<GraphQL::InterfaceType>] Visible interfaces implemented by `obj_type`
|
143
192
|
def interfaces(obj_type)
|
144
|
-
@visible_interfaces ||= read_through { |t| t.interfaces(@context).select { |i|
|
193
|
+
@visible_interfaces ||= read_through { |t| t.interfaces(@context).select { |i| visible_type?(i) } }
|
145
194
|
@visible_interfaces[obj_type]
|
146
195
|
end
|
147
196
|
|
@@ -158,25 +207,52 @@ module GraphQL
|
|
158
207
|
end
|
159
208
|
end
|
160
209
|
|
161
|
-
|
162
|
-
|
163
|
-
def union_memberships(obj_type)
|
164
|
-
@unions ||= read_through { |obj_type| @schema.union_memberships(obj_type).select { |u| visible?(u) } }
|
165
|
-
@unions[obj_type]
|
166
|
-
end
|
167
|
-
|
168
|
-
def visible_argument?(arg_defn)
|
169
|
-
visible?(arg_defn) && visible_type?(arg_defn.type.unwrap)
|
170
|
-
end
|
171
|
-
|
172
|
-
def visible_field?(owner_type, field_defn)
|
210
|
+
# @param owner [Class, Module] If provided, confirm that field has the given owner.
|
211
|
+
def visible_field?(field_defn, _ctx = nil, owner = field_defn.owner)
|
173
212
|
# This field is visible in its own right
|
174
213
|
visible?(field_defn) &&
|
175
214
|
# This field's return type is visible
|
176
|
-
|
215
|
+
visible_and_reachable_type?(field_defn.type.unwrap) &&
|
177
216
|
# This field is either defined on this object type,
|
178
217
|
# or the interface it's inherited from is also visible
|
179
|
-
((field_defn.respond_to?(:owner) && field_defn.owner ==
|
218
|
+
((field_defn.respond_to?(:owner) && field_defn.owner == owner) || field_on_visible_interface?(field_defn, owner))
|
219
|
+
end
|
220
|
+
|
221
|
+
def visible_argument?(arg_defn, _ctx = nil)
|
222
|
+
visible?(arg_defn) && visible_and_reachable_type?(arg_defn.type.unwrap)
|
223
|
+
end
|
224
|
+
|
225
|
+
def visible_type?(type_defn, _ctx = nil)
|
226
|
+
@type_visibility ||= read_through { |type_defn| visible?(type_defn) }
|
227
|
+
@type_visibility[type_defn]
|
228
|
+
end
|
229
|
+
|
230
|
+
def visible_type_membership?(type_membership, _ctx = nil)
|
231
|
+
visible?(type_membership)
|
232
|
+
end
|
233
|
+
|
234
|
+
private
|
235
|
+
|
236
|
+
def visible_and_reachable_type?(type_defn)
|
237
|
+
@visible_and_reachable_type ||= read_through do |type_defn|
|
238
|
+
next false unless visible_type?(type_defn)
|
239
|
+
next true if root_type?(type_defn) || type_defn.introspection?
|
240
|
+
|
241
|
+
if type_defn.kind.union?
|
242
|
+
visible_possible_types?(type_defn) && (referenced?(type_defn) || orphan_type?(type_defn))
|
243
|
+
elsif type_defn.kind.interface?
|
244
|
+
visible_possible_types?(type_defn)
|
245
|
+
else
|
246
|
+
referenced?(type_defn) || visible_abstract_type?(type_defn)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
@visible_and_reachable_type[type_defn]
|
251
|
+
end
|
252
|
+
|
253
|
+
def union_memberships(obj_type)
|
254
|
+
@unions ||= read_through { |obj_type| @schema.union_memberships(obj_type).select { |u| visible?(u) } }
|
255
|
+
@unions[obj_type]
|
180
256
|
end
|
181
257
|
|
182
258
|
# We need this to tell whether a field was inherited by an interface
|
@@ -195,10 +271,10 @@ module GraphQL
|
|
195
271
|
any_interface_has_visible_field = false
|
196
272
|
ints = unfiltered_interfaces(type_defn)
|
197
273
|
ints.each do |interface_type|
|
198
|
-
if (iface_field_defn = interface_type.get_field(field_defn.graphql_name))
|
274
|
+
if (iface_field_defn = interface_type.get_field(field_defn.graphql_name, @context))
|
199
275
|
any_interface_has_field = true
|
200
276
|
|
201
|
-
if interfaces(type_defn).include?(interface_type) && visible_field?(
|
277
|
+
if interfaces(type_defn).include?(interface_type) && visible_field?(iface_field_defn, nil, interface_type)
|
202
278
|
any_interface_has_visible_field = true
|
203
279
|
end
|
204
280
|
end
|
@@ -215,23 +291,6 @@ module GraphQL
|
|
215
291
|
end
|
216
292
|
end
|
217
293
|
|
218
|
-
def visible_type?(type_defn)
|
219
|
-
@type_visibility ||= read_through do |type_defn|
|
220
|
-
next false unless visible?(type_defn)
|
221
|
-
next true if root_type?(type_defn) || type_defn.introspection?
|
222
|
-
|
223
|
-
if type_defn.kind.union?
|
224
|
-
visible_possible_types?(type_defn) && (referenced?(type_defn) || orphan_type?(type_defn))
|
225
|
-
elsif type_defn.kind.interface?
|
226
|
-
visible_possible_types?(type_defn)
|
227
|
-
else
|
228
|
-
referenced?(type_defn) || visible_abstract_type?(type_defn)
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
@type_visibility[type_defn]
|
233
|
-
end
|
234
|
-
|
235
294
|
def root_type?(type_defn)
|
236
295
|
@query == type_defn ||
|
237
296
|
@mutation == type_defn ||
|
@@ -259,7 +318,7 @@ module GraphQL
|
|
259
318
|
end
|
260
319
|
|
261
320
|
def visible_possible_types?(type_defn)
|
262
|
-
possible_types(type_defn).any? { |t|
|
321
|
+
possible_types(type_defn).any? { |t| visible_and_reachable_type?(t) }
|
263
322
|
end
|
264
323
|
|
265
324
|
def visible?(member)
|
@@ -274,6 +333,7 @@ module GraphQL
|
|
274
333
|
return @reachable_type_set if defined?(@reachable_type_set)
|
275
334
|
|
276
335
|
@reachable_type_set = Set.new
|
336
|
+
rt_hash = {}
|
277
337
|
|
278
338
|
unvisited_types = []
|
279
339
|
['query', 'mutation', 'subscription'].each do |op_name|
|
@@ -283,16 +343,16 @@ module GraphQL
|
|
283
343
|
unvisited_types.concat(@schema.introspection_system.types.values)
|
284
344
|
|
285
345
|
directives.each do |dir_class|
|
286
|
-
dir_class.
|
346
|
+
arguments(dir_class).each do |arg_defn|
|
287
347
|
arg_t = arg_defn.type.unwrap
|
288
|
-
if get_type(arg_t.graphql_name)
|
348
|
+
if get_type(arg_t.graphql_name) # rubocop:disable Development/ContextIsPassedCop -- `self` is query-aware
|
289
349
|
unvisited_types << arg_t
|
290
350
|
end
|
291
351
|
end
|
292
352
|
end
|
293
353
|
|
294
354
|
@schema.orphan_types.each do |orphan_type|
|
295
|
-
if get_type(orphan_type.graphql_name)
|
355
|
+
if get_type(orphan_type.graphql_name) == orphan_type # rubocop:disable Development/ContextIsPassedCop -- `self` is query-aware
|
296
356
|
unvisited_types << orphan_type
|
297
357
|
end
|
298
358
|
end
|
@@ -300,6 +360,10 @@ module GraphQL
|
|
300
360
|
until unvisited_types.empty?
|
301
361
|
type = unvisited_types.pop
|
302
362
|
if @reachable_type_set.add?(type)
|
363
|
+
type_by_name = rt_hash[type.graphql_name] ||= type
|
364
|
+
if type_by_name != type
|
365
|
+
raise DuplicateNamesError, "Found two visible type definitions for `#{type.graphql_name}`: #{type.inspect}, #{type_by_name.inspect}"
|
366
|
+
end
|
303
367
|
if type.kind.input_object?
|
304
368
|
# recurse into visible arguments
|
305
369
|
arguments(type).each do |argument|
|