graphql 2.2.17 → 2.5.16

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 (240) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  3. data/lib/generators/graphql/install_generator.rb +46 -0
  4. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  5. data/lib/generators/graphql/templates/base_resolver.erb +2 -0
  6. data/lib/generators/graphql/templates/schema.erb +3 -0
  7. data/lib/generators/graphql/type_generator.rb +1 -1
  8. data/lib/graphql/analysis/analyzer.rb +90 -0
  9. data/lib/graphql/analysis/field_usage.rb +82 -0
  10. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  11. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  12. data/lib/graphql/analysis/query_complexity.rb +263 -0
  13. data/lib/graphql/analysis/{ast/query_depth.rb → query_depth.rb} +23 -25
  14. data/lib/graphql/analysis/visitor.rb +280 -0
  15. data/lib/graphql/analysis.rb +95 -1
  16. data/lib/graphql/autoload.rb +38 -0
  17. data/lib/graphql/backtrace/table.rb +118 -55
  18. data/lib/graphql/backtrace.rb +1 -19
  19. data/lib/graphql/current.rb +57 -0
  20. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  21. data/lib/graphql/dashboard/installable.rb +22 -0
  22. data/lib/graphql/dashboard/limiters.rb +93 -0
  23. data/lib/graphql/dashboard/operation_store.rb +199 -0
  24. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  25. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  26. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  27. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  28. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  29. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  30. data/lib/graphql/dashboard/statics/icon.png +0 -0
  31. data/lib/graphql/dashboard/subscriptions.rb +96 -0
  32. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  33. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  34. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  35. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  36. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +23 -0
  37. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  38. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  39. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  40. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  41. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  42. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  43. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  44. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  45. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  46. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  47. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  48. data/lib/graphql/dashboard.rb +158 -0
  49. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  50. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  51. data/lib/graphql/dataloader/async_dataloader.rb +46 -19
  52. data/lib/graphql/dataloader/null_dataloader.rb +51 -10
  53. data/lib/graphql/dataloader/source.rb +20 -9
  54. data/lib/graphql/dataloader.rb +153 -45
  55. data/lib/graphql/date_encoding_error.rb +1 -1
  56. data/lib/graphql/dig.rb +2 -1
  57. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  58. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  59. data/lib/graphql/execution/interpreter/resolve.rb +23 -25
  60. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +63 -5
  61. data/lib/graphql/execution/interpreter/runtime.rb +321 -222
  62. data/lib/graphql/execution/interpreter.rb +23 -30
  63. data/lib/graphql/execution/lookahead.rb +18 -11
  64. data/lib/graphql/execution/multiplex.rb +6 -5
  65. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  66. data/lib/graphql/introspection/directive_type.rb +1 -1
  67. data/lib/graphql/introspection/entry_points.rb +2 -2
  68. data/lib/graphql/introspection/field_type.rb +1 -1
  69. data/lib/graphql/introspection/schema_type.rb +6 -11
  70. data/lib/graphql/introspection/type_type.rb +5 -5
  71. data/lib/graphql/invalid_name_error.rb +1 -1
  72. data/lib/graphql/invalid_null_error.rb +20 -17
  73. data/lib/graphql/language/cache.rb +13 -0
  74. data/lib/graphql/language/comment.rb +18 -0
  75. data/lib/graphql/language/document_from_schema_definition.rb +64 -35
  76. data/lib/graphql/language/lexer.rb +72 -42
  77. data/lib/graphql/language/nodes.rb +93 -52
  78. data/lib/graphql/language/parser.rb +168 -61
  79. data/lib/graphql/language/printer.rb +31 -15
  80. data/lib/graphql/language/sanitized_printer.rb +1 -1
  81. data/lib/graphql/language.rb +61 -1
  82. data/lib/graphql/pagination/connection.rb +1 -1
  83. data/lib/graphql/query/context/scoped_context.rb +1 -1
  84. data/lib/graphql/query/context.rb +46 -47
  85. data/lib/graphql/query/null_context.rb +3 -5
  86. data/lib/graphql/query/partial.rb +179 -0
  87. data/lib/graphql/query/validation_pipeline.rb +2 -2
  88. data/lib/graphql/query/variable_validation_error.rb +1 -1
  89. data/lib/graphql/query.rb +123 -69
  90. data/lib/graphql/railtie.rb +7 -0
  91. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  92. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  93. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  94. data/lib/graphql/rubocop.rb +2 -0
  95. data/lib/graphql/schema/addition.rb +26 -13
  96. data/lib/graphql/schema/always_visible.rb +7 -2
  97. data/lib/graphql/schema/argument.rb +57 -8
  98. data/lib/graphql/schema/build_from_definition.rb +116 -49
  99. data/lib/graphql/schema/directive/flagged.rb +4 -2
  100. data/lib/graphql/schema/directive.rb +54 -2
  101. data/lib/graphql/schema/enum.rb +107 -24
  102. data/lib/graphql/schema/enum_value.rb +10 -2
  103. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  104. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  105. data/lib/graphql/schema/field.rb +134 -45
  106. data/lib/graphql/schema/field_extension.rb +1 -1
  107. data/lib/graphql/schema/has_single_input_argument.rb +6 -2
  108. data/lib/graphql/schema/input_object.rb +122 -64
  109. data/lib/graphql/schema/interface.rb +23 -5
  110. data/lib/graphql/schema/introspection_system.rb +6 -17
  111. data/lib/graphql/schema/late_bound_type.rb +4 -0
  112. data/lib/graphql/schema/list.rb +3 -3
  113. data/lib/graphql/schema/loader.rb +3 -2
  114. data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
  115. data/lib/graphql/schema/member/has_arguments.rb +44 -58
  116. data/lib/graphql/schema/member/has_dataloader.rb +62 -0
  117. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  118. data/lib/graphql/schema/member/has_directives.rb +4 -4
  119. data/lib/graphql/schema/member/has_fields.rb +26 -6
  120. data/lib/graphql/schema/member/has_interfaces.rb +6 -6
  121. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  122. data/lib/graphql/schema/member/has_validators.rb +1 -1
  123. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  124. data/lib/graphql/schema/member/type_system_helpers.rb +17 -4
  125. data/lib/graphql/schema/member.rb +1 -0
  126. data/lib/graphql/schema/mutation.rb +7 -0
  127. data/lib/graphql/schema/object.rb +25 -8
  128. data/lib/graphql/schema/printer.rb +1 -0
  129. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  130. data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
  131. data/lib/graphql/schema/resolver.rb +29 -23
  132. data/lib/graphql/schema/scalar.rb +1 -6
  133. data/lib/graphql/schema/subscription.rb +52 -6
  134. data/lib/graphql/schema/timeout.rb +19 -2
  135. data/lib/graphql/schema/type_expression.rb +2 -2
  136. data/lib/graphql/schema/union.rb +1 -1
  137. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  138. data/lib/graphql/schema/validator/required_validator.rb +92 -11
  139. data/lib/graphql/schema/validator.rb +3 -1
  140. data/lib/graphql/schema/visibility/migration.rb +188 -0
  141. data/lib/graphql/schema/visibility/profile.rb +445 -0
  142. data/lib/graphql/schema/visibility/visit.rb +190 -0
  143. data/lib/graphql/schema/visibility.rb +311 -0
  144. data/lib/graphql/schema/warden.rb +190 -20
  145. data/lib/graphql/schema.rb +695 -167
  146. data/lib/graphql/static_validation/all_rules.rb +2 -2
  147. data/lib/graphql/static_validation/base_visitor.rb +6 -5
  148. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  149. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  150. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
  151. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
  152. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  153. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  154. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +12 -2
  155. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +47 -13
  156. data/lib/graphql/static_validation/rules/fields_will_merge.rb +88 -25
  157. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  158. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  159. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
  160. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  161. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  162. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  163. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  164. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  165. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
  166. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
  167. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  168. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +7 -3
  169. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  170. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  171. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  172. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
  173. data/lib/graphql/static_validation/validation_context.rb +18 -2
  174. data/lib/graphql/static_validation/validator.rb +6 -1
  175. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +5 -3
  176. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  177. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +12 -10
  178. data/lib/graphql/subscriptions/event.rb +13 -2
  179. data/lib/graphql/subscriptions/serialize.rb +1 -1
  180. data/lib/graphql/subscriptions.rb +7 -5
  181. data/lib/graphql/testing/helpers.rb +48 -16
  182. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  183. data/lib/graphql/testing.rb +1 -0
  184. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  185. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  186. data/lib/graphql/tracing/appoptics_trace.rb +5 -1
  187. data/lib/graphql/tracing/appoptics_tracing.rb +7 -0
  188. data/lib/graphql/tracing/appsignal_trace.rb +32 -59
  189. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  190. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  191. data/lib/graphql/tracing/data_dog_trace.rb +46 -162
  192. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  193. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  194. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  195. data/lib/graphql/tracing/detailed_trace.rb +141 -0
  196. data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
  197. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  198. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  199. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  200. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  201. data/lib/graphql/tracing/notifications_trace.rb +183 -37
  202. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  203. data/lib/graphql/tracing/null_trace.rb +9 -0
  204. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  205. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  206. data/lib/graphql/tracing/perfetto_trace.rb +818 -0
  207. data/lib/graphql/tracing/platform_tracing.rb +1 -1
  208. data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
  209. data/lib/graphql/tracing/prometheus_trace.rb +73 -73
  210. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  211. data/lib/graphql/tracing/scout_trace.rb +32 -58
  212. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  213. data/lib/graphql/tracing/sentry_trace.rb +64 -98
  214. data/lib/graphql/tracing/statsd_trace.rb +33 -45
  215. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  216. data/lib/graphql/tracing/trace.rb +111 -1
  217. data/lib/graphql/tracing.rb +31 -30
  218. data/lib/graphql/type_kinds.rb +2 -1
  219. data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
  220. data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
  221. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  222. data/lib/graphql/types.rb +18 -11
  223. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  224. data/lib/graphql/version.rb +1 -1
  225. data/lib/graphql.rb +64 -54
  226. metadata +197 -22
  227. data/lib/graphql/analysis/ast/analyzer.rb +0 -91
  228. data/lib/graphql/analysis/ast/field_usage.rb +0 -82
  229. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  230. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  231. data/lib/graphql/analysis/ast/query_complexity.rb +0 -182
  232. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  233. data/lib/graphql/analysis/ast.rb +0 -94
  234. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  235. data/lib/graphql/backtrace/trace.rb +0 -93
  236. data/lib/graphql/backtrace/tracer.rb +0 -80
  237. data/lib/graphql/language/token.rb +0 -34
  238. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  239. data/lib/graphql/schema/null_mask.rb +0 -11
  240. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
