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
@@ -2,54 +2,94 @@
2
2
 
3
3
  module Julewire
4
4
  module Core
5
+ module FacadePrivateMethods
6
+ private
7
+
8
+ def punk_banner
9
+ "!!JULEWIRE PUNK!! chaos containment armed\n"
10
+ end
11
+
12
+ def punk_chaos_output(output, chaos)
13
+ options = chaos if chaos.is_a?(Hash)
14
+ Destinations::ChaosOutput.new(output, **options)
15
+ end
16
+
17
+ def emit_with_severity(severity, record, fields, &)
18
+ if !block_given? && !record.is_a?(Hash)
19
+ # Scalar eager logs stay allocation-light; lazy inputs need the wrapper
20
+ # so block-built records can still receive the eager severity.
21
+ emit(eager_severity_input(severity, record, fields))
22
+ else
23
+ emit(Records::LazyEmitInput.with_severity(severity, Core.emit_input(record, fields)), &)
24
+ end
25
+ end
26
+
27
+ def eager_severity_input(severity, record, fields)
28
+ input = if record.equal?(UNSET)
29
+ fields
30
+ elsif fields.empty?
31
+ { message: record.to_s }
32
+ else
33
+ Core.emit_input(record, fields)
34
+ end
35
+ input.delete("severity")
36
+ input[:severity] = severity
37
+ input
38
+ end
39
+
40
+ def with_cleared_configure_guard
41
+ previous_guard = Fiber[Runtime::CONFIGURE_GUARD_KEY]
42
+ Fiber[Runtime::CONFIGURE_GUARD_KEY] = nil
43
+ yield
44
+ ensure
45
+ Fiber[Runtime::CONFIGURE_GUARD_KEY] = previous_guard
46
+ end
47
+ end
48
+
5
49
  module FacadeMethods
50
+ include FacadePrivateMethods
51
+
6
52
  def runtime(name = :default)
7
- Core::RuntimeRegistry.fetch(name, current: Core::RuntimeLocator.current)
53
+ RuntimeRegistry.fetch(name)
8
54
  end
9
55
 
10
56
  def config = runtime.config
11
-
12
57
  def configure(&) = runtime.configure(&)
13
-
14
58
  def context = runtime.context
15
-
16
59
  def attributes = runtime.attributes
17
-
18
60
  def carry = runtime.carry
19
-
20
61
  def current_execution = runtime.current_execution
21
-
22
62
  def current_execution? = runtime.current_execution?
23
63
 
24
- def emit(record = Core::UNSET, **fields, &)
64
+ def emit(record = UNSET, **fields, &)
25
65
  runtime.emit(record, **fields, &)
26
66
  end
27
67
 
28
- def debug(record = Core::UNSET, **fields, &)
68
+ def debug(record = UNSET, **fields, &)
29
69
  emit_with_severity(:debug, record, fields, &)
30
70
  end
31
71
 
32
- def info(record = Core::UNSET, **fields, &)
72
+ def info(record = UNSET, **fields, &)
33
73
  emit_with_severity(:info, record, fields, &)
34
74
  end
35
75
 
36
- def warn(record = Core::UNSET, **fields, &)
76
+ def warn(record = UNSET, **fields, &)
37
77
  emit_with_severity(:warn, record, fields, &)
38
78
  end
39
79
 
40
- def error(record = Core::UNSET, **fields, &)
80
+ def error(record = UNSET, **fields, &)
41
81
  emit_with_severity(:error, record, fields, &)
42
82
  end
43
83
 
44
- def fatal(record = Core::UNSET, **fields, &)
84
+ def fatal(record = UNSET, **fields, &)
45
85
  emit_with_severity(:fatal, record, fields, &)
46
86
  end
47
87
 
48
- def unknown(record = Core::UNSET, **fields, &)
88
+ def unknown(record = UNSET, **fields, &)
49
89
  emit_with_severity(:unknown, record, fields, &)
50
90
  end
51
91
 
52
- def flush(timeout: Core::UNSET)
92
+ def flush(timeout: UNSET)
53
93
  runtime.flush(timeout: timeout)
54
94
  end
55
95
 
@@ -62,26 +102,26 @@ module Julewire
62
102
  def measure_start(key) = summary.measure_start(key)
63
103
 
64
104
  def doctor(name = :default)
65
- Core::Diagnostics::Doctor.call(runtime(name))
105
+ Diagnostics::Doctor.call(runtime(name))
66
106
  end
