graphql 2.5.14 → 2.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: deb0ac3bea401c53909c6880f133ee810f3e0882c7a023ed33b27f7b12a4c82b
4
- data.tar.gz: be2d8e55d131a62863d4ecf6665c22c9ba6c9ba467bdd205ec83d96f9b952098
3
+ metadata.gz: a45427a56efb8b050895ed510b7519a7d6093940220141c5a3853af60cf666b5
4
+ data.tar.gz: 5d7d99e8b9ceb05b43995cb2095bbf317b379ed659b8df4783716989f0eb2105
5
5
  SHA512:
6
- metadata.gz: 494296336070a677965ceaaad1e14412f1b8a6589ab3ae3466c5c00071a59f763f2af2df8aacd394c9fd912afd0a5f9df988991411213dbfab285d762d1a6e75
7
- data.tar.gz: 8d65056236ddb32eabe498d93bb026f8d7ea145b627d6a4554b2add2d8c00db202f9bbe16cae816e58aeb6dd77f3e8aed99dcaa802af2643f73b03a5d10b0b63
6
+ metadata.gz: 45080489ddda5c4d9f50eb3cfdc63cb21bfbe8b0f7890ca68e21d9533e8f794ce466eaab8986b1c7356e4f1a1aaf6811baf451ab41a0ee94320a520037e5f442
7
+ data.tar.gz: 97581b7186279e55ac3465546d99d46f03638be88662b09c6d0423ff238e69d7ff56860f8cea50b1fbb62b7c6a89c9ea344b4d69f71ffc5f36780b956f1e9228
@@ -10,7 +10,7 @@ module GraphQL
10
10
 
11
11
  def initialize(value)
12
12
  @date_value = value
13
- super("Date cannot be parsed: #{value}. \nDate must be be able to be parsed as a Ruby Date object.")
13
+ super("Date cannot be parsed: #{value}. \nDate must be able to be parsed as a Ruby Date object.")
14
14
  end
15
15
  end
16
16
  end
@@ -80,7 +80,6 @@ module GraphQL
80
80
  end
81
81
  rescue GraphQL::ExecutionError => err
82
82
  query.context.errors << err
83
- NO_OPERATION
84
83
  end
85
84
  end
86
85
  results[idx] = result
@@ -52,8 +52,9 @@ module GraphQL
52
52
 
53
53
  def build_object_type_node(object_type)
54
54
  ints = @types.interfaces(object_type)
55
+
55
56
  if !ints.empty?
56
- ints.sort_by!(&:graphql_name)
57
+ ints = ints.sort_by(&:graphql_name)
57
58
  ints.map! { |iface| build_type_name_node(iface) }
58
59
  end
59
60
 
@@ -142,7 +142,14 @@ module GraphQL
142
142
  # However, we're using result coercion here to go from Ruby value
143
143
  # to GraphQL value, so it doesn't have that feature.
144
144
  # Keep the GraphQL-type behavior but implement it manually:
145
- coerce_value = [coerce_value]
145
+ wrap_type = arg_type
146
+ while wrap_type.list?
147
+ if wrap_type.non_null?
148
+ wrap_type = wrap_type.of_type
149
+ end
150
+ wrap_type = wrap_type.of_type
151
+ coerce_value = [coerce_value]
152
+ end
146
153
  end
147
154
  arg_type.coerce_isolated_result(coerce_value)
148
155
  rescue GraphQL::Schema::Enum::UnresolvedValueError
@@ -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
@@ -10,9 +10,9 @@ 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
@@ -1708,7 +1708,7 @@ module GraphQL
1708
1708
  # If you need to support previous, non-spec behavior which allowed selecting union fields
1709
1709
  # but *not* selecting any fields on that union, set this to `true` to continue allowing that behavior.
1710
1710
  #
1711
- # If this is `true`, then {.legacy_invalid_empty_selections_on_union} will be called with {Query} objects
1711
+ # If this is `true`, then {.legacy_invalid_empty_selections_on_union_with_type} will be called with {Query} objects
1712
1712
  # with that kind of selections. You must implement that method
1713
1713
  # @param new_value [Boolean]
1714
1714
  # @return [true, false, nil]
@@ -1724,6 +1724,22 @@ module GraphQL
1724
1724
  end
1725
1725
  end
1726
1726
 
1727
+ # This method is called during validation when a previously-allowed, but non-spec
1728
+ # query is encountered where a union field has no child selections on it.
1729
+ #
1730
+ # If `legacy_invalid_empty_selections_on_union_with_type` is overridden, this method will not be called.
1731
+ #
1732
+ # You should implement this method or `legacy_invalid_empty_selections_on_union_with_type`
1733
+ # to log the violation so that you can contact clients and notify them about changing their queries.
1734
+ # Then return a suitable value to tell GraphQL-Ruby how to continue.
1735
+ # @param query [GraphQL::Query]
1736
+ # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
1737
+ # @return [String] A validation error to return for this query
1738
+ # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
1739
+ def legacy_invalid_empty_selections_on_union(query)
1740
+ raise "Implement `def self.legacy_invalid_empty_selections_on_union_with_type(query, type)` or `def self.legacy_invalid_empty_selections_on_union(query)` to handle this scenario"
1741
+ end
1742
+
1727
1743
  # This method is called during validation when a previously-allowed, but non-spec
