graphql 1.12.16 → 1.13.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +3 -1
  3. data/lib/generators/graphql/install_generator.rb +9 -2
  4. data/lib/generators/graphql/mutation_generator.rb +1 -1
  5. data/lib/generators/graphql/object_generator.rb +2 -1
  6. data/lib/generators/graphql/relay.rb +19 -11
  7. data/lib/generators/graphql/templates/schema.erb +14 -2
  8. data/lib/generators/graphql/type_generator.rb +0 -1
  9. data/lib/graphql/analysis/ast/field_usage.rb +3 -3
  10. data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
  11. data/lib/graphql/analysis/ast/visitor.rb +4 -4
  12. data/lib/graphql/backtrace/table.rb +1 -1
  13. data/lib/graphql/base_type.rb +4 -2
  14. data/lib/graphql/boolean_type.rb +1 -1
  15. data/lib/graphql/dataloader/source.rb +50 -2
  16. data/lib/graphql/dataloader.rb +93 -37
  17. data/lib/graphql/define/instance_definable.rb +1 -1
  18. data/lib/graphql/deprecated_dsl.rb +11 -3
  19. data/lib/graphql/deprecation.rb +1 -5
  20. data/lib/graphql/directive/deprecated_directive.rb +1 -1
  21. data/lib/graphql/directive/include_directive.rb +1 -1
  22. data/lib/graphql/directive/skip_directive.rb +1 -1
  23. data/lib/graphql/directive.rb +0 -4
  24. data/lib/graphql/enum_type.rb +5 -1
  25. data/lib/graphql/execution/errors.rb +1 -0
  26. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  27. data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -2
  28. data/lib/graphql/execution/interpreter/runtime.rb +39 -23
  29. data/lib/graphql/execution/lookahead.rb +2 -2
  30. data/lib/graphql/execution/multiplex.rb +4 -1
  31. data/lib/graphql/float_type.rb +1 -1
  32. data/lib/graphql/id_type.rb +1 -1
  33. data/lib/graphql/int_type.rb +1 -1
  34. data/lib/graphql/integer_encoding_error.rb +18 -2
  35. data/lib/graphql/introspection/directive_type.rb +1 -1
  36. data/lib/graphql/introspection/entry_points.rb +2 -2
  37. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  38. data/lib/graphql/introspection/field_type.rb +2 -2
  39. data/lib/graphql/introspection/input_value_type.rb +10 -4
  40. data/lib/graphql/introspection/schema_type.rb +2 -2
  41. data/lib/graphql/introspection/type_type.rb +10 -10
  42. data/lib/graphql/language/block_string.rb +2 -6
  43. data/lib/graphql/language/document_from_schema_definition.rb +4 -2
  44. data/lib/graphql/language/lexer.rb +0 -3
  45. data/lib/graphql/language/lexer.rl +0 -4
  46. data/lib/graphql/language/nodes.rb +12 -2
  47. data/lib/graphql/language/parser.rb +442 -434
  48. data/lib/graphql/language/parser.y +5 -4
  49. data/lib/graphql/language/printer.rb +6 -1
  50. data/lib/graphql/language/sanitized_printer.rb +5 -5
  51. data/lib/graphql/language/token.rb +0 -4
  52. data/lib/graphql/name_validator.rb +0 -4
  53. data/lib/graphql/pagination/connections.rb +35 -16
  54. data/lib/graphql/query/arguments.rb +1 -1
  55. data/lib/graphql/query/arguments_cache.rb +1 -1
  56. data/lib/graphql/query/context.rb +15 -2
  57. data/lib/graphql/query/literal_input.rb +1 -1
  58. data/lib/graphql/query/null_context.rb +12 -7
  59. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  60. data/lib/graphql/query/validation_pipeline.rb +1 -1
  61. data/lib/graphql/query/variables.rb +5 -1
  62. data/lib/graphql/query.rb +4 -0
  63. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  64. data/lib/graphql/relay/global_id_resolve.rb +1 -1
  65. data/lib/graphql/relay/page_info.rb +1 -1
  66. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  67. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  68. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  69. data/lib/graphql/rubocop.rb +4 -0
  70. data/lib/graphql/schema/addition.rb +37 -28
  71. data/lib/graphql/schema/argument.rb +79 -34
  72. data/lib/graphql/schema/build_from_definition.rb +5 -5
  73. data/lib/graphql/schema/directive/feature.rb +1 -1
  74. data/lib/graphql/schema/directive/flagged.rb +2 -2
  75. data/lib/graphql/schema/directive/include.rb +1 -1
  76. data/lib/graphql/schema/directive/skip.rb +1 -1
  77. data/lib/graphql/schema/directive/transform.rb +1 -1
  78. data/lib/graphql/schema/directive.rb +7 -3
  79. data/lib/graphql/schema/enum.rb +60 -10
  80. data/lib/graphql/schema/enum_value.rb +6 -0
  81. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  82. data/lib/graphql/schema/field.rb +140 -42
  83. data/lib/graphql/schema/field_extension.rb +52 -2
  84. data/lib/graphql/schema/find_inherited_value.rb +1 -0
  85. data/lib/graphql/schema/finder.rb +5 -5
  86. data/lib/graphql/schema/input_object.rb +13 -14
  87. data/lib/graphql/schema/interface.rb +11 -20
  88. data/lib/graphql/schema/introspection_system.rb +1 -1
  89. data/lib/graphql/schema/list.rb +3 -1
  90. data/lib/graphql/schema/member/accepts_definition.rb +15 -3
  91. data/lib/graphql/schema/member/build_type.rb +0 -4
  92. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  93. data/lib/graphql/schema/member/has_arguments.rb +145 -57
  94. data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
  95. data/lib/graphql/schema/member/has_fields.rb +76 -18
  96. data/lib/graphql/schema/member/has_interfaces.rb +90 -0
  97. data/lib/graphql/schema/member.rb +1 -0
  98. data/lib/graphql/schema/non_null.rb +3 -1
  99. data/lib/graphql/schema/object.rb +10 -75
  100. data/lib/graphql/schema/printer.rb +1 -1
  101. data/lib/graphql/schema/relay_classic_mutation.rb +37 -3
  102. data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
  103. data/lib/graphql/schema/resolver.rb +49 -64
  104. data/lib/graphql/schema/scalar.rb +2 -0
  105. data/lib/graphql/schema/subscription.rb +17 -9
  106. data/lib/graphql/schema/traversal.rb +1 -1
  107. data/lib/graphql/schema/type_expression.rb +1 -1
  108. data/lib/graphql/schema/type_membership.rb +18 -4
  109. data/lib/graphql/schema/union.rb +8 -1
  110. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  111. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  112. data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
  113. data/lib/graphql/schema/validator/format_validator.rb +4 -5
  114. data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
  115. data/lib/graphql/schema/validator/length_validator.rb +5 -3
  116. data/lib/graphql/schema/validator/numericality_validator.rb +13 -2
  117. data/lib/graphql/schema/validator.rb +33 -25
  118. data/lib/graphql/schema/warden.rb +116 -52
  119. data/lib/graphql/schema.rb +124 -27
  120. data/lib/graphql/static_validation/base_visitor.rb +8 -5
  121. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  122. data/lib/graphql/static_validation/error.rb +3 -1
  123. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  124. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  125. data/lib/graphql/static_validation/rules/fields_will_merge.rb +52 -26
  126. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  127. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  128. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
  129. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
  130. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +7 -7
  131. data/lib/graphql/static_validation/validation_context.rb +8 -2
  132. data/lib/graphql/static_validation/validator.rb +15 -12
  133. data/lib/graphql/string_encoding_error.rb +13 -3
  134. data/lib/graphql/string_type.rb +1 -1
  135. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +15 -5
  136. data/lib/graphql/subscriptions/event.rb +66 -13
  137. data/lib/graphql/subscriptions/serialize.rb +1 -1
  138. data/lib/graphql/subscriptions.rb +17 -19
  139. data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
  140. data/lib/graphql/types/int.rb +1 -1
  141. data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
  142. data/lib/graphql/types/relay/default_relay.rb +5 -1
  143. data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
  144. data/lib/graphql/types/relay/has_node_field.rb +1 -1
  145. data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
  146. data/lib/graphql/types/string.rb +1 -1
  147. data/lib/graphql/unauthorized_error.rb +1 -1
  148. data/lib/graphql/version.rb +1 -1
  149. data/lib/graphql.rb +10 -32
  150. data/readme.md +1 -1
  151. metadata +13 -6
