julewire-core 1.0.1 → 1.1.1

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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/docs/configuration.md +14 -7
  4. data/docs/context-and-propagation.md +20 -0
  5. data/docs/contracts.md +23 -49
  6. data/docs/extensions-and-api.md +78 -82
  7. data/docs/health-schema.md +12 -2
  8. data/docs/internals.md +21 -20
  9. data/docs/outputs-and-lifecycle.md +10 -6
  10. data/docs/records-and-data-policy.md +36 -5
  11. data/lib/julewire/core/cli/doctor.rb +20 -16
  12. data/lib/julewire/core/cli/line_helpers.rb +23 -12
  13. data/lib/julewire/core/cli/log_formats/core_json_decoder.rb +3 -3
  14. data/lib/julewire/core/cli/log_formats.rb +9 -9
  15. data/lib/julewire/core/cli/tail.rb +19 -14
  16. data/lib/julewire/core/cli/transcode.rb +4 -5
  17. data/lib/julewire/core/cli.rb +1 -1
  18. data/lib/julewire/core/configuration.rb +1 -1
  19. data/lib/julewire/core/context_store.rb +51 -87
  20. data/lib/julewire/core/destinations/chaos_output.rb +2 -2
  21. data/lib/julewire/core/destinations/collection.rb +8 -16
  22. data/lib/julewire/core/destinations/definition.rb +1 -9
  23. data/lib/julewire/core/destinations/destination.rb +13 -63
  24. data/lib/julewire/core/destinations/processor_handling.rb +78 -0
  25. data/lib/julewire/core/destinations/registry.rb +1 -1
  26. data/lib/julewire/core/destinations/sink.rb +0 -1
  27. data/lib/julewire/core/destinations/synchronized_output.rb +14 -13
  28. data/lib/julewire/core/destinations/tail_sampling.rb +18 -27
  29. data/lib/julewire/core/destinations/write_step.rb +5 -5
  30. data/lib/julewire/core/destinations.rb +0 -7
  31. data/lib/julewire/core/diagnostics/callback_notifier.rb +4 -2
  32. data/lib/julewire/core/diagnostics/doctor.rb +16 -9
  33. data/lib/julewire/core/diagnostics/failure_snapshot.rb +6 -2
  34. data/lib/julewire/core/diagnostics/health.rb +41 -57
  35. data/lib/julewire/core/diagnostics/integration_health_store.rb +11 -24
  36. data/lib/julewire/core/diagnostics/internal_records.rb +8 -12
  37. data/lib/julewire/core/diagnostics/invalid_severity_reporter.rb +16 -55
  38. data/lib/julewire/core/diagnostics/meta_observer.rb +37 -44
  39. data/lib/julewire/core/diagnostics/process_integration_health.rb +0 -5
  40. data/lib/julewire/core/diagnostics/tail/renderer.rb +1 -1
  41. data/lib/julewire/core/diagnostics/tail.rb +18 -22
  42. data/lib/julewire/core/execution/boundary.rb +2 -8
  43. data/lib/julewire/core/execution/handle.rb +12 -17
  44. data/lib/julewire/core/execution/lineage.rb +16 -56
  45. data/lib/julewire/core/execution/measurement_handle.rb +6 -10
  46. data/lib/julewire/core/execution/scope.rb +15 -20
  47. data/lib/julewire/core/execution/scope_fields.rb +3 -12
  48. data/lib/julewire/core/execution/scope_identity.rb +1 -9
  49. data/lib/julewire/core/execution/scope_snapshot.rb +18 -39
  50. data/lib/julewire/core/execution/summary_state.rb +16 -19
  51. data/lib/julewire/core/execution/view.rb +8 -17
  52. data/lib/julewire/core/facade_methods.rb +67 -65
  53. data/lib/julewire/core/fields/attribute_keys.rb +0 -2
  54. data/lib/julewire/core/fields/bags.rb +23 -27
  55. data/lib/julewire/core/fields/field_set.rb +29 -19
  56. data/lib/julewire/core/fields/field_stack.rb +68 -134
  57. data/lib/julewire/core/fields/internal/deletion.rb +6 -9
  58. data/lib/julewire/core/fields/internal.rb +19 -38
  59. data/lib/julewire/core/fields/lookup.rb +9 -13
  60. data/lib/julewire/core/fields/section_proxy.rb +1 -9
  61. data/lib/julewire/core/fields/stack_set.rb +7 -12
  62. data/lib/julewire/core/fields/static_labels.rb +1 -1
  63. data/lib/julewire/core/integration/configurable.rb +4 -3
  64. data/lib/julewire/core/integration/destination_health.rb +17 -7
  65. data/lib/julewire/core/integration/event_subscriber.rb +1 -1
  66. data/lib/julewire/core/integration/facade.rb +12 -15
  67. data/lib/julewire/core/integration/fork_hooks.rb +12 -36
  68. data/lib/julewire/core/integration/health.rb +0 -3
  69. data/lib/julewire/core/integration/ivar_state.rb +0 -5
  70. data/lib/julewire/core/integration/protocol.rb +20 -0
  71. data/lib/julewire/core/integration/settings.rb +4 -4
  72. data/lib/julewire/core/integration/subscriber_install.rb +2 -2
  73. data/lib/julewire/core/integration/subscription.rb +3 -2
  74. data/lib/julewire/core/integration/values.rb +0 -17
  75. data/lib/julewire/core/local_storage.rb +3 -19
  76. data/lib/julewire/core/processing/invalid_result_failure.rb +23 -0
  77. data/lib/julewire/core/processing/level_threshold.rb +7 -14
  78. data/lib/julewire/core/processing/match.rb +6 -4
  79. data/lib/julewire/core/processing/pipeline.rb +46 -40
  80. data/lib/julewire/core/processing/processor_chain.rb +22 -11
  81. data/lib/julewire/core/processing/processor_registry.rb +3 -4
  82. data/lib/julewire/core/processing/record_field_transform.rb +0 -4
  83. data/lib/julewire/core/processing/sampling.rb +1 -3
  84. data/lib/julewire/core/processing.rb +2 -2
  85. data/lib/julewire/core/propagation/carrier.rb +14 -14
  86. data/lib/julewire/core/propagation.rb +15 -2
  87. data/lib/julewire/core/records/build_input.rb +75 -0
  88. data/lib/julewire/core/records/console_formatter.rb +6 -6
  89. data/lib/julewire/core/records/deconstruct.rb +1 -1
  90. data/lib/julewire/core/records/display_message.rb +2 -4
  91. data/lib/julewire/core/records/draft.rb +125 -262
  92. data/lib/julewire/core/records/lazy_emit_input.rb +2 -12
  93. data/lib/julewire/core/records/metadata.rb +4 -3
  94. data/lib/julewire/core/records/public_projection.rb +2 -2
  95. data/lib/julewire/core/records/raw_input.rb +1 -1
  96. data/lib/julewire/core/records/record.rb +19 -37
  97. data/lib/julewire/core/records/severity.rb +7 -15
  98. data/lib/julewire/core/runtime.rb +67 -71
  99. data/lib/julewire/core/runtime_registry.rb +20 -15
  100. data/lib/julewire/core/runtime_state.rb +5 -3
  101. data/lib/julewire/core/scheduling/shared_scheduler.rb +28 -22
  102. data/lib/julewire/core/serialization/backtrace_limiter.rb +13 -9
  103. data/lib/julewire/core/serialization/bounded_transform.rb +2 -12
  104. data/lib/julewire/core/serialization/bounded_traversal.rb +49 -66
  105. data/lib/julewire/core/serialization/deep_compact_empty.rb +9 -11
  106. data/lib/julewire/core/serialization/deep_freeze.rb +45 -6
  107. data/lib/julewire/core/serialization/encoding_sanitizer.rb +2 -3
  108. data/lib/julewire/core/serialization/exception_shape.rb +7 -6
  109. data/lib/julewire/core/serialization/json_encoder.rb +10 -13
  110. data/lib/julewire/core/serialization/serializer.rb +23 -50
  111. data/lib/julewire/core/serialization/serializer_pool.rb +9 -0
  112. data/lib/julewire/core/serialization/text_encoder.rb +5 -5
  113. data/lib/julewire/core/serialization/truncation_metadata.rb +63 -48
  114. data/lib/julewire/core/serialization/value_copy.rb +60 -227
  115. data/lib/julewire/core/serialization/value_traversal.rb +10 -127
  116. data/lib/julewire/core/testing.rb +6 -56
  117. data/lib/julewire/core/validation.rb +2 -3
  118. data/lib/julewire/core/version.rb +1 -1
  119. data/lib/julewire/core.rb +2 -7
  120. metadata +6 -19
  121. data/lib/julewire/core/scheduling/deadline_scheduler.rb +0 -207
  122. data/lib/julewire/core/testing/chaos/catalog.rb +0 -72
  123. data/lib/julewire/core/testing/chaos/core_runtime.rb +0 -120
  124. data/lib/julewire/core/testing/chaos/destination.rb +0 -55
  125. data/lib/julewire/core/testing/chaos/emitter.rb +0 -20
  126. data/lib/julewire/core/testing/chaos/raising_output.rb +0 -42
  127. data/lib/julewire/core/testing/chaos.rb +0 -80
  128. data/lib/julewire/core/testing/contracts/component.rb +0 -162
  129. data/lib/julewire/core/testing/contracts/deadline_scheduler.rb +0 -59
  130. data/lib/julewire/core/testing/contracts/integration.rb +0 -166
  131. data/lib/julewire/core/testing/contracts/integration_fields.rb +0 -36
  132. data/lib/julewire/core/testing/contracts/record_draft.rb +0 -37
  133. data/lib/julewire/core/testing/contracts/runtime.rb +0 -178
  134. data/lib/julewire/core/testing/contracts/wire.rb +0 -60
  135. data/lib/julewire/core/testing/contracts.rb +0 -24
  136. data/lib/julewire/core/testing/coverage.rb +0 -58
  137. 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)