@@ -0,0 +1,311 @@
1
+ # frozen_string_literal: true
2
+ require "graphql/schema/visibility/profile"
3
+ require "graphql/schema/visibility/migration"
4
+ require "graphql/schema/visibility/visit"
5
+
6
+ module GraphQL
7
+ class Schema
8
+ # Use this plugin to make some parts of your schema hidden from some viewers.
9
+ #
10
+ class Visibility
11
+ # @param schema [Class<GraphQL::Schema>]
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?` and `Rails.env.staging?`)
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? || Rails.env.staging?) : nil), migration_errors: false)
16
+ profiles&.each { |name, ctx|
17
+ ctx[:visibility_profile] = name
18
+ ctx.freeze
19
+ }
20
+ schema.visibility = self.new(schema, dynamic: dynamic, preload: preload, profiles: profiles, migration_errors: migration_errors)
21
+ end
22
+
23
+ def initialize(schema, dynamic:, preload:, profiles:, migration_errors:)
24
+ @schema = schema
25
+ schema.use_visibility_profile = true
26
+ schema.visibility_profile_class = if migration_errors
27
+ Visibility::Migration
28
+ else
29
+ Visibility::Profile
30
+ end
31
+ @preload = preload
32
+ @profiles = profiles
33
+ @cached_profiles = {}
34
+ @dynamic = dynamic
35
+ @migration_errors = migration_errors
36
+ # Top-level type caches:
37
+ @visit = nil
38
+ @interface_type_memberships = nil
39
+ @directives = nil
40
+ @types = nil
41
+ @all_references = nil
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
54
+ end
55
+
56
+ def all_directives
57
+ load_all
58
+ @directives
59
+ end
60
+
61
+ def all_interface_type_memberships
62
+ load_all
63
+ @interface_type_memberships
64
+ end
65
+
66
+ def all_references
67
+ load_all
68
+ @all_references
69
+ end
70
+
71
+ def get_type(type_name)
72
+ load_all
73
+ @types[type_name]
74
+ end
75
+
76
+ attr_accessor :types
77
+
78
+ def preload?
79
+ @preload
80
+ end
81
+
82
+ def preload
83
+ # Traverse the schema now (and in the *_configured hooks below)
84
+ # To make sure things are loaded during boot
85
+ @preloaded_types = Set.new
86
+ types_to_visit = [
87
+ @schema.query,
88
+ @schema.mutation,
89
+ @schema.subscription,
90
+ *@schema.introspection_system.types.values,
91
+ *@schema.introspection_system.entry_points.map { |ep| ep.type.unwrap },
92
+ *@schema.orphan_types,
93
+ ]
94
+ # Root types may have been nil:
95
+ types_to_visit.compact!
96
+ ensure_all_loaded(types_to_visit)
97
+ @profiles.each do |profile_name, example_ctx|
98
+ prof = profile_for(example_ctx)
99
+ prof.preload
100
+ end
101
+ end
102
+
103
+ # @api private
104
+ def query_configured(query_type)
105
+ if @preload
106
+ ensure_all_loaded([query_type])
107
+ end
108
+ end
109
+
110
+ # @api private
111
+ def mutation_configured(mutation_type)
112
+ if @preload
113
+ ensure_all_loaded([mutation_type])
114
+ end
115
+ end
116
+
117
+ # @api private
118
+ def subscription_configured(subscription_type)
119
+ if @preload
120
+ ensure_all_loaded([subscription_type])
121
+ end
122
+ end
123
+
124
+ # @api private
125
+ def orphan_types_configured(orphan_types)
126
+ if @preload
127
+ ensure_all_loaded(orphan_types)
128
+ end
129
+ end
130
+
131
+ # @api private
132
+ def introspection_system_configured(introspection_system)
133
+ if @preload
134
+ introspection_types = [
135
+ *@schema.introspection_system.types.values,
136
+ *@schema.introspection_system.entry_points.map { |ep| ep.type.unwrap },
137
+ ]
138
+ ensure_all_loaded(introspection_types)
139
+ end
140
+ end
141
+
142
+ # Make another Visibility for `schema` based on this one
143
+ # @return [Visibility]
144
+ # @api private
145
+ def dup_for(other_schema)
146
+ self.class.new(
147
+ other_schema,
148
+ dynamic: @dynamic,
149
+ preload: @preload,
150
+ profiles: @profiles,
151
+ migration_errors: @migration_errors
152
+ )
153
+ end
154
+
155
+ def migration_errors?
156
+ @migration_errors
157
+ end
158
+
159
+ attr_reader :cached_profiles
160
+
161
+ def profile_for(context)
162
+ if !@profiles.empty?
163
+ visibility_profile = context[:visibility_profile]
164
+ if @profiles.include?(visibility_profile)
165
+ profile_ctx = @profiles[visibility_profile]
166
+ @cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: profile_ctx, schema: @schema, visibility: self)
167
+ elsif @dynamic
168
+ if context.is_a?(Query::NullContext)
169
+ top_level_profile
170
+ else
171
+ @schema.visibility_profile_class.new(context: context, schema: @schema, visibility: self)
172
+ end
173
+ elsif !context.key?(:visibility_profile)
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."
175
+ else
176
+ raise ArgumentError, "`#{visibility_profile.inspect}` isn't allowed for `visibility_profile:` (must be one of #{@profiles.keys.map(&:inspect).join(", ")}). Or, add `#{visibility_profile.inspect}` to the list of profiles in the schema definition."
177
+ end
178
+ elsif context.is_a?(Query::NullContext)
179
+ top_level_profile
180
+ else
181
+ @schema.visibility_profile_class.new(context: context, schema: @schema, visibility: self)
182
+ end
183
+ end
184
+
185
+ attr_reader :top_level
186
+
187
+ # @api private
188
+ attr_reader :unfiltered_interface_type_memberships
189
+
190
+ def top_level_profile(refresh: false)
191
+ if refresh
192
+ @top_level_profile = nil
193
+ end
194
+ @top_level_profile ||= @schema.visibility_profile_class.new(context: Query::NullContext.instance, schema: @schema, visibility: self)
195
+ end
196
+
197
+ private
198
+
199
+ def ensure_all_loaded(types_to_visit)
200
+ while (type = types_to_visit.shift)
201
+ if type.kind.fields? && @preloaded_types.add?(type)
202
+ type.all_field_definitions.each do |field_defn|
203
+ field_defn.ensure_loaded
204
+ types_to_visit << field_defn.type.unwrap
205
+ end
206
+ end
207
+ end
208
+ top_level_profile(refresh: true)
209
+ nil
210
+ end
211
+
212
+ def load_all(types: nil)
213
+ if @visit.nil?
214
+ # Set up the visit system
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
220
+ @directives = []
221
+ @types = {} # String => Module
222
+ @all_references = Hash.new { |h, member| h[member] = Set.new.compare_by_identity }.compare_by_identity
223
+ @unions_for_references = Set.new
224
+ @visit = Visibility::Visit.new(@schema) do |member|
225
+ if member.is_a?(Module)
226
+ type_name = member.graphql_name
227
+ if (prev_t = @types[type_name])
228
+ if prev_t.is_a?(Array)
229
+ prev_t << member
230
+ else
231
+ @types[type_name] = [member, prev_t]
232
+ end
233
+ else
234
+ @types[member.graphql_name] = member
235
+ end
236
+ member.directives.each { |dir| @all_references[dir.class] << member }
237
+ if member < GraphQL::Schema::Directive
238
+ @directives << member
239
+ elsif member.respond_to?(:interface_type_memberships)
240
+ member.interface_type_memberships.each do |itm|
241
+ @all_references[itm.abstract_type] << member
242
+ # `itm.object_type` may not actually be `member` if this implementation
243
+ # is inherited from a superclass
244
+ @interface_type_memberships[itm.abstract_type][member] << itm
245
+ end
246
+ elsif member < GraphQL::Schema::Union
247
+ @unions_for_references << member
248
+ end
249
+ elsif member.is_a?(GraphQL::Schema::Argument)
250
+ member.validate_default_value
251
+ @all_references[member.type.unwrap] << member
252
+ if !(dirs = member.directives).empty?
253
+ dir_owner = member.owner
254
+ if dir_owner.respond_to?(:owner)
255
+ dir_owner = dir_owner.owner
256
+ end
257
+ dirs.each { |dir| @all_references[dir.class] << dir_owner }
258
+ end
259
+ elsif member.is_a?(GraphQL::Schema::Field)
260
+ @all_references[member.type.unwrap] << member
261
+ if !(dirs = member.directives).empty?
262
+ dir_owner = member.owner
263
+ dirs.each { |dir| @all_references[dir.class] << dir_owner }
264
+ end
265
+ elsif member.is_a?(GraphQL::Schema::EnumValue)
266
+ if !(dirs = member.directives).empty?
267
+ dir_owner = member.owner
268
+ dirs.each { |dir| @all_references[dir.class] << dir_owner }
269
+ end
270
+ end
271
+ true
272
+ end
273
+
274
+ @schema.root_types.each { |t| @all_references[t] << true }
275
+ @schema.introspection_system.types.each_value { |t| @all_references[t] << true }
276
+ @schema.directives.each_value { |dir_class| @all_references[dir_class] << true }
277
+
278
+ @visit.visit_each(types: []) # visit default directives
279
+ end
280
+
281
+ if types
282
+ @visit.visit_each(types: types, directives: [])
283
+ elsif @loaded_all == false
284
+ @loaded_all = true
285
+ @visit.visit_each
286
+ else
287
+ # already loaded all
288
+ return
289
+ end
290
+
291
+ # TODO: somehow don't iterate over all these,
292
+ # only the ones that may have been modified
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
298
+ end
299
+ end
300
+ end
301
+
302
+ @unions_for_references.each do |union_type|
303
+ refs = @all_references[union_type]
304
+ union_type.all_possible_types.each do |object_type|
305
+ @all_references[object_type] |= refs # Add new items
306
+ end
307
+ end
308
+ end
309
+ end
310
+ end
311
+ end
@@ -19,6 +19,17 @@ module GraphQL
19
19
  PassThruWarden
