graphql 1.12.18 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/mutation_generator.rb +1 -1
  3. data/lib/generators/graphql/object_generator.rb +2 -1
  4. data/lib/generators/graphql/relay.rb +19 -11
  5. data/lib/generators/graphql/templates/schema.erb +14 -2
  6. data/lib/generators/graphql/type_generator.rb +0 -1
  7. data/lib/graphql/analysis/ast/field_usage.rb +2 -2
  8. data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
  9. data/lib/graphql/analysis/ast/visitor.rb +4 -4
  10. data/lib/graphql/backtrace/table.rb +1 -1
  11. data/lib/graphql/dataloader/source.rb +30 -2
  12. data/lib/graphql/dataloader.rb +55 -22
  13. data/lib/graphql/deprecation.rb +1 -5
  14. data/lib/graphql/directive.rb +0 -4
  15. data/lib/graphql/enum_type.rb +5 -1
  16. data/lib/graphql/execution/errors.rb +1 -0
  17. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  18. data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -2
  19. data/lib/graphql/execution/interpreter/runtime.rb +20 -12
  20. data/lib/graphql/execution/lookahead.rb +2 -2
  21. data/lib/graphql/execution/multiplex.rb +1 -1
  22. data/lib/graphql/integer_encoding_error.rb +18 -2
  23. data/lib/graphql/introspection/directive_type.rb +1 -1
  24. data/lib/graphql/introspection/entry_points.rb +2 -2
  25. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  26. data/lib/graphql/introspection/field_type.rb +2 -2
  27. data/lib/graphql/introspection/input_value_type.rb +4 -4
  28. data/lib/graphql/introspection/schema_type.rb +2 -2
  29. data/lib/graphql/introspection/type_type.rb +10 -10
  30. data/lib/graphql/language/block_string.rb +0 -4
  31. data/lib/graphql/language/document_from_schema_definition.rb +4 -2
  32. data/lib/graphql/language/lexer.rb +0 -3
  33. data/lib/graphql/language/lexer.rl +0 -4
  34. data/lib/graphql/language/nodes.rb +2 -1
  35. data/lib/graphql/language/parser.rb +442 -434
  36. data/lib/graphql/language/parser.y +5 -4
  37. data/lib/graphql/language/printer.rb +6 -1
  38. data/lib/graphql/language/sanitized_printer.rb +5 -5
  39. data/lib/graphql/language/token.rb +0 -4
  40. data/lib/graphql/name_validator.rb +0 -4
  41. data/lib/graphql/pagination/connections.rb +35 -16
  42. data/lib/graphql/query/arguments.rb +1 -1
  43. data/lib/graphql/query/arguments_cache.rb +1 -1
  44. data/lib/graphql/query/context.rb +5 -2
  45. data/lib/graphql/query/literal_input.rb +1 -1
  46. data/lib/graphql/query/null_context.rb +12 -7
  47. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  48. data/lib/graphql/query/validation_pipeline.rb +1 -1
  49. data/lib/graphql/query/variables.rb +5 -1
  50. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  51. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  52. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  53. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  54. data/lib/graphql/rubocop.rb +4 -0
  55. data/lib/graphql/schema/addition.rb +37 -28
  56. data/lib/graphql/schema/argument.rb +6 -6
  57. data/lib/graphql/schema/build_from_definition.rb +5 -5
  58. data/lib/graphql/schema/directive/feature.rb +1 -1
  59. data/lib/graphql/schema/directive/flagged.rb +2 -2
  60. data/lib/graphql/schema/directive/include.rb +1 -1
  61. data/lib/graphql/schema/directive/skip.rb +1 -1
  62. data/lib/graphql/schema/directive/transform.rb +1 -1
  63. data/lib/graphql/schema/directive.rb +2 -2
  64. data/lib/graphql/schema/enum.rb +57 -9
  65. data/lib/graphql/schema/enum_value.rb +4 -0
  66. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  67. data/lib/graphql/schema/field.rb +92 -17
  68. data/lib/graphql/schema/find_inherited_value.rb +1 -0
  69. data/lib/graphql/schema/finder.rb +5 -5
  70. data/lib/graphql/schema/input_object.rb +6 -5
  71. data/lib/graphql/schema/interface.rb +8 -19
  72. data/lib/graphql/schema/member/accepts_definition.rb +8 -1
  73. data/lib/graphql/schema/member/build_type.rb +0 -4
  74. data/lib/graphql/schema/member/has_arguments.rb +62 -14
  75. data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
  76. data/lib/graphql/schema/member/has_fields.rb +76 -18
  77. data/lib/graphql/schema/member/has_interfaces.rb +90 -0
  78. data/lib/graphql/schema/member.rb +1 -0
  79. data/lib/graphql/schema/object.rb +7 -74
  80. data/lib/graphql/schema/printer.rb +1 -1
  81. data/lib/graphql/schema/relay_classic_mutation.rb +29 -3
  82. data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
  83. data/lib/graphql/schema/resolver.rb +29 -5
  84. data/lib/graphql/schema/subscription.rb +11 -1
  85. data/lib/graphql/schema/type_expression.rb +1 -1
  86. data/lib/graphql/schema/type_membership.rb +18 -4
  87. data/lib/graphql/schema/union.rb +6 -1
  88. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  89. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  90. data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
  91. data/lib/graphql/schema/validator/format_validator.rb +4 -5
  92. data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
  93. data/lib/graphql/schema/validator/length_validator.rb +5 -3
  94. data/lib/graphql/schema/validator/numericality_validator.rb +8 -1
  95. data/lib/graphql/schema/validator.rb +36 -25
  96. data/lib/graphql/schema/warden.rb +116 -52
  97. data/lib/graphql/schema.rb +87 -15
  98. data/lib/graphql/static_validation/base_visitor.rb +5 -5
  99. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  100. data/lib/graphql/static_validation/error.rb +3 -1
  101. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  102. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  103. data/lib/graphql/static_validation/rules/fields_will_merge.rb +41 -22
  104. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  105. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  106. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
  107. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +7 -7
  108. data/lib/graphql/static_validation/validation_context.rb +2 -1
  109. data/lib/graphql/string_encoding_error.rb +13 -3
  110. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +6 -4
  111. data/lib/graphql/subscriptions/event.rb +65 -13
  112. data/lib/graphql/subscriptions.rb +17 -19
  113. data/lib/graphql/types/int.rb +1 -1
  114. data/lib/graphql/types/relay/has_node_field.rb +1 -1
  115. data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
  116. data/lib/graphql/types/string.rb +1 -1
  117. data/lib/graphql/unauthorized_error.rb +1 -1
  118. data/lib/graphql/version.rb +1 -1
  119. data/lib/graphql.rb +9 -31
  120. metadata +12 -5