@@ -152,9 +148,9 @@ module Julewire
152
148
 
153
149
  def raw_omitted_value?(value) = DeepCompactEmpty.omitted?(value)
154
150
 
155
- def key_value(key, _depth = nil, _path = nil) = serialize_key(key)
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)
@@ -4,39 +4,41 @@ module Julewire
4
4
  module Core
5
5
  module Serialization
6
6
  module TruncationMetadata
7
+ NAMES = %i[truncated truncated_fields limits max_array_items max_depth max_hash_keys max_string_bytes].freeze
7
8
  KEYS = {
8
- string: {
9
- truncated: "truncated",
10
- truncated_fields: "truncated_fields",
11
- limits: "limits",
12
- max_array_items: "max_array_items",
13
- max_depth: "max_depth",
14
- max_hash_keys: "max_hash_keys",
15
- max_string_bytes: "max_string_bytes"
16
- }.freeze,
17
- symbol: {
18
- truncated: :truncated,
19
- truncated_fields: :truncated_fields,
20
- limits: :limits,
21
- max_array_items: :max_array_items,
22
- max_depth: :max_depth,
23
- max_hash_keys: :max_hash_keys,
24
- max_string_bytes: :max_string_bytes
25
- }.freeze
9
+ string: NAMES.to_h { [it, it.to_s] }.freeze,
10
+ symbol: NAMES.to_h { [it, it] }.freeze
26
11
  }.freeze
