graphql 2.5.9 → 2.5.26

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.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  4. data/lib/graphql/analysis.rb +20 -13
  5. data/lib/graphql/dashboard/application_controller.rb +41 -0
  6. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  7. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  8. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  9. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
  10. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
  11. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
  12. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
  13. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
  14. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
  15. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
  16. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
  17. data/lib/graphql/dashboard.rb +11 -73
  18. data/lib/graphql/dataloader/active_record_association_source.rb +14 -2
  19. data/lib/graphql/dataloader/async_dataloader.rb +22 -11
  20. data/lib/graphql/dataloader/null_dataloader.rb +54 -9
  21. data/lib/graphql/dataloader.rb +75 -23
  22. data/lib/graphql/date_encoding_error.rb +1 -1
  23. data/lib/graphql/execution/field_resolve_step.rb +631 -0
  24. data/lib/graphql/execution/finalize.rb +217 -0
  25. data/lib/graphql/execution/input_values.rb +261 -0
  26. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  27. data/lib/graphql/execution/interpreter/resolve.rb +10 -16
  28. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
  29. data/lib/graphql/execution/interpreter/runtime.rb +28 -33
  30. data/lib/graphql/execution/interpreter.rb +8 -22
  31. data/lib/graphql/execution/lazy.rb +1 -1
  32. data/lib/graphql/execution/load_argument_step.rb +64 -0
  33. data/lib/graphql/execution/multiplex.rb +1 -1
  34. data/lib/graphql/execution/next.rb +90 -0
  35. data/lib/graphql/execution/prepare_object_step.rb +128 -0
  36. data/lib/graphql/execution/runner.rb +410 -0
  37. data/lib/graphql/execution/selections_step.rb +91 -0
  38. data/lib/graphql/execution.rb +8 -4
  39. data/lib/graphql/execution_error.rb +17 -10
  40. data/lib/graphql/introspection/directive_type.rb +7 -3
  41. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  42. data/lib/graphql/introspection/entry_points.rb +11 -3
  43. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  44. data/lib/graphql/introspection/field_type.rb +13 -5
  45. data/lib/graphql/introspection/input_value_type.rb +21 -13
  46. data/lib/graphql/introspection/type_type.rb +64 -28
  47. data/lib/graphql/invalid_null_error.rb +11 -5
  48. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  49. data/lib/graphql/language/lexer.rb +20 -9
  50. data/lib/graphql/language/nodes.rb +5 -1
  51. data/lib/graphql/language/parser.rb +1 -0
  52. data/lib/graphql/language.rb +21 -12
  53. data/lib/graphql/pagination/connection.rb +2 -0
  54. data/lib/graphql/pagination/connections.rb +32 -0
  55. data/lib/graphql/query/context.rb +11 -4
  56. data/lib/graphql/query/null_context.rb +9 -3
  57. data/lib/graphql/query/partial.rb +18 -3
  58. data/lib/graphql/query.rb +10 -1
  59. data/lib/graphql/runtime_error.rb +6 -0
  60. data/lib/graphql/schema/addition.rb +3 -1
  61. data/lib/graphql/schema/argument.rb +17 -0
  62. data/lib/graphql/schema/build_from_definition.rb +15 -2
  63. data/lib/graphql/schema/directive.rb +45 -13
  64. data/lib/graphql/schema/field/connection_extension.rb +4 -37
  65. data/lib/graphql/schema/field/scope_extension.rb +18 -13
  66. data/lib/graphql/schema/field.rb +87 -48
  67. data/lib/graphql/schema/field_extension.rb +11 -8
  68. data/lib/graphql/schema/interface.rb +26 -0
  69. data/lib/graphql/schema/list.rb +5 -1
  70. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
  71. data/lib/graphql/schema/member/has_arguments.rb +43 -14
  72. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  73. data/lib/graphql/schema/member/has_dataloader.rb +37 -0
  74. data/lib/graphql/schema/member/has_fields.rb +86 -5
  75. data/lib/graphql/schema/member/has_interfaces.rb +2 -2
  76. data/lib/graphql/schema/member/type_system_helpers.rb +16 -2
  77. data/lib/graphql/schema/member.rb +5 -0
  78. data/lib/graphql/schema/non_null.rb +1 -1
  79. data/lib/graphql/schema/object.rb +1 -0
  80. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  81. data/lib/graphql/schema/resolver.rb +60 -1
  82. data/lib/graphql/schema/subscription.rb +0 -2
  83. data/lib/graphql/schema/validator/required_validator.rb +45 -5
  84. data/lib/graphql/schema/visibility/migration.rb +2 -2
  85. data/lib/graphql/schema/visibility/profile.rb +140 -56
  86. data/lib/graphql/schema/visibility.rb +31 -18
  87. data/lib/graphql/schema/wrapper.rb +7 -1
  88. data/lib/graphql/schema.rb +108 -32
  89. data/lib/graphql/static_validation/all_rules.rb +1 -1
  90. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  91. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  92. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  93. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  94. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  95. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  96. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
  97. data/lib/graphql/static_validation/rules/fields_will_merge.rb +322 -256
  98. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  99. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  100. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  101. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  102. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  103. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  104. data/lib/graphql/static_validation/validation_context.rb +1 -1
  105. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  106. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +34 -10
  107. data/lib/graphql/subscriptions/event.rb +1 -0
  108. data/lib/graphql/subscriptions.rb +36 -1
  109. data/lib/graphql/testing/helpers.rb +12 -9
  110. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  111. data/lib/graphql/testing.rb +1 -0
  112. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  113. data/lib/graphql/tracing/detailed_trace.rb +70 -7
  114. data/lib/graphql/tracing/null_trace.rb +1 -1
  115. data/lib/graphql/tracing/perfetto_trace.rb +209 -79
  116. data/lib/graphql/tracing/sentry_trace.rb +3 -1
  117. data/lib/graphql/tracing/trace.rb +6 -0
  118. data/lib/graphql/type_kinds.rb +1 -0
  119. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  120. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  121. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  122. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  123. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  124. data/lib/graphql/unauthorized_error.rb +9 -1
  125. data/lib/graphql/version.rb +1 -1
  126. data/lib/graphql.rb +7 -3
  127. metadata +21 -3
