julewire-core 1.0.1 → 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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -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
@@ -35,12 +35,11 @@ module Julewire
35
35
  @prepare_values = !@transform.nil?
36
36
  @track_paths = @prepare_values && !track_paths.equal?(false)
37
37
  end
38
+ private_class_method :new
38
39
 
39
40
  def call(value)
40
41
  @root = value
41
42
  walk(value)
42
- ensure
43
- @root = nil
44
43
  end
45
44
 
46
45
  private
@@ -50,16 +49,7 @@ module Julewire
50
49
  transformed.equal?(CONTINUE) ? value : transformed
51
50
  end
52
51
 
53
- def truncation_metadata(fields)
54
- TruncationMetadata.build(
55
- fields,
56
- key_style: :symbol,
57
- max_array_items: @max_array_items,
58
- max_depth: @max_depth,
59
- max_hash_keys: @max_hash_keys,
60
- max_string_bytes: @max_string_bytes
61
- )
62
- end
52
+ def truncation_metadata(fields) = super(fields, key_style: :symbol)
63
53
  end
64
54
  end
65
55
  end
@@ -20,10 +20,12 @@ module Julewire
20
20
  max_array_items: DEFAULT_MAX_ARRAY_ITEMS,
21
21
  max_depth: DEFAULT_MAX_DEPTH,
22
22
  max_hash_keys: DEFAULT_MAX_HASH_KEYS,
23
- max_string_bytes: DEFAULT_MAX_STRING_BYTES
23
+ max_string_bytes: DEFAULT_MAX_STRING_BYTES,
24
+ key_style: :string
24
25
  )