20
20
  end
21
21
 
22
+ def self.types_from_context(context)
23
+ context.types || PassThruWarden
24
+ rescue NoMethodError
25
+ # this might be a hash which won't respond to #warden
26
+ PassThruWarden
27
+ end
28
+
29
+ def self.use(schema)
30
+ # no-op
31
+ end
32
+
22
33
  # @param visibility_method [Symbol] a Warden method to call for this entry
23
34
  # @param entry [Object, Array<Object>] One or more definitions for a given name in a GraphQL Schema
24
35
  # @param context [GraphQL::Query::Context]
@@ -61,23 +72,33 @@ module GraphQL
61
72
  def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end
62
73
  def arguments(owner, ctx); owner.arguments(ctx); end
63
74
  def loadable?(type, ctx); type.visible?(ctx); end
75
+ def loadable_possible_types(type, ctx); type.possible_types(ctx); end
76
+ def visibility_profile
77
+ @visibility_profile ||= Warden::VisibilityProfile.new(self)
78
+ end
64
79
  end
65
80
  end
66
81
 
67
82
  class NullWarden
68
83
  def initialize(_filter = nil, context:, schema:)
69
84
  @schema = schema
85
+ @visibility_profile = Warden::VisibilityProfile.new(self)
70
86
  end
71
87
 