@@ -7,6 +7,7 @@ module GraphQL
7
7
  class Object < GraphQL::Schema::Member
8
8
  extend GraphQL::Schema::Member::AcceptsDefinition
9
9
  extend GraphQL::Schema::Member::HasFields
10
+ extend GraphQL::Schema::Member::HasInterfaces
10
11
 
11
12
  # @return [Object] the application object this type is wrapping
12
13
  attr_reader :object
@@ -103,84 +104,16 @@ module GraphQL
103
104
  super
104
105
  end
105
106
 
106
- def implements(*new_interfaces, **options)
107
- new_memberships = []
108
- new_interfaces.each do |int|
109
- if int.is_a?(Module)
110
- unless int.include?(GraphQL::Schema::Interface)
111
- raise "#{int} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules."
112
- end
113
-
114
- new_memberships << int.type_membership_class.new(int, self, **options)
115
-
116
- # Include the methods here,
117
- # `.fields` will use the inheritance chain
118
- # to find inherited fields
119
- include(int)
120
- elsif int.is_a?(GraphQL::InterfaceType)
121
- new_memberships << int.type_membership_class.new(int, self, **options)
122
- elsif int.is_a?(String) || int.is_a?(GraphQL::Schema::LateBoundType)
123
- if options.any?
124
- raise ArgumentError, "`implements(...)` doesn't support options with late-loaded types yet. Remove #{options} and open an issue to request this feature."
125
- end
126
- new_memberships << int
127
- else
128
- raise ArgumentError, "Unexpected interface definition (expected module): #{int} (#{int.class})"
129
- end
130
- end
131
-
132
- # Remove any interfaces which are being replaced (late-bound types are updated in place this way)
133
- own_interface_type_memberships.reject! { |old_i_m|
134
- old_int_type = old_i_m.respond_to?(:abstract_type) ? old_i_m.abstract_type : old_i_m
135
- old_name = Schema::Member::BuildType.to_type_name(old_int_type)
136
-
137
- new_memberships.any? { |new_i_m|
138
- new_int_type = new_i_m.respond_to?(:abstract_type) ? new_i_m.abstract_type : new_i_m
139
- new_name = Schema::Member::BuildType.to_type_name(new_int_type)
140
-
141
- new_name == old_name
142
- }
143
- }
144
- own_interface_type_memberships.concat(new_memberships)
145
- end
146
-
147
- def own_interface_type_memberships
148
- @own_interface_type_memberships ||= []
149
- end
150
-
151
- def interface_type_memberships
152
- own_interface_type_memberships + (superclass.respond_to?(:interface_type_memberships) ? superclass.interface_type_memberships : [])
153
- end
154
-
155
- # param context [Query::Context] If omitted, skip filtering.
156
- def interfaces(context = GraphQL::Query::NullContext)
157
- visible_interfaces = []
158
- unfiltered = context == GraphQL::Query::NullContext
159
- own_interface_type_memberships.each do |type_membership|
160
- # During initialization, `type_memberships` can hold late-bound types
161
- case type_membership
162
- when String, Schema::LateBoundType
163
- visible_interfaces << type_membership
164
- when Schema::TypeMembership
165
- if unfiltered || type_membership.visible?(context)
166
- visible_interfaces << type_membership.abstract_type
167
- end
168
- else
169
- raise "Invariant: Unexpected type_membership #{type_membership.class}: #{type_membership.inspect}"
170
- end
171
- end
172
- visible_interfaces + (superclass <= GraphQL::Schema::Object ? superclass.interfaces(context) : [])
173
- end
174
-
175
107
  # @return [Hash<String => GraphQL::Schema::Field>] All of this object's fields, indexed by name
