julewire-core 1.0.0 → 1.1.0

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 (139) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/docs/advanced-configuration.md +2 -1
  4. data/docs/configuration.md +14 -7
  5. data/docs/context-and-propagation.md +26 -4
  6. data/docs/contracts.md +25 -50
  7. data/docs/extensions-and-api.md +87 -88
  8. data/docs/health-schema.md +12 -2
  9. data/docs/internals.md +24 -23
  10. data/docs/outputs-and-lifecycle.md +15 -8
  11. data/docs/records-and-data-policy.md +38 -7
  12. data/lib/julewire/core/cli/doctor.rb +20 -16
  13. data/lib/julewire/core/cli/line_helpers.rb +23 -12
  14. data/lib/julewire/core/cli/log_formats/core_json_decoder.rb +3 -3
  15. data/lib/julewire/core/cli/log_formats/record_decoder.rb +1 -1
  16. data/lib/julewire/core/cli/log_formats.rb +9 -9
  17. data/lib/julewire/core/cli/tail.rb +19 -14
  18. data/lib/julewire/core/cli/transcode.rb +4 -5
  19. data/lib/julewire/core/cli.rb +1 -1
  20. data/lib/julewire/core/configuration.rb +1 -1
  21. data/lib/julewire/core/context_store.rb +56 -87
  22. data/lib/julewire/core/destinations/chaos_output.rb +2 -2
  23. data/lib/julewire/core/destinations/collection.rb +8 -16
  24. data/lib/julewire/core/destinations/definition.rb +1 -9
  25. data/lib/julewire/core/destinations/destination.rb +16 -66
  26. data/lib/julewire/core/destinations/processor_handling.rb +78 -0
  27. data/lib/julewire/core/destinations/registry.rb +1 -1
  28. data/lib/julewire/core/destinations/sink.rb +0 -1
  29. data/lib/julewire/core/destinations/synchronized_output.rb +54 -17
  30. data/lib/julewire/core/destinations/tail_sampling.rb +18 -27
  31. data/lib/julewire/core/destinations/write_step.rb +5 -5
  32. data/lib/julewire/core/destinations.rb +0 -7
  33. data/lib/julewire/core/diagnostics/callback_notifier.rb +4 -2
  34. data/lib/julewire/core/diagnostics/doctor.rb +22 -15
  35. data/lib/julewire/core/diagnostics/failure_snapshot.rb +9 -3
  36. data/lib/julewire/core/diagnostics/health.rb +41 -57
  37. data/lib/julewire/core/diagnostics/integration_health_store.rb +11 -24
  38. data/lib/julewire/core/diagnostics/internal_records.rb +8 -12
  39. data/lib/julewire/core/diagnostics/invalid_severity_reporter.rb +16 -55
  40. data/lib/julewire/core/diagnostics/meta_observer.rb +37 -44
  41. data/lib/julewire/core/diagnostics/process_integration_health.rb +0 -5
  42. data/lib/julewire/core/diagnostics/tail/renderer.rb +1 -1
  43. data/lib/julewire/core/diagnostics/tail.rb +18 -22
  44. data/lib/julewire/core/execution/boundary.rb +2 -8
  45. data/lib/julewire/core/execution/handle.rb +14 -17
  46. data/lib/julewire/core/execution/lineage.rb +26 -62
  47. data/lib/julewire/core/execution/measurement_handle.rb +6 -10
  48. data/lib/julewire/core/execution/scope.rb +17 -22
  49. data/lib/julewire/core/execution/scope_fields.rb +4 -13
  50. data/lib/julewire/core/execution/scope_identity.rb +1 -9
  51. data/lib/julewire/core/execution/scope_snapshot.rb +18 -39
  52. data/lib/julewire/core/execution/summary_state.rb +16 -19
  53. data/lib/julewire/core/execution/view.rb +8 -17
  54. data/lib/julewire/core/facade_methods.rb +67 -65
  55. data/lib/julewire/core/fields/attribute_keys.rb +0 -2
  56. data/lib/julewire/core/fields/bags.rb +23 -27
  57. data/lib/julewire/core/fields/field_set.rb +52 -16
  58. data/lib/julewire/core/fields/field_stack.rb +69 -109
  59. data/lib/julewire/core/fields/internal/deletion.rb +6 -9
  60. data/lib/julewire/core/fields/internal.rb +35 -26
  61. data/lib/julewire/core/fields/lookup.rb +9 -13
  62. data/lib/julewire/core/fields/section_proxy.rb +1 -9
  63. data/lib/julewire/core/fields/stack_set.rb +9 -14
  64. data/lib/julewire/core/fields/static_labels.rb +1 -1
  65. data/lib/julewire/core/integration/configurable.rb +4 -3
  66. data/lib/julewire/core/integration/destination_health.rb +28 -7
  67. data/lib/julewire/core/integration/event_subscriber.rb +1 -1
  68. data/lib/julewire/core/integration/facade.rb +12 -15
  69. data/lib/julewire/core/integration/fork_hooks.rb +12 -36
  70. data/lib/julewire/core/integration/health.rb +0 -3
  71. data/lib/julewire/core/integration/ivar_state.rb +0 -5
  72. data/lib/julewire/core/integration/protocol.rb +20 -0
  73. data/lib/julewire/core/integration/settings.rb +4 -4
  74. data/lib/julewire/core/integration/subscriber_install.rb +2 -2
  75. data/lib/julewire/core/integration/subscription.rb +3 -2
  76. data/lib/julewire/core/integration/values.rb +0 -17
  77. data/lib/julewire/core/local_storage.rb +3 -19
  78. data/lib/julewire/core/processing/invalid_result_failure.rb +23 -0
  79. data/lib/julewire/core/processing/level_threshold.rb +7 -14
  80. data/lib/julewire/core/processing/match.rb +6 -4
  81. data/lib/julewire/core/processing/pipeline.rb +46 -40
  82. data/lib/julewire/core/processing/processor_chain.rb +22 -11
  83. data/lib/julewire/core/processing/processor_registry.rb +3 -4
  84. data/lib/julewire/core/processing/record_field_transform.rb +0 -4
  85. data/lib/julewire/core/processing/sampling.rb +1 -3
  86. data/lib/julewire/core/processing.rb +2 -2
  87. data/lib/julewire/core/propagation/carrier.rb +73 -19
  88. data/lib/julewire/core/propagation.rb +18 -4
  89. data/lib/julewire/core/records/build_input.rb +75 -0
  90. data/lib/julewire/core/records/console_formatter.rb +6 -6
  91. data/lib/julewire/core/records/deconstruct.rb +1 -1
  92. data/lib/julewire/core/records/display_message.rb +2 -4
  93. data/lib/julewire/core/records/draft.rb +127 -258
  94. data/lib/julewire/core/records/lazy_emit_input.rb +2 -12
  95. data/lib/julewire/core/records/metadata.rb +4 -3
  96. data/lib/julewire/core/records/public_projection.rb +2 -2
  97. data/lib/julewire/core/records/raw_input.rb +1 -1
  98. data/lib/julewire/core/records/record.rb +30 -18
  99. data/lib/julewire/core/records/severity.rb +7 -15
  100. data/lib/julewire/core/runtime.rb +87 -72
  101. data/lib/julewire/core/runtime_registry.rb +20 -15
  102. data/lib/julewire/core/runtime_state.rb +5 -3
  103. data/lib/julewire/core/scheduling/shared_scheduler.rb +28 -22
  104. data/lib/julewire/core/serialization/backtrace_limiter.rb +13 -9
  105. data/lib/julewire/core/serialization/bounded_transform.rb +3 -2
  106. data/lib/julewire/core/serialization/bounded_traversal.rb +72 -83
  107. data/lib/julewire/core/serialization/deep_compact_empty.rb +9 -11
  108. data/lib/julewire/core/serialization/deep_freeze.rb +45 -6
  109. data/lib/julewire/core/serialization/encoding_sanitizer.rb +2 -3
  110. data/lib/julewire/core/serialization/exception_shape.rb +7 -6
  111. data/lib/julewire/core/serialization/json_encoder.rb +10 -13
  112. data/lib/julewire/core/serialization/serializer.rb +22 -49
  113. data/lib/julewire/core/serialization/serializer_pool.rb +9 -0
  114. data/lib/julewire/core/serialization/text_encoder.rb +5 -5
  115. data/lib/julewire/core/serialization/truncation_metadata.rb +157 -0
  116. data/lib/julewire/core/serialization/value_copy.rb +183 -109
  117. data/lib/julewire/core/serialization/value_traversal.rb +10 -127
  118. data/lib/julewire/core/testing.rb +6 -56
  119. data/lib/julewire/core/validation.rb +2 -3
  120. data/lib/julewire/core/version.rb +1 -1
  121. data/lib/julewire/core.rb +2 -7
  122. metadata +7 -19
  123. data/lib/julewire/core/scheduling/deadline_scheduler.rb +0 -207
  124. data/lib/julewire/core/testing/chaos/catalog.rb +0 -72
  125. data/lib/julewire/core/testing/chaos/core_runtime.rb +0 -120
  126. data/lib/julewire/core/testing/chaos/destination.rb +0 -55
  127. data/lib/julewire/core/testing/chaos/emitter.rb +0 -20
  128. data/lib/julewire/core/testing/chaos/raising_output.rb +0 -42
  129. data/lib/julewire/core/testing/chaos.rb +0 -80
  130. data/lib/julewire/core/testing/contracts/component.rb +0 -162
  131. data/lib/julewire/core/testing/contracts/deadline_scheduler.rb +0 -59
  132. data/lib/julewire/core/testing/contracts/integration.rb +0 -166
  133. data/lib/julewire/core/testing/contracts/integration_fields.rb +0 -36
  134. data/lib/julewire/core/testing/contracts/record_draft.rb +0 -37
  135. data/lib/julewire/core/testing/contracts/runtime.rb +0 -178
  136. data/lib/julewire/core/testing/contracts/wire.rb +0 -60
  137. data/lib/julewire/core/testing/contracts.rb +0 -24
  138. data/lib/julewire/core/testing/coverage.rb +0 -58
  139. data/lib/julewire/core/testing/test_reports.rb +0 -78