@@ -8,6 +8,13 @@ module GraphQL
8
8
  #
9
9
  # (This is for specifying mutually exclusive sets of arguments.)
10
10
  #
11
+ # If you use {GraphQL::Schema::Visibility} to hide all the arguments in a `one_of: [..]` set,
12
+ # then a developer-facing {GraphQL::Error} will be raised during execution. Pass `allow_all_hidden: true` to
13
+ # skip validation in this case instead.
14
+ #
15
+ # This validator also implements `argument ... required: :nullable`. If an argument has `required: :nullable`
16
+ # but it's hidden with {GraphQL::Schema::Visibility}, then this validator doesn't run.
17
+ #
11
18
  # @example Require exactly one of these arguments
12
19
  #
13
20
  # field :update_amount, IngredientAmount, null: false do
@@ -37,15 +44,17 @@ module GraphQL
37
44
  class RequiredValidator < Validator
38
45
  # @param one_of [Array<Symbol>] A list of arguments, exactly one of which is required for this field
39
46
  # @param argument [Symbol] An argument that is required for this field
47
+ # @param allow_all_hidden [Boolean] If `true`, then this validator won't run if all the `one_of: ...` arguments have been hidden
40
48
  # @param message [String]
41
- def initialize(one_of: nil, argument: nil, message: nil, **default_options)
49
+ def initialize(one_of: nil, argument: nil, allow_all_hidden: nil, message: nil, **default_options)
42
50
  @one_of = if one_of
43
51
  one_of
44
52
  elsif argument
45
- [argument]
53
+ [ argument ]
46
54
  else
47
55
  raise ArgumentError, "`one_of:` or `argument:` must be given in `validates required: {...}`"
48
56
  end
57
+ @allow_all_hidden = allow_all_hidden.nil? ? !!argument : allow_all_hidden
49
58
  @message = message