1728
1744
  # query is encountered where a union field has no child selections on it.
1729
1745
  #
@@ -1731,11 +1747,12 @@ module GraphQL
1731
1747
  # and notify them about changing their queries. Then return a suitable value to
1732
1748
  # tell GraphQL-Ruby how to continue.
1733
1749
  # @param query [GraphQL::Query]
1750
+ # @param type1 [Module] A GraphQL type definition
1734
1751
  # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
1735
1752
  # @return [String] A validation error to return for this query
1736
1753
  # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
1737
- def legacy_invalid_empty_selections_on_union(query)
1738
- raise "Implement `def self.legacy_invalid_empty_selections_on_union(query)` to handle this scenario"
1754
+ def legacy_invalid_empty_selections_on_union_with_type(query, type)
1755
+ legacy_invalid_empty_selections_on_union(query)
1739
1756
  end
1740
1757
 
1741
1758
  # This setting controls how GraphQL-Ruby handles overlapping selections on scalar types when the types
@@ -49,7 +49,7 @@ module GraphQL
49
49
  if !resolved_type.kind.fields?
50
50
  case @schema.allow_legacy_invalid_empty_selections_on_union
51
51
  when true
52
- legacy_invalid_empty_selection_result = @schema.legacy_invalid_empty_selections_on_union(@context.query)
52
+ legacy_invalid_empty_selection_result = @schema.legacy_invalid_empty_selections_on_union_with_type(@context.query, resolved_type)
53
53
  case legacy_invalid_empty_selection_result
54
54
  when :return_validation_error
55
55
  # keep `return_validation_error = true`
@@ -61,7 +61,7 @@ module GraphQL
61
61
  return_validation_error = false
62
62
  legacy_invalid_empty_selection_result = nil
63
63
  else
64
- raise GraphQL::InvariantError, "Unexpected return value from legacy_invalid_empty_selections_on_union, must be `:return_validation_error`, String, or nil (got: #{legacy_invalid_empty_selection_result.inspect})"
64
+ raise GraphQL::InvariantError, "Unexpected return value from legacy_invalid_empty_selections_on_union_with_type, must be `:return_validation_error`, String, or nil (got: #{legacy_invalid_empty_selection_result.inspect})"
65
65
  end
66
66
  when false
67
67
  # pass -- error below
@@ -81,6 +81,7 @@ module GraphQL
81
81
  # end
82
82
  # end
83
83
  #
84
+ # @see GraphQL::Testing::MockActionCable for test helpers
84
85
  class ActionCableSubscriptions < GraphQL::Subscriptions
85
86
  SUBSCRIPTION_PREFIX = "graphql-subscription:"
86
87
  EVENT_PREFIX = "graphql-event:"
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Testing
4
+ # A stub implementation of ActionCable.
5
+ # Any methods to support the mock backend have `mock` in the name.
6
+ #
7
+ # @example Configuring your schema to use MockActionCable in the test environment
8
+ # class MySchema < GraphQL::Schema
9
+ # # Use MockActionCable in test:
10
+ # use GraphQL::Subscriptions::ActionCableSubscriptions,
11
+ # action_cable: Rails.env.test? ? GraphQL::Testing::MockActionCable : ActionCable
12
+ # end
13
+ #
14
+ # @example Clearing old data before each test
15
+ # setup do
16
+ # GraphQL::Testing::MockActionCable.clear_mocks
17
+ # end
18
+ #
19
+ # @example Using MockActionCable in a test case
20
+ # # Create a channel to use in the test, pass it to GraphQL
21
+ # mock_channel = GraphQL::Testing::MockActionCable.get_mock_channel
22
+ # ActionCableTestSchema.execute("subscription { newsFlash { text } }", context: { channel: mock_channel })
23
+ #
24
+ # # Trigger a subscription update
25
+ # ActionCableTestSchema.subscriptions.trigger(:news_flash, {}, {text: "After yesterday's rain, someone stopped on Rio Road to help a box turtle across five lanes of traffic"})
26
+ #
27
+ # # Check messages on the channel
28
+ # expected_msg = {
29
+ # result: {
30
+ # "data" => {
31
+ # "newsFlash" => {
32
+ # "text" => "After yesterday's rain, someone stopped on Rio Road to help a box turtle across five lanes of traffic"
33
+ # }
34
+ # }
35
+ # },
36
+ # more: true,
37
+ # }
38
+ # assert_equal [expected_msg], mock_channel.mock_broadcasted_messages
39
+ #
40
+ class MockActionCable
41
+ class MockChannel
42
+ def initialize
43
+ @mock_broadcasted_messages = []
44
+ end
45
+
46
+ # @return [Array<Hash>] Payloads "sent" to this channel by GraphQL-Ruby
47
+ attr_reader :mock_broadcasted_messages
48
+
49
+ # Called by ActionCableSubscriptions. Implements a Rails API.
50
+ def stream_from(stream_name, coder: nil, &block)
51
+ # Rails uses `coder`, we don't
52
+ block ||= ->(msg) { @mock_broadcasted_messages << msg }
53
+ MockActionCable.mock_stream_for(stream_name).add_mock_channel(self, block)
54
+ end
55
+ end
56
+
57
+ # Used by mock code
58
+ # @api private
59
+ class MockStream
60
+ def initialize
61
+ @mock_channels = {}
62
+ end
63
+
64
+ def add_mock_channel(channel, handler)
65
+ @mock_channels[channel] = handler
66
+ end
67
+
68
+ def mock_broadcast(message)
69
+ @mock_channels.each do |channel, handler|
70
+ handler && handler.call(message)
71
+ end
72
+ end
73
+ end
74
+
75
+ class << self
76
+ # Call this before each test run to make sure that MockActionCable's data is empty
77
+ def clear_mocks
78
+ @mock_streams = {}
79
+ end
80
+
81
+ # Implements Rails API
82
+ def server
83
+ self
84
+ end
85
+
86
+ # Implements Rails API
87
+ def broadcast(stream_name, message)
88
+ stream = @mock_streams[stream_name]
89
+ stream && stream.mock_broadcast(message)
90
+ end
91
+
92
+ # Used by mock code
93
+ def mock_stream_for(stream_name)
94
+ @mock_streams[stream_name] ||= MockStream.new
95
+ end
96
+
97
+ # Use this as `context[:channel]` to simulate an ActionCable channel
98
+ #
99
+ # @return [GraphQL::Testing::MockActionCable::MockChannel]
100
+ def get_mock_channel
101
+ MockChannel.new
102
+ end
103
+
104
+ # @return [Array<String>] Streams that currently have subscribers
105
+ def mock_stream_names
106
+ @mock_streams.keys
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -1,2 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/testing/helpers"
3
+ require "graphql/testing/mock_action_cable"
@@ -9,8 +9,12 @@ module GraphQL
9
9
  # When `MySchema.detailed_trace?(query)` returns `true`, a profiler-specific `trace_mode: ...` will be used for the query,
