graphql 2.3.14 → 2.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  3. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  4. data/lib/generators/graphql/type_generator.rb +1 -1
  5. data/lib/graphql/analysis.rb +1 -1
  6. data/lib/graphql/execution/interpreter/resolve.rb +10 -6
  7. data/lib/graphql/language/comment.rb +18 -0
  8. data/lib/graphql/language/document_from_schema_definition.rb +38 -4
  9. data/lib/graphql/language/lexer.rb +15 -12
  10. data/lib/graphql/language/nodes.rb +22 -14
  11. data/lib/graphql/language/parser.rb +5 -0
  12. data/lib/graphql/language/printer.rb +23 -7
  13. data/lib/graphql/language.rb +6 -5
  14. data/lib/graphql/query.rb +1 -1
  15. data/lib/graphql/schema/argument.rb +13 -1
  16. data/lib/graphql/schema/enum.rb +1 -0
  17. data/lib/graphql/schema/enum_value.rb +9 -1
  18. data/lib/graphql/schema/field.rb +23 -3
  19. data/lib/graphql/schema/interface.rb +1 -0
  20. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  21. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  22. data/lib/graphql/schema/member/has_fields.rb +2 -2
  23. data/lib/graphql/schema/resolver.rb +3 -4
  24. data/lib/graphql/schema/visibility/migration.rb +188 -0
  25. data/lib/graphql/schema/visibility/subset.rb +509 -0
  26. data/lib/graphql/schema/visibility.rb +30 -0
  27. data/lib/graphql/schema.rb +46 -41
  28. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  29. data/lib/graphql/testing/helpers.rb +1 -1
  30. data/lib/graphql/tracing/notifications_trace.rb +2 -2
  31. data/lib/graphql/version.rb +1 -1
  32. metadata +6 -4
  33. data/lib/graphql/schema/subset.rb +0 -509
  34. data/lib/graphql/schema/types_migration.rb +0 -187
@@ -106,7 +106,7 @@ module GraphQL
106
106
  # @param subscription [Class] A {GraphQL::Schema::Subscription} class to use for field configuration
107
107
  # @return [GraphQL::Schema:Field] an instance of `self`
108
108
  # @see {.initialize} for other options
109
- def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
109
+ def self.from_options(name = nil, type = nil, desc = nil, comment: nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
110
110
  if (resolver_class = resolver || mutation || subscription)
111
111
  # Add a reference to that parent class
112
112
  kwargs[:resolver_class] = resolver_class
@@ -116,6 +116,10 @@ module GraphQL
116
116
  kwargs[:name] = name
117
117
  end
118
118
 
119
+ if comment
120
+ kwargs[:comment] = comment
121
+ end
122
+
119
123
  if !type.nil?
120
124
  if desc
121
125
  if kwargs[:description]
@@ -212,6 +216,7 @@ module GraphQL
212
216
  # @param owner [Class] The type that this field belongs to
213
217
  # @param null [Boolean] (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null`
214
218
  # @param description [String] Field description
219
+ # @param comment [String] Field comment
215
220
  # @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
216
221
  # @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`)
217
222
  # @param hash_key [String, Symbol] The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`)
@@ -236,7 +241,7 @@ module GraphQL
236
241
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
237
242
  # @param validates [Array<Hash>] Configurations for validating this field
238
243
  # @param fallback_value [Object] A fallback value if the method is not defined
239
- def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
244
+ def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
240
245
  if name.nil?
241
246
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
242
247
  end
@@ -252,6 +257,7 @@ module GraphQL
252
257
  @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
253
258
 
254
259
  @description = description
260
+ @comment = comment
255
261
  @type = @owner_type = @own_validators = @own_directives = @own_arguments = @arguments_statically_coercible = nil # these will be prepared later if necessary
256
262
 
257
263
  self.deprecation_reason = deprecation_reason
@@ -400,6 +406,20 @@ module GraphQL
400
406
  end
401
407
  end
402
408
 
409
+ # @param text [String]
410
+ # @return [String, nil]
411
+ def comment(text = nil)
412
+ if text
413
+ @comment = text
414
+ elsif !NOT_CONFIGURED.equal?(@comment)
415
+ @comment
416
+ elsif @resolver_class
417
+ @resolver_class.comment
418
+ else
419
+ nil
420
+ end
421
+ end
422
+
403
423
  # Read extension instances from this field,
404
424
  # or add new classes/options to be initialized on this field.
405
425
  # Extensions are executed in the order they are added.
@@ -490,7 +510,7 @@ module GraphQL
490
510
  if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size)