50
59
  super(**default_options)
51
60
  end
@@ -54,10 +63,17 @@ module GraphQL
54
63
  fully_matched_conditions = 0
55
64
  partially_matched_conditions = 0
56
65
 
66
+ visible_keywords = context.types.arguments(@validated).map(&:keyword)
67
+ no_visible_conditions = true
68
+
57
69
  if !value.nil?
58
70
  @one_of.each do |one_of_condition|
59
71
  case one_of_condition
60
72
  when Symbol
73
+ if no_visible_conditions && visible_keywords.include?(one_of_condition)
74
+ no_visible_conditions = false
75
+ end
76
+
61
77
  if value.key?(one_of_condition)
62
78
  fully_matched_conditions += 1
63
79
  end
@@ -66,6 +82,9 @@ module GraphQL
66
82
  full_match = true
67
83
 
68
84
  one_of_condition.each do |k|
85
+ if no_visible_conditions && visible_keywords.include?(k)
86
+ no_visible_conditions = false
87
+ end
69
88
  if value.key?(k)
70
89
  any_match = true
71
90
  else
@@ -88,6 +107,18 @@ module GraphQL
88
107
  end
89
108
  end
90
109
 
110
+ if no_visible_conditions
111
+ if @allow_all_hidden
112
+ return nil
113
+ else
114
+ raise GraphQL::Error, <<~ERR
115
+ #{@validated.path} validates `required: ...` but all required arguments were hidden.
116
+
117
+ Update your schema definition to allow the client to see some fields or skip validation by adding `required: { ..., allow_all_hidden: true }`
118
+ ERR
119
+ end
120
+ end
121
+
91
122
  if fully_matched_conditions == 1 && partially_matched_conditions == 0
92
123
  nil # OK
93
124
  else
@@ -96,17 +127,26 @@ module GraphQL
96
127
  end
97
128
 
98
129
  def build_message(context)
99
- argument_definitions = @validated.arguments(context).values
130
+ argument_definitions = context.types.arguments(@validated)
131
+
100
132
  required_names = @one_of.map do |arg_keyword|
101
133
  if arg_keyword.is_a?(Array)
102
134
  names = arg_keyword.map { |arg| arg_keyword_to_graphql_name(argument_definitions, arg) }
135
+ names.compact! # hidden arguments are `nil`
103
136
  "(" + names.join(" and ") + ")"
104
137
  else
105
138
  arg_keyword_to_graphql_name(argument_definitions, arg_keyword)
106
139
  end
107
140
  end
141
+ required_names.compact! # remove entries for hidden arguments
142
+
108
143
 
109
- if required_names.size == 1
144
+ case required_names.size
145
+ when 0
146
+ # The required definitions were hidden from the client.
147
+ # Another option here would be to raise an error in the application....
148
+ "%{validated} is missing a required argument."
149
+ when 1
110
150
  "%{validated} must include the following argument: #{required_names.first}."
111
151
  else
112
152
  "%{validated} must include exactly one of the following arguments: #{required_names.join(", ")}."
@@ -115,7 +155,7 @@ module GraphQL
115
155
 
116
156
  def arg_keyword_to_graphql_name(argument_definitions, arg_keyword)
117
157
  argument_definition = argument_definitions.find { |defn| defn.keyword == arg_keyword }
118
- argument_definition.graphql_name
158
+ argument_definition&.graphql_name
119
159
  end
120
160
  end
121
161
  end
@@ -76,10 +76,10 @@ module GraphQL
76
76
  end
77
77
  end
78
78
 
79
- def initialize(context:, schema:, name: nil)
79
+ def initialize(context:, schema:, name: nil, visibility:)
80
80
  @name = name
81
81
  @skip_error = context[:skip_visibility_migration_error] || context.is_a?(Query::NullContext) || context.is_a?(Hash)
82
- @profile_types = GraphQL::Schema::Visibility::Profile.new(context: context, schema: schema)
82
+ @profile_types = GraphQL::Schema::Visibility::Profile.new(context: context, schema: schema, visibility: visibility)
83
83
  if !@skip_error