10
10
  # overriding the one in `context[:trace_mode]`.
11
11
  #
12
+ # By default, the detailed tracer calls `.inspect` on application objects returned from fields. You can customize
13
+ # this behavior by extending {DetailedTrace} and overriding {#inspect_object}. You can opt out of debug annotations
14
+ # entirely with `use ..., debug: false` or for a single query with `context: { detailed_trace_debug: false }`.
15
+ #
12
16
  # __Redis__: The sampler stores its results in a provided Redis database. Depending on your needs,
13
- # You can configure this database to retail all data (persistent) or to expire data according to your rules.
17
+ # You can configure this database to retain all data (persistent) or to expire data according to your rules.
14
18
  # If you need to save traces indefinitely, you can download them from Perfetto after opening them there.
15
19
  #
16
20
  # @example Adding the sampler to your schema
@@ -27,10 +31,29 @@ module GraphQL
27
31
  # end
28
32
  #
29
33
  # @see Graphql::Dashboard GraphQL::Dashboard for viewing stored results
34
+ #
35
+ # @example Customizing debug output in traces
36
+ # class CustomDetailedTrace < GraphQL::Tracing::DetailedTrace
37
+ # def inspect_object(object)
38
+ # if object.is_a?(SomeThing)
39
+ # # handle it specially ...
40
+ # else
41
+ # super
42
+ # end
43
+ # end
44
+ # end
45
+ #
46
+ # @example disabling debug annotations completely
47
+ # use DetailedTrace, debug: false, ...
48
+ #
49
+ # @example disabling debug annotations for one query
50
+ # MySchema.execute(query_str, context: { detailed_trace_debug: false })
51
+ #
30
52
  class DetailedTrace
31
53
  # @param redis [Redis] If provided, profiles will be stored in Redis for later review
32
54
  # @param limit [Integer] A maximum number of profiles to store
33
- def self.use(schema, trace_mode: :profile_sample, memory: false, redis: nil, limit: nil)
55
+ # @param debug [Boolean] if `false`, it won't create `debug` annotations in Perfetto traces (reduces overhead)
56
+ def self.use(schema, trace_mode: :profile_sample, memory: false, debug: debug?, redis: nil, limit: nil)
34
57
  storage = if redis
35
58
  RedisBackend.new(redis: redis, limit: limit)
36
59
  elsif memory
@@ -38,13 +61,15 @@ module GraphQL
38
61
  else
39
62
  raise ArgumentError, "Pass `redis: ...` to store traces in Redis for later review"
40
63
  end
41
- schema.detailed_trace = self.new(storage: storage, trace_mode: trace_mode)
64
+ detailed_trace = self.new(storage: storage, trace_mode: trace_mode, debug: debug)
65
+ schema.detailed_trace = detailed_trace
42
66
  schema.trace_with(PerfettoTrace, mode: trace_mode, save_profile: true)
43
67
  end
44
68
 
45
- def initialize(storage:, trace_mode:)
69
+ def initialize(storage:, trace_mode:, debug:)
46
70
  @storage = storage
47
71
  @trace_mode = trace_mode
72
+ @debug = debug
48
73
  end
49
74
 
50
75
  # @return [Symbol] The trace mode to use when {Schema.detailed_trace?} returns `true`
@@ -55,6 +80,11 @@ module GraphQL
55
80
  @storage.save_trace(operation_name, duration_ms, begin_ms, trace_data)
56
81
  end
57
82
 