@@ -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.
@@ -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?(_ctx)
30
- true
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
@@ -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 type_membership.visible?(context)
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,10 @@ module GraphQL
28
29
  end
29
30
  end
30
31
 
32
+ def all_possible_types
33
+ type_memberships.map(&:object_type)
34
+ end
35
+
31
36
  def to_graphql
32
37
  type_defn = GraphQL::UnionType.new
33
38
  type_defn.name = graphql_name
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Validator
6
+ # Use this to specifically reject values that respond to `.blank?` and respond truthy for that method.
7
+ #
8
+ # @example Require a non-empty string for an argument
9
+ # argument :name, String, required: true, validate: { allow_blank: false }
10
+ class AllowBlankValidator < Validator
11
+ def initialize(allow_blank_positional, allow_blank: nil, message: "%{validated} can't be blank", **default_options)
12
+ @message = message
13
+ super(**default_options)
14
+ @allow_blank = allow_blank.nil? ? allow_blank_positional : allow_blank
15
+ end
16
+
17
+ def validate(_object, _context, value)
18
+ if value.respond_to?(:blank?) && value.blank?
19
+ if (value.nil? && @allow_null) || @allow_blank
20
+ # pass
21
+ else
22
+ @message
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Validator
6
+ # Use this to specifically reject or permit `nil` values (given as `null` from GraphQL).
7
+ #
8
+ # @example require a non-null value for an argument if it is provided
9
+ # argument :name, String, required: false, validates: { allow_null: false }
10
+ class AllowNullValidator < Validator
11
+ MESSAGE = "%{validated} can't be null"
12
+ def initialize(allow_null_positional, allow_null: nil, message: MESSAGE, **default_options)
13
+ @message = message
14
+ super(**default_options)
15
+ @allow_null = allow_null.nil? ? allow_null_positional : allow_null
16
+ end
17
+
18
+ def validate(_object, _context, value)
19
+ if value.nil? && !@allow_null
20
+ @message
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -21,7 +21,9 @@ module GraphQL
21
21
  end