84
84
  context[:visibility_migration_running] = true
85
85
  warden_ctx_vals = context.to_h.dup
@@ -31,10 +31,40 @@ module GraphQL
31
31
  # @return [Symbol, nil]
32
32
  attr_reader :name
33
33
 
34
- def initialize(name: nil, context:, schema:)
34
+ def freeze
35
+ @cached_visible.default_proc = nil
36
+ @cached_visible_fields.default_proc = nil
37
+ @cached_visible_fields.each do |type, fields|
38
+ fields.default_proc = nil
39
+ end
40
+ @cached_visible_arguments.default_proc = nil
41
+ @cached_visible_arguments.each do |type, fields|
42
+ fields.default_proc = nil
43
+ end
44
+ @cached_parent_fields.default_proc = nil
45
+ @cached_parent_fields.each do |type, fields|
46
+ fields.default_proc = nil
47
+ end
48
+ @cached_parent_arguments.default_proc = nil
49
+ @cached_parent_arguments.each do |type, args|
50
+ args.default_proc = nil
51
+ end
52
+ @cached_possible_types.default_proc = nil
53
+ @cached_enum_values.default_proc = nil
54
+ @cached_fields.default_proc = nil
55
+ @cached_arguments.default_proc = nil
56
+ @loadable_possible_types.default_proc = nil
57
+ @cached_field_result.default_proc = nil
58
+ @cached_field_result.each { |_, h| h.default_proc = nil }
59
+ @cached_type_result.default_proc = nil
60
+ super
61
+ end
62
+
63
+ def initialize(name: nil, context:, schema:, visibility:)
35
64
  @name = name
36
65
  @context = context
37
66
  @schema = schema
67
+ @visibility = visibility
38
68
  @all_types = {}
39
69
  @all_types_loaded = false
40
70
  @unvisited_types = []
@@ -95,6 +125,14 @@ module GraphQL
95
125
  end.compare_by_identity
96
126
 
97
127
  @loadable_possible_types = Hash.new { |h, union_type| h[union_type] = union_type.possible_types }.compare_by_identity
128
+
129
+ # Combined cache for field(owner, field_name) — avoids repeated kind check + parent lookup + visibility check
130
+ @cached_field_result = Hash.new { |h, owner|
131
+ h[owner] = Hash.new { |h2, field_name| h2[field_name] = compute_field(owner, field_name) }
132
+ }.compare_by_identity
133
+
134
+ # Cache for type(type_name) — avoids repeated get_type + visibility + referenced? checks
135
+ @cached_type_result = Hash.new { |h, type_name| h[type_name] = compute_type(type_name) }
98
136
  end
99
137
 
100
138
  def field_on_visible_interface?(field, owner)
@@ -122,58 +160,11 @@ module GraphQL
122
160
  end
123
161
 
124
162
  def type(type_name)
125
- t = @schema.visibility.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
126
- if t
127
- if t.is_a?(Array)
128
- vis_t = nil
129
- t.each do |t_defn|
130
- if @cached_visible[t_defn] && referenced?(t_defn)
131
- if vis_t.nil?
132
- vis_t = t_defn
133
- else
134
- raise_duplicate_definition(vis_t, t_defn)
135
- end
136
- end
137
- end
138
- vis_t
139
- else
140
- if t && @cached_visible[t] && referenced?(t)
141
- t
142
- else
143
- nil
144
- end
145
- end
146
- end
163
+ @cached_type_result[type_name]
147
164
  end
148
165
 
149
166
  def field(owner, field_name)
150
- f = if owner.kind.fields? && (field = @cached_parent_fields[owner][field_name])
151
- field
152
- elsif owner == query_root && (entry_point_field = @schema.introspection_system.entry_point(name: field_name))
153
- entry_point_field
154
- elsif (dynamic_field = @schema.introspection_system.dynamic_field(name: field_name))
155
- dynamic_field
156
- else
157
- nil
158
- end
159
- if f.is_a?(Array)
160
- visible_f = nil
161
- f.each do |f_defn|
162
- if @cached_visible_fields[owner][f_defn]
163
-
164
- if visible_f.nil?
165
- visible_f = f_defn
166
- else
167
- raise_duplicate_definition(visible_f, f_defn)
168
- end
169
- end
170
- end
171
- visible_f&.ensure_loaded
172
- elsif f && @cached_visible_fields[owner][f.ensure_loaded]
173
- f
174
- else
175
- nil
176
- end
167
+ @cached_field_result[owner][field_name]
177
168
  end
