karafka-core 2.6.0 → 2.6.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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +3 -3
- data/.github/workflows/push.yml +1 -1
- data/CHANGELOG.md +11 -0
- data/Gemfile.lock +1 -1
- data/lib/karafka/core/configurable/node.rb +118 -10
- data/lib/karafka/core/contractable/contract.rb +44 -82
- data/lib/karafka/core/monitoring/monitor.rb +9 -1
- data/lib/karafka/core/version.rb +1 -1
- data/renovate.json +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2d9f6ba569f0c3a911cbe3ce80c545f3069b5aa2560782cf88bd11c97baa0432
|
|
4
|
+
data.tar.gz: 54f274da23e99841809f884ea871ce9a55705d76d3c4e5c40b0ef2ffbe0ae75a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3a6c6ba64216479cebb2c4f574dff194ae6665189f382b0a60193c5b4ec3ab4d55f4ff97d7fb3fc90800f105a5284da27493b5694adff6e1a6c0738fa5795f65
|
|
7
|
+
data.tar.gz: 8ea0eddcd9ae95603041f8a74a1a26311cd9aa13744759dd6e7a699eb6ce682fd89596ef85e77ee2fd280d4aa3eca14d8f2ddd7a75dc322fe6e7bb33903ac7b6
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -37,7 +37,7 @@ jobs:
|
|
|
37
37
|
run: "[ -e $APT_DEPS ] || sudo apt-get install -y --no-install-recommends $APT_DEPS"
|
|
38
38
|
|
|
39
39
|
- name: Set up Ruby
|
|
40
|
-
uses: ruby/setup-ruby@
|
|
40
|
+
uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0
|
|
41
41
|
with:
|
|
42
42
|
ruby-version: ${{matrix.ruby}}
|
|
43
43
|
bundler: 'latest'
|
|
@@ -69,7 +69,7 @@ jobs:
|
|
|
69
69
|
with:
|
|
70
70
|
fetch-depth: 0
|
|
71
71
|
- name: Set up Ruby
|
|
72
|
-
uses: ruby/setup-ruby@
|
|
72
|
+
uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0
|
|
73
73
|
with:
|
|
74
74
|
ruby-version: '4.0.5'
|
|
75
75
|
bundler-cache: true
|
|
@@ -86,7 +86,7 @@ jobs:
|
|
|
86
86
|
with:
|
|
87
87
|
fetch-depth: 0
|
|
88
88
|
- name: Set up Ruby
|
|
89
|
-
uses: ruby/setup-ruby@
|
|
89
|
+
uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0
|
|
90
90
|
with:
|
|
91
91
|
ruby-version: '4.0.5'
|
|
92
92
|
bundler-cache: true
|
data/.github/workflows/push.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Karafka Core Changelog
|
|
2
2
|
|
|
3
|
+
## 2.6.1 (2026-06-15)
|
|
4
|
+
- [Enhancement] Speed up `Contract#call` by ~1.25x for minimal and ~1.4x for fully populated data: resolve rule paths with a single `Hash#fetch` per level instead of `key?` + `[]`, inline the per-rule type dispatch into the rules loop, and compare the dig sentinel via `#equal?` so `#==` is never dispatched to the validated (user-provided) values. This is the per-message validation path in WaterDrop producers.
|
|
5
|
+
- [Fix] `Contract#call` with rule paths of 3+ keys no longer raises `NoMethodError` when an intermediate value is not a `Hash` and reports the path as missing instead, consistent with the 2-key path behavior.
|
|
6
|
+
- [Change] Reject reserved setting names with an `ArgumentError` in `Configurable::Node#setting` and `#register`: internal state names (`node_name`, `children`, `nestings`, `compiled`, `configs_refs`, `local_defs`) and the node public API names (`setting`, `configure`, `to_h`, `deep_dup`, `register`, `compile`). Previously such names silently shadowed the node own accessors, breaking `deep_dup` or `to_h`, and assignments like `config.children = value` corrupted the node internal state.
|
|
7
|
+
- [Enhancement] Skip the event name mapping hash lookup in `Monitor#instrument` when no namespace is used and the event id is already a `String`, which is the case for all events in the Karafka ecosystem (~1.2x faster dispatch on the common no-subscribers path). Symbol event ids and namespaced monitors keep going through the mapping.
|
|
8
|
+
- [Enhancement] Mirror config values into instance variables and use `attr_reader` based readers in `Configurable::Node`, yielding ~1.4x faster flat and ~1.6x faster nested settings reads on hot paths. `@configs_refs` remains the canonical store; non-identifier setting names (e.g. registered names with dashes) keep the previous hash-based accessors.
|
|
9
|
+
- [Enhancement] Instantiate each `Configurable::Node` through a per-layout anonymous subclass so the ivar-backed settings do not grow object shape variations on the shared `Node` class (which would degrade ivar access and trigger Ruby performance warnings). `deep_dup` reuses the template's subclass, so duplicated configs share object shapes.
|
|
10
|
+
- [Fix] Symbolize setting names at definition time (`setting`, same as `register`) and on config store writes so `String` setting names work end to end (accessors, `#to_h`, recompilation state) and cannot corrupt node internal state when matching reserved internal names (previously string-named settings were quietly broken as accessors and the store disagreed on the key type).
|
|
11
|
+
- [Change] Config nodes are now instances of anonymous `Node` subclasses: `is_a?(Karafka::Core::Configurable::Node)` still holds, but `instance_of?(Node)` is now `false` and `node.class.name` is `nil`.
|
|
12
|
+
- [Change] Assigning a setting on a frozen config node now raises `FrozenError` (previously the write silently mutated internal storage despite the freeze).
|
|
13
|
+
|
|
3
14
|
## 2.6.0 (2026-06-10)
|
|
4
15
|
- [Enhancement] Add `Node#register` to allow runtime key-value registration on compiled nodes without going through the static `setting` DSL. Useful for dynamic registries (e.g. named clusters) where setting names are not known at class-load time.
|
|
5
16
|
- [Enhancement] Replace version-gated `Warning[:performance]` with a `Warning.categories`-based loop that enables all opt-in Ruby warning categories automatically, picking up new categories (e.g. `strict_unused_block` in Ruby 3.4+) without future patches.
|
data/Gemfile.lock
CHANGED
|
@@ -15,6 +15,49 @@ module Karafka
|
|
|
15
15
|
# We need to be able to redefine children for deep copy
|
|
16
16
|
attr_accessor :children
|
|
17
17
|
|
|
18
|
+
# Names that cannot be used as setting names because they would collide with the node
|
|
19
|
+
# internal state or the node public API: their accessors would shadow the node own
|
|
20
|
+
# readers (breaking for example `#deep_dup` or `#to_h`) and writers like `children=`
|
|
21
|
+
# would overwrite internal ivars. `#setting` and `#register` reject them upfront and
|
|
22
|
+
# `#ivar_backed?` keeps guarding the ivar mirror as defense in depth.
|
|
23
|
+
# Private method names are deliberately not reserved: that would make internal
|
|
24
|
+
# implementation details part of the public contract
|
|
25
|
+
RESERVED_NAMES = %i[
|
|
26
|
+
node_name
|
|
27
|
+
children
|
|
28
|
+
nestings
|
|
29
|
+
compiled
|
|
30
|
+
configs_refs
|
|
31
|
+
local_defs
|
|
32
|
+
setting
|
|
33
|
+
configure
|
|
34
|
+
to_h
|
|
35
|
+
deep_dup
|
|
36
|
+
register
|
|
37
|
+
compile
|
|
38
|
+
].to_h { |name| [name, true] }.freeze
|
|
39
|
+
|
|
40
|
+
# Setting names that match this format can be backed by instance variables and use the
|
|
41
|
+
# fast `attr_reader` based readers. Others (e.g. registered names with dashes) fall back
|
|
42
|
+
# to the hash-based accessors
|
|
43
|
+
IVAR_NAMEABLE_FORMAT = /\A[A-Za-z_][A-Za-z0-9_]*\z/
|
|
44
|
+
|
|
45
|
+
private_constant :RESERVED_NAMES, :IVAR_NAMEABLE_FORMAT
|
|
46
|
+
|
|
47
|
+
class << self
|
|
48
|
+
# Builds each node through its own anonymous subclass. Since setting values are
|
|
49
|
+
# mirrored into instance variables for fast access and each node layout carries a
|
|
50
|
+
# different set of them, instantiating nodes directly from this class would grow its
|
|
51
|
+
# object shape variations past the Ruby limit, degrading ivar access for all nodes.
|
|
52
|
+
# A subclass per layout keeps shape variations per class minimal (late `setting`
|
|
53
|
+
# calls after inheritance or runtime `register` calls may add a few more, staying
|
|
54
|
+
# well under the limit). `#deep_dup` reuses the subclass of its template, so
|
|
55
|
+
# duplicated configs share shapes as well.
|
|
56
|
+
def new(...)
|
|
57
|
+
equal?(Node) ? Class.new(self).new(...) : super
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
18
61
|
# @param node_name [Symbol] node name
|
|
19
62
|
# @param nestings [Proc] block for nested settings
|
|
20
63
|
# @param evaluate [Boolean] when false, skip evaluating the nestings block. Used by
|
|
@@ -31,12 +74,20 @@ module Karafka
|
|
|
31
74
|
|
|
32
75
|
# Allows for a single leaf or nested node definition
|
|
33
76
|
#
|
|
34
|
-
# @param node_name [Symbol] setting or nested node name
|
|
77
|
+
# @param node_name [Symbol, String] setting or nested node name
|
|
35
78
|
# @param default [Object] default value
|
|
36
79
|
# @param constructor [#call, nil] callable or nil
|
|
37
80
|
# @param lazy [Boolean] is this a lazy leaf
|
|
38
81
|
# @param block [Proc] block for nested settings
|
|
82
|
+
# @raise [ArgumentError] when the name is reserved for the node internal state
|
|
39
83
|
def setting(node_name, default: nil, constructor: nil, lazy: false, &block)
|
|
84
|
+
# Symbolize at definition time (same as `#register`) so the config store, accessors,
|
|
85
|
+
# `#to_h` and the compile state checks all agree on the key type also when a String
|
|
86
|
+
# name is provided
|
|
87
|
+
node_name = node_name.to_sym
|
|
88
|
+
|
|
89
|
+
prevent_reserved_names!(node_name)
|
|
90
|
+
|
|
40
91
|
@children << if block
|
|
41
92
|
Node.new(node_name, block)
|
|
42
93
|
else
|
|
@@ -85,7 +136,8 @@ module Karafka
|
|
|
85
136
|
# and non-side-effect usage on an instance/inherited.
|
|
86
137
|
# @return [Node] duplicated node
|
|
87
138
|
def deep_dup
|
|
88
|
-
|
|
139
|
+
# Same-layout nodes reuse the class of their template so they share object shapes
|
|
140
|
+
dupped = self.class.new(node_name, nestings, evaluate: false)
|
|
89
141
|
|
|
90
142
|
children.each do |value|
|
|
91
143
|
dupped.children << if value.is_a?(Leaf)
|
|
@@ -116,16 +168,19 @@ module Karafka
|
|
|
116
168
|
# @param name [Symbol, String] setting name
|
|
117
169
|
# @param value [Object] the setting value assigned immediately; also used as the default
|
|
118
170
|
# when the node is deep-duped and recompiled on a new instance
|
|
119
|
-
# @raise [ArgumentError] when the name is already taken
|
|
171
|
+
# @raise [ArgumentError] when the name is already taken or reserved for the node
|
|
172
|
+
# internal state
|
|
120
173
|
def register(name, value)
|
|
121
174
|
name = name.to_sym
|
|
122
175
|
|
|
176
|
+
prevent_reserved_names!(name)
|
|
177
|
+
|
|
123
178
|
raise ArgumentError, "#{name} is already registered" if @configs_refs.key?(name)
|
|
124
179
|
|
|
125
180
|
leaf = Leaf.new(name, value, nil, true, false)
|
|
126
181
|
@children << leaf
|
|
127
182
|
build_accessors(leaf)
|
|
128
|
-
|
|
183
|
+
config_write(name, value)
|
|
129
184
|
end
|
|
130
185
|
|
|
131
186
|
# Converts the settings definitions into end children
|
|
@@ -161,7 +216,7 @@ module Karafka
|
|
|
161
216
|
if lazy_leaf && !initialized
|
|
162
217
|
build_dynamic_accessor(value)
|
|
163
218
|
else
|
|
164
|
-
|
|
219
|
+
config_write(value.node_name, initialized)
|
|
165
220
|
end
|
|
166
221
|
end
|
|
167
222
|
|
|
@@ -207,6 +262,12 @@ module Karafka
|
|
|
207
262
|
|
|
208
263
|
# Builds regular accessors for value fetching
|
|
209
264
|
#
|
|
265
|
+
# Settings with names that can form valid instance variables get `attr_reader` based
|
|
266
|
+
# readers backed by an ivar mirror of the config value. This is significantly faster
|
|
267
|
+
# than a method with a hash lookup, which matters since settings are read on hot paths
|
|
268
|
+
# across the whole ecosystem. `@configs_refs` remains the canonical store used by
|
|
269
|
+
# `#to_h`, `#compile` and `#register`, with `#config_write` keeping the mirror in sync.
|
|
270
|
+
#
|
|
210
271
|
# @param value [Leaf]
|
|
211
272
|
def build_accessors(value)
|
|
212
273
|
reader_name = value.node_name.to_sym
|
|
@@ -219,17 +280,64 @@ module Karafka
|
|
|
219
280
|
if reader_respond ? !@local_defs.key?(reader_name) : true
|
|
220
281
|
@local_defs[reader_name] = true
|
|
221
282
|
|
|
222
|
-
|
|
223
|
-
|
|
283
|
+
if ivar_backed?(reader_name)
|
|
284
|
+
singleton_class.attr_reader(reader_name)
|
|
285
|
+
else
|
|
286
|
+
define_singleton_method(reader_name) do
|
|
287
|
+
@configs_refs[reader_name]
|
|
288
|
+
end
|
|
224
289
|
end
|
|
225
290
|
end
|
|
226
291
|
|
|
227
|
-
return if respond_to?(:"#{
|
|
292
|
+
return if respond_to?(:"#{reader_name}=")
|
|
228
293
|
|
|
229
|
-
|
|
230
|
-
|
|
294
|
+
if ivar_backed?(reader_name)
|
|
295
|
+
ivar_name = :"@#{reader_name}"
|
|
296
|
+
|
|
297
|
+
define_singleton_method(:"#{reader_name}=") do |new_value|
|
|
298
|
+
instance_variable_set(ivar_name, @configs_refs[reader_name] = new_value)
|
|
299
|
+
end
|
|
300
|
+
else
|
|
301
|
+
define_singleton_method(:"#{reader_name}=") do |new_value|
|
|
302
|
+
@configs_refs[reader_name] = new_value
|
|
303
|
+
end
|
|
231
304
|
end
|
|
232
305
|
end
|
|
306
|
+
|
|
307
|
+
# Writes a config value to the canonical store and mirrors it into the backing instance
|
|
308
|
+
# variable when the setting uses the fast ivar-backed reader
|
|
309
|
+
#
|
|
310
|
+
# @param name [Symbol, String] setting name
|
|
311
|
+
# @param value [Object] config value assigned to the setting
|
|
312
|
+
def config_write(name, value)
|
|
313
|
+
# Accessors operate on symbolized names, so the store has to be keyed consistently.
|
|
314
|
+
# This also guarantees that a String name matching a reserved internal name is
|
|
315
|
+
# recognized by the `ivar_backed?` guard and cannot corrupt the node internal state
|
|
316
|
+
name = name.to_sym
|
|
317
|
+
|
|
318
|
+
@configs_refs[name] = value
|
|
319
|
+
instance_variable_set(:"@#{name}", value) if ivar_backed?(name)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# @param name [Symbol] setting name
|
|
323
|
+
# @return [Boolean] true if this setting can be backed by an instance variable and use
|
|
324
|
+
# the fast `attr_reader` based reader
|
|
325
|
+
def ivar_backed?(name)
|
|
326
|
+
!RESERVED_NAMES.key?(name) && IVAR_NAMEABLE_FORMAT.match?(name)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Rejects setting names that would collide with the node internal state. Without this,
|
|
330
|
+
# such names would shadow the node own accessors, breaking `#deep_dup` and silently
|
|
331
|
+
# corrupting internals on assignment (e.g. `config.children = value` hitting the node
|
|
332
|
+
# own `attr_writer`)
|
|
333
|
+
#
|
|
334
|
+
# @param name [Symbol] already symbolized setting name
|
|
335
|
+
# @raise [ArgumentError] when the name is reserved
|
|
336
|
+
def prevent_reserved_names!(name)
|
|
337
|
+
return unless RESERVED_NAMES.key?(name)
|
|
338
|
+
|
|
339
|
+
raise ArgumentError, "#{name} is a reserved name and cannot be used as a setting name"
|
|
340
|
+
end
|
|
233
341
|
end
|
|
234
342
|
end
|
|
235
343
|
end
|
|
@@ -15,7 +15,7 @@ module Karafka
|
|
|
15
15
|
DIG_MISS = Object.new
|
|
16
16
|
|
|
17
17
|
# Empty array for scope default to avoid allocating a new Array on each
|
|
18
|
-
# `#call` / `#validate!` invocation. Safe because scope is never mutated
|
|
18
|
+
# `#call` / `#validate!` invocation. Safe because scope is never mutated - it is only
|
|
19
19
|
# used in `scope + rule.path` which creates a new Array.
|
|
20
20
|
EMPTY_ARRAY = [].freeze
|
|
21
21
|
|
|
@@ -81,6 +81,12 @@ module Karafka
|
|
|
81
81
|
|
|
82
82
|
# Runs the validation
|
|
83
83
|
#
|
|
84
|
+
# The per-rule handling is inlined instead of dispatching to per-type methods because
|
|
85
|
+
# this runs per rule per validation, including the per-message validations in
|
|
86
|
+
# WaterDrop. Required and optional rules share the whole flow except the missing-key
|
|
87
|
+
# handling. `DIG_MISS` is compared via `#equal?` so we never dispatch `#==` to the
|
|
88
|
+
# validated (user-provided) values.
|
|
89
|
+
#
|
|
84
90
|
# @param data [Hash] hash with data we want to validate
|
|
85
91
|
# @param scope [Array<String>] scope of this contract (if any) or empty array if no parent
|
|
86
92
|
# scope is needed if contract starts from root
|
|
@@ -89,13 +95,28 @@ module Karafka
|
|
|
89
95
|
errors = []
|
|
90
96
|
|
|
91
97
|
self.class.rules.each do |rule|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
if rule.type == :virtual
|
|
99
|
+
result = rule.validator.call(data, errors, self)
|
|
100
|
+
|
|
101
|
+
next if result == true
|
|
102
|
+
|
|
103
|
+
result&.each do |sub_result|
|
|
104
|
+
sub_result[0] = scope + sub_result[0]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
errors.push(*result)
|
|
108
|
+
else
|
|
109
|
+
for_checking = dig(data, rule.path)
|
|
110
|
+
|
|
111
|
+
if DIG_MISS.equal?(for_checking)
|
|
112
|
+
errors << [scope + rule.path, :missing] if rule.type == :required
|
|
113
|
+
else
|
|
114
|
+
result = rule.validator.call(for_checking, data, errors, self)
|
|
115
|
+
|
|
116
|
+
next if result == true
|
|
117
|
+
|
|
118
|
+
errors << [scope + rule.path, result || :format]
|
|
119
|
+
end
|
|
99
120
|
end
|
|
100
121
|
end
|
|
101
122
|
|
|
@@ -121,98 +142,39 @@ module Karafka
|
|
|
121
142
|
|
|
122
143
|
private
|
|
123
144
|
|
|
124
|
-
# Runs validation for rules on fields that are required and adds errors (if any) to the
|
|
125
|
-
# errors array
|
|
126
|
-
#
|
|
127
|
-
# @param data [Hash] input hash
|
|
128
|
-
# @param rule [Rule] validation rule
|
|
129
|
-
# @param errors [Array] array with errors from previous rules (if any)
|
|
130
|
-
# @param scope [Array<String>]
|
|
131
|
-
def validate_required(data, rule, errors, scope)
|
|
132
|
-
for_checking = dig(data, rule.path)
|
|
133
|
-
|
|
134
|
-
# We need to compare `DIG_MISS` against stuff because of the ownership of the `#==` method
|
|
135
|
-
if for_checking == DIG_MISS
|
|
136
|
-
errors << [scope + rule.path, :missing]
|
|
137
|
-
else
|
|
138
|
-
result = rule.validator.call(for_checking, data, errors, self)
|
|
139
|
-
|
|
140
|
-
return if result == true
|
|
141
|
-
|
|
142
|
-
errors << [scope + rule.path, result || :format]
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# Runs validation for rules on fields that are optional and adds errors (if any) to the
|
|
147
|
-
# errors array
|
|
148
|
-
#
|
|
149
|
-
# @param data [Hash] input hash
|
|
150
|
-
# @param rule [Rule] validation rule
|
|
151
|
-
# @param errors [Array] array with errors from previous rules (if any)
|
|
152
|
-
# @param scope [Array<String>]
|
|
153
|
-
def validate_optional(data, rule, errors, scope)
|
|
154
|
-
for_checking = dig(data, rule.path)
|
|
155
|
-
|
|
156
|
-
return if for_checking == DIG_MISS
|
|
157
|
-
|
|
158
|
-
result = rule.validator.call(for_checking, data, errors, self)
|
|
159
|
-
|
|
160
|
-
return if result == true
|
|
161
|
-
|
|
162
|
-
errors << [scope + rule.path, result || :format]
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# Runs validation for rules on virtual fields (aggregates, etc) and adds errors (if any) to
|
|
166
|
-
# the errors array
|
|
167
|
-
#
|
|
168
|
-
# @param data [Hash] input hash
|
|
169
|
-
# @param rule [Rule] validation rule
|
|
170
|
-
# @param errors [Array] array with errors from previous rules (if any)
|
|
171
|
-
# @param scope [Array<String>]
|
|
172
|
-
def validate_virtual(data, rule, errors, scope)
|
|
173
|
-
result = rule.validator.call(data, errors, self)
|
|
174
|
-
|
|
175
|
-
return if result == true
|
|
176
|
-
|
|
177
|
-
result&.each do |sub_result|
|
|
178
|
-
sub_result[0] = scope + sub_result[0]
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
errors.push(*result)
|
|
182
|
-
end
|
|
183
|
-
|
|
184
145
|
# Tries to dig for a given key in a hash and returns it with indication whether or not it
|
|
185
146
|
# was possible to find it (dig returns nil and we don't know if it wasn't the digged key
|
|
186
147
|
# value)
|
|
187
148
|
#
|
|
149
|
+
# Uses `Hash#fetch` with the `DIG_MISS` sentinel as the default, which resolves presence
|
|
150
|
+
# and value in a single hash lookup instead of a `key?` check followed by `[]`. This
|
|
151
|
+
# runs per rule per validation, including the per-message validations in WaterDrop,
|
|
152
|
+
# hence the lookup count matters. `fetch` with a default ignores `default_proc`, same
|
|
153
|
+
# as the previous `key?` based logic.
|
|
154
|
+
#
|
|
188
155
|
# @param data [Hash]
|
|
189
156
|
# @param keys [Array<Symbol>]
|
|
190
157
|
# @return [DIG_MISS, Object] found element or DIGG_MISS indicating that not found
|
|
191
158
|
def dig(data, keys)
|
|
192
159
|
case keys.length
|
|
193
160
|
when 1
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return DIG_MISS unless data.key?(key)
|
|
197
|
-
|
|
198
|
-
data[key]
|
|
161
|
+
data.fetch(keys[0], DIG_MISS)
|
|
199
162
|
when 2
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return DIG_MISS unless data.key?(key1)
|
|
163
|
+
mid = data.fetch(keys[0], DIG_MISS)
|
|
203
164
|
|
|
204
|
-
|
|
165
|
+
return DIG_MISS if DIG_MISS.equal?(mid)
|
|
166
|
+
return DIG_MISS unless mid.is_a?(Hash)
|
|
205
167
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
mid[keys[1]]
|
|
168
|
+
mid.fetch(keys[1], DIG_MISS)
|
|
209
169
|
else
|
|
210
170
|
current = data
|
|
211
171
|
|
|
212
172
|
keys.each do |nesting|
|
|
213
|
-
return DIG_MISS unless current.
|
|
173
|
+
return DIG_MISS unless current.is_a?(Hash)
|
|
174
|
+
|
|
175
|
+
current = current.fetch(nesting, DIG_MISS)
|
|
214
176
|
|
|
215
|
-
|
|
177
|
+
return DIG_MISS if DIG_MISS.equal?(current)
|
|
216
178
|
end
|
|
217
179
|
|
|
218
180
|
current
|
|
@@ -28,7 +28,15 @@ module Karafka
|
|
|
28
28
|
# @param event_id [String, Symbol] event id
|
|
29
29
|
# @param payload [Hash]
|
|
30
30
|
def instrument(event_id, payload = EMPTY_HASH, &)
|
|
31
|
-
|
|
31
|
+
# With no namespace, string event ids already are the full event names. This is the
|
|
32
|
+
# case for all the events in the Karafka ecosystem, so we can skip the mapping hash
|
|
33
|
+
# lookup on this hot path. Symbols still go through the mapping to be converted into
|
|
34
|
+
# strings without allocating on each call.
|
|
35
|
+
full_event_name = if @namespace.nil? && event_id.is_a?(String)
|
|
36
|
+
event_id
|
|
37
|
+
else
|
|
38
|
+
@mapped_events[event_id] ||= [event_id, @namespace].compact.join(".")
|
|
39
|
+
end
|
|
32
40
|
|
|
33
41
|
@notifications_bus.instrument(full_event_name, payload, &)
|
|
34
42
|
end
|
data/lib/karafka/core/version.rb
CHANGED
data/renovate.json
CHANGED