@@ -28,7 +28,7 @@ module Julewire
28
28
  max_array_items: DEFAULT_MAX_ARRAY_ITEMS,
29
29
  max_hash_keys: DEFAULT_MAX_HASH_KEYS,
30
30
  compact_empty: false,
31
- max_backtrace_lines: Core::MAX_BACKTRACE_LINES
31
+ max_backtrace_lines: MAX_BACKTRACE_LINES
32
32
  )
33
33
  new(
34
34
  max_depth: max_depth,
@@ -47,8 +47,7 @@ module Julewire
47
47
  max_array_items: DEFAULT_MAX_ARRAY_ITEMS,
48
48
  max_hash_keys: DEFAULT_MAX_HASH_KEYS,
49
49
  compact_empty: false,
50
- max_backtrace_lines: Core::MAX_BACKTRACE_LINES,
51
- copy_strings: true
50
+ max_backtrace_lines: MAX_BACKTRACE_LINES
52
51
  )
53
52
  super(
54
53
  max_array_items: max_array_items,
@@ -63,7 +62,6 @@ module Julewire
63
62
  name: :max_backtrace_lines
64
63
  )
65
64
  @compact_empty = compact_empty
66
- @copy_strings = copy_strings
67
65
  end
68
66
 
69
67
  def serialize(value)