25
26
  TruncationMetadata.build(
26
27
  fields,
28
+ key_style: key_style,
27
29
  max_array_items: max_array_items,
28
30
  max_depth: max_depth,
29
31
  max_hash_keys: max_hash_keys,
@@ -40,57 +42,52 @@ module Julewire
40
42
  @max_hash_keys = Validation.validate_integer_limit!(max_hash_keys, name: :max_hash_keys)
41
43
  @max_string_bytes = Validation.validate_integer_limit!(max_string_bytes, name: :max_string_bytes)
42
44
  @truncation_key = truncation_key
43
- @last_truncated = false
44
- @compact_empty = false
45
- @prepare_values = false
46
- @track_paths = false
47
45
  end
48
46
 
49
47
  private
50
48
 
51
49
  def walk(value)
52
- @last_truncated = false
53
50
  traverse(value) { |root, depth| walk_value(root, depth, nil, nil) }
54
51
  ensure
55
52
  @last_truncated = false
56
53
  end
57
54
 
58
55
  def walk_value(value, depth, key, path)
59
- @last_truncated = false
60
56
  value = prepare_value(value, depth, key, path) if @prepare_values
61
- return max_depth_value if depth >= @max_depth
57
+ return max_depth_value if depth == @max_depth
62
58
  return walk_container(value, depth, path) if value.is_a?(Array) || hash_like?(value)
63
59
 
64
- scalar_value(value, depth, key, path)
60
+ scalar_value(value, depth)
65
61
  rescue StandardError => e
66
- @last_truncated = false
67
62
  error_value(e)
68
63
  end
69
64
 
70
- def prepare_value(value, _depth, _key, _path) = value
71
-
72
65
  def hash_like?(value) = value.is_a?(Hash)
73
66
 
74
- def scalar_value(value, _depth, _key, _path)
75
- value.is_a?(String) ? string_value(value) : clear_truncated(value)
67
+ def scalar_value(value, _depth)
68
+ value.is_a?(String) ? string_value(value) : value
76
69
  end
77
70
 
78
71
  # Transform-stage errors must bubble so processors can fail closed.
79
- def error_value(error)
80
- raise error
72
+ def error_value(_error)
73
+ raise
81
74
  end
82
75
 
83
76
  def walk_container(value, depth, path)
84
- return circular_value if traversal_seen?(value)
77
+ seen = traversal_seen
78
+ return circular_value if seen.include?(value)
85
79
 
86
- with_marked_traversal_container(value) do
80
+ seen.add(value)
81
+ begin
87
82
  value.is_a?(Array) ? walk_array(value, depth, path) : walk_hash(value, depth, path)
83
+ ensure
84
+ seen.delete(value)
88
85
  end
89
86
  end
90
87
 
91
88
  def circular_value
92
89
  @last_truncated = true
93
- Core::CIRCULAR_REFERENCE
90
+ CIRCULAR_REFERENCE
94
91
  end
95
92
 
96
93
  def max_depth_value
@@ -98,7 +95,7 @@ module Julewire
98
95
  end
99
96
 
100
97
  def walk_hash(value, depth, path)
101
- return walk_compact_hash(value, depth, path) if @compact_empty
98
+ return walk_compact_hash(value, depth) if @compact_empty
102
99
 
103
100
  walk_full_hash(value, depth, path)
104
101
  end
@@ -109,31 +106,29 @@ module Julewire
109
106
  track_paths = @track_paths
110
107
  visited = 0
111
108
  value.each do |raw_key, item|
112
- if visited >= @max_hash_keys
109
+ if visited == @max_hash_keys
113
110
  fields = append_truncation_field(fields, "hash_keys")
114
111
  break
115
112
  end
116
113
 
117
114
  visited += 1
118
- child = walk_value(item, depth + 1, raw_key, track_paths ? path_for(path, raw_key) : nil)
119
- child_truncated = consume_truncated
120
- key = key_value(raw_key, depth, path)
121
- key_truncated = consume_truncated
115
+ child_path = path_for(path, raw_key) if track_paths
116
+ child = walk_value(item, depth + 1, raw_key, child_path)
117
+ key = key_value(raw_key)
122
118
  result[key] = child
123
- fields = record_hash_truncation(fields, raw_key, key, key_truncated, child_truncated)
119
+ fields = record_hash_truncation(fields, key, consume_truncated)
124
120
  end
125
121
  finish_hash(result, fields)
126
122
  end
127
123
 
128
- def walk_compact_hash(value, depth, path)
124
+ def walk_compact_hash(value, depth)
129
125
  fields = nil
130
126
  result = {}
131
- track_paths = @track_paths
132
127
  visited = 0
133
128
  value.each do |raw_key, item|
134
129
  # The hash cap bounds visited input entries, not final output keys, so
135
130
  # serialized-key collisions and compacted entries cannot hide work.
136
- if visited >= @max_hash_keys
131
+ if visited == @max_hash_keys
137
132
  fields = append_truncation_field(fields, "hash_keys")
138
133
  break
139
134
  end
@@ -141,31 +136,29 @@ module Julewire
141
136
  visited += 1
142
137
  next if raw_omitted_value?(item)
143
138
 
144
- child = walk_value(item, depth + 1, raw_key, track_paths ? path_for(path, raw_key) : nil)
145
- child_truncated = consume_truncated
139
+ child = walk_value(item, depth + 1, nil, nil)
146
140
  next if omitted_value?(child)
147
141
 
148
- key = key_value(raw_key, depth, path)
149
- key_truncated = consume_truncated
142
+ key = key_value(raw_key)
150
143
  result[key] = child
151
- fields = record_hash_truncation(fields, raw_key, key, key_truncated, child_truncated)
144
+ fields = record_hash_truncation(fields, key, consume_truncated)
152
145
  end
153
146
  finish_hash(result, fields)
154
147
  end
155
148
 
156
149
  def walk_array(value, depth, path)
157
- return walk_compact_array(value, depth, path) if @compact_empty
150
+ return walk_compact_array(value, depth) if @compact_empty
158
151
 
159
152
  walk_full_array(value, depth, path)
160
153
  end
161
154
 
162
155
  def walk_full_array(value, depth, path)
163
- fields = nil
156
+ truncated = false
164
157
  result = []
165
158
  visited = 0
166
159
  value.each do |item|
167
- if visited >= @max_array_items
168
- fields = append_truncation_field(fields, "array_items")
160
+ if visited == @max_array_items
161
+ truncated = true
169
162
  break
170
163
  end
171
164
 
@@ -173,65 +166,60 @@ module Julewire
173
166
  child = walk_value(item, depth + 1, nil, path)
174
167
  child_truncated = consume_truncated
175
168
  result << child
176
- fields = append_truncation_field(fields, "array_items") if child_truncated
169
+ truncated = true if child_truncated
177
170
  end
178
- finish_array(result, fields)
171
+ finish_array(result, truncated && "array_items")
179
172
  end
180
173
 
181
- def walk_compact_array(value, depth, path)
182
- fields = nil
174
+ def walk_compact_array(value, depth)
175
+ truncated = false
183
176
  result = []
184
177
  visited = 0
185
178
  value.each do |item|
186
- if visited >= @max_array_items
187
- fields = append_truncation_field(fields, "array_items")
179
+ if visited == @max_array_items
180
+ truncated = true
188
181
  break
189
182
  end
190
183
 
191
184
  visited += 1
192
185
  next if raw_omitted_value?(item)
193
186
 
194
- child = walk_value(item, depth + 1, nil, path)
187
+ child = walk_value(item, depth + 1, nil, nil)
195
188
  child_truncated = consume_truncated
196
189
  next if omitted_value?(child)
197
190
 
198
191
  result << child
199
- fields = append_truncation_field(fields, "array_items") if child_truncated
192
+ truncated = true if child_truncated
200
193
  end
201
- finish_array(result, fields)
194
+ finish_array(result, truncated && "array_items")
202
195
  end
203
196
 
204
- def omitted_value?(_value) = false
205
-
206
- def raw_omitted_value?(_value) = false
207
-
208
- def key_value(key, _depth = nil, _path = nil)
209
- clear_truncated(key.is_a?(String) ? copy_string(key) : key)
210
- end
197
+ def key_value(key) = key
211
198
 
212
- def record_hash_truncation(fields, raw_key, _key, _key_truncated, child_truncated)
213
- return fields unless child_truncated
199
+ def record_hash_truncation(fields, key, truncated)
200
+ return fields unless truncated
214
201
 
215
- append_truncation_field(fields, raw_key.to_s)
202
+ append_truncation_field(fields, key.to_s)
216
203
  end
217
204
 
218
205
  def finish_hash(result, fields)
219
- return clear_truncated(result) unless fields
206
+ return result unless fields
220
207
 
221
208
  result[@truncation_key] = truncation_metadata(fields) if @truncation_key
222
209
  mark_truncated(result)
223
210
  end
224
211
 
225
212
  def finish_array(result, fields)
226
- return clear_truncated(result) unless fields
213
+ return result unless fields
227
214
 
228
215
  result << { @truncation_key => truncation_metadata(fields) } if @truncation_key
229
216
  mark_truncated(result)
230
217
  end
231
218
 
232
- def truncation_metadata(fields)
219
+ def truncation_metadata(fields, key_style: :string)
233
220
  self.class.truncation_metadata(
234
221
  fields,
222
+ key_style: key_style,
235
223
  max_array_items: @max_array_items,
236
224
  max_depth: @max_depth,
237
225
  max_hash_keys: @max_hash_keys,
@@ -240,7 +228,7 @@ module Julewire
240
228
  end
241
229
 
242
230
  def string_value(value)
243
- return clear_truncated(copy_string(value)) if value.bytesize <= @max_string_bytes
231
+ return copy_string(value) if value.bytesize <= @max_string_bytes
244
232
 
245
233
  mark_truncated("#{value.byteslice(0, @max_string_bytes).scrub("?")}#{TRUNCATED_SUFFIX}")
246
234
  end
@@ -250,11 +238,6 @@ module Julewire
250
238
  value
251
239
  end
252
240
 
253
- def clear_truncated(value)
254
- @last_truncated = false
255
- value
256
- end
257
-
258
241
  def consume_truncated
259
242
  truncated = @last_truncated
260
243
  @last_truncated = false
@@ -27,23 +27,21 @@ module Julewire
27
27
  private
28
28
 
29
29
  def compact_value!(value)
30
- return compact_hash!(value) if value.is_a?(Hash)
31
- return compact_array!(value) if value.is_a?(Array)
32
-
33
- value
30
+ if value.is_a?(Hash)
31
+ compact_hash!(value)
32
+ elsif value.is_a?(Array)
33
+ compact_array!(value)
34
+ else
35
+ value
36
+ end
34
37
  end
35
38
 
36
39
  def compact_hash!(value)
37
40
  with_traversal_container(value, value) do
38
41
  value.each do |key, item|
39
42
  compacted = compact_value!(item)
40
- if self.class.omitted?(compacted)
41
- value.delete(key)
42
- elsif !compacted.equal?(item)
43
- value[key] = compacted
44
- end
43
+ value.delete(key) if self.class.omitted?(compacted)
45
44
  end
46
- value
47
45
  end
48
46
  end
49
47
 
@@ -57,7 +55,7 @@ module Julewire
57
55
  value[index] = compacted
58
56
  index += 1
59
57
  end
60
- value.slice!(index, value.length - index) if index < value.length
58
+ value.slice!(index, value.length)
61
59
  value
62
60
  end
63
61
  end
@@ -7,9 +7,46 @@ module Julewire
7
7
  include ValueTraversal
8
8
 
9
9
  class << self
10
- def call(value, max_depth: Core::NORMALIZATION_MAX_DEPTH, trust_frozen: false)
10
+ def call(value, max_depth: NORMALIZATION_MAX_DEPTH, trust_frozen: false, validate_symbol_keys: false)
11
+ validate_symbol_keys(value) if validate_symbol_keys
11
12
  new(max_depth, trust_frozen: trust_frozen).call(value)
12
13
  end
14
+
15
+ def validate_symbol_keys(value)
16
+ # WeakMap is identity-keyed and accepts new keys while enumerating,
17
+ # so reachable containers form one finite work inventory without a
18
+ # separate cycle-skip branch.
19
+ containers = ObjectSpace::WeakMap.new
20
+ containers[value] = nil if container?(value)
21
+ containers.each_key do |current|
22
+ if current.is_a?(Hash)
23
+ current.each do |key, item|
24
+ validate_symbol_key!(key)
25
+ containers[item] = nil if container?(item)
26
+ end
27
+ else
28
+ current.each { containers[it] = nil if container?(it) }
29
+ end
30
+ end
31
+ value
32
+ end
33
+
34
+ def validate_symbol_hash(value)
35
+ raise TypeError, "owned data must be a Hash" unless value.is_a?(Hash)
36
+
37
+ validate_symbol_keys(value)
38
+ end
39
+
40
+ private
41
+
42
+ def container?(value)
43
+ value.is_a?(Hash) || value.is_a?(Array)
44
+ end
45
+
46
+ def validate_symbol_key!(key)
47
+ raise TypeError, Fields::Internal::RECORD_STRING_KEY_ERROR if key.is_a?(String)
48
+ raise TypeError, Fields::Internal::RECORD_SYMBOL_KEY_ERROR unless key.instance_of?(Symbol)
49
+ end
13
50
  end
14
51
 
15
52
  def initialize(max_depth, trust_frozen:)
@@ -24,7 +61,7 @@ module Julewire
24
61
  private
25
62
 
26
63
  def freeze_value(value, depth)
27
- return value.freeze if value.is_a?(String)
64
+ value.freeze if value.is_a?(String)
28
65
  return value if @trust_frozen && value.frozen?
29
66
  return freeze_container(value, depth) if value.is_a?(Hash) || value.is_a?(Array)
30
67
 
@@ -32,7 +69,7 @@ module Julewire
32
69
  end
33
70
 
34
71
  def freeze_container(value, depth)
35
- return Serializer::MAX_DEPTH_VALUE.freeze if depth_limited?(depth)
72
+ return Serializer::MAX_DEPTH_VALUE if depth_limited?(depth)
36
73
 
37
74
  with_traversal_container(value, value) do
38
75
  value.is_a?(Hash) ? freeze_hash(value, depth) : freeze_array(value, depth)
@@ -40,11 +77,13 @@ module Julewire
40
77
  end
41
78
 
42
79
  def depth_limited?(depth)
43
- @max_depth && depth >= @max_depth
80
+ depth == @max_depth
44
81
  end
45
82
 
46
83
  def freeze_hash(value, depth)
47
- value.each { |key, item| freeze_child(value, key, item, depth) }
84
+ value.each do |key, item|
85
+ freeze_child(value, key, item, depth)
86
+ end
48
87
  value.freeze
49
88
  end
50
89
 
@@ -55,7 +94,7 @@ module Julewire
55
94
 
56
95
  def freeze_child(value, key, item, depth)
57
96
  frozen = freeze_value(item, depth + 1)
58
- value[key] = frozen unless value.frozen? || frozen.equal?(item)
97
+ value[key] = frozen unless value.frozen?
59
98
  end
60
99
  end
61
100
  end
@@ -9,7 +9,6 @@ module Julewire
9
9
  raise TypeError, "value must be a String" unless value.is_a?(String)
10
10
 
11
11
  return value if valid_utf8?(value) || valid_ascii_only?(value)
12
- return value.scrub("?") if utf8?(value)
13
12
 
14
13
  encode_utf8(value)
15
14
  rescue EncodingError
@@ -23,7 +22,7 @@ module Julewire
23
22
  end
24
23
 
25
24
  def valid_ascii_only?(value)
26
- value.ascii_only? && value.valid_encoding?
25
+ value.ascii_only?
27
26
  end
28
27
 
29
28
  def utf8?(value)
@@ -31,7 +30,7 @@ module Julewire
31
30
  end
32
31
 
33
32
  def encode_utf8(value)
34
- value.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "?")
33
+ value.encode(Encoding::UTF_8, invalid: :replace, replace: "?")
35
34
  end
36
35
  end
37
36
  end
@@ -9,7 +9,7 @@ module Julewire
9
9
  DEFAULT_MAX_CAUSE_DEPTH = 5
10
10
 
11
11
  class << self
12
- def call(error, max_backtrace_lines: Core::MAX_BACKTRACE_LINES, max_cause_depth: DEFAULT_MAX_CAUSE_DEPTH)
12
+ def call(error, max_backtrace_lines: MAX_BACKTRACE_LINES, max_cause_depth: DEFAULT_MAX_CAUSE_DEPTH)
13
13
  new(
14
14
  max_backtrace_lines: max_backtrace_lines,
15
15
  max_cause_depth: max_cause_depth
@@ -32,7 +32,7 @@ module Julewire
32
32
  def shape_exception(error, depth)
33
33
  return error unless error.is_a?(Exception)
34
34
 
35
- with_traversal_container(error, Core::CIRCULAR_REFERENCE) do
35
+ with_traversal_container(error, CIRCULAR_REFERENCE) do
36
36
  exception_hash(error, depth)
37
37
  end
38
38
  end
@@ -50,7 +50,7 @@ module Julewire
50
50
  cause = exception_cause(error)
51
51
  next unless cause
52
52
 
53
- if depth >= @max_cause_depth
53
+ if depth == @max_cause_depth
54
54
  result[:cause_truncated] = true
55
55
  else
56
56
  result[:cause] = shape_exception(cause, depth + 1)
@@ -59,20 +59,21 @@ module Julewire
59
59
  end
60
60
 
61
61
  def class_name(error)
62
- error.class.name || error.class.to_s
62
+ error.class.to_s
63
63
  rescue StandardError
64
64
  "Exception"
65
65
  end
66
66
 
67
67
  def error_message(error)
68
68
  message = error.message
69
- message.is_a?(String) ? message.dup : message.to_s
69
+ message.instance_of?(String) ? message.dup : message.to_s
70
70
  rescue StandardError
71
71
  "[Unavailable]"
72
72
  end
73
73
 
74
74
  def backtrace(error)
75
- @backtrace_limiter.call(backtrace: Core::Fields::FieldSet.deep_dup(error.backtrace))[:backtrace]
75
+ limited = @backtrace_limiter.call(backtrace: Fields::FieldSet.deep_dup(error.backtrace))
76
+ limited.fetch(:backtrace)
76
77
  rescue StandardError
77
78
  nil
78
79
  end
@@ -13,7 +13,7 @@ module Julewire
13
13
  max_array_items: Serializer::DEFAULT_MAX_ARRAY_ITEMS,
14
14
  max_hash_keys: Serializer::DEFAULT_MAX_HASH_KEYS,
15
15
  compact_empty: true,
16
- max_backtrace_lines: Core::MAX_BACKTRACE_LINES,
16
+ max_backtrace_lines: MAX_BACKTRACE_LINES,
17
17
  append_newline: true
18
18
  )
19
19
  @max_depth = max_depth
@@ -23,6 +23,7 @@ module Julewire
23
23
  @compact_empty = compact_empty
24
24
  @max_backtrace_lines = max_backtrace_lines
25
25
  @line_suffix = append_newline ? "\n" : ""
26
+ # `append_newline` is suffix-only; it does not change serializer shape.
26
27
  @serializer_key = [
27
28
  @max_depth,
28
29
  @max_string_bytes,
@@ -30,11 +31,11 @@ module Julewire
30
31
  @max_hash_keys,
31
32
  @compact_empty,
32
33
  @max_backtrace_lines
33
- ].freeze
34
+ ]
34
35
  end