27
12
  METADATA_KEYS = KEYS.fetch(:symbol).values_at(:truncated, :truncated_fields, :limits).freeze
28
- METADATA_KEY_NAMES = METADATA_KEYS.map(&:to_s).freeze
13
+ METADATA_KEY_NAMES = KEYS.fetch(:string).values_at(:truncated, :truncated_fields, :limits).freeze
29
14
  LIMIT_KEYS = KEYS.fetch(:symbol).values_at(
30
15
  :max_array_items,
31
16
  :max_depth,
32
17
  :max_hash_keys,
33
18
  :max_string_bytes
34
19
  ).freeze
35
- LIMIT_KEY_NAMES = LIMIT_KEYS.map(&:to_s).freeze
36
- [KEYS, METADATA_KEYS, METADATA_KEY_NAMES, LIMIT_KEYS, LIMIT_KEY_NAMES].each do |constant|
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|
37
38
  ::Ractor.make_shareable(constant) if defined?(::Ractor) && ::Ractor.respond_to?(:make_shareable)
38
39
  end
39
- private_constant :KEYS, :METADATA_KEYS, :METADATA_KEY_NAMES, :LIMIT_KEYS, :LIMIT_KEY_NAMES
40
+ private_constant :NAMES, :KEYS, :METADATA_KEYS, :METADATA_KEY_NAMES, :LIMIT_KEYS, :LIMIT_KEY_NAMES,
41
+ :SYMBOL_METADATA_KEYS, :STRING_METADATA_KEYS
40
42
 
