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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/docs/configuration.md +14 -7
- data/docs/context-and-propagation.md +20 -0
- data/docs/contracts.md +23 -49
- data/docs/extensions-and-api.md +78 -82
- data/docs/health-schema.md +12 -2
- data/docs/internals.md +21 -20
- data/docs/outputs-and-lifecycle.md +10 -6
- data/docs/records-and-data-policy.md +36 -5
- data/lib/julewire/core/cli/doctor.rb +20 -16
- data/lib/julewire/core/cli/line_helpers.rb +23 -12
- data/lib/julewire/core/cli/log_formats/core_json_decoder.rb +3 -3
- data/lib/julewire/core/cli/log_formats.rb +9 -9
- data/lib/julewire/core/cli/tail.rb +19 -14
- data/lib/julewire/core/cli/transcode.rb +4 -5
- data/lib/julewire/core/cli.rb +1 -1
- data/lib/julewire/core/configuration.rb +1 -1
- data/lib/julewire/core/context_store.rb +51 -87
- data/lib/julewire/core/destinations/chaos_output.rb +2 -2
- data/lib/julewire/core/destinations/collection.rb +8 -16
- data/lib/julewire/core/destinations/definition.rb +1 -9
- data/lib/julewire/core/destinations/destination.rb +13 -63
- data/lib/julewire/core/destinations/processor_handling.rb +78 -0
- data/lib/julewire/core/destinations/registry.rb +1 -1
- data/lib/julewire/core/destinations/sink.rb +0 -1
- data/lib/julewire/core/destinations/synchronized_output.rb +14 -13
- data/lib/julewire/core/destinations/tail_sampling.rb +18 -27
- data/lib/julewire/core/destinations/write_step.rb +5 -5
- data/lib/julewire/core/destinations.rb +0 -7
- data/lib/julewire/core/diagnostics/callback_notifier.rb +4 -2
- data/lib/julewire/core/diagnostics/doctor.rb +16 -9
- data/lib/julewire/core/diagnostics/failure_snapshot.rb +6 -2
- data/lib/julewire/core/diagnostics/health.rb +41 -57
- data/lib/julewire/core/diagnostics/integration_health_store.rb +11 -24
- data/lib/julewire/core/diagnostics/internal_records.rb +8 -12
- data/lib/julewire/core/diagnostics/invalid_severity_reporter.rb +16 -55
- data/lib/julewire/core/diagnostics/meta_observer.rb +37 -44
- data/lib/julewire/core/diagnostics/process_integration_health.rb +0 -5
- data/lib/julewire/core/diagnostics/tail/renderer.rb +1 -1
- data/lib/julewire/core/diagnostics/tail.rb +18 -22
- data/lib/julewire/core/execution/boundary.rb +2 -8
- data/lib/julewire/core/execution/handle.rb +12 -17
- data/lib/julewire/core/execution/lineage.rb +16 -56
- data/lib/julewire/core/execution/measurement_handle.rb +6 -10
- data/lib/julewire/core/execution/scope.rb +15 -20
- data/lib/julewire/core/execution/scope_fields.rb +3 -12
- data/lib/julewire/core/execution/scope_identity.rb +1 -9
- data/lib/julewire/core/execution/scope_snapshot.rb +18 -39
- data/lib/julewire/core/execution/summary_state.rb +16 -19
- data/lib/julewire/core/execution/view.rb +8 -17
- data/lib/julewire/core/facade_methods.rb +67 -65
- data/lib/julewire/core/fields/attribute_keys.rb +0 -2
- data/lib/julewire/core/fields/bags.rb +23 -27
- data/lib/julewire/core/fields/field_set.rb +29 -19
- data/lib/julewire/core/fields/field_stack.rb +68 -134
- data/lib/julewire/core/fields/internal/deletion.rb +6 -9
- data/lib/julewire/core/fields/internal.rb +19 -38
- data/lib/julewire/core/fields/lookup.rb +9 -13
- data/lib/julewire/core/fields/section_proxy.rb +1 -9
- data/lib/julewire/core/fields/stack_set.rb +7 -12
- data/lib/julewire/core/fields/static_labels.rb +1 -1
- data/lib/julewire/core/integration/configurable.rb +4 -3
- data/lib/julewire/core/integration/destination_health.rb +17 -7
- data/lib/julewire/core/integration/event_subscriber.rb +1 -1
- data/lib/julewire/core/integration/facade.rb +12 -15
- data/lib/julewire/core/integration/fork_hooks.rb +12 -36
- data/lib/julewire/core/integration/health.rb +0 -3
- data/lib/julewire/core/integration/ivar_state.rb +0 -5
- data/lib/julewire/core/integration/protocol.rb +20 -0
- data/lib/julewire/core/integration/settings.rb +4 -4
- data/lib/julewire/core/integration/subscriber_install.rb +2 -2
- data/lib/julewire/core/integration/subscription.rb +3 -2
- data/lib/julewire/core/integration/values.rb +0 -17
- data/lib/julewire/core/local_storage.rb +3 -19
- data/lib/julewire/core/processing/invalid_result_failure.rb +23 -0
- data/lib/julewire/core/processing/level_threshold.rb +7 -14
- data/lib/julewire/core/processing/match.rb +6 -4
- data/lib/julewire/core/processing/pipeline.rb +46 -40
- data/lib/julewire/core/processing/processor_chain.rb +22 -11
- data/lib/julewire/core/processing/processor_registry.rb +3 -4
- data/lib/julewire/core/processing/record_field_transform.rb +0 -4
- data/lib/julewire/core/processing/sampling.rb +1 -3
- data/lib/julewire/core/processing.rb +2 -2
- data/lib/julewire/core/propagation/carrier.rb +14 -14
- data/lib/julewire/core/propagation.rb +15 -2
- data/lib/julewire/core/records/build_input.rb +75 -0
- data/lib/julewire/core/records/console_formatter.rb +6 -6
- data/lib/julewire/core/records/deconstruct.rb +1 -1
- data/lib/julewire/core/records/display_message.rb +2 -4
- data/lib/julewire/core/records/draft.rb +125 -262
- data/lib/julewire/core/records/lazy_emit_input.rb +2 -12
- data/lib/julewire/core/records/metadata.rb +4 -3
- data/lib/julewire/core/records/public_projection.rb +2 -2
- data/lib/julewire/core/records/raw_input.rb +1 -1
- data/lib/julewire/core/records/record.rb +19 -37
- data/lib/julewire/core/records/severity.rb +7 -15
- data/lib/julewire/core/runtime.rb +67 -71
- data/lib/julewire/core/runtime_registry.rb +20 -15
- data/lib/julewire/core/runtime_state.rb +5 -3
- data/lib/julewire/core/scheduling/shared_scheduler.rb +28 -22
- data/lib/julewire/core/serialization/backtrace_limiter.rb +13 -9
- data/lib/julewire/core/serialization/bounded_transform.rb +2 -12
- data/lib/julewire/core/serialization/bounded_traversal.rb +49 -66
- data/lib/julewire/core/serialization/deep_compact_empty.rb +9 -11
- data/lib/julewire/core/serialization/deep_freeze.rb +45 -6
- data/lib/julewire/core/serialization/encoding_sanitizer.rb +2 -3
- data/lib/julewire/core/serialization/exception_shape.rb +7 -6
- data/lib/julewire/core/serialization/json_encoder.rb +10 -13
- data/lib/julewire/core/serialization/serializer.rb +23 -50
- data/lib/julewire/core/serialization/serializer_pool.rb +9 -0
- data/lib/julewire/core/serialization/text_encoder.rb +5 -5
- data/lib/julewire/core/serialization/truncation_metadata.rb +63 -48
- data/lib/julewire/core/serialization/value_copy.rb +60 -227
- data/lib/julewire/core/serialization/value_traversal.rb +10 -127
- data/lib/julewire/core/testing.rb +6 -56
- data/lib/julewire/core/validation.rb +2 -3
- data/lib/julewire/core/version.rb +1 -1
- data/lib/julewire/core.rb +2 -7
- metadata +6 -19
- data/lib/julewire/core/scheduling/deadline_scheduler.rb +0 -207
- data/lib/julewire/core/testing/chaos/catalog.rb +0 -72
- data/lib/julewire/core/testing/chaos/core_runtime.rb +0 -120
- data/lib/julewire/core/testing/chaos/destination.rb +0 -55
- data/lib/julewire/core/testing/chaos/emitter.rb +0 -20
- data/lib/julewire/core/testing/chaos/raising_output.rb +0 -42
- data/lib/julewire/core/testing/chaos.rb +0 -80
- data/lib/julewire/core/testing/contracts/component.rb +0 -162
- data/lib/julewire/core/testing/contracts/deadline_scheduler.rb +0 -59
- data/lib/julewire/core/testing/contracts/integration.rb +0 -166
- data/lib/julewire/core/testing/contracts/integration_fields.rb +0 -36
- data/lib/julewire/core/testing/contracts/record_draft.rb +0 -37
- data/lib/julewire/core/testing/contracts/runtime.rb +0 -178
- data/lib/julewire/core/testing/contracts/wire.rb +0 -60
- data/lib/julewire/core/testing/contracts.rb +0 -24
- data/lib/julewire/core/testing/coverage.rb +0 -58
- data/lib/julewire/core/testing/test_reports.rb +0 -78
|
@@ -26,35 +26,31 @@ module Julewire
|
|
|
26
26
|
source
|
|
27
27
|
].freeze
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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:
|
|
50
|
-
context:
|
|
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:
|
|
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:
|
|
67
|
-
attributes:
|
|
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:
|
|
74
|
-
payload:
|
|
75
|
-
metrics:
|
|
76
|
-
error:
|
|
77
|
-
summary:
|
|
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 =
|
|
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)
|
|
17
|
-
coerced
|
|
23
|
+
merge!(coerced, keyword_fields)
|
|
18
24
|
end
|
|
19
25
|
|
|
20
26
|
def merge(left, right)
|
|
@@ -25,7 +31,7 @@ module Julewire
|
|
|
25
31
|
return target unless fields.is_a?(Hash)
|
|
26
32
|
|
|
27
33
|
fields.each do |key, value|
|
|
28
|
-
target[
|
|
34
|
+
target[normalized_field_key(key)] = copy_field_value(value)
|
|
29
35
|
end
|
|
30
36
|
|
|
31
37
|
target
|
|
@@ -48,24 +54,18 @@ module Julewire
|
|
|
48
54
|
end
|
|
49
55
|
|
|
50
56
|
def frozen_copy(value)
|
|
51
|
-
|
|
57
|
+
Internal.frozen_copy(value)
|
|
52
58
|
end
|
|
53
59
|
|
|
54
60
|
def value_for(hash, key, default: nil)
|
|
55
61
|
return default unless hash.is_a?(Hash)
|
|
56
62
|
|
|
57
|
-
|
|
58
|
-
return hash[normalized] if hash.key?(normalized)
|
|
59
|
-
|
|
60
|
-
default
|
|
63
|
+
hash.fetch(Internal.normalize_key(key), default)
|
|
61
64
|
end
|
|
62
65
|
|
|
63
66
|
private
|
|
64
67
|
|
|
65
68
|
def deep_dup_with(value, preserve_truncation_metadata:)
|
|
66
|
-
return {} if value.is_a?(Hash) && value.empty?
|
|
67
|
-
return [] if value.is_a?(Array) && value.empty?
|
|
68
|
-
|
|
69
69
|
Serialization::ValueCopy.call(
|
|
70
70
|
value,
|
|
71
71
|
preserve_truncation_metadata: preserve_truncation_metadata
|
|
@@ -73,14 +73,11 @@ module Julewire
|
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
def deep_symbolize_keys_with(value, preserve_truncation_metadata:)
|
|
76
|
-
return {} if value.is_a?(Hash) && value.empty?
|
|
77
|
-
return [] if value.is_a?(Array) && value.empty?
|
|
78
|
-
|
|
79
76
|
Serialization::ValueCopy.call(
|
|
80
77
|
value,
|
|
81
|
-
max_array_items:
|
|
82
|
-
max_hash_keys:
|
|
83
|
-
max_string_bytes:
|
|
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,
|
|
84
81
|
preserve_truncation_metadata: preserve_truncation_metadata,
|
|
85
82
|
symbolize_keys: true
|
|
86
83
|
)
|
|
@@ -90,12 +87,25 @@ module Julewire
|
|
|
90
87
|
if fields.is_a?(Hash)
|
|
91
88
|
merge!(target, fields)
|
|
92
89
|
elsif invalid == :wrap
|
|
93
|
-
target[VALUE_KEY] =
|
|
90
|
+
target[VALUE_KEY] = copy_field_value(fields)
|
|
94
91
|
elsif invalid == :raise
|
|
95
92
|
raise ArgumentError, "fields must be a Hash"
|
|
96
93
|
end
|
|
97
94
|
end
|
|
98
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
|
+
|
|
99
109
|
def copy_field_value(value) = deep_symbolize_keys(value)
|
|
100
110
|
end
|
|
101
111
|
end
|
|
@@ -4,8 +4,8 @@ module Julewire
|
|
|
4
4
|
module Core
|
|
5
5
|
module Fields
|
|
6
6
|
# @api internal
|
|
7
|
-
# Immutable layers keep snapshots stable while each stack tracks only
|
|
8
|
-
# current head and
|
|
7
|
+
# Immutable layers keep snapshots stable while each stack tracks only the
|
|
8
|
+
# current head and snapshot cache.
|
|
9
9
|
class FieldStack
|
|
10
10
|
EMPTY_HASH = {}.freeze
|
|
11
11
|
private_constant :EMPTY_HASH
|
|
@@ -13,26 +13,37 @@ module Julewire
|
|
|
13
13
|
class Layer
|
|
14
14
|
attr_reader :fields, :parent
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
class << self
|
|
17
|
+
def fields(parent, fields, clear_parent_deletes: true)
|
|
18
|
+
clear_parent_fields = fields if clear_parent_deletes
|
|
19
|
+
new(
|
|
20
|
+
parent,
|
|
21
|
+
fields,
|
|
22
|
+
delete_paths: nil,
|
|
23
|
+
clear_parent_fields:
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def delete_paths(parent, paths)
|
|
28
|
+
new(parent, nil, delete_paths: paths, clear_parent_fields: nil)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private :new
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def initialize(parent, fields, delete_paths:, clear_parent_fields:)
|
|
17
35
|
@parent = parent
|
|
18
36
|
@fields = fields
|
|
19
37
|
@delete_paths = delete_paths
|
|
20
|
-
@
|
|
21
|
-
@owned = owned
|
|
22
|
-
@active_delete_paths_computed = false
|
|
23
|
-
@active_delete_paths = nil
|
|
24
|
-
@snapshot = nil
|
|
25
|
-
@value_cache = nil
|
|
38
|
+
@clear_parent_fields = clear_parent_fields
|
|
26
39
|
end
|
|
27
40
|
|
|
28
|
-
def owned? = @owned
|
|
29
|
-
|
|
30
41
|
def snapshot
|
|
31
42
|
@snapshot ||= build_snapshot
|
|
32
43
|
end
|
|
33
44
|
|
|
34
45
|
def value_for(key)
|
|
35
|
-
return @value_cache
|
|
46
|
+
return @value_cache.fetch(key) if @value_cache&.key?(key)
|
|
36
47
|
|
|
37
48
|
value = if delete_paths_for_key?(key)
|
|
38
49
|
FieldSet.value_for(snapshot, key, default: MISSING)
|
|
@@ -44,82 +55,42 @@ module Julewire
|
|
|
44
55
|
end
|
|
45
56
|
|
|
46
57
|
def active_delete_paths
|
|
47
|
-
return @active_delete_paths if
|
|
58
|
+
return @active_delete_paths if instance_variable_defined?(:@active_delete_paths)
|
|
48
59
|
|
|
49
60
|
@active_delete_paths = build_active_delete_paths
|
|
50
|
-
@active_delete_paths_computed = true
|
|
51
|
-
@active_delete_paths
|
|
52
61
|
end
|
|
53
62
|
|
|
54
63
|
def snapshot_cached?
|
|
55
64
|
!@snapshot.nil?
|
|
56
65
|
end
|
|
57
66
|
|
|
58
|
-
def delete_paths_for_snapshot
|
|
59
|
-
@clear_parent_deletes ? @delete_paths : active_delete_paths
|
|
60
|
-
end
|
|
61
|
-
|
|
62
67
|
def merge_into(snapshot)
|
|
63
|
-
|
|
64
|
-
Fields::Internal.merge_owned!(snapshot, FieldSet.deep_symbolize_owned_keys(@fields))
|
|
65
|
-
else
|
|
66
|
-
FieldSet.merge!(snapshot, @fields)
|
|
67
|
-
end
|
|
68
|
+
Internal.merge_owned!(snapshot, FieldSet.deep_dup_owned(@fields))
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
private
|
|
71
72
|
|
|
72
73
|
def build_snapshot
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
paths = source.delete_paths_for_snapshot
|
|
80
|
-
Fields::Internal.apply_delete_paths!(snapshot, paths) if paths
|
|
81
|
-
end
|
|
82
|
-
Fields::Internal.frozen_owned_copy(snapshot)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def build_direct_snapshot
|
|
86
|
-
snapshot = merge_into({})
|
|
87
|
-
paths = delete_paths_for_snapshot
|
|
88
|
-
Fields::Internal.apply_delete_paths!(snapshot, paths) if paths
|
|
89
|
-
Fields::Internal.frozen_owned_copy(snapshot)
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def build_parent_snapshot
|
|
93
|
-
snapshot = FieldSet.deep_dup_owned(@parent.snapshot)
|
|
94
|
-
merge_into(snapshot)
|
|
95
|
-
paths = delete_paths_for_snapshot
|
|
96
|
-
Fields::Internal.apply_delete_paths!(snapshot, paths) if paths
|
|
97
|
-
Fields::Internal.frozen_owned_copy(snapshot)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def source_snapshot_base
|
|
101
|
-
source = source_chain_base
|
|
102
|
-
source ? FieldSet.deep_dup_owned(source.snapshot) : {}
|
|
74
|
+
sources, base = source_layers_and_base
|
|
75
|
+
snapshot = base ? FieldSet.deep_dup_owned(base.snapshot) : {}
|
|
76
|
+
sources.reverse_each { it.merge_into(snapshot) }
|
|
77
|
+
paths = active_delete_paths
|
|
78
|
+
Internal.apply_delete_paths!(snapshot, paths) if paths
|
|
79
|
+
Internal.frozen_owned_copy(snapshot)
|
|
103
80
|
end
|
|
104
81
|
|
|
105
82
|
def frozen_field_value(value)
|
|
106
|
-
|
|
83
|
+
Internal.frozen_owned_copy(value)
|
|
107
84
|
end
|
|
108
85
|
|
|
109
|
-
def
|
|
110
|
-
sources
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
sources << source
|
|
114
|
-
source = source.parent
|
|
115
|
-
end
|
|
116
|
-
sources
|
|
117
|
-
end
|
|
86
|
+
def source_layers_and_base(source = self, seen_sources = [], sources = [])
|
|
87
|
+
return [sources, source] if source.nil? || source.snapshot_cached?
|
|
88
|
+
|
|
89
|
+
raise Error, "field stack layer cycle" if seen_sources.include?(source)
|
|
118
90
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
source
|
|
122
|
-
source
|
|
91
|
+
seen_sources << source
|
|
92
|
+
sources << source
|
|
93
|
+
source_layers_and_base(source.parent, seen_sources, sources)
|
|
123
94
|
end
|
|
124
95
|
|
|
125
96
|
def parent_value_for(key)
|
|
@@ -134,16 +105,14 @@ module Julewire
|
|
|
134
105
|
|
|
135
106
|
def build_active_delete_paths
|
|
136
107
|
paths = @parent&.active_delete_paths
|
|
137
|
-
paths = clear_active_delete_paths(paths) if paths
|
|
108
|
+
paths = clear_active_delete_paths(paths) if paths
|
|
138
109
|
paths = append_delete_paths(paths) if @delete_paths
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
paths.empty? ? nil : paths
|
|
110
|
+
paths
|
|
142
111
|
end
|
|
143
112
|
|
|
144
113
|
def clear_active_delete_paths(paths)
|
|
145
114
|
paths = paths.dup
|
|
146
|
-
|
|
115
|
+
Internal.clear_delete_paths!(paths, @clear_parent_fields)
|
|
147
116
|
paths
|
|
148
117
|
end
|
|
149
118
|
|
|
@@ -153,51 +122,35 @@ module Julewire
|
|
|
153
122
|
end
|
|
154
123
|
private_constant :Layer
|
|
155
124
|
|
|
156
|
-
def initialize(fields =
|
|
125
|
+
def initialize(fields = nil, delete_paths: false, source: nil)
|
|
157
126
|
@source = source
|
|
158
127
|
@delete_paths_enabled = delete_paths
|
|
159
|
-
|
|
160
|
-
@snapshot_version = nil
|
|
161
|
-
@snapshot = nil
|
|
162
|
-
@value_cache = nil
|
|
163
|
-
add(fields) if fields.is_a?(Hash) && !fields.empty?
|
|
128
|
+
add(fields)
|
|
164
129
|
end
|
|
165
130
|
|
|
166
131
|
def snapshot
|
|
167
|
-
return @snapshot if @
|
|
132
|
+
return @snapshot if @snapshot
|
|
168
133
|
|
|
169
134
|
@snapshot = @source ? @source.snapshot : EMPTY_HASH
|
|
170
|
-
@snapshot_version = @version
|
|
171
|
-
@snapshot
|
|
172
135
|
end
|
|
173
136
|
|
|
174
|
-
def
|
|
137
|
+
def branch
|
|
175
138
|
self.class.new(delete_paths: @delete_paths_enabled, source: @source)
|
|
176
139
|
end
|
|
177
140
|
|
|
178
141
|
def value_for(key, default:)
|
|
179
|
-
|
|
180
|
-
return cache[key] if cache&.key?(key)
|
|
181
|
-
|
|
182
|
-
if key.is_a?(String)
|
|
183
|
-
key = Fields::Internal.normalize_key(key)
|
|
184
|
-
cache = @value_cache
|
|
185
|
-
return cache[key] if cache&.key?(key)
|
|
186
|
-
end
|
|
187
|
-
|
|
142
|
+
key = Internal.normalize_key(key)
|
|
188
143
|
value = source_value_for(key)
|
|
189
144
|
return default if value.equal?(MISSING)
|
|
190
145
|
|
|
191
|
-
|
|
146
|
+
value
|
|
192
147
|
end
|
|
193
148
|
|
|
194
149
|
def add(fields = nil, owned: false, **keyword_fields)
|
|
195
150
|
fields = field_input(fields, keyword_fields, owned: owned)
|
|
196
|
-
return unless fields.is_a?(Hash)
|
|
197
151
|
return if fields.empty?
|
|
198
152
|
|
|
199
|
-
|
|
200
|
-
@source = Layer.new(@source, fields, clear_parent_deletes: true, owned: owned)
|
|
153
|
+
@source = Layer.fields(@source, fields)
|
|
201
154
|
invalidate_snapshot!
|
|
202
155
|
end
|
|
203
156
|
|
|
@@ -205,17 +158,15 @@ module Julewire
|
|
|
205
158
|
return if path.empty?
|
|
206
159
|
return unless @delete_paths_enabled
|
|
207
160
|
|
|
208
|
-
@source = Layer.
|
|
161
|
+
@source = Layer.delete_paths(@source, [path])
|
|
209
162
|
invalidate_snapshot!
|
|
210
163
|
end
|
|
211
164
|
|
|
212
165
|
def with(fields = nil, owned: false, **keyword_fields, &)
|
|
213
166
|
fields = field_input(fields, keyword_fields, owned: owned)
|
|
214
|
-
return yield unless fields.is_a?(Hash)
|
|
215
167
|
return yield if fields.empty?
|
|
216
168
|
|
|
217
|
-
fields
|
|
218
|
-
with_layer(fields, owned: owned, &)
|
|
169
|
+
with_layer(fields, &)
|
|
219
170
|
end
|
|
220
171
|
|
|
221
172
|
def without(path, &)
|
|
@@ -223,34 +174,25 @@ module Julewire
|
|
|
223
174
|
|
|
224
175
|
return yield unless @delete_paths_enabled
|
|
225
176
|
|
|
226
|
-
|
|
177
|
+
with_delete_layer([path], &)
|
|
227
178
|
end
|
|
228
179
|
|
|
229
180
|
private
|
|
230
181
|
|
|
231
182
|
def field_input(fields, keyword_fields, owned:)
|
|
232
183
|
if owned
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return fields.merge(keyword_fields) if fields.is_a?(Hash)
|
|
184
|
+
fields = keyword_fields if fields.nil?
|
|
185
|
+
Serialization::DeepFreeze.validate_symbol_hash(fields)
|
|
236
186
|
|
|
237
|
-
return keyword_fields
|
|
187
|
+
return fields.merge(keyword_fields)
|
|
238
188
|
end
|
|
239
189
|
|
|
240
|
-
return FieldSet.deep_symbolize_keys(fields) if keyword_fields.empty?
|
|
241
|
-
|
|
242
190
|
FieldSet.coerce(fields, keyword_fields)
|
|
243
191
|
end
|
|
244
192
|
|
|
245
|
-
def with_layer(fields
|
|
193
|
+
def with_layer(fields)
|
|
246
194
|
previous_source = @source
|
|
247
|
-
@source = Layer.
|
|
248
|
-
previous_source,
|
|
249
|
-
fields,
|
|
250
|
-
delete_paths: delete_paths,
|
|
251
|
-
clear_parent_deletes: false,
|
|
252
|
-
owned: owned
|
|
253
|
-
)
|
|
195
|
+
@source = Layer.fields(previous_source, fields, clear_parent_deletes: false)
|
|
254
196
|
invalidate_snapshot!
|
|
255
197
|
begin
|
|
256
198
|
yield
|
|
@@ -260,34 +202,26 @@ module Julewire
|
|
|
260
202
|
end
|
|
261
203
|
end
|
|
262
204
|
|
|
263
|
-
def
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
205
|
+
def with_delete_layer(delete_paths)
|
|
206
|
+
previous_source = @source
|
|
207
|
+
@source = Layer.delete_paths(previous_source, delete_paths)
|
|
208
|
+
invalidate_snapshot!
|
|
209
|
+
begin
|
|
210
|
+
yield
|
|
211
|
+
ensure
|
|
212
|
+
@source = previous_source
|
|
213
|
+
invalidate_snapshot!
|
|
214
|
+
end
|
|
267
215
|
end
|
|
268
216
|
|
|
269
217
|
def source_value_for(key)
|
|
270
218
|
return MISSING unless @source
|
|
271
219
|
|
|
272
|
-
|
|
273
|
-
# Single-layer hits avoid Layer's delete-path/cache bookkeeping.
|
|
274
|
-
field_value = FieldSet.value_for(@source.fields, key, default: MISSING)
|
|
275
|
-
return frozen_source_value(field_value, @source.owned?) unless field_value.equal?(MISSING)
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
value = @source.value_for(key)
|
|
279
|
-
value.equal?(MISSING) ? MISSING : value
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
def frozen_source_value(value, owned)
|
|
283
|
-
owned ? Fields::Internal.frozen_owned_copy(value) : Fields::Internal.frozen_copy(value)
|
|
220
|
+
@source.value_for(key)
|
|
284
221
|
end
|
|
285
222
|
|
|
286
223
|
def invalidate_snapshot!
|
|
287
|
-
@version += 1
|
|
288
224
|
@snapshot = nil
|
|
289
|
-
@snapshot_version = nil
|
|
290
|
-
@value_cache = nil
|
|
291
225
|
end
|
|
292
226
|
end
|
|
293
227
|
end
|
|
@@ -11,12 +11,10 @@ module Julewire
|
|
|
11
11
|
return target if normalized_path.empty?
|
|
12
12
|
|
|
13
13
|
deep_delete_path!(target, normalized_path)
|
|
14
|
-
target
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
def apply_delete_paths!(target, paths)
|
|
18
17
|
paths.each { delete_path!(target, it) }
|
|
19
|
-
target
|
|
20
18
|
end
|
|
21
19
|
|
|
22
20
|
def clear_delete_paths!(paths, fields)
|
|
@@ -27,7 +25,7 @@ module Julewire
|
|
|
27
25
|
end
|
|
28
26
|
|
|
29
27
|
def normalize_path(path)
|
|
30
|
-
Array(path).flatten.
|
|
28
|
+
Array(path).flatten.map { Internal.normalize_key(it) }
|
|
31
29
|
end
|
|
32
30
|
|
|
33
31
|
private
|
|
@@ -37,7 +35,7 @@ module Julewire
|
|
|
37
35
|
|
|
38
36
|
fields.flat_map do |key, value|
|
|
39
37
|
path = prefix + [Internal.normalize_key(key)]
|
|
40
|
-
nested =
|
|
38
|
+
nested = field_paths(value, path)
|
|
41
39
|
nested.empty? ? [path] : nested
|
|
42
40
|
end
|
|
43
41
|
end
|
|
@@ -53,12 +51,11 @@ module Julewire
|
|
|
53
51
|
key = path.first
|
|
54
52
|
if path.one?
|
|
55
53
|
Internal.delete_key!(target, key)
|
|
56
|
-
|
|
54
|
+
else
|
|
55
|
+
child = FieldSet.value_for(target, key)
|
|
56
|
+
deep_delete_path!(child, path.drop(1))
|
|
57
|
+
Internal.delete_key!(target, key) if child.is_a?(Hash) && child.empty?
|
|
57
58
|
end
|
|
58
|
-
|
|
59
|
-
child = FieldSet.value_for(target, key)
|
|
60
|
-
deep_delete_path!(child, path.drop(1))
|
|
61
|
-
Internal.delete_key!(target, key) if child.is_a?(Hash) && child.empty?
|
|
62
59
|
end
|
|
63
60
|
end
|
|
64
61
|
end
|
|
@@ -4,13 +4,16 @@ module Julewire
|
|
|
4
4
|
module Core
|
|
5
5
|
module Fields
|
|
6
6
|
module Internal
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
FIELD_KEY_ERROR = "field keys must be String or Symbol"
|
|
8
|
+
RECORD_STRING_KEY_ERROR = "record must not use string keys"
|
|
9
|
+
RECORD_SYMBOL_KEY_ERROR = "record keys must be Symbols"
|
|
10
10
|
|
|
11
11
|
class << self
|
|
12
12
|
def normalize_key(key)
|
|
13
|
-
key.is_a?(String)
|
|
13
|
+
return key.to_sym if key.is_a?(String)
|
|
14
|
+
return key if key.instance_of?(Symbol)
|
|
15
|
+
|
|
16
|
+
raise TypeError, FIELD_KEY_ERROR
|
|
14
17
|
end
|
|
15
18
|
|
|
16
19
|
def delete_key!(target, key)
|
|
@@ -25,26 +28,12 @@ module Julewire
|
|
|
25
28
|
frozen_copy_with(value, preserve_truncation_metadata: true)
|
|
26
29
|
end
|
|
27
30
|
|
|
28
|
-
def frozen_deep_symbolize_keys(value)
|
|
29
|
-
frozen_deep_symbolize_keys_with(value, preserve_truncation_metadata: false)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def frozen_deep_symbolize_owned_keys(value)
|
|
33
|
-
frozen_deep_symbolize_keys_with(value, preserve_truncation_metadata: true)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def delete_path!(target, path) = Deletion.delete_path!(target, path)
|
|
37
|
-
|
|
38
31
|
def apply_delete_paths!(target, paths) = Deletion.apply_delete_paths!(target, paths)
|
|
39
32
|
|
|
40
33
|
def clear_delete_paths!(paths, fields) = Deletion.clear_delete_paths!(paths, fields)
|
|
41
34
|
|
|
42
35
|
def normalize_path(path) = Deletion.normalize_path(path)
|
|
43
36
|
|
|
44
|
-
def deep_merge(left, right)
|
|
45
|
-
deep_merge!(FieldSet.deep_symbolize_keys(left), right)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
37
|
def deep_merge!(target, fields)
|
|
49
38
|
merge_values!(target, fields) do |value, existing|
|
|
50
39
|
if existing.is_a?(Hash) && value.is_a?(Hash)
|
|
@@ -56,7 +45,7 @@ module Julewire
|
|
|
56
45
|
end
|
|
57
46
|
|
|
58
47
|
def deep_merge_owned!(target, fields)
|
|
59
|
-
|
|
48
|
+
merge_owned_values!(target, fields) do |value, existing|
|
|
60
49
|
if existing.is_a?(Hash) && value.is_a?(Hash)
|
|
61
50
|
deep_merge_owned!(existing, value)
|
|
62
51
|
else
|
|
@@ -66,15 +55,12 @@ module Julewire
|
|
|
66
55
|
end
|
|
67
56
|
|
|
68
57
|
def merge_owned!(target, fields)
|
|
69
|
-
|
|
58
|
+
merge_owned_values!(target, fields) { |value, _existing| value }
|
|
70
59
|
end
|
|
71
60
|
|
|
72
61
|
private
|
|
73
62
|
|
|
74
63
|
def frozen_copy_with(value, preserve_truncation_metadata:)
|
|
75
|
-
return EMPTY_HASH if value.is_a?(Hash) && value.empty?
|
|
76
|
-
return EMPTY_ARRAY if value.is_a?(Array) && value.empty?
|
|
77
|
-
|
|
78
64
|
Serialization::ValueCopy.call(
|
|
79
65
|
value,
|
|
80
66
|
freeze_values: true,
|
|
@@ -82,21 +68,6 @@ module Julewire
|
|
|
82
68
|
)
|
|
83
69
|
end
|
|
84
70
|
|
|
85
|
-
def frozen_deep_symbolize_keys_with(value, preserve_truncation_metadata:)
|
|
86
|
-
return EMPTY_HASH if value.is_a?(Hash) && value.empty?
|
|
87
|
-
return EMPTY_ARRAY if value.is_a?(Array) && value.empty?
|
|
88
|
-
|
|
89
|
-
Serialization::ValueCopy.call(
|
|
90
|
-
value,
|
|
91
|
-
freeze_values: true,
|
|
92
|
-
max_array_items: Serialization::Serializer::DEFAULT_MAX_ARRAY_ITEMS,
|
|
93
|
-
max_hash_keys: Serialization::Serializer::DEFAULT_MAX_HASH_KEYS,
|
|
94
|
-
max_string_bytes: Serialization::Serializer::DEFAULT_MAX_STRING_BYTES,
|
|
95
|
-
preserve_truncation_metadata: preserve_truncation_metadata,
|
|
96
|
-
symbolize_keys: true
|
|
97
|
-
)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
71
|
def merge_values!(target, fields)
|
|
101
72
|
return target unless fields.is_a?(Hash)
|
|
102
73
|
|
|
@@ -108,6 +79,16 @@ module Julewire
|
|
|
108
79
|
|
|
109
80
|
target
|
|
110
81
|
end
|
|
82
|
+
|
|
83
|
+
def merge_owned_values!(target, fields)
|
|
84
|
+
return target unless fields.is_a?(Hash)
|
|
85
|
+
|
|
86
|
+
fields.each do |key, value|
|
|
87
|
+
target[key] = yield value, target[key]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
target
|
|
91
|
+
end
|
|
111
92
|
end
|
|
112
93
|
end
|
|
113
94
|
end
|