178
169
 
179
170
  def fields(owner)
@@ -250,8 +241,8 @@ module GraphQL
250
241
  end
251
242
 
252
243
  def directives
253
- @all_directives ||= @schema.visibility.all_directives.select { |dir|
254
- @cached_visible[dir] && @schema.visibility.all_references[dir].any? { |ref| ref == true || (@cached_visible[ref] && referenced?(ref)) }
244
+ @all_directives ||= @visibility.all_directives.select { |dir|
245
+ @cached_visible[dir] && @visibility.all_references[dir].any? { |ref| ref == true || (@cached_visible[ref] && referenced?(ref)) }
255
246
  }
256
247
  end
257
248
 
@@ -276,8 +267,99 @@ module GraphQL
276
267
  @cached_visible[enum_value]
277
268
  end
278
269
 
270
+ def preload
271
+ load_all_types
272
+ @all_types.each do |type_name, type_defn|
273
+ type(type_name)
274
+ if type_defn.kind.fields?
275
+ fields(type_defn).each do |f|
276
+ field(type_defn, f.graphql_name)
277
+ arguments(f).each do |arg|
278
+ argument(f, arg.graphql_name)
279
+ end
280
+ end
281
+ @schema.introspection_system.dynamic_fields.each do |f|
282
+ field(type_defn, f.graphql_name)
283
+ end
284
+ elsif type_defn.kind.input_object?
285
+ arguments(type_defn).each do |arg|
286
+ argument(type_defn, arg.graphql_name)
287
+ end
288
+ elsif type_defn.kind.enum?
289
+ enum_values(type_defn)
290
+ end
291
+ # Lots more to do here
292
+ end
293
+ @schema.introspection_system.entry_points.each do |f|
294
+ arguments(f).each do |arg|
295
+ argument(f, arg.graphql_name)
296
+ end
297
+ field(@schema.query, f.graphql_name)
298
+ end
299
+ @schema.introspection_system.dynamic_fields.each do |f|
300
+ arguments(f).each do |arg|
301
+ argument(f, arg.graphql_name)
302
+ end
303
+ end
304
+
305
+ end
306
+
279
307
  private
280
308
 
309
+ def compute_type(type_name)
310
+ t = @visibility.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
311
+ if t
312
+ if t.is_a?(Array)
313
+ vis_t = nil
314
+ t.each do |t_defn|
315
+ if @cached_visible[t_defn] && referenced?(t_defn)
316
+ if vis_t.nil?
317
+ vis_t = t_defn
318
+ else
319
+ raise_duplicate_definition(vis_t, t_defn)
320
+ end
321
+ end
322
+ end
323
+ vis_t
324
+ else
325
+ if t && @cached_visible[t] && referenced?(t)
326
+ t
327
+ else
328
+ nil
329
+ end
330
+ end
331
+ end
332
+ end
333
+
334
+ def compute_field(owner, field_name)
335
+ f = if owner.kind.fields? && (field = @cached_parent_fields[owner][field_name])
336
+ field
337
+ elsif owner == query_root && (entry_point_field = @schema.introspection_system.entry_point(name: field_name))
338
+ entry_point_field
339
+ elsif (dynamic_field = @schema.introspection_system.dynamic_field(name: field_name))
340
+ dynamic_field
341
+ else
342
+ nil
343
+ end
344
+ if f.is_a?(Array)
345
+ visible_f = nil
346
+ f.each do |f_defn|
347
+ if @cached_visible_fields[owner][f_defn]
348
+ if visible_f.nil?
349
+ visible_f = f_defn
350
+ else
351
+ raise_duplicate_definition(visible_f, f_defn)
352
+ end
353
+ end
354
+ end
355
+ visible_f&.ensure_loaded
356
+ elsif f && @cached_visible_fields[owner][f.ensure_loaded]
357
+ f
358
+ else
359
+ nil
360
+ end
361
+ end
362
+
281
363
  def non_duplicate_items(definitions, visibility_cache)
282
364
  non_dups = []
283
365
  names = Set.new
@@ -322,7 +404,7 @@ module GraphQL
322
404
  end
323
405
 
324
406
  def referenced?(type_defn)
325
- @schema.visibility.all_references[type_defn].any? do |ref|
407
+ @visibility.all_references[type_defn].any? do |ref|
326
408
  case ref
327
409
  when GraphQL::Schema::Argument
328
410
  @cached_visible_arguments[ref.owner][ref]
@@ -340,9 +422,11 @@ module GraphQL
340
422
  case type.kind.name
341
423
  when "INTERFACE"
342
424
  pts = []
343
- @schema.visibility.all_interface_type_memberships[type].each do |(itm, impl_type)|
344
- if @cached_visible[itm] && @cached_visible[impl_type] && referenced?(impl_type)
345
- pts << impl_type
425
+ @visibility.all_interface_type_memberships[type].each do |impl_type, type_memberships|
426
+ if impl_type.kind.object? && referenced?(impl_type) && @cached_visible[impl_type]
427
+ if type_memberships.any? { |itm| @cached_visible[itm] }
428
+ pts << impl_type
429
+ end
346
430
  end
347
431
  end
348
432
  pts
@@ -10,17 +10,14 @@ module GraphQL
10
10
  class Visibility
11
11
  # @param schema [Class<GraphQL::Schema>]
12
12
  # @param profiles [Hash<Symbol => Hash>] A hash of `name => context` pairs for preloading visibility profiles
13
- # @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?`)
13
+ # @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?` and `Rails.env.staging?`)
14
14
  # @param migration_errors [Boolean] if `true`, raise an error when `Visibility` and `Warden` return different results
15
- def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails.env) ? Rails.env.production? : nil), migration_errors: false)
15
+ def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails.env) ? (Rails.env.production? || Rails.env.staging?) : nil), migration_errors: false)
16
16
  profiles&.each { |name, ctx|
17
17
  ctx[:visibility_profile] = name
18
18
  ctx.freeze
19
19
  }