491
511
  max_possible_page_size = arguments[:last]
492
512
  end
493
- elsif arguments.is_a?(GraphQL::UnauthorizedError)
513
+ elsif arguments.is_a?(GraphQL::ExecutionError) || arguments.is_a?(GraphQL::UnauthorizedError)
494
514
  raise arguments
495
515
  end
496
516
 
@@ -63,6 +63,7 @@ module GraphQL
63
63
 
64
64
  child_class.introspection(introspection)
65
65
  child_class.description(description)
66
+ child_class.comment(nil)
66
67
  # If interfaces are mixed into each other, only define this class once
67
68
  if !child_class.const_defined?(:UnresolvedTypeError, false)
68
69
  add_unresolved_type_error(child_class)
@@ -50,12 +50,27 @@ module GraphQL
50
50
  end
51
51
  end
52
52
 
53
+ # Call this method to provide a new comment; OR
54
+ # call it without an argument to get the comment
55
+ # @param new_comment [String]
56
+ # @return [String, nil]
57
+ def comment(new_comment = NOT_CONFIGURED)
58
+ if !NOT_CONFIGURED.equal?(new_comment)
59
+ @comment = new_comment
60
+ elsif defined?(@comment)
61
+ @comment
62
+ else
63
+ nil
64
+ end
65
+ end
66
+
53
67
  # This pushes some configurations _down_ the inheritance tree,
54
68
  # in order to prevent repetitive lookups at runtime.
55
69
  module ConfigurationExtension
56
70
  def inherited(child_class)
57
71
  child_class.introspection(introspection)
58
72
  child_class.description(description)
73
+ child_class.comment(nil)
59
74
  child_class.default_graphql_name = nil
60
75
 
61
76
  if defined?(@graphql_name) && @graphql_name && (self.name.nil? || graphql_name != default_graphql_name)
@@ -135,7 +135,7 @@ module GraphQL
135
135
 
136
136
  def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
137
137
  warden = Warden.from_context(context)
138
- skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)
138
+ skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Subset)
139
139
  for ancestor in ancestors
140
140
  if ancestor.respond_to?(:own_arguments) &&
141
141
  (a = ancestor.own_arguments[argument_name]) &&
@@ -210,7 +210,7 @@ module GraphQL
210
210
  # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
211
211
  def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
212
212
  warden = Warden.from_context(context)
213
- if (arg_config = own_arguments[argument_name]) && ((context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)) || (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden)))
213
+ if (arg_config = own_arguments[argument_name]) && ((context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Subset)) || (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden)))
214
214
  visible_arg || arg_config
215
215
  elsif defined?(@resolver_class) && @resolver_class
216
216
  @resolver_class.get_field_argument(argument_name, context)
@@ -99,7 +99,7 @@ module GraphQL
99
99
  module InterfaceMethods
100
100
  def get_field(field_name, context = GraphQL::Query::NullContext.instance)
101
101
  warden = Warden.from_context(context)
102
- skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)
102
+ skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Subset)
103
103
  for ancestor in ancestors
104
104
  if ancestor.respond_to?(:own_fields) &&
105
105
  (f_entry = ancestor.own_fields[field_name]) &&
@@ -135,7 +135,7 @@ module GraphQL
135
135
  # Objects need to check that the interface implementation is visible, too
136
136
  warden = Warden.from_context(context)
137
137
  ancs = ancestors
138
- skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Subset)
138
+ skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Subset)
139
139
  i = 0
140
140
  while (ancestor = ancs[i])
141
141
  if ancestor.respond_to?(:own_fields) &&
@@ -8,6 +8,7 @@ module GraphQL
8
8
  # - Arguments, via `.argument(...)` helper, which will be applied to the field.
9
9
  # - Return type, via `.type(..., null: ...)`, which will be applied to the field.
10
10
  # - Description, via `.description(...)`, which will be applied to the field
