graphql 2.5.19 → 2.5.20

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/dashboard/application_controller.rb +41 -0
  3. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  4. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  5. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  6. data/lib/graphql/dashboard.rb +9 -74
  7. data/lib/graphql/dataloader/null_dataloader.rb +7 -3
  8. data/lib/graphql/execution/batching/field_compatibility.rb +150 -0
  9. data/lib/graphql/execution/batching/field_resolve_step.rb +408 -0
  10. data/lib/graphql/execution/batching/prepare_object_step.rb +112 -0
  11. data/lib/graphql/execution/batching/runner.rb +352 -0
  12. data/lib/graphql/execution/batching/selections_step.rb +37 -0
  13. data/lib/graphql/execution/batching.rb +62 -0
  14. data/lib/graphql/execution_error.rb +13 -10
  15. data/lib/graphql/introspection/directive_type.rb +7 -3
  16. data/lib/graphql/introspection/entry_points.rb +6 -2
  17. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  18. data/lib/graphql/introspection/field_type.rb +13 -5
  19. data/lib/graphql/introspection/input_value_type.rb +21 -13
  20. data/lib/graphql/introspection/type_type.rb +64 -28
  21. data/lib/graphql/invalid_null_error.rb +11 -5
  22. data/lib/graphql/query/context.rb +3 -2
  23. data/lib/graphql/query/null_context.rb +9 -3
  24. data/lib/graphql/schema/argument.rb +4 -0
  25. data/lib/graphql/schema/build_from_definition.rb +26 -6
  26. data/lib/graphql/schema/field.rb +76 -1
  27. data/lib/graphql/schema/member/has_dataloader.rb +9 -0
  28. data/lib/graphql/schema/member/has_fields.rb +3 -0
  29. data/lib/graphql/schema/resolver.rb +3 -1
  30. data/lib/graphql/schema/visibility.rb +1 -1
  31. data/lib/graphql/schema.rb +33 -7
  32. data/lib/graphql/subscriptions.rb +1 -1
  33. data/lib/graphql/tracing/perfetto_trace.rb +1 -1
  34. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  35. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  36. data/lib/graphql/types/relay/has_node_field.rb +5 -8
  37. data/lib/graphql/types/relay/has_nodes_field.rb +5 -8
  38. data/lib/graphql/unauthorized_error.rb +5 -1
  39. data/lib/graphql/version.rb +1 -1
  40. data/lib/graphql.rb +3 -0
  41. metadata +11 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a94d75c592000805bf9fd18507343c0b3428f1cf1d9a1828edc921828fd2735
4
- data.tar.gz: 2984583eb702965d43e45f58f1ac29b6a782b8ecf02b4ee1953b5c5bbb6ec872
3
+ metadata.gz: fab0bc8a5ed6bcbe53f688f9d0209a7c05808f9dd35fe487407825ee7b5b4000
4
+ data.tar.gz: 460c780b3ccc6c3b173aef304448c13c84e7f3e28f6b1eb2785e483ee615c848
5
5
  SHA512:
6
- metadata.gz: 743bd603f22bb38bdff984c75b37eea32d460aeafd50617fc52dc9c4b93fe1d3265df196b728432a7b2d6494a6ba20c47b658ee84b387377037ea173844c654b
7
- data.tar.gz: 12354210f6208443a5ec0dc931606440de29bfe0bdb7c6468e7faf9bc013a67671eab62fcc3bd88405d1ffb840a5c0e04be337d9a6707d511a2046d315722f63
6
+ metadata.gz: 8919741b4446074f12cba8aba4ce72b97bb40e400f969d056629c57f3b94d57bc00cdb187784dec5eda4ce4e6c937e1ea856b7505ad40ce0692b2c2c596df43e
7
+ data.tar.gz: a559f8b81eb3148a85cc6e7aeb86023efb4f93ee9230fb72dc9e631e9cda7ae88dd05ab10af250bde8d4620eeb34804b4c81f78c30c58e9c670c58bd26090d01
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ require "action_controller"
3
+
4
+ module Graphql
5
+ class Dashboard < Rails::Engine
6
+ class ApplicationController < ActionController::Base
7
+ protect_from_forgery with: :exception
8
+ prepend_view_path(File.expand_path("../views", __FILE__))
9
+
10
+ content_security_policy do |policy|
11
+ policy.default_src(:self) if policy.default_src(*policy.default_src).blank?
12
+ policy.connect_src(:self) if policy.connect_src(*policy.connect_src).blank?
13
+ policy.base_uri(:none) if policy.base_uri(*policy.base_uri).blank?
14
+ policy.font_src(:self) if policy.font_src(*policy.font_src).blank?
15
+ policy.img_src(:self, :data) if policy.img_src(*policy.img_src).blank?
16
+ policy.object_src(:none) if policy.object_src(*policy.object_src).blank?
17
+ policy.script_src(:self) if policy.script_src(*policy.script_src).blank?
18
+ policy.style_src(:self) if policy.style_src(*policy.style_src).blank?
19
+ policy.form_action(:self) if policy.form_action(*policy.form_action).blank?
20
+ policy.frame_ancestors(:none) if policy.frame_ancestors(*policy.frame_ancestors).blank?
21
+ end
22
+
23
+ def schema_class
24
+ @schema_class ||= begin
25
+ schema_param = request.query_parameters["schema"] || params[:schema]
26
+ case schema_param
27
+ when Class
28
+ schema_param
29
+ when String
30
+ schema_param.constantize
31
+ else
32
+ raise "Missing `params[:schema]`, please provide a class or string to `mount GraphQL::Dashboard, schema: ...`"
33
+ end
34
+ end
35
+ end
36
+ helper_method :schema_class
37
+ end
38
+ end
39
+ end
40
+
41
+ ActiveSupport.run_load_hooks(:graphql_dashboard_application_controller, GraphQL::Dashboard::ApplicationController)
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ module Graphql
3
+ class Dashboard < Rails::Engine
4
+ class LandingsController < ApplicationController
5
+ def show
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ module Graphql
3
+ class Dashboard < Rails::Engine
4
+ class StaticsController < ApplicationController
5
+ skip_forgery_protection
6
+ # Use an explicit list of files to avoid any chance of reading other files from disk
7
+ STATICS = {}
8
+
9
+ [
10
+ "icon.png",
11
+ "header-icon.png",
12
+ "charts.min.css",
13
+ "dashboard.css",
14
+ "dashboard.js",
15
+ "bootstrap-5.3.3.min.css",
16
+ "bootstrap-5.3.3.min.js",
17
+ ].each do |static_file|
18
+ STATICS[static_file] = File.expand_path("../statics/#{static_file}", __FILE__)
19
+ end
20
+
21
+ def show
22
+ expires_in 1.year, public: true
23
+ if (filepath = STATICS[params[:id]])
24
+ render file: filepath
25
+ else
26
+ head :not_found
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require_relative "./installable"
2
3
  module Graphql
3
4
  class Dashboard < Rails::Engine
4
5
  module Subscriptions
@@ -6,7 +7,7 @@ module Graphql
6
7
  include Installable
7
8
 
8
9
  def feature_installed?
9
- schema_class.subscriptions.is_a?(GraphQL::Pro::Subscriptions)
10
+ defined?(GraphQL::Pro::Subscriptions) && schema_class.subscriptions.is_a?(GraphQL::Pro::Subscriptions)
10
11
  end
11
12
 
12
13
  INSTALLABLE_COMPONENT_HEADER_HTML = "GraphQL-Pro Subscriptions aren't installed on this schema yet.".html_safe
@@ -35,6 +35,15 @@ module Graphql
35
35
  class Dashboard < Rails::Engine
36
36
  engine_name "graphql_dashboard"
37
37
  isolate_namespace(Graphql::Dashboard)
38
+
39
+ autoload :ApplicationController, "graphql/dashboard/application_controller"
40
+ autoload :LandingsController, "graphql/dashboard/landings_controller"
41
+ autoload :StaticsController, "graphql/dashboard/statics_controller"
42
+ autoload :DetailedTraces, "graphql/dashboard/detailed_traces"
43
+ autoload :Subscriptions, "graphql/dashboard/subscriptions"
44
+ autoload :OperationStore, "graphql/dashboard/operation_store"
45
+ autoload :Limiters, "graphql/dashboard/limiters"
46
+
38
47
  routes do
39
48
  root "landings#show"
40
49
  resources :statics, only: :show, constraints: { id: /[0-9A-Za-z\-.]+/ }
@@ -77,85 +86,11 @@ module Graphql
77
86
  resources :subscriptions, only: [:show], constraints: { id: /[a-zA-Z0-9\-]+/ }
78
87
  post "/subscriptions/clear_all", to: "subscriptions#clear_all", as: :clear_all
79
88
  end