88
+ # No-op, but for compatibility:
89
+ attr_writer :skip_warning
90
+
91
+ attr_reader :visibility_profile
92
+
72
93
  def visible_field?(field_defn, _ctx = nil, owner = nil); true; end
73
94
  def visible_argument?(arg_defn, _ctx = nil); true; end
74
95
  def visible_type?(type_defn, _ctx = nil); true; end
75
- def visible_enum_value?(enum_value, _ctx = nil); true; end
96
+ def visible_enum_value?(enum_value, _ctx = nil); enum_value.visible?(Query::NullContext.instance); end
76
97
  def visible_type_membership?(type_membership, _ctx = nil); true; end
77
98
  def interface_type_memberships(obj_type, _ctx = nil); obj_type.interface_type_memberships; end
78
- def get_type(type_name); @schema.get_type(type_name); end # rubocop:disable Development/ContextIsPassedCop
99
+ def get_type(type_name); @schema.get_type(type_name, Query::NullContext.instance, false); end # rubocop:disable Development/ContextIsPassedCop
79
100
  def arguments(argument_owner, ctx = nil); argument_owner.all_argument_definitions; end
80
- def enum_values(enum_defn); enum_defn.enum_values; end # rubocop:disable Development/ContextIsPassedCop
101
+ def enum_values(enum_defn); enum_defn.enum_values(Query::NullContext.instance); end # rubocop:disable Development/ContextIsPassedCop
81
102
  def get_argument(parent_type, argument_name); parent_type.get_argument(argument_name); end # rubocop:disable Development/ContextIsPassedCop