@@ -77,27 +75,25 @@ module Julewire
77
75
 
78
76
  private
79
77
 
80
- def scalar_value(value, depth, _key, _path)
78
+ def scalar_value(value, depth)
81
79
  return serialize_exception(value, depth) if value.is_a?(Exception)
82
80
 
83
81
  case value
84
82
  when nil, true, false
85
- clear_truncated(value)
83
+ value
86
84
  when Numeric
87
85
  serialize_numeric(value)
88
86
  when Symbol
89
- clear_truncated(value.to_s)
87
+ value.to_s
90
88
  when String
91
89
  serialize_string(value)
92
- when Time, DateTime, Date
90
+ when Time, Date
93
91
  serialize_temporal(value)
94
92
  else
95
93
  return serialize_iso8601_temporal(value) if zone_temporal?(value)
96
94
 
97
95
  serialize_object(value)
98
96
  end
99
- rescue StandardError => e
100
- clear_truncated(unserializable_marker(e))
101
97
  end
102
98
 
103
99
  def serialize_exception(error, depth)
@@ -116,30 +112,30 @@ module Julewire
116
112
  end
117
113
 
118
114
  def serialize_numeric(value)
119
- return serialize_float(value) if value.is_a?(Float)
120
- return clear_truncated(value) if value.is_a?(Integer)
121
- return serialize_string(value.to_s("F")) if defined?(BigDecimal) && value.is_a?(BigDecimal)
115
+ return serialize_float(value) if value.instance_of?(Float)
116
+ return value if value.instance_of?(Integer)
117
+ return serialize_string(value.to_s("F")) if defined?(BigDecimal) && value.instance_of?(::BigDecimal)
122
118
 
123
- serialize_string(EncodingSanitizer.call(value.to_s))
119
+ serialize_string(value.to_s)
124
120
  end