176
108
  # @see get_field A faster way to find one field by name ({#fields} merges hashes of inherited fields; {#get_field} just looks up one field.)
177
- def fields
109
+ def fields(context = GraphQL::Query::NullContext)
178
110
  all_fields = super
111
+ # This adds fields from legacy-style interfaces only.
112
+ # Multi-fields are not supported here.
179
113
  interfaces.each do |int|
180
- # Include legacy-style interfaces, too
181
114
  if int.is_a?(GraphQL::InterfaceType)
182
115
  int_f = {}
183
- int.fields.each do |name, legacy_field|
116
+ int.fields.each do |name, legacy_field| # rubocop:disable Development/ContextIsPassedCop -- legacy-related
184
117
  int_f[name] = field_class.from_options(name, field: legacy_field)
185
118
  end
186
119
  all_fields = int_f.merge(all_fields)
@@ -189,6 +122,8 @@ module GraphQL
189
122
  all_fields
190
123
  end
191
124
 
125
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
126
+
192
127
  # @return [GraphQL::ObjectType]
193
128
  def to_graphql
194
129
  obj_type = GraphQL::ObjectType.new
@@ -198,9 +133,9 @@ module GraphQL
198
133
  obj_type.introspection = introspection
199
134
  obj_type.mutation = mutation