20
20
  schema.visibility = self.new(schema, dynamic: dynamic, preload: preload, profiles: profiles, migration_errors: migration_errors)
21
- if preload
22
- schema.visibility.preload
23
- end
24
21
  end
25
22
 
26
23
  def initialize(schema, dynamic:, preload:, profiles:, migration_errors:)
@@ -43,6 +40,17 @@ module GraphQL
43
40
  @types = nil
44
41
  @all_references = nil
45
42
  @loaded_all = false
43
+ if preload
44
+ self.preload
45
+ end
46
+ end
47
+
48
+ def freeze
49
+ load_all
50
+ @visit = true
51
+ @interface_type_memberships.default_proc = nil
52
+ @all_references.default_proc = nil
53
+ super
46
54
  end
47
55
 
48
56
  def all_directives
@@ -65,6 +73,8 @@ module GraphQL
65
73
  @types[type_name]
66
74
  end
67
75
 
76
+ attr_accessor :types
77
+
68
78
  def preload?
69
79
  @preload
70
80
  end
@@ -86,7 +96,7 @@ module GraphQL
86
96
  ensure_all_loaded(types_to_visit)
87
97
  @profiles.each do |profile_name, example_ctx|
88
98
  prof = profile_for(example_ctx)
89
- prof.all_types # force loading
99
+ prof.preload
90
100
  end
91
101
  end
92
102
 
@@ -153,12 +163,12 @@ module GraphQL
153
163
  visibility_profile = context[:visibility_profile]
154
164
  if @profiles.include?(visibility_profile)
155
165
  profile_ctx = @profiles[visibility_profile]