22
22
 
23
23
  def validate(_object, _context, value)
24
- if @in_list.include?(value)
24
+ if permitted_empty_value?(value)
25
+ # pass
26
+ elsif @in_list.include?(value)
25
27
  @message
26
28
  end
27
29
  end
@@ -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,10 @@ module GraphQL
38
34
  end
39
35
 
40
36
  def validate(_object, _context, value)
41
- if (@with_pattern && !value.match?(@with_pattern)) ||
37
+ if permitted_empty_value?(value)
38
+ # Do nothing
39
+ elsif value.nil? ||
40
+ (@with_pattern && !value.match?(@with_pattern)) ||
42
41
  (@without_pattern && value.match?(@without_pattern))
43
42
  @message
44
43
  end
@@ -23,7 +23,9 @@ module GraphQL
23
23
  end
24
24
 
25
25
  def validate(_object, _context, value)
26
- if !@in_list.include?(value)
26
+ if permitted_empty_value?(value)
27
+ # pass
28
+ elsif !@in_list.include?(value)
27
29
  @message
28
30
  end
29
31
  end
@@ -43,11 +43,13 @@ module GraphQL
43
43
  end
44
44
 
45
45
  def validate(_object, _context, value)
46
- if @maximum && value.length > @maximum
46
+ return if permitted_empty_value?(value) # pass in this case
47
+ length = value.nil? ? 0 : value.length
48
+ if @maximum && length > @maximum
47
49
  partial_format(@too_long, { count: @maximum })
48
- elsif @minimum && value.length < @minimum
50
+ elsif @minimum && length < @minimum
49
51
  partial_format(@too_short, { count: @minimum })
50
- elsif @is && value.length != @is
52
+ elsif @is && length != @is
51
53
  partial_format(@wrong_length, { count: @is })
52
54
  end
53
55
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module GraphQL
2
3
  class Schema
3
4
  class Validator
@@ -32,6 +33,7 @@ module GraphQL
32
33
  equal_to: nil, other_than: nil,
33
34
  odd: nil, even: nil, within: nil,
34
35
  message: "%{validated} must be %{comparison} %{target}",
36
+ null_message: Validator::AllowNullValidator::MESSAGE,
35
37
  **default_options
36
38
  )
37
39
 
@@ -45,11 +47,16 @@ module GraphQL
45
47
  @even = even
46
48
  @within = within
47
49
  @message = message
50
+ @null_message = null_message
48
51
  super(**default_options)
49
52
  end
50
53
 
51
54
  def validate(object, context, value)
52
- if @greater_than && value <= @greater_than
55
+ if permitted_empty_value?(value)
56
+ # pass in this case
57
+ elsif value.nil? # @allow_null is handled in the parent class
58
+ @null_message
59
+ elsif @greater_than && value <= @greater_than
53
60
  partial_format(@message, { comparison: "greater than", target: @greater_than })
54
61
  elsif @greater_than_or_equal_to && value < @greater_than_or_equal_to
55
62
  partial_format(@message, { comparison: "greater than or equal to", target: @greater_than_or_equal_to })
@@ -7,7 +7,6 @@ module GraphQL
7
7
  # @return [GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class<GraphQL::Schema::InputObject>]
8
8
  attr_reader :validated
9
9
 
10
- # TODO should this implement `if:` and `unless:` ?
11
10
  # @param validated [GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class<GraphQL::Schema::InputObject>] The argument or argument owner this validator is attached to
12
11
  # @param allow_blank [Boolean] if `true`, then objects that respond to `.blank?` and return true for `.blank?` will skip this validation
13
12
  # @param allow_null [Boolean] if `true`, then incoming `null`s will skip this validation
@@ -25,26 +24,6 @@ module GraphQL
25
24
  raise GraphQL::RequiredImplementationMissingError, "Validator classes should implement #validate"
26
25
  end
27
26
 
28
- # This is called by the validation system and eventually calls {#validate}.
29
- # @api private
30
- def apply(object, context, value)
31
- if value.nil?
32
- if @allow_null
33
- nil # skip this
34
- else
35
- "%{validated} can't be null"
36
- end
37
- elsif value.respond_to?(:blank?) && value.blank?
38
- if @allow_blank
39
- nil # skip this
40
- else
41
- "%{validated} can't be blank"
42
- end
43
- else
44
- validate(object, context, value)
45
- end
46
- end
47
-
48
27
  # This is like `String#%`, but it supports the case that only some of `string`'s