200
135
  obj_type.ast_node = ast_node
201
- fields.each do |field_name, field_inst|
202
- field_defn = field_inst.to_graphql
203
- obj_type.fields[field_defn.name] = field_defn
136
+ fields.each do |field_name, field_inst| # rubocop:disable Development/ContextIsPassedCop -- legacy-related
137
+ field_defn = field_inst.to_graphql(silence_deprecation_warning: true)
138
+ obj_type.fields[field_defn.name] = field_defn # rubocop:disable Development/ContextIsPassedCop -- legacy-related
204
139
  end
205
140
 
206
141
  obj_type.metadata[:type_class] = self
@@ -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, null: true
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.", null: true)
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,20 +140,29 @@ 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 = arguments
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
- mutation_args.each do |_name, arg|
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
129
155
  end
130
156
  end
131
157
  end
158
+
159
+ private
160
+
161
+ def authorize_arguments(args, values)
162
+ # remove the `input` wrapper to match values
163
+ input_args = args["input"].type.unwrap.arguments(context)
164
+ super(input_args, values)
165
+ end
132
166
  end
133
167
  end
134
168
  end
@@ -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 = 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 |name, f|
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.
@@ -28,7 +28,7 @@ module GraphQL
28
28
  include Schema::Member::HasPath
29
29
  extend Schema::Member::HasPath
30
30
 
31
- # @param object [Object] the initialize object, pass to {Query.initialize} as `root_value`
31
+ # @param object [Object] The application object that this field is being resolved on
32
32
  # @param context [GraphQL::Query::Context]
33
33
  # @param field [GraphQL::Schema::Field]
34
34
  def initialize(object:, context:, field:)
@@ -37,10 +37,9 @@ 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
- @arguments_loads_as_type = self.class.arguments_loads_as_type
44
43
  @prepared_arguments = nil
45
44
  end
46
45
 
@@ -110,7 +109,7 @@ module GraphQL
110
109
  public_send(self.class.resolve_method)
111
110
  end
112
111
  else
113
- nil
112
+ raise GraphQL::UnauthorizedFieldError.new(context: context, object: object, type: field.owner, field: field)
114
113
  end
115
114
  end
116
115
  end
@@ -146,7 +145,25 @@ module GraphQL
146
145
  # @raise [GraphQL::UnauthorizedError] To signal an authorization failure
147
146
  # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
148
147
  def authorized?(**inputs)
149
- self.class.arguments.each_value do |argument|
148
+ arg_owner = @field # || self.class
149
+ args = arg_owner.arguments(context)
150
+ authorize_arguments(args, inputs)
151
+ end
152
+
153
+ # Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
154
+ #
155
+ # By default, the error is re-raised and passed along to {{Schema.unauthorized_object}}.
156
+ #
157
+ # Any value returned here will be used _instead of_ of the loaded object.
158
+ # @param err [GraphQL::UnauthorizedError]
159
+ def unauthorized_object(err)
160
+ raise err
161
+ end
162
+
163
+ private
164
+
165
+ def authorize_arguments(args, inputs)
166
+ args.each_value do |argument|
150
167
  arg_keyword = argument.keyword
151
168
  if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
152
169
  arg_auth, err = argument.authorized?(self, arg_value, context)
@@ -161,8 +178,6 @@ module GraphQL
161
178
  end
162
179
  end
163
180
 
164
- private
165
-
166
181
  def load_arguments(args)
167
182
  prepared_args = {}
168
183
  prepare_lazies = []
@@ -170,18 +185,14 @@ module GraphQL
170
185
  args.each do |key, value|
171
186
  arg_defn = @arguments_by_keyword[key]