83
+ # @return [Boolean]
84
+ def debug?
85
+ @debug
86
+ end
87
+
58
88
  # @param last [Integer]
59
89
  # @param before [Integer] Timestamp in milliseconds since epoch
60
90
  # @return [Enumerable<StoredTrace>]
@@ -77,6 +107,24 @@ module GraphQL
77
107
  @storage.delete_all_traces
78
108
  end
79
109
 
110
+ def inspect_object(object)
111
+ self.class.inspect_object(object)
112
+ end
113
+
114
+ def self.inspect_object(object)
115
+ if defined?(ActiveRecord::Relation) && object.is_a?(ActiveRecord::Relation)
116
+ "#{object.class}, .to_sql=#{object.to_sql.inspect}"
117
+ else
118
+ object.inspect
119
+ end
120
+ end
121
+
122
+ # Default debug setting
123
+ # @return [true]
124
+ def self.debug?
125
+ true
126
+ end
127
+
80
128
  class StoredTrace
81
129
  def initialize(id:, operation_name:, duration_ms:, begin_ms:, trace_data:)
82
130
  @id = id
@@ -60,11 +60,37 @@ module GraphQL
60
60
  DA_FETCH_KEYS_IID = 13
61
61
  DA_STR_VAL_NIL_IID = 14
62
62
 
63
+ REVERSE_DEBUG_NAME_LOOKUP = {
64
+ DA_OBJECT_IID => "object",
65
+ DA_RESULT_IID => "result",
66
+ DA_ARGUMENTS_IID => "arguments",
67
+ DA_FETCH_KEYS_IID => "fetch keys",
68
+ }
69
+
70
+ DEBUG_INSPECT_CATEGORY_IIDS = [15]
71
+ DA_DEBUG_INSPECT_CLASS_IID = 16
72
+ DEBUG_INSPECT_EVENT_NAME_IID = 17
73
+ DA_DEBUG_INSPECT_FOR_IID = 18
74
+
63
75
  # @param active_support_notifications_pattern [String, RegExp, false] A filter for `ActiveSupport::Notifications`, if it's present. Or `false` to skip subscribing.
64
76
  def initialize(active_support_notifications_pattern: nil, save_profile: false, **_rest)
65
77
  super
66
78
  @active_support_notifications_pattern = active_support_notifications_pattern
67
79
  @save_profile = save_profile
80
+
81
+ query = if @multiplex
82
+ @multiplex.queries.first
83
+ else
84
+ @query # could still be nil in some initializations
85
+ end
86
+
87
+ @detailed_trace = query&.schema&.detailed_trace || DetailedTrace
88
+ @create_debug_annotations = if (ctx = query&.context).nil? || (ctx_debug = ctx[:detailed_trace_debug]).nil?
89
+ @detailed_trace.debug?
90
+ else
91
+ ctx_debug
92
+ end
93
+
68
94
  Fiber[:graphql_flow_stack] = nil
69
95
  @sequence_id = object_id
70
96
  @pid = Process.pid
@@ -110,6 +136,10 @@ module GraphQL
110
136
  @objects_counter_id = :objects_counter.object_id
111
137
  @fibers_counter_id = :fibers_counter.object_id
112
138
  @fields_counter_id = :fields_counter.object_id
139
+ @counts_objects = [@objects_counter_id]
140
+ @counts_objects_and_fields = [@objects_counter_id, @fields_counter_id]
141
+ @counts_fibers = [@fibers_counter_id]
142
+ @counts_fibers_and_objects = [@fibers_counter_id, @objects_counter_id]
113
143
  @begin_validate = nil
114
144
  @begin_time = nil
115
145
  @packets = []
@@ -132,16 +162,19 @@ module GraphQL
132
162
  EventCategory.new(name: "ActiveSupport::Notifications", iid: ACTIVE_SUPPORT_NOTIFICATIONS_CATEGORY_IIDS.first),
133
163
  EventCategory.new(name: "Authorized", iid: AUTHORIZED_CATEGORY_IIDS.first),
134
164
  EventCategory.new(name: "Resolve Type", iid: RESOLVE_TYPE_CATEGORY_IIDS.first),
165
+ EventCategory.new(name: "Debug Inspect", iid: DEBUG_INSPECT_CATEGORY_IIDS.first),
135
166
  ],
136
167
  debug_annotation_names: [
137
- DebugAnnotationName.new(name: "object", iid: DA_OBJECT_IID),
138
- DebugAnnotationName.new(name: "arguments", iid: DA_ARGUMENTS_IID),
139
- DebugAnnotationName.new(name: "result", iid: DA_RESULT_IID),
140
- DebugAnnotationName.new(name: "fetch keys", iid: DA_FETCH_KEYS_IID),
168
+ *REVERSE_DEBUG_NAME_LOOKUP.map { |(iid, name)| DebugAnnotationName.new(name: name, iid: iid) },
169
+ DebugAnnotationName.new(name: "inspect instance of", iid: DA_DEBUG_INSPECT_CLASS_IID),
170
+ DebugAnnotationName.new(name: "inspecting for", iid: DA_DEBUG_INSPECT_FOR_IID)
141
171
  ],
142
172
  debug_annotation_string_values: [
143
173
  InternedString.new(str: "(nil)", iid: DA_STR_VAL_NIL_IID),
144
174
  ],