49
28
  # values are present in `substitutions`
50
29
  def partial_format(string, substitutions)
@@ -55,6 +34,12 @@ module GraphQL
55
34
  string
56
35
  end
57
36
 
37
+ # @return [Boolean] `true` if `value` is `nil` and this validator has `allow_null: true` or if value is `.blank?` and this validator has `allow_blank: true`
38
+ def permitted_empty_value?(value)
39
+ (value.nil? && @allow_null) ||
40
+ (@allow_blank && value.respond_to?(:blank?) && value.blank?)
41
+ end
42
+
58
43
  # @param schema_member [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class<GraphQL::Schema::InputObject>]
59
44
  # @param validates_hash [Hash{Symbol => Hash}, Hash{Class => Hash} nil] A configuration passed as `validates:`
60
45
  # @return [Array<Validator>]
@@ -62,6 +47,24 @@ module GraphQL
62
47
  if validates_hash.nil? || validates_hash.empty?
63
48
  EMPTY_ARRAY
64
49
  else
50
+ validates_hash = validates_hash.dup
51
+ allow_null = validates_hash.delete(:allow_null)
52
+ allow_blank = validates_hash.delete(:allow_blank)
53
+
54
+ # This could be {...}.compact on Ruby 2.4+
55
+ default_options = {}
56
+ if !allow_null.nil?
57
+ default_options[:allow_null] = allow_null
58
+ end
59
+ if !allow_blank.nil?
60
+ default_options[:allow_blank] = allow_blank
61
+ end
62
+
63
+ # allow_nil or allow_blank are the _only_ validations:
64
+ if validates_hash.empty?
65
+ validates_hash = default_options
66
+ end
67
+
65
68
  validates_hash.map do |validator_name, options|
66
69
  validator_class = case validator_name
67
70
  when Class
@@ -69,7 +72,11 @@ module GraphQL
69
72
  else
70
73
  all_validators[validator_name] || raise(ArgumentError, "unknown validation: #{validator_name.inspect}")
71
74
  end
72
- validator_class.new(validated: schema_member, **options)
75
+ if options.is_a?(Hash)
76
+ validator_class.new(validated: schema_member, **(default_options.merge(options)))
77
+ else
78
+ validator_class.new(options, validated: schema_member, **default_options)
79
+ end
73
80
  end
74
81
  end
75
82
  end
@@ -122,10 +129,10 @@ module GraphQL
122
129
 
123
130
  validators.each do |validator|
124
131
  validated = as || validator.validated
125
- errors = validator.apply(object, context, value)
132
+ errors = validator.validate(object, context, value)
126
133
  if errors &&
127
- (errors.is_a?(Array) && errors != EMPTY_ARRAY) ||
128
- (errors.is_a?(String))
134
+ (errors.is_a?(Array) && errors != EMPTY_ARRAY) ||
135
+ (errors.is_a?(String))
129
136
  if all_errors.frozen? # It's empty
130
137
  all_errors = []
131
138
  end
@@ -161,3 +168,7 @@ require "graphql/schema/validator/exclusion_validator"
161
168
  GraphQL::Schema::Validator.install(:exclusion, GraphQL::Schema::Validator::ExclusionValidator)
162
169
  require "graphql/schema/validator/required_validator"
163
170
  GraphQL::Schema::Validator.install(:required, GraphQL::Schema::Validator::RequiredValidator)
171
+ require "graphql/schema/validator/allow_null_validator"
172
+ GraphQL::Schema::Validator.install(:allow_null, GraphQL::Schema::Validator::AllowNullValidator)
173
+ require "graphql/schema/validator/allow_blank_validator"
174
+ GraphQL::Schema::Validator.install(:allow_blank, GraphQL::Schema::Validator::AllowBlankValidator)
@@ -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 visible_type?(t)
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 && visible_type?(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?(type, field_defn)
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| visible_type?(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).each_value.select { |f| visible_field?(t, f) } }
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
- @visible_enum_values ||= read_through { |e| e.values.each_value.select { |enum_value_defn| visible?(enum_value_defn) } }
139
- @visible_enum_values[enum_defn]
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| visible?(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
- private
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
- visible_type?(field_defn.type.unwrap) &&
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 == owner_type) || field_on_visible_interface?(field_defn, owner_type))
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?(interface_type, iface_field_defn)
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| visible_type?(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.arguments.values.each do |arg_defn|
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|