172
187
  if arg_defn
173
- if value.nil?
174
- prepared_args[key] = value
175
- else
176
- prepped_value = prepared_args[key] = load_argument(key, value)
177
- if context.schema.lazy?(prepped_value)
178
- prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
179
- prepared_args[key] = finished_prepped_value
180
- end
188
+ prepped_value = prepared_args[key] = arg_defn.load_and_authorize_value(self, value, context)
189
+ if context.schema.lazy?(prepped_value)
190
+ prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
191
+ prepared_args[key] = finished_prepped_value
181
192
  end
182
193
  end
183
194
  else
184
- # These are `extras: [...]`
195
+ # these are `extras:`
185
196
  prepared_args[key] = value
186
197
  end
187
198
  end
@@ -194,8 +205,8 @@ module GraphQL
194
205
  end
195
206
  end
196
207
 
197
- def load_argument(name, value)
198
- public_send("load_#{name}", value)
208
+ def get_argument(name, context = GraphQL::Query::NullContext)
209
+ self.class.get_argument(name, context)
199
210
  end
200
211
 
201
212
  class << self
@@ -218,8 +229,10 @@ module GraphQL
218
229
  own_extras + (superclass.respond_to?(:extras) ? superclass.extras : [])
219
230
  end
220
231
 
221
- # Specifies whether or not the field is nullable. Defaults to `true`
222
- # TODO unify with {#type}
232
+ # If `true` (default), then the return type for this resolver will be nullable.
233
+ # If `false`, then the return type is non-null.
234
+ #
235
+ # @see #type which sets the return type of this field and accepts a `null:` option
223
236
  # @param allow_null [Boolean] Whether or not the response can be null
224
237
  def null(allow_null = nil)
225
238
  if !allow_null.nil?
@@ -298,13 +311,27 @@ module GraphQL
298
311
  end
299
312
 
300
313
  def field_options