175
+ event_names: [
176
+ EventName.new(name: "#{(@detailed_trace.is_a?(Class) ? @detailed_trace : @detailed_trace.class).name}#inspect_object", iid: DEBUG_INSPECT_EVENT_NAME_IID)
177
+ ],
145
178
  ),
146
179
  trusted_packet_sequence_id: @sequence_id,
147
180
  sequence_flags: 2,
@@ -180,11 +213,9 @@ module GraphQL
180
213
  @packets << trace_packet(
181
214
  type: TrackEvent::Type::TYPE_SLICE_BEGIN,
182
215
  track_uuid: fid,
183
- name: "Multiplex",
184
- debug_annotations: [
185
- payload_to_debug("query_string", multiplex.queries.map(&:sanitized_query_string).join("\n\n"))
186
- ]
187
- )
216
+ name: "Multiplex"
217
+ ) { [ payload_to_debug("query_string", multiplex.queries.map(&:sanitized_query_string).join("\n\n")) ] }
218
+
188
219
  result = super
189
220
 
190
221
  @packets << trace_packet(
@@ -209,7 +240,7 @@ module GraphQL
209
240
  track_uuid: fid,
210
241
  name: query.context.current_path.join("."),
211
242
  category_iids: FIELD_EXECUTE_CATEGORY_IIDS,
212
- extra_counter_track_uuids: [@objects_counter_id],
243
+ extra_counter_track_uuids: @counts_objects,
213
244
  extra_counter_values: [count_allocations],
214
245
  )
215
246
  @packets << packet
@@ -218,19 +249,23 @@ module GraphQL
218
249
  end
219
250
 
220
251
  def end_execute_field(field, object, arguments, query, app_result)
252
+ end_ts = ts
221
253
  start_field = fiber_flow_stack.pop
222
- start_field.track_event = dup_with(start_field.track_event, {
223
- debug_annotations: [
224
- payload_to_debug(nil, object.object, iid: DA_OBJECT_IID, intern_value: true),
225
- payload_to_debug(nil, arguments, iid: DA_ARGUMENTS_IID),
226
- payload_to_debug(nil, app_result, iid: DA_RESULT_IID, intern_value: true)
227
- ]
228
- })
254
+ if @create_debug_annotations
255
+ start_field.track_event = dup_with(start_field.track_event,{
256
+ debug_annotations: [
257
+ payload_to_debug(nil, object.object, iid: DA_OBJECT_IID, intern_value: true),
258
+ payload_to_debug(nil, arguments, iid: DA_ARGUMENTS_IID),
259
+ payload_to_debug(nil, app_result, iid: DA_RESULT_IID, intern_value: true)
260
+ ]
261
+ })
262
+ end
229
263
 
230
264
  @packets << trace_packet(
265
+ timestamp: end_ts,
231
266
  type: TrackEvent::Type::TYPE_SLICE_END,
232
267
  track_uuid: fid,
233
- extra_counter_track_uuids: [@objects_counter_id, @fields_counter_id],
268
+ extra_counter_track_uuids: @counts_objects_and_fields,
234
269
  extra_counter_values: [count_allocations, count_fields],
235
270
  )
236
271
  super
@@ -240,22 +275,24 @@ module GraphQL
240
275
  @packets << trace_packet(
241
276
  type: TrackEvent::Type::TYPE_SLICE_BEGIN,
242
277
  track_uuid: fid,
243
- extra_counter_track_uuids: [@objects_counter_id],
278
+ extra_counter_track_uuids: @counts_objects,
244
279
  extra_counter_values: [count_allocations],
245
- name: "Analysis",
246
- debug_annotations: [
247
- payload_to_debug("analyzers_count", analyzers.size),
248
- payload_to_debug("analyzers", analyzers),
249
- ]
250
- )
280
+ name: "Analysis") {
281
+ [
282
+ payload_to_debug("analyzers_count", analyzers.size),
283
+ payload_to_debug("analyzers", analyzers),
284
+ ]
285
+ }
251
286
  super
252
287
  end
253
288
 
254
289
  def end_analyze_multiplex(m, analyzers)
290
+ end_ts = ts
255
291
  @packets << trace_packet(
292
+ timestamp: end_ts,
256
293
  type: TrackEvent::Type::TYPE_SLICE_END,
257
294
  track_uuid: fid,
258
- extra_counter_track_uuids: [@objects_counter_id],
295
+ extra_counter_track_uuids: @counts_objects,
259
296
  extra_counter_values: [count_allocations],
260
297
  )
261
298
  super
@@ -265,50 +302,57 @@ module GraphQL
265
302
  @packets << trace_packet(
266
303
  type: TrackEvent::Type::TYPE_SLICE_BEGIN,
267
304
  track_uuid: fid,
268
- extra_counter_track_uuids: [@objects_counter_id],
305
+ extra_counter_track_uuids: @counts_objects,
269
306
  extra_counter_values: [count_allocations],
270
307
  name: "Parse"
271
308
  )
272
309
  result = super
310
+ end_ts = ts
273
311
  @packets << trace_packet(
312
+ timestamp: end_ts,
274
313
  type: TrackEvent::Type::TYPE_SLICE_END,
275
314
  track_uuid: fid,
276
- extra_counter_track_uuids: [@objects_counter_id],
315
+ extra_counter_track_uuids: @counts_objects,
277
316
  extra_counter_values: [count_allocations],
278
317
  )