82
103
  def types; @schema.types; end # rubocop:disable Development/ContextIsPassedCop
83
104
  def root_type_for_operation(op_name); @schema.root_type_for_operation(op_name); end
@@ -86,11 +107,94 @@ module GraphQL
86
107
  def get_field(parent_type, field_name); @schema.get_field(parent_type, field_name); end
87
108
  def reachable_type?(type_name); true; end
88
109
  def loadable?(type, _ctx); true; end
110
+ def loadable_possible_types(abstract_type, _ctx); union_type.possible_types; end
89
111
  def reachable_types; @schema.types.values; end # rubocop:disable Development/ContextIsPassedCop
90
- def possible_types(type_defn); @schema.possible_types(type_defn); end
112
+ def possible_types(type_defn); @schema.possible_types(type_defn, Query::NullContext.instance, false); end
91
113
  def interfaces(obj_type); obj_type.interfaces; end
92
114
  end
93
115
 
116
+ def visibility_profile
117
+ @visibility_profile ||= VisibilityProfile.new(self)
118
+ end
119
+
120
+ class VisibilityProfile
121
+ def initialize(warden)
122
+ @warden = warden
123
+ end
124
+
125
+ def directives
126
+ @warden.directives
127
+ end
128
+
129
+ def directive_exists?(dir_name)
130
+ @warden.directives.any? { |d| d.graphql_name == dir_name }
131
+ end
132
+
133
+ def type(name)
134
+ @warden.get_type(name)
135
+ end
136
+
137
+ def field(owner, field_name)
138
+ @warden.get_field(owner, field_name)
139
+ end
140
+
141
+ def argument(owner, arg_name)
142
+ @warden.get_argument(owner, arg_name)
143
+ end
144
+
145
+ def query_root
146
+ @warden.root_type_for_operation("query")
147
+ end
148
+
149
+ def mutation_root
150
+ @warden.root_type_for_operation("mutation")
151
+ end
152
+
153
+ def subscription_root
154
+ @warden.root_type_for_operation("subscription")
155
+ end
156
+
157
+ def arguments(owner)
158
+ @warden.arguments(owner)
159
+ end
160
+
161
+ def fields(owner)
162
+ @warden.fields(owner)
163
+ end
164
+
165
+ def possible_types(type)
166
+ @warden.possible_types(type)
167
+ end
168
+
169
+ def enum_values(enum_type)
170
+ @warden.enum_values(enum_type)
171
+ end
172
+
173
+ def all_types
174
+ @warden.reachable_types
175
+ end
176
+
177
+ def interfaces(obj_type)
178
+ @warden.interfaces(obj_type)
179
+ end
180
+
181
+ def loadable?(t, ctx) # TODO remove ctx here?
182
+ @warden.loadable?(t, ctx)
183
+ end
184
+
185
+ def loadable_possible_types(t, ctx)
186
+ @warden.loadable_possible_types(t, ctx)
187
+ end
188
+
189
+ def reachable_type?(type_name)
190
+ !!@warden.reachable_type?(type_name)
191
+ end
192
+
193
+ def visible_enum_value?(enum_value, ctx = nil)
194
+ @warden.visible_enum_value?(enum_value, ctx)
195
+ end
196
+ end
197
+
94
198
  # @param context [GraphQL::Query::Context]