156
- @cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: profile_ctx, schema: @schema)
166
+ @cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: profile_ctx, schema: @schema, visibility: self)
157
167
  elsif @dynamic
158
168
  if context.is_a?(Query::NullContext)
159
169
  top_level_profile
160
170
  else
161
- @schema.visibility_profile_class.new(context: context, schema: @schema)
171
+ @schema.visibility_profile_class.new(context: context, schema: @schema, visibility: self)
162
172
  end
163
173
  elsif !context.key?(:visibility_profile)
164
174
  raise ArgumentError, "#{@schema} expects a visibility profile, but `visibility_profile:` wasn't passed. Provide a `visibility_profile:` value or add `dynamic: true` to your visibility configuration."
@@ -168,7 +178,7 @@ module GraphQL
168
178
  elsif context.is_a?(Query::NullContext)
169
179
  top_level_profile
170
180
  else
171
- @schema.visibility_profile_class.new(context: context, schema: @schema)
181
+ @schema.visibility_profile_class.new(context: context, schema: @schema, visibility: self)
172
182
  end
173
183
  end
174
184
 
@@ -181,7 +191,7 @@ module GraphQL
181
191
  if refresh
182
192
  @top_level_profile = nil
183
193
  end
184
- @top_level_profile ||= @schema.visibility_profile_class.new(context: Query::NullContext.instance, schema: @schema)
194
+ @top_level_profile ||= @schema.visibility_profile_class.new(context: @schema.null_context, schema: @schema, visibility: self)
185
195
  end
186
196
 
187
197
  private
@@ -202,7 +212,11 @@ module GraphQL
202
212
  def load_all(types: nil)
203
213
  if @visit.nil?
204
214
  # Set up the visit system
205
- @interface_type_memberships = Hash.new { |h, interface_type| h[interface_type] = [] }.compare_by_identity
215
+ @interface_type_memberships = Hash.new { |h, interface_type|
216
+ h[interface_type] = Hash.new { |h2, obj_type|
217
+ h2[obj_type] = []
218
+ }.compare_by_identity
219
+ }.compare_by_identity
206
220
  @directives = []
207
221
  @types = {} # String => Module
208
222
  @all_references = Hash.new { |h, member| h[member] = Set.new.compare_by_identity }.compare_by_identity
@@ -227,7 +241,7 @@ module GraphQL
227
241
  @all_references[itm.abstract_type] << member
228
242
  # `itm.object_type` may not actually be `member` if this implementation
229
243
  # is inherited from a superclass
230
- @interface_type_memberships[itm.abstract_type] << [itm, member]
244
+ @interface_type_memberships[itm.abstract_type][member] << itm
231
245
  end
232
246
  elsif member < GraphQL::Schema::Union
233
247
  @unions_for_references << member
@@ -276,12 +290,11 @@ module GraphQL
276
290
 
277
291
  # TODO: somehow don't iterate over all these,
278
292
  # only the ones that may have been modified
279
- @interface_type_memberships.each do |int_type, type_membership_pairs|
280
- referers = @all_references[int_type].select { |r| r.is_a?(GraphQL::Schema::Field) }
281
- if !referers.empty?
282
- type_membership_pairs.each do |(type_membership, impl_type)|
283
- # Add new items only:
284
- @all_references[impl_type] |= referers
293
+ @interface_type_memberships.each do |int_type, obj_type_memberships|
294
+ referrers = @all_references[int_type].select { |r| r.is_a?(GraphQL::Schema::Field) }
295
+ if !referrers.empty?
296
+ obj_type_memberships.each_key do |impl_type|
297
+ @all_references[impl_type] |= referrers
285
298
  end
286
299
  end
287
300
  end
@@ -13,7 +13,13 @@ module GraphQL
13
13
  end
14
14
 
15
15
  def unwrap
16
- @of_type.unwrap
16
+ @unwrapped ||= @of_type.unwrap
17
+ end
18
+
19
+ def freeze
20
+ unwrap
21
+ to_type_signature
22
+ super
17
23
  end
18
24
 
19
25
  def ==(other)