125
121
 
126
122
  def serialize_float(value)
127
- return clear_truncated(value) if value.finite?
128
- return clear_truncated(NAN_VALUE) if value.nan?
123
+ return value if value.finite?
124
+ return NAN_VALUE if value.nan?
129
125
 
130
- clear_truncated(value.positive? ? INFINITY_VALUE : NEGATIVE_INFINITY_VALUE)
126
+ value.positive? ? INFINITY_VALUE : NEGATIVE_INFINITY_VALUE
131
127
  end
132
128
 
133
129
  def serialize_temporal(value)
134
- return clear_truncated(value.getutc.iso8601(9)) if value.is_a?(Time)
135
- return clear_truncated(value.iso8601(9)) if value.is_a?(DateTime)
130
+ return value.getutc.iso8601(9) if value.is_a?(Time)
131
+ return value.iso8601(9) if value.is_a?(DateTime)
136
132
 
137
- clear_truncated(value.iso8601)
133
+ value.iso8601
138
134
  end
139
135
 
140
136
  def serialize_iso8601_temporal(value)
141
137
  temporal = value.respond_to?(:utc) ? value.utc : value
142
- clear_truncated(EncodingSanitizer.call(temporal.iso8601(9)))
138
+ EncodingSanitizer.call(temporal.iso8601(9))
143
139
  end
144
140
 
145
141
  def zone_temporal?(value)
@@ -154,7 +150,7 @@ module Julewire
154
150
 
155
151
  def key_value(key) = serialize_key(key)
156
152
 
157
- def error_value(error) = clear_truncated(unserializable_marker(error))
153
+ def error_value(error) = unserializable_marker(error)
158
154
 
159
155
  def serialize_key(key)
160
156
  case key
@@ -170,31 +166,18 @@ module Julewire
170
166
  end
171
167
 
172
168
  def serialize_symbol_key(key)
173
- name = key.name
174
- return serialize_trusted_key_string(name) if safe_trusted_key_name?(name)
175
-
176
- serialize_key_string(name)
177
- end
178
-
179
- def safe_trusted_key_name?(value)
180
- value.ascii_only? || (value.encoding == Encoding::UTF_8 && value.valid_encoding?)
169
+ serialize_key_string(key.name)
181
170
  end
182
171
 
183
172
  def serialize_key_string(value)
184
173
  string = EncodingSanitizer.call(value)
185
- return clear_truncated(copy_string(string)) if string.bytesize <= MAX_KEY_BYTES
174
+ return copy_string(string) if string.bytesize <= MAX_KEY_BYTES
186
175
 
187
176
  mark_truncated("#{string.byteslice(0, MAX_KEY_BYTES).scrub("?")}#{TRUNCATED_SUFFIX}")
188
177
  end
189
178
 
190
- def serialize_trusted_key_string(value)
191
- return clear_truncated(value) if value.bytesize <= MAX_KEY_BYTES
192
-
193
- mark_truncated("#{value.byteslice(0, MAX_KEY_BYTES).scrub("?")}#{TRUNCATED_SUFFIX}")
194
- end
195
-
196
179
  def serialize_object(value)
197
- clear_truncated(object_marker(value))
180
+ object_marker(value)
198
181
  end
199
182
 
200
183
  def object_marker(value)
@@ -213,20 +196,10 @@ module Julewire
213
196
 
214
197
  def serialize_string(value)
215
198
  string = EncodingSanitizer.call(value)
216
- return clear_truncated(copy_string(string)) if string.bytesize <= @max_string_bytes
199
+ return copy_string(string) if string.bytesize <= @max_string_bytes
217
200
 
218
201
  mark_truncated("#{string.byteslice(0, @max_string_bytes).scrub("?")}#{TRUNCATED_SUFFIX}")
219
202
  end
220
-
221
- def copy_string(value)
222
- value.frozen? || !@copy_strings ? value : value.dup
223
- end
224
-
225
- def record_hash_truncation(fields, _raw_key, key, key_truncated, child_truncated)
226
- return fields unless key_truncated || child_truncated
227
-
228
- append_truncation_field(fields, key)
229
- end
230
203
  end
231
204
  end
232
205
  end
@@ -14,6 +14,15 @@ module Julewire
14
14
  end
15
15
  pool[serializer_key] ||= yield
16
16
  end