67
107
 
68
108
  def tail(name = :default, **)
69
- Core::Diagnostics::Tail.attach!(runtime(name), **)
109
+ Diagnostics::Tail.attach!(runtime(name), **)
70
110
  end
71
111
 
72
112
  def observe_self!(name = :default, **)
73
- Core::Diagnostics::MetaObserver.attach!(name, **)
113
+ Diagnostics::MetaObserver.attach!(name, **)
74
114
  end
75
115
 
76
- def dev!(name = :default, output: $stdout, color: Core::UNSET, chaos: false, banner: chaos, tail: true)
77
- color = output.respond_to?(:tty?) ? output.tty? : true if color.equal?(Core::UNSET)
116
+ def dev!(name = :default, output: $stdout, color: UNSET, chaos: false, banner: chaos, tail: true)
117
+ color = output.respond_to?(:tty?) ? output.tty? : true if color.equal?(UNSET)
78
118
  punk!(name, output: output, color: color, chaos: chaos, banner: banner)
79
119
  return unless tail
80
120
 
81
121
  tail_options = tail == true ? {} : tail
82
122
  raise ArgumentError, "tail must be true, false, or an options Hash" unless tail_options.is_a?(Hash)
83
123
 
84
- Core::Diagnostics::Tail.attach!(runtime(name), **tail_options)
124
+ Tail.attach!(runtime(name), **tail_options)
85
125
  end
86
126
 
87
127
  def punk!(name = :default, output: $stdout, color: true, chaos: false, banner: chaos)
@@ -102,21 +142,19 @@ module Julewire
102
142
  def fiber(**, &)
103
143
  raise ArgumentError, "block required" unless block_given?
104
144
 
105
- envelope = Core::Propagation.capture_local
145
+ envelope = Propagation.capture_local
106
146
  Fiber.new(**) do |*args|
107
147
  with_cleared_configure_guard do
108
- Core::Propagation.restore(envelope) { yield(*args) }
148
+ Propagation.restore(envelope, owned: true) { yield(*args) }
109
149
  end
110
150
  end
111
151
  end
112
152
 
113
153
  def labels = runtime.labels
114
-
115
154
  def after_fork! = runtime.after_fork!
155
+ def reset! = runtime.reset_facade!
116
156
 
117
- def reset! = runtime.reset!
118
-
119
- def close(timeout: Core::UNSET)
157
+ def close(timeout: UNSET)
120
158
  runtime.close(timeout: timeout)
121
159
  end
122
160
 
@@ -129,10 +167,10 @@ module Julewire
129
167
  def thread(*, &)
130
168
  raise ArgumentError, "block required" unless block_given?
131
169
 
132
- envelope = Core::Propagation.capture_local
170
+ envelope = Propagation.capture_local
133
171
  Thread.new(*) do |*thread_args|
134
172
  with_cleared_configure_guard do
135
- Core::Propagation.restore(envelope) { yield(*thread_args) }
173
+ Propagation.restore(envelope, owned: true) { yield(*thread_args) }
136
174
  end
137
175
  end
138
176
  end
@@ -140,42 +178,6 @@ module Julewire
140
178
  def with_execution(type:, **, &)
141
179
  runtime.with_execution(type: type, **, &)
142
180
  end
143
-
144
- private
145
-
146
- def punk_banner
147
- "!!JULEWIRE PUNK!! chaos containment armed\n"
148
- end
149
-
150
- def punk_chaos_output(output, chaos)
151
- options = chaos.is_a?(Hash) ? chaos : {}
152
- Core::Destinations::ChaosOutput.new(output, **options)
153
- end
154
-
155
- def emit_with_severity(severity, record, fields, &)
156
- if record.equal?(Core::UNSET)
157
- fields.delete("severity")
158
- fields[:severity] = severity
159
- runtime.emit(fields, &)
160
- elsif !block_given? && !record.is_a?(Hash)
161
- # Scalar eager logs stay allocation-light; lazy inputs need the wrapper
162
- # so block-built records can still receive the eager severity.
163
- input = fields.empty? ? { message: record.to_s } : Core.emit_input(record, fields)
164
- input.delete("severity")
165
- input[:severity] = severity
166
- runtime.emit(input)
167
- else
168
- runtime.emit(Core::Records::LazyEmitInput.with_severity(severity, Core.emit_input(record, fields)), &)
169
- end
170
- end
171
-
172
- def with_cleared_configure_guard
173
- previous_guard = Fiber[Core::Runtime::CONFIGURE_GUARD_KEY]
174
- Fiber[Core::Runtime::CONFIGURE_GUARD_KEY] = nil
175
- yield
176
- ensure
177
- Fiber[Core::Runtime::CONFIGURE_GUARD_KEY] = previous_guard
178
- end
179
181
  end