279
318
  result
280
319
  end
281
320
 
282
321
  def begin_validate(query, validate)
283
- @packets << @begin_validate = trace_packet(
322
+ @begin_validate = trace_packet(
284
323
  type: TrackEvent::Type::TYPE_SLICE_BEGIN,
285
324
  track_uuid: fid,
286
- extra_counter_track_uuids: [@objects_counter_id],
325
+ extra_counter_track_uuids: @counts_objects,
287
326
  extra_counter_values: [count_allocations],
288
- name: "Validate",
289
- debug_annotations: [
290
- payload_to_debug("validate?", validate),
291
- ]
292
- )
327
+ name: "Validate") {
328
+ [payload_to_debug("validate?", validate)]
329
+ }
330
+
331
+ @packets << @begin_validate
293
332
  super
294
333
  end
295
334
 
296
335
  def end_validate(query, validate, validation_errors)
336
+ end_ts = ts
297
337
  @packets << trace_packet(
338
+ timestamp: end_ts,
298
339
  type: TrackEvent::Type::TYPE_SLICE_END,
299
340
  track_uuid: fid,
300
- extra_counter_track_uuids: [@objects_counter_id],
341
+ extra_counter_track_uuids: @counts_objects,
301
342
  extra_counter_values: [count_allocations],
302
343
  )
303
- @begin_validate.track_event = dup_with(
304
- @begin_validate.track_event,
305
- {
306
- debug_annotations: [
307
- @begin_validate.track_event.debug_annotations.first,
308
- payload_to_debug("valid?", validation_errors.empty?)
309
- ]
310
- }
311
- )
344
+
345
+ if @create_debug_annotations
346
+ new_bv_track_event = dup_with(
347
+ @begin_validate.track_event, {
348
+ debug_annotations: [
349
+ @begin_validate.track_event.debug_annotations.first,
350
+ payload_to_debug("valid?", validation_errors.empty?)
351
+ ]
352
+ }
353
+ )
354
+ @begin_validate.track_event = new_bv_track_event
355
+ end
312
356
  super
313
357
  end
314
358
 
@@ -318,7 +362,7 @@ module GraphQL
318
362
  track_uuid: fid,
319
363
  name: "Create Execution Fiber",
320
364
  category_iids: DATALOADER_CATEGORY_IIDS,
321
- extra_counter_track_uuids: [@fibers_counter_id, @objects_counter_id],
365
+ extra_counter_track_uuids: @counts_fibers_and_objects,
322
366
  extra_counter_values: [count_fibers(1), count_allocations]
323
367
  )
324
368
  @packets << track_descriptor_packet(@did, fid, "Exec Fiber ##{fid}")
@@ -331,7 +375,7 @@ module GraphQL
331
375
  track_uuid: fid,
332
376
  name: "Create Source Fiber",
333
377
  category_iids: DATALOADER_CATEGORY_IIDS,
334
- extra_counter_track_uuids: [@fibers_counter_id, @objects_counter_id],
378
+ extra_counter_track_uuids: @counts_fibers_and_objects,
335
379
  extra_counter_values: [count_fibers(1), count_allocations]
336
380
  )
337
381
  @packets << track_descriptor_packet(@did, fid, "Source Fiber ##{fid}")
@@ -385,7 +429,7 @@ module GraphQL
385
429
  track_uuid: fid,
386
430
  name: "Fiber Exit",
387
431
  category_iids: DATALOADER_CATEGORY_IIDS,
388
- extra_counter_track_uuids: [@fibers_counter_id],
432
+ extra_counter_track_uuids: @counts_fibers,
389
433
  extra_counter_values: [count_fibers(-1)],
390
434
  )
391
435
  super
@@ -415,31 +459,34 @@ module GraphQL
415
459
  fds = @flow_ids[source]
416
460
  fds_copy = fds.dup
417
461
  fds.clear
462
+
418
463
  packet = trace_packet(
419
464
  type: TrackEvent::Type::TYPE_SLICE_BEGIN,
420
465
  track_uuid: fid,
421
466
  name_iid: @source_name_iids[source.class],
422
467
  category_iids: DATALOADER_CATEGORY_IIDS,
423
468
  flow_ids: fds_copy,
424
- extra_counter_track_uuids: [@objects_counter_id],
425
- extra_counter_values: [count_allocations],
426
- debug_annotations: [
427
- payload_to_debug(nil, source.pending.values, iid: DA_FETCH_KEYS_IID, intern_value: true),
428
- *(source.instance_variables - [:@pending, :@fetching, :@results, :@dataloader]).map { |iv|
429
- payload_to_debug(iv.to_s, source.instance_variable_get(iv), intern_value: true)
430
- }
431
- ]
432
- )
469
+ extra_counter_track_uuids: @counts_objects,
470
+ extra_counter_values: [count_allocations]) {
471
+ [
472
+ payload_to_debug(nil, source.pending.values, iid: DA_FETCH_KEYS_IID, intern_value: true),
473
+ *(source.instance_variables - [:@pending, :@fetching, :@results, :@dataloader]).map { |iv|
474
+ payload_to_debug(iv.to_s, source.instance_variable_get(iv), intern_value: true)
475
+ }
476
+ ]
477
+ }
433
478
  @packets << packet