11
+ # - Comment, via `.comment(...)`, which will be applied to the field
11
12
  # - Resolution, via `#resolve(**args)` method, which will be called to resolve the field.
12
13
  # - `#object` and `#context` accessors for use during `#resolve`.
13
14
  #
@@ -19,7 +20,7 @@ module GraphQL
19
20
  # @see {GraphQL::Function} `Resolver` is a replacement for `GraphQL::Function`
20
21
  class Resolver
21
22
  include Schema::Member::GraphQLTypeNames
22
- # Really we only need description from here, but:
23
+ # Really we only need description & comment from here, but:
23
24
  extend Schema::Member::BaseDSLMethods
24
25
  extend GraphQL::Schema::Member::HasArguments
25
26
  extend GraphQL::Schema::Member::HasValidators
@@ -408,9 +409,7 @@ module GraphQL
408
409
 
409
410
  private
410
411
 
411
- def own_extensions
412
- @own_extensions
413
- end
412
+ attr_reader :own_extensions
414
413
  end
415
414
  end
416
415
  end
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Visibility
5
+ # You can use this to see how {GraphQL::Schema::Warden} and {GraphQL::Schema::Visibility::Subset}
6
+ # handle `.visible?` differently in your schema.
7
+ #
8
+ # It runs the same method on both implementations and raises an error when the results diverge.
9
+ #
10
+ # To fix the error, modify your schema so that both implementations return the same thing.
11
+ # Or, open an issue on GitHub to discuss the difference.
12
+ #
13
+ # This plugin adds overhead to runtime and may cause unexpected crashes -- **don't** use it in production!
14
+ #
15
+ # This plugin adds two keys to `context` when running:
16
+ #
17
+ # - `visibility_migration_running: true`
18
+ # - For the {Warden} which it instantiates, it adds `visibility_migration_warden_running: true`.
19
+ #
20
+ # Use those keys to modify your `visible?` behavior as needed.
21
+ #
22
+ # Also, in a pinch, you can set `skip_visibility_migration_error: true` in context to turn off this behavior per-query.
23
+ # (In that case, it uses {Subset} directly.)
24
+ #
25
+ # @example Adding this plugin
26
+ #
27
+ # use GraphQL::Schema::Visibility::Migration
28
+ #
29
+ class Migration < GraphQL::Schema::Visibility::Subset
30
+ def self.use(schema)
31
+ schema.subset_class = self
32
+ end
33
+
34
+ class RuntimeTypesMismatchError < GraphQL::Error
35
+ def initialize(method_called, warden_result, subset_result, method_args)
36
+ super(<<~ERR)
37
+ Mismatch in types for `##{method_called}(#{method_args.map(&:inspect).join(", ")})`:
38
+
39
+ #{compare_results(warden_result, subset_result)}
40
+
41
+ Update your `.visible?` implementation to make these implementations return the same value.
42
+
43
+ See: https://graphql-ruby.org/authorization/visibility_migration.html
44
+ ERR
45
+ end
46
+
47
+ private
48
+ def compare_results(warden_result, subset_result)
49
+ if warden_result.is_a?(Array) && subset_result.is_a?(Array)
50
+ all_results = warden_result | subset_result
51
+ all_results.sort_by!(&:graphql_name)
52
+
53
+ entries_text = all_results.map { |entry| "#{entry.graphql_name} (#{entry})"}
54
+ width = entries_text.map(&:size).max
55
+ yes = " ✔ "
56
+ no = " "
57
+ res = "".dup
58
+ res << "#{"Result".center(width)} Warden Subset \n"
59
+ all_results.each_with_index do |entry, idx|
60
+ res << "#{entries_text[idx].ljust(width)}#{warden_result.include?(entry) ? yes : no}#{subset_result.include?(entry) ? yes : no}\n"
61
+ end
62
+ res << "\n"
63
+ else
64
+ "- Warden returned: #{humanize(warden_result)}\n\n- Subset returned: #{humanize(subset_result)}"
65
+ end
66
+ end
67
+ def humanize(val)
68
+ case val
69
+ when Array
70
+ "#{val.size}: #{val.map { |v| humanize(v) }.sort.inspect}"
71
+ when Module
72
+ if val.respond_to?(:graphql_name)
73
+ "#{val.graphql_name} (#{val.inspect})"
74
+ else
75
+ val.inspect
76
+ end
77
+ else
78
+ val.inspect
79
+ end
80
+ end
81
+ end
82
+
83
+ def initialize(context:, schema:)
84
+ @skip_error = context[:skip_visibility_migration_error]
85
+ context[:visibility_migration_running] = true
86
+ @subset_types = GraphQL::Schema::Visibility::Subset.new(context: context, schema: schema)
87
+ if !@skip_error
88
+ warden_ctx_vals = context.to_h.dup
89
+ warden_ctx_vals[:visibility_migration_warden_running] = true
90
+ if defined?(schema::WardenCompatSchema)
91
+ warden_schema = schema::WardenCompatSchema
92
+ else
93
+ warden_schema = Class.new(schema)
94
+ warden_schema.use_schema_visibility = false
95
+ # TODO public API
96
+ warden_schema.send(:add_type_and_traverse, [warden_schema.query, warden_schema.mutation, warden_schema.subscription].compact, root: true)
97
+ warden_schema.send(:add_type_and_traverse, warden_schema.directives.values + warden_schema.orphan_types, root: false)
98
+ end
99
+ warden_ctx = GraphQL::Query::Context.new(query: context.query, values: warden_ctx_vals)
100
+ example_warden = GraphQL::Schema::Warden.new(schema: warden_schema, context: warden_ctx)
101
+ @warden_types = example_warden.schema_subset
102
+ warden_ctx.warden = example_warden
103
+ warden_ctx.types = @warden_types
104
+ end
105
+ end
106
+
107
+ def loaded_types
108
+ @subset_types.loaded_types
109
+ end
110
+
111
+ PUBLIC_SUBSET_METHODS = [
112
+ :enum_values,
113
+ :interfaces,
114
+ :all_types,
115
+ :fields,
116
+ :loadable?,
117
+ :type,
118
+ :arguments,
119
+ :argument,
120
+ :directive_exists?,
121
+ :directives,
122
+ :field,
123
+ :query_root,
124
+ :mutation_root,
125
+ :possible_types,
126
+ :subscription_root,
127
+ :reachable_type?
128
+ ]
129
+
130
+ PUBLIC_SUBSET_METHODS.each do |subset_method|
131
+ define_method(subset_method) do |*args|
132
+ call_method_and_compare(subset_method, args)
133
+ end
134
+ end
135
+
136
+ def call_method_and_compare(method, args)
137
+ res_1 = @subset_types.public_send(method, *args)
138
+ if @skip_error
139
+ return res_1
140
+ end
141
+
142
+ res_2 = @warden_types.public_send(method, *args)
143
+ normalized_res_1 = res_1.is_a?(Array) ? Set.new(res_1) : res_1
144
+ normalized_res_2 = res_2.is_a?(Array) ? Set.new(res_2) : res_2
145
+ if !equivalent_schema_members?(normalized_res_1, normalized_res_2)
146
+ # Raise the errors with the orignally returned values:
147
+ err = RuntimeTypesMismatchError.new(method, res_2, res_1, args)
148
+ raise err
149
+ else
150
+ res_1
151
+ end
152
+ end
153
+
154
+ def equivalent_schema_members?(member1, member2)
155
+ if member1.class != member2.class
156
+ return false
157
+ end
158
+
159
+ case member1
160
+ when Set
161
+ member1_array = member1.to_a.sort_by(&:graphql_name)
162
+ member2_array = member2.to_a.sort_by(&:graphql_name)
163
+ member1_array.each_with_index do |inner_member1, idx|
164
+ inner_member2 = member2_array[idx]
165
+ equivalent_schema_members?(inner_member1, inner_member2)
166
+ end
167
+ when GraphQL::Schema::Field
168
+ member1.ensure_loaded
169
+ member2.ensure_loaded
170
+ if member1.introspection? && member2.introspection?
171
+ member1.inspect == member2.inspect
172
+ else
173
+ member1 == member2
174
+ end
175
+ when Module
176
+ if member1.introspection? && member2.introspection?
177
+ member1.graphql_name == member2.graphql_name
178
+ else
179
+ member1 == member2
180
+ end
181
+ else
182
+ member1 == member2
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end