41
43
  class << self
42
44
  def build(fields, max_array_items:, max_depth:, max_hash_keys:, max_string_bytes:, key_style: :string,
@@ -66,12 +68,25 @@ module Julewire
66
68
 
67
69
  def valid?(value, max_fields: nil)
68
70
  return false unless value.is_a?(Hash)
69
- return false unless valid_top_level_keys?(value)
70
- return false unless fetch_key(value, :truncated) == true
71
71
 
72
- fields = fetch_key(value, :truncated_fields)
73
- limits = fetch_key(value, :limits)
74
- valid_fields?(fields, max_fields: max_fields) && valid_limits?(limits)
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
75
90
  end
76
91
 
77
92
  private
@@ -97,43 +112,43 @@ module Julewire
97
112
  metadata.freeze
98
113
  end
99
114
 
100
- def fetch_key(value, key)
101
- return value[key] if value.key?(key)
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)
102
123
 
103
- value[key.to_s]
124
+ copied[target_keys.fetch(name)] = limits.fetch(source_key)
125
+ end
104
126
  end
105
127
 
106
- def valid_top_level_keys?(value)
107
- return false if value.length > METADATA_KEYS.length * 2
128
+ def metadata_keys(value)
129
+ return SYMBOL_METADATA_KEYS if exact_keys?(value, METADATA_KEYS)
108
130
 
109
- value.keys.all? { known_key?(it, METADATA_KEYS, METADATA_KEY_NAMES) } &&
110
- METADATA_KEYS.all? { value.key?(it) || value.key?(it.to_s) }
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) }
111
136
  end
112
137
 
113
138
  def valid_fields?(fields, max_fields:)
114
139
  return false unless fields.is_a?(Array)
140
+ return false if max_fields && fields.length > max_fields
115
141
 
116
- seen = 0
117
- fields.each do |field|
118
- seen += 1
119
- return false if max_fields && seen > max_fields
120
- return false unless field.is_a?(String) || field.is_a?(Symbol)
121
- end
122
- true
142
+ fields.all?(String)
123
143
  end
124
144
 
125
- def valid_limits?(limits)
145
+ def valid_limits?(limits, limit_keys:)
126
146
  return false unless limits.is_a?(Hash)
127
- return false if limits.length > LIMIT_KEYS.length * 2
128
147
 
129
148
  limits.all? do |key, value|
130
- known_key?(key, LIMIT_KEYS, LIMIT_KEY_NAMES) && (value.nil? || value.is_a?(Integer))
149
+ limit_keys.include?(key) && (value.nil? || value.instance_of?(Integer))
131
150
  end
132
151
  end
133
-
134
- def known_key?(key, symbol_keys, string_keys)
135
- symbol_keys.include?(key) || string_keys.include?(key)
136
- end
137
152
  end
138
153
  end
139
154
  private_constant :TruncationMetadata