314
+
315
+ all_args = {}
316
+ all_argument_definitions.each do |arg|
317
+ if (prev_entry = all_args[arg.graphql_name])
318
+ if prev_entry.is_a?(Array)
319
+ prev_entry << arg
320
+ else
321
+ all_args[arg.graphql_name] = [prev_entry, arg]
322
+ end
323
+ else
324
+ all_args[arg.graphql_name] = arg
325
+ end
326
+ end
327
+
301
328
  field_opts = {
302
329
  type: type_expr,
303
330
  description: description,
304
331
  extras: extras,
305
332
  resolver_method: :resolve_with_support,
306
333
  resolver_class: self,
307
- arguments: arguments,
334
+ arguments: all_args,
308
335
  null: null,
309
336
  complexity: complexity,
310
337
  broadcastable: broadcastable?,
@@ -332,47 +359,9 @@ module GraphQL
332
359
  # also add some preparation hook methods which will be used for this argument
333
360
  # @see {GraphQL::Schema::Argument#initialize} for the signature
334
361
  def argument(*args, **kwargs, &block)
335
- loads = kwargs[:loads]
336
362
  # Use `from_resolver: true` to short-circuit the InputObject's own `loads:` implementation
337
363
  # so that we can support `#load_{x}` methods below.
338
- arg_defn = super(*args, from_resolver: true, **kwargs)
339
- own_arguments_loads_as_type[arg_defn.keyword] = loads if loads
340
-
341
- if !method_defined?(:"load_#{arg_defn.keyword}")
342
- if loads && arg_defn.type.list?
343
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
344
- def load_#{arg_defn.keyword}(values)
345
- argument = @arguments_by_keyword[:#{arg_defn.keyword}]
346
- lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
347
- context.schema.after_lazy(values) do |values2|
348
- GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, lookup_as_type, value, context) })
349
- end
350
- end
351
- RUBY
352
- elsif loads
353
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
354
- def load_#{arg_defn.keyword}(value)
355
- argument = @arguments_by_keyword[:#{arg_defn.keyword}]
356
- lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
357
- load_application_object(argument, lookup_as_type, value, context)
358
- end
359
- RUBY
360
- else
361
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
362
- def load_#{arg_defn.keyword}(value)
363
- value
364
- end
365
- RUBY
366
- end
367
- end
368
-
369
- arg_defn
370
- end
371
-
372
- # @api private
373
- def arguments_loads_as_type
374
- inherited_lookups = superclass.respond_to?(:arguments_loads_as_type) ? superclass.arguments_loads_as_type : {}
375
- inherited_lookups.merge(own_arguments_loads_as_type)
364
+ super(*args, from_resolver: true, **kwargs)
376
365
  end
377
366
 
378
367
  # Registers new extension
@@ -408,10 +397,6 @@ module GraphQL
408
397
  def own_extensions
409
398
  @own_extensions
410
399
  end
411
-
412
- def own_arguments_loads_as_type
413
- @own_arguments_loads_as_type ||= {}
414
- end
415
400
  end
416
401
  end
417
402
  end
@@ -14,6 +14,8 @@ module GraphQL
14
14
  val
15
15
  end
16
16
 
17
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
18
+
17
19
  def to_graphql
18
20
  type_defn = GraphQL::ScalarType.new
19
21
  type_defn.name = graphql_name
@@ -14,7 +14,7 @@ module GraphQL
14
14
  class Subscription < GraphQL::Schema::Resolver
15
15
  extend GraphQL::Schema::Resolver::HasPayloadType
16
16
  extend GraphQL::Schema::Member::HasFields
17
-
17
+ NO_UPDATE = :no_update
18
18
  # The generated payload type is required; If there's no payload,
19
19
  # propagate null.
20
20
  null false
@@ -58,11 +58,9 @@ module GraphQL
58
58
  end
59
59
  end
60
60
 
61
- # Default implementation returns the root object.
61
+ # The default implementation returns nothing on subscribe.
62
62
  # Override it to return an object or
63
- # `:no_response` to return nothing.
64
- #
65
- # The default is `:no_response`.
63
+ # `:no_response` to (explicitly) return nothing.
66
64
  def subscribe(args = {})
67
65
  :no_response
68
66
  end
@@ -70,7 +68,7 @@ module GraphQL
70
68
  # Wrap the user-provided `#update` hook
71
69
  def resolve_update(**args)
72
70
  ret_val = args.any? ? update(**args) : update
73
- if ret_val == :no_update
71
+ if ret_val == NO_UPDATE
74
72
  context.namespace(:subscriptions)[:no_update] = true
75
73
  context.skip
76
74
  else
@@ -79,7 +77,7 @@ module GraphQL
79
77
  end
80
78
 
81
79
  # The default implementation returns the root object.
82
- # Override it to return `:no_update` if you want to
80
+ # Override it to return {NO_UPDATE} if you want to
83
81
  # skip updates sometimes. Or override it to return a different object.
84
82
  def update(args = {})
85
83
  object
@@ -105,10 +103,12 @@ module GraphQL
105
103
  # Call this method to provide a new subscription_scope; OR
106
104
  # call it without an argument to get the subscription_scope
107
105
  # @param new_scope [Symbol]
106
+ # @param optional [Boolean] If true, then don't require `scope:` to be provided to updates to this subscription.
108
107
  # @return [Symbol]
109
- def self.subscription_scope(new_scope = READING_SCOPE)
108
+ def self.subscription_scope(new_scope = READING_SCOPE, optional: false)
110
109
  if new_scope != READING_SCOPE
111
110
  @subscription_scope = new_scope
111
+ @subscription_scope_optional = optional
112
112
  elsif defined?(@subscription_scope)
113
113
  @subscription_scope
114
114
  else
@@ -116,6 +116,14 @@ module GraphQL
116
116
  end
117
117
  end
118
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
+
119
127
  # This is called during initial subscription to get a "name" for this subscription.
120
128
  # Later, when `.trigger` is called, this will be called again to build another "name".
121
129
  # Any subscribers with matching topic will begin the update flow.
@@ -124,7 +132,7 @@ module GraphQL
124
132
  # In that implementation, only `.trigger` calls with _exact matches_ result in updates to subscribers.
125
133
  #
126
134
  # To implement a filtered stream-type subscription flow, override this method to return a string with field name and subscription scope.
127
- # Then, implement {#update} to compare its arguments to the current `object` and return `:no_update` when an
135
+ # Then, implement {#update} to compare its arguments to the current `object` and return {NO_UPDATE} when an
128
136
  # update should be filtered out.
129
137
  #
130
138
  # @see {#update} for how to skip updates when an event comes with a matching topic.
@@ -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?(_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,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
@@ -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