434
479
  fiber_flow_stack << packet
435
480
  super
436
481
  end
437
482
 
438
483
  def end_dataloader_source(source)
484
+ end_ts = ts
439
485
  @packets << trace_packet(
486
+ timestamp: end_ts,
440
487
  type: TrackEvent::Type::TYPE_SLICE_END,
441
488
  track_uuid: fid,
442
- extra_counter_track_uuids: [@objects_counter_id],
489
+ extra_counter_track_uuids: @counts_objects,
443
490
  extra_counter_values: [count_allocations],
444
491
  )
445
492
  fiber_flow_stack.pop
@@ -451,7 +498,7 @@ module GraphQL
451
498
  type: TrackEvent::Type::TYPE_SLICE_BEGIN,
452
499
  track_uuid: fid,
453
500
  category_iids: AUTHORIZED_CATEGORY_IIDS,
454
- extra_counter_track_uuids: [@objects_counter_id],
501
+ extra_counter_track_uuids: @counts_objects,
455
502
  extra_counter_values: [count_allocations],
456
503
  name_iid: @auth_name_iids[type],
457
504
  )
@@ -461,14 +508,18 @@ module GraphQL
461
508
  end
462
509
 
463
510
  def end_authorized(type, obj, ctx, is_authorized)
511
+ end_ts = ts
464
512
  @packets << trace_packet(
513
+ timestamp: end_ts,
465
514
  type: TrackEvent::Type::TYPE_SLICE_END,
466
515
  track_uuid: fid,
467
- extra_counter_track_uuids: [@objects_counter_id],
516
+ extra_counter_track_uuids: @counts_objects,
468
517
  extra_counter_values: [count_allocations],
469
518
  )
470
519
  beg_auth = fiber_flow_stack.pop
471
- beg_auth.track_event = dup_with(beg_auth.track_event, { debug_annotations: [payload_to_debug("authorized?", is_authorized)] })
520
+ if @create_debug_annotations
521
+ beg_auth.track_event = dup_with(beg_auth.track_event, { debug_annotations: [payload_to_debug("authorized?", is_authorized)] })
522
+ end
472
523
  super
473
524
  end
474
525
 
@@ -477,7 +528,7 @@ module GraphQL
477
528
  type: TrackEvent::Type::TYPE_SLICE_BEGIN,
478
529
  track_uuid: fid,
479
530
  category_iids: RESOLVE_TYPE_CATEGORY_IIDS,
480
- extra_counter_track_uuids: [@objects_counter_id],
531
+ extra_counter_track_uuids: @counts_objects,
481
532
  extra_counter_values: [count_allocations],
482
533
  name_iid: @resolve_type_name_iids[type],
483
534
  )
@@ -487,14 +538,18 @@ module GraphQL
487
538
  end
488
539
 
489
540
  def end_resolve_type(type, value, context, resolved_type)
541
+ end_ts = ts
490
542
  @packets << trace_packet(
543
+ timestamp: end_ts,
491
544
  type: TrackEvent::Type::TYPE_SLICE_END,
492
545
  track_uuid: fid,
493
- extra_counter_track_uuids: [@objects_counter_id],
546
+ extra_counter_track_uuids: @counts_objects,
494
547
  extra_counter_values: [count_allocations],
495
548
  )
496
549
  rt_begin = fiber_flow_stack.pop
497
- rt_begin.track_event = dup_with(rt_begin.track_event, { debug_annotations: [payload_to_debug("resolved_type", resolved_type, intern_value: true)] })
550
+ if @create_debug_annotations
551
+ rt_begin.track_event = dup_with(rt_begin.track_event, { debug_annotations: [payload_to_debug("resolved_type", resolved_type, intern_value: true)] })
552
+ end
498
553
  super
499
554
  end
500
555
 
@@ -546,7 +601,6 @@ module GraphQL
546
601
  def payload_to_debug(k, v, iid: nil, intern_value: false)
547
602
  if iid.nil?
548
603
  iid = @interned_da_name_ids[k]
549
- k = nil
550
604
  end
551
605
  case v
552
606
  when String
@@ -578,15 +632,41 @@ module GraphQL
578
632
  when Symbol
579
633
  debug_annotation(iid, :string_value, v.inspect)
580
634
  when Array
581
- debug_annotation(iid, :array_values, v.map { |v2| payload_to_debug(nil, v2, intern_value: intern_value) }.compact)
635
+ debug_annotation(iid, :array_values, v.each_with_index.map { |v2, idx| payload_to_debug((k ? "#{k}.#{idx}" : String(idx)), v2, intern_value: intern_value) }.compact)
582
636
  when Hash
583
637
  debug_annotation(iid, :dict_entries, v.map { |k2, v2| payload_to_debug(k2, v2, intern_value: intern_value) }.compact)
584
638
  else