17
+
18
+ def serialize(pool_key, serializer_key, payload, &)
19
+ serialize_with(serializer(pool_key, serializer_key, &), payload, &)
20
+ end
21
+
22
+ def serialize_with(serializer, payload)
23
+ serializer = yield if serializer.in_use?
24
+ serializer.serialize(payload)
25
+ end
17
26
  end
18
27
  end
19
28
  end
@@ -48,13 +48,13 @@ module Julewire
48
48
  positive: true
49
49
  )
50
50
  @color = color
51
- @line_suffix = append_newline ? "\n" : ""
51
+ @append_newline = append_newline
52
52
  @theme = validate_theme(theme)
53
53
  end
54
54
 
55
55
  def call(payload)
56
56
  text = payload.is_a?(String) ? payload : line_for(payload)
57
- "#{text}#{@line_suffix}"
57
+ @append_newline ? "#{text}\n" : text
58
58
  end
59
59
 
60
60
  private
@@ -82,7 +82,7 @@ module Julewire
82
82
  # Console output is human-facing; JSON keeps nanosecond precision.
83
83
  return value.iso8601(6) if value.respond_to?(:iso8601)
84
84
 
85
- value.to_s
85
+ value
86
86
  end
87
87
 
88
88
  def severity(payload)
@@ -123,7 +123,7 @@ module Julewire
123
123
  def compact_hash(name, value)
124
124
  return unless value.is_a?(Hash) && !value.empty?
125
125
 
126
- "#{name}=#{truncate(JSON.generate(value, allow_nan: false))}"
126
+ "#{name}=#{truncate(JSON.generate(value))}"
127
127
  rescue StandardError
128
128
  "#{name}=#{truncate(value.inspect)}"
129
129
  end
@@ -135,7 +135,7 @@ module Julewire
135
135
  end
136
136
 
137
137
  def value_at(payload, key)
138
- Fields::Lookup.value(payload, key)
138
+ Fields::Lookup.wire_value(payload, key)
139
139
  end
140
140
 