95
199
  # @param schema [GraphQL::Schema]
96
200
  def initialize(context:, schema:)
@@ -100,17 +204,19 @@ module GraphQL
100
204
  @mutation = @schema.mutation
101
205
  @subscription = @schema.subscription
102
206
  @context = context
103
- @visibility_cache = read_through { |m| schema.visible?(m, context) }
104
- @visibility_cache.compare_by_identity
207
+ @visibility_cache = read_through { |m| check_visible(schema, m) }
105
208
  # Initialize all ivars to improve object shape consistency:
106
209
  @types = @visible_types = @reachable_types = @visible_parent_fields =
107
210
  @visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays =
108
211
  @visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships =
109
212
  @visible_and_reachable_type = @unions = @unfiltered_interfaces =
110
- @reachable_type_set =
213
+ @reachable_type_set = @visibility_profile = @loadable_possible_types =
111
214
  nil
215
+ @skip_warning = schema.plugins.any? { |(plugin, _opts)| plugin == GraphQL::Schema::Warden }
112
216
  end
113
217
 
218
+ attr_writer :skip_warning
219
+
114
220
  # @return [Hash<String, GraphQL::BaseType>] Visible types in the schema
115
221
  def types
116
222
  @types ||= begin
@@ -126,13 +232,28 @@ module GraphQL
126
232
 