35
36
 
36
37
  def call(payload)
37
- JSON.generate(serialized_payload(payload), allow_nan: false).tap do |json|
38
+ JSON.generate(serialized_payload(payload)).tap do |json|
38
39
  json << @line_suffix
39
40
  end
40
41
  end
@@ -42,14 +43,11 @@ module Julewire
42
43
  private
43
44
 
44
45
  def serialized_payload(payload)
45
- serializer = cached_serializer
46
- return build_serializer.serialize(payload) if serializer.in_use?
47
-
48
- serializer.serialize(payload)
49
- end
50
-
51
- def cached_serializer
52
- SerializerPool.serializer(:julewire_core_json_encoder_serializers, @serializer_key) { build_serializer }
46
+ SerializerPool.serialize(
47
+ :julewire_core_json_encoder_serializers,
48
+ @serializer_key,
49
+ payload
50
+ ) { build_serializer }
53
51
  end
54
52
 
55
53
  def build_serializer
@@ -59,8 +57,7 @@ module Julewire
59
57
  max_array_items: @max_array_items,
60
58
  max_hash_keys: @max_hash_keys,
61
59
  compact_empty: @compact_empty,
62
- max_backtrace_lines: @max_backtrace_lines,
63
- copy_strings: false
60
+ max_backtrace_lines: @max_backtrace_lines
64
61
  )
65
62
  end
66
63
  end