80
-
81
- ApplicationController.include(Dashboard.routes.url_helpers)
82
- end
83
-
84
- class ApplicationController < ActionController::Base
85
- protect_from_forgery with: :exception
86
- prepend_view_path(File.join(__FILE__, "../dashboard/views"))
87
-
88
- content_security_policy do |policy|
89
- policy.default_src(:self) if policy.default_src(*policy.default_src).blank?
90
- policy.connect_src(:self) if policy.connect_src(*policy.connect_src).blank?
91
- policy.base_uri(:none) if policy.base_uri(*policy.base_uri).blank?
92
- policy.font_src(:self) if policy.font_src(*policy.font_src).blank?
93
- policy.img_src(:self, :data) if policy.img_src(*policy.img_src).blank?
94
- policy.object_src(:none) if policy.object_src(*policy.object_src).blank?
95
- policy.script_src(:self) if policy.script_src(*policy.script_src).blank?
96
- policy.style_src(:self) if policy.style_src(*policy.style_src).blank?
97
- policy.form_action(:self) if policy.form_action(*policy.form_action).blank?
98
- policy.frame_ancestors(:none) if policy.frame_ancestors(*policy.frame_ancestors).blank?
99
- end
100
-
101
- def schema_class
102
- @schema_class ||= begin
103
- schema_param = request.query_parameters["schema"] || params[:schema]
104
- case schema_param
105
- when Class
106
- schema_param
107
- when String
108
- schema_param.constantize
109
- else
110
- raise "Missing `params[:schema]`, please provide a class or string to `mount GraphQL::Dashboard, schema: ...`"
111
- end
112
- end
113
- end
114
- helper_method :schema_class
115
- end
116
-
117
- class LandingsController < ApplicationController
118
- def show
119
- end
120
- end
121
-
122
- class StaticsController < ApplicationController
123
- skip_forgery_protection
124
- # Use an explicit list of files to avoid any chance of reading other files from disk
125
- STATICS = {}
126
-
127
- [
128
- "icon.png",
129
- "header-icon.png",
130
- "charts.min.css",
131
- "dashboard.css",
132
- "dashboard.js",
133
- "bootstrap-5.3.3.min.css",
134
- "bootstrap-5.3.3.min.js",
135
- ].each do |static_file|
136
- STATICS[static_file] = File.expand_path("../dashboard/statics/#{static_file}", __FILE__)
137
- end
138
-
139
- def show
140
- expires_in 1.year, public: true
141
- if (filepath = STATICS[params[:id]])
142
- render file: filepath
143
- else
144
- head :not_found
145
- end
146
- end
147
89
  end
148
90
  end
149
91
  end
150
92
 
151
- require 'graphql/dashboard/detailed_traces'
152
- require 'graphql/dashboard/limiters'
153
- require 'graphql/dashboard/operation_store'
154
- require 'graphql/dashboard/subscriptions'
155
-
156
93
  # Rails expects the engine to be called `Graphql::Dashboard`,
157
94
  # but `GraphQL::Dashboard` is consistent with this gem's naming.
158
95
  # So define both constants to refer to the same class.
159
96
  GraphQL::Dashboard = Graphql::Dashboard
160
-
161
- ActiveSupport.run_load_hooks(:graphql_dashboard_application_controller, GraphQL::Dashboard::ApplicationController)
@@ -37,13 +37,17 @@ module GraphQL
37
37
  end
38
38
 
39
39
  def run_isolated
40
- new_dl = self.class.new
40
+ # Reuse this instance because execution code may already have a reference to _this_ `dataloader` inside the given block.
41
+ prev_lazies_at_depth = @lazies_at_depth
42
+ @lazies_at_depth = @lazies_at_depth.dup.clear
41
43
  res = nil