127
233
  # @return [Boolean] True if this type is used for `loads:` but not in the schema otherwise and not _explicitly_ hidden.
128
234
  def loadable?(type, _ctx)
129
- !reachable_type_set.include?(type) && visible_type?(type)
235
+ visible_type?(type) &&
236
+ !referenced?(type) &&
237
+ (type.respond_to?(:interfaces) ? interfaces(type).all? { |i| loadable?(i, _ctx) } : true)
238
+ end
239
+
240
+ # This abstract type was determined to be used for `loads` only.
241
+ # All its possible types are valid possibilities here -- no filtering.
242
+ def loadable_possible_types(abstract_type, _ctx)
243
+ @loadable_possible_types ||= read_through do |t|
244
+ if t.is_a?(Class) # union
245
+ t.possible_types
246
+ else
247
+ @schema.possible_types(abstract_type)
248
+ end
249
+ end
250
+ @loadable_possible_types[abstract_type]
130
251
  end
131
252
 
132
253
  # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)
133
254
  def get_type(type_name)
134
255
  @visible_types ||= read_through do |name|
135
- type_defn = @schema.get_type(name, @context)
256
+ type_defn = @schema.get_type(name, @context, false)
136
257
  if type_defn && visible_and_reachable_type?(type_defn)
137
258
  type_defn
138
259
  else
@@ -179,7 +300,7 @@ module GraphQL
179
300
  # @return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`
180
301
  def possible_types(type_defn)
181
302
  @visible_possible_types ||= read_through { |type_defn|
182
- pt = @schema.possible_types(type_defn, @context)
303
+ pt = @schema.possible_types(type_defn, @context, false)
183
304
  pt.select { |t| visible_and_reachable_type?(t) }
184
305
  }
185
306
  @visible_possible_types[type_defn]
@@ -197,7 +318,7 @@ module GraphQL
197
318
  def arguments(argument_owner, ctx = nil)