585
- debug_str = if defined?(ActiveRecord::Relation) && v.is_a?(ActiveRecord::Relation)
586
- "#{v.class}, .to_sql=#{v.to_sql.inspect}"
587
- else
588
- v.inspect
639
+ class_name_iid = @interned_da_string_values[v.class.name]
640
+ da = [
641
+ debug_annotation(DA_DEBUG_INSPECT_CLASS_IID, :string_value_iid, class_name_iid),
642
+ ]
643
+ if k
644
+ k_str_value_iid = @interned_da_string_values[k]
645
+ da << debug_annotation(DA_DEBUG_INSPECT_FOR_IID, :string_value_iid, k_str_value_iid)
646
+ elsif iid
647
+ k = REVERSE_DEBUG_NAME_LOOKUP[iid] || @interned_da_name_ids.key(iid)
648
+ if k.nil?
649
+ da << debug_annotation(DA_DEBUG_INSPECT_FOR_IID, :string_value_iid, DA_STR_VAL_NIL_IID)
650
+ else
651
+ k_str_value_iid = @interned_da_string_values[k]
652
+ da << debug_annotation(DA_DEBUG_INSPECT_FOR_IID, :string_value_iid, k_str_value_iid)
653
+ end
589
654
  end
655
+
656
+ @packets << trace_packet(
657
+ type: TrackEvent::Type::TYPE_SLICE_BEGIN,
658
+ track_uuid: fid,
659
+ name_iid: DEBUG_INSPECT_EVENT_NAME_IID,
660
+ category_iids: DEBUG_INSPECT_CATEGORY_IIDS,
661
+ extra_counter_track_uuids: @counts_objects,
662
+ extra_counter_values: [count_allocations],
663
+ debug_annotations: da,
664
+ )
665
+ debug_str = @detailed_trace.inspect_object(v)
666
+ @packets << trace_packet(
667
+ type: TrackEvent::Type::TYPE_SLICE_END,
668
+ track_uuid: fid,
669
+ )
590
670
  if intern_value
591
671
  str_iid = @interned_da_string_values[debug_str]
592
672
  debug_annotation(iid, :string_value_iid, str_iid)
@@ -622,10 +702,14 @@ module GraphQL
622
702
  Fiber[:graphql_flow_stack] ||= []
623
703
  end
624
704
 
625
- def trace_packet(event_attrs)
705
+ def trace_packet(timestamp: ts, **event_attrs)
706
+ if @create_debug_annotations && block_given?
707
+ event_attrs[:debug_annotations] = yield
708
+ end
709
+ track_event = TrackEvent.new(event_attrs)
626
710
  TracePacket.new(
627
- timestamp: ts,
628
- track_event: TrackEvent.new(event_attrs),
711
+ timestamp: timestamp,
712
+ track_event: track_event,
629
713
  trusted_packet_sequence_id: @sequence_id,
630
714
  sequence_flags: 2,
631
715
  interned_data: new_interned_data
@@ -690,9 +774,9 @@ module GraphQL
690
774
 
691
775
  def subscribe_to_active_support_notifications(pattern)
692
776
  @as_subscriber = ActiveSupport::Notifications.monotonic_subscribe(pattern) do |name, start, finish, id, payload|
693
- metadata = payload.map { |k, v| payload_to_debug(k, v, intern_value: true) }
694
- metadata.compact!
695
- te = if metadata.empty?
777
+ metadata = @create_debug_annotations ? payload.map { |k, v| payload_to_debug(String(k), v, intern_value: true) } : nil
778
+ metadata&.compact!
779
+ te = if metadata.nil? || metadata.empty?
696
780
  TrackEvent.new(
697
781
  type: TrackEvent::Type::TYPE_SLICE_BEGIN,
698
782
  track_uuid: fid,
@@ -721,7 +805,7 @@ module GraphQL
721
805
  type: TrackEvent::Type::TYPE_SLICE_END,
722
806
  track_uuid: fid,
723
807
  name: name,
724
- extra_counter_track_uuids: [@objects_counter_id],
808
+ extra_counter_track_uuids: @counts_objects,
725
809
  extra_counter_values: [count_allocations]
726
810
  ),
727
811
  trusted_packet_sequence_id: @sequence_id,
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.5.14"
3
+ VERSION = "2.5.15"
4
4
  end
data/lib/graphql.rb CHANGED
@@ -4,7 +4,7 @@ require "json"
4
4
  require "set"
5
5
  require "singleton"
6
6
  require "forwardable"
7
- require "fiber/storage"
7
+ require "fiber/storage" if RUBY_VERSION < "3.2.0"
8
8
  require "graphql/autoload"
9
9
 
10
10
  module GraphQL
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.14
4
+ version: 2.5.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-10-08 00:00:00.000000000 Z
10
+ date: 2025-12-09 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64
@@ -23,20 +23,6 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
- - !ruby/object:Gem::Dependency
27
- name: fiber-storage
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: '0'
33
- type: :runtime
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: '0'
40
26
  - !ruby/object:Gem::Dependency
41
27
  name: logger
42
28
  requirement: !ruby/object:Gem::Requirement
@@ -729,6 +715,7 @@ files:
729
715
  - lib/graphql/subscriptions/serialize.rb
730
716
  - lib/graphql/testing.rb
731
717
  - lib/graphql/testing/helpers.rb
718
+ - lib/graphql/testing/mock_action_cable.rb
732
719
  - lib/graphql/tracing.rb
733
720
  - lib/graphql/tracing/active_support_notifications_trace.rb
734
721
  - lib/graphql/tracing/active_support_notifications_tracing.rb