42
- new_dl.append_job {
44
+ append_job {
43
45
  res = yield
44
46
  }
45
- new_dl.run
47
+ run
46
48
  res
49
+ ensure
50
+ @lazies_at_depth = prev_lazies_at_depth
47
51
  end
48
52
 
49
53
  def clear_cache; end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ module Batching
5
+ module FieldCompatibility
6
+ def resolve_all_load_arguments(frs, object_from_id_receiver, arguments, argument_owner, context)
7
+ arg_defns = context.types.arguments(argument_owner)
8
+ arg_defns.each do |arg_defn|
9
+ if arg_defn.loads
10
+ if arguments.key?(arg_defn.keyword)
11
+ id = arguments.delete(arg_defn.keyword)
12
+ if !id.nil?
13
+ value = if arg_defn.type.list?
14
+ id.map { |inner_id|
15
+ object_from_id_receiver.load_and_authorize_application_object(arg_defn, inner_id, context)
16
+ }
17
+ else
18
+ object_from_id_receiver.load_and_authorize_application_object(arg_defn, id, context)
19
+ end
20
+
21
+ if frs.runner.resolves_lazies
22
+ value = frs.sync(value)
23
+ end
24
+ if value.is_a?(GraphQL::Error)
25
+ value.path = frs.path
26
+ return value
27
+ end
28
+ else
29
+ value = nil
30
+ end
31
+ arguments[arg_defn.keyword] = value
32
+ end
33
+ elsif (input_type = arg_defn.type.unwrap).kind.input_object? &&
34
+ (value = arguments[arg_defn.keyword]) # TODO lists
35
+ resolve_all_load_arguments(frs, object_from_id_receiver, value, input_type, context)
36
+ end
37
+ end
38
+ nil
39
+ end
40
+
41
+ def resolve_batch(frs, objects, context, kwargs)
42
+ if @batch_mode && !:direct_send.equal?(@batch_mode)
43
+ return super
44
+ end
45
+
46
+ if !@resolver_class
47
+ maybe_err = resolve_all_load_arguments(frs, self, kwargs, self, context)
48
+ if maybe_err
49
+ return maybe_err
50
+ end
51
+ end
52
+ if extras.include?(:lookahead)
53
+ if kwargs.frozen?
54
+ kwargs = kwargs.dup
55
+ end
56
+ kwargs[:lookahead] = Execution::Lookahead.new(
57
+ query: context.query,
58
+ ast_nodes: frs.ast_nodes || Array(frs.ast_node),
59
+ field: self,
60
+ )
61
+ end
62
+
63
+ if extras.include?(:ast_node)
64
+ if kwargs.frozen?
65
+ kwargs = kwargs.dup
66
+ end
67
+ kwargs[:ast_node] = frs.ast_node
68
+ end
69
+
70
+ if @owner.method_defined?(@resolver_method)
71
+ results = []
72
+ frs.selections_step.graphql_objects.each_with_index do |obj_inst, idx|
73
+ if frs.object_is_authorized[idx]
74
+ if dynamic_introspection
75
+ obj_inst = @owner.wrap(obj_inst, context)
76
+ end
77
+ results << with_extensions(obj_inst, kwargs, context) do |obj, ruby_kwargs|
78
+ if ruby_kwargs.empty?
79
+ obj.public_send(@resolver_method)
80
+ else
81
+ obj.public_send(@resolver_method, **ruby_kwargs)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ results
87
+ elsif @resolver_class
88
+ objects.map do |o|
89
+ resolver_inst_kwargs = kwargs.dup
90
+ resolver_inst = @resolver_class.new(object: o, context: context, field: self)
91
+ maybe_err = resolve_all_load_arguments(frs, resolver_inst, resolver_inst_kwargs, self, context)
92
+ if maybe_err
93
+ next maybe_err
94
+ end
95
+ resolver_inst_kwargs = if @resolver_class < Schema::HasSingleInputArgument
96
+ resolver_inst_kwargs[:input]
97
+ else
98
+ resolver_inst_kwargs
99
+ end
100
+ with_extensions(o, resolver_inst_kwargs, context) do |obj, ruby_kwargs|
101
+ resolver_inst.object = obj
102
+ resolver_inst.prepared_arguments = ruby_kwargs
103
+ is_authed, new_return_value = resolver_inst.authorized?(**ruby_kwargs)
104
+ if frs.runner.resolves_lazies && frs.runner.schema.lazy?(is_authed)
105
+ is_authed, new_return_value = frs.runner.schema.sync_lazy(is_authed)
106
+ end
107
+ if is_authed
108
+ resolver_inst.call_resolve(ruby_kwargs)
109
+ else
110
+ new_return_value
111
+ end
112
+ end
113
+ rescue RuntimeError => err
114
+ err
115
+ rescue StandardError => stderr
116
+ begin
117
+ context.query.handle_or_reraise(stderr)
118
+ rescue GraphQL::ExecutionError => ex_err
119
+ ex_err
120
+ end
121
+ end
122
+ elsif objects.first.is_a?(Hash)
123
+ objects.map { |o| o[method_sym] || o[graphql_name] }
124
+ elsif objects.first.is_a?(Interpreter::RawValue)
125
+ objects
126
+ else
127
+ # need to use connection extension if present, and extensions expect object type instances
128
+ if extensions.empty?
129
+ objects.map { |o| o.public_send(@method_sym)}
130
+ else
131
+ results = []
132
+ frs.selections_step.graphql_objects.each_with_index do |obj_inst, idx|
133
+ if frs.object_is_authorized[idx]
134
+ results << with_extensions(obj_inst, EmptyObjects::EMPTY_HASH, context) do |obj, arguments|
135
+ if arguments.empty?
136
+ obj.object.public_send(@method_sym)
137
+ else
138
+ obj.object.public_send(@method_sym, **arguments)
139
+ end
140
+ end
141
+ end
142
+ end
143
+ results
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end