198
319
  @visible_arguments ||= read_through { |o|
199
320
  args = o.arguments(@context)
200
- if args.any?
321
+ if !args.empty?
201
322
  args = args.values
202
323
  args.select! { |a| visible_argument?(a, @context) }
203
324
  args
@@ -229,7 +350,7 @@ module GraphQL
229
350
  def interfaces(obj_type)
230
351
  @visible_interfaces ||= read_through { |t|
231
352
  ints = t.interfaces(@context)
232
- if ints.any?
353
+ if !ints.empty?
233
354
  ints.select! { |i| visible_type?(i) }
234
355
  end
235
356
  ints
@@ -289,13 +410,13 @@ module GraphQL
289
410
  next true if root_type?(type_defn) || type_defn.introspection?
290
411
 
291
412
  if type_defn.kind.union?
292
- possible_types(type_defn).any? && (referenced?(type_defn) || orphan_type?(type_defn))
413
+ !possible_types(type_defn).empty? && (referenced?(type_defn) || orphan_type?(type_defn))
293
414
  elsif type_defn.kind.interface?
294
- if possible_types(type_defn).any?
415
+ if !possible_types(type_defn).empty?
295
416
  true
296
417
  else
297
418
  if @context.respond_to?(:logger) && (logger = @context.logger)
298
- logger.debug { "Interface `#{type_defn.graphql_name}` hidden because it has no visible implementors" }
419
+ logger.debug { "Interface `#{type_defn.graphql_name}` hidden because it has no visible implementers" }
299
420
  end
300
421
  false
301
422
  end
@@ -363,8 +484,7 @@ module GraphQL
363
484
  end
364
485
 
365
486
  def referenced?(type_defn)
366
- graphql_name = type_defn.unwrap.graphql_name
367
- members = @schema.references_to(graphql_name)
487
+ members = @schema.references_to(type_defn)
368
488
  members.any? { |m| visible?(m) }
369
489
  end
370
490
 
@@ -377,11 +497,61 @@ module GraphQL
377
497
  end
378
498
 
379
499
  def read_through
380
- h = Hash.new { |h, k| h[k] = yield(k) }
381
- h.compare_by_identity
382
- h
500
+ Hash.new { |h, k| h[k] = yield(k) }.compare_by_identity
383
501
  end
384
502
 
503
+ def check_visible(schema, member)
504
+ if schema.visible?(member, @context)
505
+ true
506
+ elsif @skip_warning
507
+ false
508
+ else
509
+ member_s = member.respond_to?(:path) ? member.path : member.inspect
510
+ member_type = case member
511
+ when Module
512
+ if member.respond_to?(:kind)
513
+ member.kind.name.downcase
514
+ else
515
+ ""
516
+ end
517
+ when GraphQL::Schema::Field
518
+ "field"
519
+ when GraphQL::Schema::EnumValue
520
+ "enum value"
521
+ when GraphQL::Schema::Argument
522
+ "argument"
523
+ else
524
+ ""
525
+ end
526
+
527
+ schema_s = schema.name ? "#{schema.name}'s" : ""
528
+ schema_name = schema.name ? "#{schema.name}" : "your schema"
529
+ warn(ADD_WARDEN_WARNING % { schema_s: schema_s, schema_name: schema_name, member: member_s, member_type: member_type })
530
+ @skip_warning = true # only warn once per query
531
+ # If there's no schema name, add the backtrace for additional context:
532
+ if schema_s == ""
533
+ puts caller.map { |l| " #{l}"}
534
+ end
535
+ false
536
+ end
537
+ end
538
+
539
+ ADD_WARDEN_WARNING = <<~WARNING
540
+ DEPRECATION: %{schema_s} "%{member}" %{member_type} returned `false` for `.visible?` but `GraphQL::Schema::Visibility` isn't configured yet.
541
+
542
+ Address this warning by adding:
543
+
544
+ use GraphQL::Schema::Visibility
545
+
546
+ to the definition for %{schema_name}. (Future GraphQL-Ruby versions won't check `.visible?` methods by default.)
547
+
548
+ Alternatively, for legacy behavior, add:
549
+
550
+ use GraphQL::Schema::Warden # legacy visibility behavior
551
+
552
+ For more information see: https://graphql-ruby.org/authorization/visibility.html
553
+ WARNING
554
+
385
555
  def reachable_type_set
386
556
  return @reachable_type_set if @reachable_type_set
387
557