180
182
  end
181
183
  end
@@ -45,8 +45,6 @@ module Julewire
45
45
 
46
46
  fields.compact
47
47
  end
48
-
49
- def from(neutral) = neutral.is_a?(Hash) ? neutral : {}
50
48
  end
51
49
  end
52
50
  end
@@ -26,35 +26,31 @@ module Julewire
26
26
  source
27
27
  ].freeze
28
28
 
29
- class << self
30
- private
31
-
32
- def define(name, **capabilities)
33
- Definition.new(
34
- name: name,
35
- record_hash: true,
36
- transform_container: true,
37
- app_write: false,
38
- integration_write: false,
39
- propagate: false,
40
- emit_by_default: true,
41
- delete_paths: false,
42
- stack: false,
43
- **capabilities
44
- )
45
- end
29
+ definition = lambda do |name, **capabilities|
30
+ Definition.new(
31
+ name: name,
32
+ record_hash: true,
33
+ transform_container: true,
34
+ app_write: false,
35
+ integration_write: false,
36
+ propagate: false,
37
+ emit_by_default: true,
38
+ delete_paths: false,
39
+ stack: false,
40
+ **capabilities
41
+ )
46
42
  end
47
43
 
48
44
  DEFINITIONS = {
49
- execution: define(:execution, integration_write: true, propagate: true),
50
- context: define(
45
+ execution: definition.call(:execution, integration_write: true, propagate: true),
46
+ context: definition.call(
51
47
  :context,
52
48
  app_write: true,
53
49
  integration_write: true,
54
50
  propagate: true,
55
51
  stack: true
56
52
  ),
57
- carry: define(
53
+ carry: definition.call(
58
54
  :carry,
59
55
  app_write: true,
60
56
  integration_write: true,
@@ -63,18 +59,18 @@ module Julewire
63
59
  delete_paths: true,
64
60
  stack: true
65
61
  ),
66
- neutral: define(:neutral, integration_write: true, emit_by_default: false, stack: true),
67
- attributes: define(
62
+ neutral: definition.call(:neutral, integration_write: true, emit_by_default: false, stack: true),
63
+ attributes: definition.call(
68
64
  :attributes,
69
65
  app_write: true,
70
66
  integration_write: true,
71
67
  stack: true
72
68
  ),
73
- labels: define(:labels),
74
- payload: define(:payload),
75
- metrics: define(:metrics),
76
- error: define(:error, record_hash: false),
77
- summary: define(
69
+ labels: definition.call(:labels),
70
+ payload: definition.call(:payload),
71
+ metrics: definition.call(:metrics),
72
+ error: definition.call(:error, record_hash: false),
73
+ summary: definition.call(
78
74
  :summary,
79
75
  record_hash: false,
80
76
  transform_container: false,
@@ -5,16 +5,22 @@ module Julewire
5
5
  module Fields
6
6
  # @api integration_spi
7
7
  module FieldSet
8
+ Serializer = Serialization::Serializer
9
+ TRUNCATION_METADATA_KEY = Serializer::TRUNCATION_METADATA_KEY.to_sym
10
+ RESERVED_TRUNCATION_METADATA_ERROR =
11
+ "#{Serializer::TRUNCATION_METADATA_KEY} is reserved for Julewire truncation metadata".freeze
12
+ INVALID_MODES = %i[ignore raise wrap].freeze
8
13
  VALUE_KEY = :value
14
+ private_constant :Serializer, :TRUNCATION_METADATA_KEY, :RESERVED_TRUNCATION_METADATA_ERROR, :INVALID_MODES
9
15
 
10
16
  class << self
11
17
  # Public ingress accepts String or Symbol keys. Core stores Symbol keys
12
18
  # after normalization so extension contracts stay simple.
13
- def coerce(fields = nil, keyword_fields = {}, invalid: :ignore)
19
+ def coerce(fields = nil, keyword_fields = nil, invalid: :ignore)
20
+ validate_invalid_mode!(invalid)
14
21
  coerced = {}
15
22
  coerce_fields!(coerced, fields, invalid: invalid) unless fields.nil?
16
- merge!(coerced, keyword_fields) unless keyword_fields.empty?
17
- coerced
23
+ merge!(coerced, keyword_fields)
18
24
  end
19
25
 
20
26
  def merge(left, right)
@@ -25,51 +31,81 @@ module Julewire
25
31
  return target unless fields.is_a?(Hash)
26
32
 
27
33
  fields.each do |key, value|
28
- target[Fields::Internal.normalize_key(key)] = copy_field_value(value)
34
+ target[normalized_field_key(key)] = copy_field_value(value)
29
35
  end
30
36
 
31
37
  target
32
38
  end
33
39
 
34
40
  def deep_dup(value)
35
- return {} if value.is_a?(Hash) && value.empty?
36
- return [] if value.is_a?(Array) && value.empty?
41
+ deep_dup_with(value, preserve_truncation_metadata: false)
42
+ end
37
43
 
38
- Serialization::ValueCopy.call(value)
44
+ def deep_dup_owned(value)
45
+ deep_dup_with(value, preserve_truncation_metadata: true)
39
46
  end
40
47
 
41
48
  def deep_symbolize_keys(value)
42
- return {} if value.is_a?(Hash) && value.empty?
43
- return [] if value.is_a?(Array) && value.empty?
49
+ deep_symbolize_keys_with(value, preserve_truncation_metadata: false)
50
+ end
44
51
 
45
- Serialization::ValueCopy.call(value, symbolize_keys: true)
52
+ def deep_symbolize_owned_keys(value)
53
+ deep_symbolize_keys_with(value, preserve_truncation_metadata: true)
46
54
  end
47
55
 
48
56
  def frozen_copy(value)
49
- Fields::Internal.frozen_copy(value)
57
+ Internal.frozen_copy(value)
50
58
  end
51
59
 
52
60
  def value_for(hash, key, default: nil)
53
61
  return default unless hash.is_a?(Hash)
54
62
 
55
- normalized = key.is_a?(String) ? Fields::Internal.normalize_key(key) : key
56
- return hash[normalized] if hash.key?(normalized)
57
-
58
- default
63
+ hash.fetch(Internal.normalize_key(key), default)
59
64
  end
60
65
 
61
66
  private
62
67
 
68
+ def deep_dup_with(value, preserve_truncation_metadata:)
69
+ Serialization::ValueCopy.call(
70
+ value,
71
+ preserve_truncation_metadata: preserve_truncation_metadata
72
+ )
73
+ end
74
+
75
+ def deep_symbolize_keys_with(value, preserve_truncation_metadata:)
76
+ Serialization::ValueCopy.call(
77
+ value,
78
+ max_array_items: Serializer::DEFAULT_MAX_ARRAY_ITEMS,
79
+ max_hash_keys: Serializer::DEFAULT_MAX_HASH_KEYS,
80
+ max_string_bytes: Serializer::DEFAULT_MAX_STRING_BYTES,
81
+ preserve_truncation_metadata: preserve_truncation_metadata,
82
+ symbolize_keys: true
83
+ )
84
+ end
85
+
63
86
  def coerce_fields!(target, fields, invalid:)
64
87
  if fields.is_a?(Hash)
65
88
  merge!(target, fields)
66
89
  elsif invalid == :wrap
67
- target[VALUE_KEY] = deep_dup(fields)
90
+ target[VALUE_KEY] = copy_field_value(fields)
68
91
  elsif invalid == :raise
69
92
  raise ArgumentError, "fields must be a Hash"
70
93
  end
71
94
  end
72
95
 
96
+ def validate_invalid_mode!(invalid)
97
+ return if INVALID_MODES.include?(invalid)
98
+
99
+ raise ArgumentError, "invalid field coercion mode: #{invalid.inspect}"
100
+ end
101
+
102
+ def normalized_field_key(key)
103
+ normalized_key = Internal.normalize_key(key)
104
+ raise ArgumentError, RESERVED_TRUNCATION_METADATA_ERROR if normalized_key == TRUNCATION_METADATA_KEY
105
+
106
+ normalized_key
107
+ end
108
+
73
109
  def copy_field_value(value) = deep_symbolize_keys(value)
74
110
  end
75
111
  end