141
141
  def blank?(value)
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Julewire
4
+ module Core
5
+ module Serialization
6
+ module TruncationMetadata
7
+ NAMES = %i[truncated truncated_fields limits max_array_items max_depth max_hash_keys max_string_bytes].freeze
8
+ KEYS = {
9
+ string: NAMES.to_h { [it, it.to_s] }.freeze,
10
+ symbol: NAMES.to_h { [it, it] }.freeze
11
+ }.freeze
12
+ METADATA_KEYS = KEYS.fetch(:symbol).values_at(:truncated, :truncated_fields, :limits).freeze
13
+ METADATA_KEY_NAMES = KEYS.fetch(:string).values_at(:truncated, :truncated_fields, :limits).freeze
14
+ LIMIT_KEYS = KEYS.fetch(:symbol).values_at(
15
+ :max_array_items,
16
+ :max_depth,
17
+ :max_hash_keys,
18
+ :max_string_bytes
19
+ ).freeze
20
+ LIMIT_KEY_NAMES = KEYS.fetch(:string).values_at(
21
+ :max_array_items,
22
+ :max_depth,
23
+ :max_hash_keys,
24
+ :max_string_bytes
25
+ ).freeze
26
+ SYMBOL_METADATA_KEYS = KEYS.fetch(:symbol).merge(limit_keys: LIMIT_KEYS).freeze
27
+ STRING_METADATA_KEYS = KEYS.fetch(:string).merge(limit_keys: LIMIT_KEY_NAMES).freeze
28
+ [
29
+ NAMES,
30
+ KEYS,
31
+ METADATA_KEYS,
32
+ METADATA_KEY_NAMES,
33
+ LIMIT_KEYS,
34
+ LIMIT_KEY_NAMES,
35
+ SYMBOL_METADATA_KEYS,
36
+ STRING_METADATA_KEYS
37
+ ].each do |constant|
38
+ ::Ractor.make_shareable(constant) if defined?(::Ractor) && ::Ractor.respond_to?(:make_shareable)
39
+ end
40
+ private_constant :NAMES, :KEYS, :METADATA_KEYS, :METADATA_KEY_NAMES, :LIMIT_KEYS, :LIMIT_KEY_NAMES,
41
+ :SYMBOL_METADATA_KEYS, :STRING_METADATA_KEYS
42
+
43
+ class << self
44
+ def build(fields, max_array_items:, max_depth:, max_hash_keys:, max_string_bytes:, key_style: :string,
45
+ compact_limits: false, freeze_values: false)
46
+ keys = KEYS.fetch(key_style)
47
+ limits = limits_hash(
48
+ keys,
49
+ max_array_items: max_array_items,
50
+ max_depth: max_depth,
51
+ max_hash_keys: max_hash_keys,
52
+ max_string_bytes: max_string_bytes,
53
+ compact_limits: compact_limits
54
+ )
55
+ metadata = {
56
+ keys.fetch(:truncated) => true,
57
+ keys.fetch(:truncated_fields) => field_list(fields),
58
+ keys.fetch(:limits) => limits
59
+ }
60
+ freeze_values ? deep_freeze(metadata, keys) : metadata
61
+ end
62
+
63
+ def append_field(fields, field)
64
+ fields ||= []
65
+ fields << field unless fields.include?(field)
66
+ fields
67
+ end
68
+
69
+ def valid?(value, max_fields: nil)
70
+ return false unless value.is_a?(Hash)
71
+
72
+ keys = metadata_keys(value)
73
+ return false unless keys
74
+ return false unless value.fetch(keys.fetch(:truncated)) == true
75
+
76
+ valid_fields?(value.fetch(keys.fetch(:truncated_fields)), max_fields: max_fields) &&
77
+ valid_limits?(value.fetch(keys.fetch(:limits)), limit_keys: keys.fetch(:limit_keys))
78
+ end
79
+
80
+ def copy(value, freeze_values:, key_style: :symbol)
81
+ source_keys = metadata_keys(value)
82
+ target_keys = KEYS.fetch(key_style)
83
+ metadata = {
84
+ target_keys.fetch(:truncated) => true,
85
+ target_keys.fetch(:truncated_fields) => copy_fields(value.fetch(source_keys.fetch(:truncated_fields))),
86
+ target_keys.fetch(:limits) => copy_limits(value.fetch(source_keys.fetch(:limits)), source_keys,
87
+ target_keys)
88
+ }
89
+ freeze_values ? deep_freeze(metadata, target_keys) : metadata
90
+ end
91
+
92
+ private
93
+
94
+ def field_list(fields)
95
+ Array(fields).uniq
96
+ end
97
+
98
+ def limits_hash(keys, max_array_items:, max_depth:, max_hash_keys:, max_string_bytes:, compact_limits:)
99
+ limits = {
100
+ keys.fetch(:max_array_items) => max_array_items,
101
+ keys.fetch(:max_depth) => max_depth,
102
+ keys.fetch(:max_hash_keys) => max_hash_keys,
103
+ keys.fetch(:max_string_bytes) => max_string_bytes
104
+ }
105
+ compact_limits ? limits.compact : limits
106
+ end
107
+
108
+ def deep_freeze(metadata, keys)
109
+ metadata.fetch(keys.fetch(:truncated_fields)).each(&:freeze)
110
+ metadata.fetch(keys.fetch(:truncated_fields)).freeze
111
+ metadata.fetch(keys.fetch(:limits)).freeze
112
+ metadata.freeze
113
+ end
114
+
115
+ def copy_fields(fields)
116
+ fields.map(&:dup)
117
+ end
118
+
119
+ def copy_limits(limits, source_keys, target_keys)
120
+ LIMIT_KEYS.each_with_object({}) do |name, copied|
121
+ source_key = source_keys.fetch(name)
122
+ next unless limits.key?(source_key)
123
+
124
+ copied[target_keys.fetch(name)] = limits.fetch(source_key)
125
+ end
126
+ end
127
+
128
+ def metadata_keys(value)
129
+ return SYMBOL_METADATA_KEYS if exact_keys?(value, METADATA_KEYS)
130
+
131
+ STRING_METADATA_KEYS if exact_keys?(value, METADATA_KEY_NAMES)
132
+ end
133
+
134
+ def exact_keys?(value, keys)
135
+ value.length == keys.length && keys.all? { value.key?(it) }
136
+ end
137
+
138
+ def valid_fields?(fields, max_fields:)
139
+ return false unless fields.is_a?(Array)
140
+ return false if max_fields && fields.length > max_fields
141
+
142
+ fields.all?(String)
143
+ end
144
+
145
+ def valid_limits?(limits, limit_keys:)
146
+ return false unless limits.is_a?(Hash)
147
+
148
+ limits.all? do |key, value|
149
+ limit_keys.include?(key) && (value.nil? || value.instance_of?(Integer))
150
+ end
151
+ end
152
+ end
153
+ end
154
+ private_constant :TruncationMetadata
155
+ end
156
+ end
157
+ end