karafka 2.1.12 → 2.2.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 (71) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +41 -0
  4. data/Gemfile.lock +1 -1
  5. data/bin/record_rss +50 -0
  6. data/config/locales/errors.yml +4 -0
  7. data/config/locales/pro_errors.yml +17 -0
  8. data/lib/karafka/admin.rb +21 -33
  9. data/lib/karafka/connection/client.rb +1 -1
  10. data/lib/karafka/contracts/config.rb +24 -0
  11. data/lib/karafka/errors.rb +3 -0
  12. data/lib/karafka/instrumentation/vendors/datadog/logger_listener.rb +5 -2
  13. data/lib/karafka/messages/builders/message.rb +8 -4
  14. data/lib/karafka/pro/active_job/consumer.rb +1 -1
  15. data/lib/karafka/pro/cleaner/errors.rb +27 -0
  16. data/lib/karafka/pro/cleaner/messages/message.rb +46 -0
  17. data/lib/karafka/pro/cleaner/messages/messages.rb +42 -0
  18. data/lib/karafka/pro/cleaner.rb +41 -0
  19. data/lib/karafka/pro/contracts/base.rb +23 -0
  20. data/lib/karafka/pro/contracts/server_cli_options.rb +111 -0
  21. data/lib/karafka/pro/encryption/errors.rb +4 -1
  22. data/lib/karafka/pro/loader.rb +6 -2
  23. data/lib/karafka/pro/processing/strategies/dlq/default.rb +6 -0
  24. data/lib/karafka/pro/routing/features/active_job/builder.rb +45 -0
  25. data/lib/karafka/pro/routing/features/active_job.rb +26 -0
  26. data/lib/karafka/pro/routing/features/dead_letter_queue/contracts/topic.rb +53 -0
  27. data/lib/karafka/pro/routing/features/delaying/contracts/topic.rb +41 -0
  28. data/lib/karafka/pro/routing/features/expiring/contracts/topic.rb +41 -0
  29. data/lib/karafka/pro/routing/features/filtering/contracts/topic.rb +44 -0
  30. data/lib/karafka/pro/routing/features/long_running_job/{contract.rb → contracts/topic.rb} +14 -11
  31. data/lib/karafka/pro/routing/features/{filtering/contract.rb → patterns/builder.rb} +13 -16
  32. data/lib/karafka/pro/routing/features/patterns/config.rb +54 -0
  33. data/lib/karafka/pro/routing/features/patterns/consumer_group.rb +68 -0
  34. data/lib/karafka/pro/routing/features/patterns/contracts/consumer_group.rb +62 -0
  35. data/lib/karafka/pro/routing/features/patterns/contracts/pattern.rb +46 -0
  36. data/lib/karafka/pro/routing/features/patterns/contracts/topic.rb +41 -0
  37. data/lib/karafka/pro/routing/features/patterns/detector.rb +68 -0
  38. data/lib/karafka/pro/routing/features/patterns/pattern.rb +81 -0
  39. data/lib/karafka/pro/routing/features/{delaying/contract.rb → patterns/patterns.rb} +11 -14
  40. data/lib/karafka/pro/routing/features/patterns/topic.rb +50 -0
  41. data/lib/karafka/pro/routing/features/patterns/topics.rb +53 -0
  42. data/lib/karafka/pro/routing/features/patterns.rb +33 -0
  43. data/lib/karafka/pro/routing/features/pausing/contracts/topic.rb +51 -0
  44. data/lib/karafka/pro/routing/features/throttling/contracts/topic.rb +44 -0
  45. data/lib/karafka/pro/routing/features/virtual_partitions/contracts/topic.rb +55 -0
  46. data/lib/karafka/routing/consumer_group.rb +1 -1
  47. data/lib/karafka/routing/features/active_job/contracts/topic.rb +44 -0
  48. data/lib/karafka/routing/features/active_job/proxy.rb +14 -0
  49. data/lib/karafka/routing/features/base/expander.rb +8 -2
  50. data/lib/karafka/routing/features/base.rb +4 -2
  51. data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +46 -0
  52. data/lib/karafka/routing/features/declaratives/contracts/topic.rb +33 -0
  53. data/lib/karafka/routing/features/manual_offset_management/contracts/topic.rb +27 -0
  54. data/lib/karafka/routing/router.rb +0 -11
  55. data/lib/karafka/routing/subscription_group.rb +9 -0
  56. data/lib/karafka/routing/topic.rb +5 -0
  57. data/lib/karafka/server.rb +9 -4
  58. data/lib/karafka/setup/config.rb +45 -0
  59. data/lib/karafka/version.rb +1 -1
  60. data.tar.gz.sig +0 -0
  61. metadata +37 -15
  62. metadata.gz.sig +0 -0
  63. data/lib/karafka/pro/routing/features/dead_letter_queue/contract.rb +0 -50
  64. data/lib/karafka/pro/routing/features/expiring/contract.rb +0 -38
  65. data/lib/karafka/pro/routing/features/pausing/contract.rb +0 -48
  66. data/lib/karafka/pro/routing/features/throttling/contract.rb +0 -41
  67. data/lib/karafka/pro/routing/features/virtual_partitions/contract.rb +0 -52
  68. data/lib/karafka/routing/features/active_job/contract.rb +0 -41
  69. data/lib/karafka/routing/features/dead_letter_queue/contract.rb +0 -42
  70. data/lib/karafka/routing/features/declaratives/contract.rb +0 -30
  71. data/lib/karafka/routing/features/manual_offset_management/contract.rb +0 -24
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Contracts
17
+ # Contract for validating correctness of the server cli command options.
18
+ # It differs slightly from the OSS one because it is aware of the routing patterns
19
+ class ServerCliOptions < ::Karafka::Contracts::ServerCliOptions
20
+ configure do |config|
21
+ config.error_messages = YAML.safe_load(
22
+ File.read(
23
+ File.join(Karafka.gem_root, 'config', 'locales', 'errors.yml')
24
+ )
25
+ ).fetch('en').fetch('validations').fetch('server_cli_options')
26
+ end
27
+
28
+ %i[
29
+ include
30
+ exclude
31
+ ].each do |action|
32
+ optional(:"#{action}_consumer_groups") { |cg| cg.is_a?(Array) }
33
+ optional(:"#{action}_subscription_groups") { |sg| sg.is_a?(Array) }
34
+ optional(:"#{action}_topics") { |topics| topics.is_a?(Array) }
35
+
36
+ virtual do |data, errors|
37
+ next unless errors.empty?
38
+
39
+ value = data.fetch(:"#{action}_consumer_groups")
40
+
41
+ # If there were no consumer_groups declared in the server cli, it means that we will
42
+ # run all of them and no need to validate them here at all
43
+ next if value.empty?
44
+ next if (value - Karafka::App.consumer_groups.map(&:name)).empty?
45
+
46
+ # Found unknown consumer groups
47
+ [[[:"#{action}_consumer_groups"], :consumer_groups_inclusion]]
48
+ end
49
+
50
+ virtual do |data, errors|
51
+ next unless errors.empty?
52
+
53
+ value = data.fetch(:"#{action}_subscription_groups")
54
+
55
+ # If there were no subscription_groups declared in the server cli, it means that we
56
+ # will run all of them and no need to validate them here at all
57
+ next if value.empty?
58
+
59
+ subscription_groups = Karafka::App
60
+ .consumer_groups
61
+ .map(&:subscription_groups)
62
+ .flatten
63
+ .map(&:name)
64
+
65
+ next if (value - subscription_groups).empty?
66
+
67
+ # Found unknown subscription groups
68
+ [[[:"#{action}_subscription_groups"], :subscription_groups_inclusion]]
69
+ end
70
+
71
+ virtual do |data, errors|
72
+ next unless errors.empty?
73
+
74
+ value = data.fetch(:"#{action}_topics")
75
+
76
+ # If there were no topics declared in the server cli, it means that we will
77
+ # run all of them and no need to validate them here at all
78
+ next if value.empty?
79
+
80
+ topics = Karafka::App
81
+ .consumer_groups
82
+ .map(&:subscription_groups)
83
+ .flatten
84
+ .map(&:topics)
85
+ .map { |gtopics| gtopics.map(&:name) }
86
+ .flatten
87
+
88
+ next if (value - topics).empty?
89
+
90
+ # If there are any patterns defined, we cannot report on topics inclusions because
91
+ # topics may be added during boot or runtime. We go with simple assumption:
92
+ # if there are patterns defined, we do not check the inclusions at all
93
+ next unless Karafka::App.consumer_groups.map(&:patterns).flatten.empty?
94
+
95
+ # Found unknown topics
96
+ [[[:"#{action}_topics"], :topics_inclusion]]
97
+ end
98
+ end
99
+
100
+ # Makes sure we have anything to subscribe to when we start the server
101
+ virtual do |_, errors|
102
+ next unless errors.empty?
103
+
104
+ next unless Karafka::App.subscription_groups.empty?
105
+
106
+ [[%i[include_topics], :topics_missing]]
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -16,8 +16,11 @@ module Karafka
16
16
  module Encryption
17
17
  # Encryption related errors
18
18
  module Errors
19
+ # Base for all the encryption errors
20
+ BaseError = Class.new(::Karafka::Errors::BaseError)
21
+
19
22
  # Raised when we have encountered encryption key with version we do not have
20
- PrivateKeyNotFound = Class.new(::Karafka::Errors::BaseError)
23
+ PrivateKeyNotFound = Class.new(BaseError)
21
24
  end
22
25
  end
23
26
  end
@@ -66,10 +66,12 @@ module Karafka
66
66
 
67
67
  private
68
68
 
69
- # @return [Array<Module>] extra non-routing related pro features
69
+ # @return [Array<Module>] extra non-routing related pro features and routing components
70
+ # that need to have some special configuration stuff injected into config, etc
70
71
  def features
71
72
  [
72
- Encryption
73
+ Encryption,
74
+ Cleaner
73
75
  ]
74
76
  end
75
77
 
@@ -78,6 +80,8 @@ module Karafka
78
80
  def reconfigure(config)
79
81
  icfg = config.internal
80
82
 
83
+ icfg.cli.contract = Contracts::ServerCliOptions.new
84
+
81
85
  icfg.processing.coordinator_class = Processing::Coordinator
82
86
  icfg.processing.partitioner_class = Processing::Partitioner
83
87
  icfg.processing.scheduler = Processing::Scheduler.new
@@ -74,6 +74,12 @@ module Karafka
74
74
  # @param skippable_message [Array<Karafka::Messages::Message>] message we want to
75
75
  # dispatch to DLQ
76
76
  def dispatch_to_dlq(skippable_message)
77
+ # DLQ should never try to dispatch a message that was cleaned. It message was
78
+ # cleaned, we will not have all the needed data. If you see this error, it means
79
+ # that your processing flow is not as expected and you have cleaned message that
80
+ # should not be cleaned as it should go to the DLQ
81
+ raise(Cleaner::Errors::MessageCleanedError) if skippable_message.cleaned?
82
+
77
83
  producer.produce_async(
78
84
  build_dlq_message(
79
85
  skippable_message
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class ActiveJob < Base
19
+ # Pro ActiveJob builder expansions
20
+ module Builder
21
+ # This method simplifies routes definition for ActiveJob patterns / queues by
22
+ # auto-injecting the consumer class and other things needed
23
+ #
24
+ # @param regexp_or_name [String, Symbol, Regexp] pattern name or regexp to use
25
+ # auto-generated regexp names
26
+ # @param regexp [Regexp, nil] activejob regexp pattern or nil when regexp is provided
27
+ # as the first argument
28
+ # @param block [Proc] block that we can use for some extra configuration
29
+ def active_job_pattern(regexp_or_name, regexp = nil, &block)
30
+ pattern(regexp_or_name, regexp) do
31
+ consumer App.config.internal.active_job.consumer_class
32
+ active_job true
33
+ manual_offset_management true
34
+
35
+ next unless block
36
+
37
+ instance_eval(&block)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ # Small extension to make ActiveJob work with pattern matching.
19
+ # Since our `#active_job_topic` is just a topic wrapper, we can introduce a similar
20
+ # `#active_job_pattern` to align with pattern building
21
+ class ActiveJob < Base
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class DeadLetterQueue < Base
19
+ # Namespace for DLQ contracts
20
+ module Contracts
21
+ # Extended rules for dead letter queue settings
22
+ class Topic < Karafka::Contracts::Base
23
+ configure do |config|
24
+ config.error_messages = YAML.safe_load(
25
+ File.read(
26
+ File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
27
+ )
28
+ ).fetch('en').fetch('validations').fetch('topic')
29
+ end
30
+
31
+ # Make sure that when we use virtual partitions with DLQ, at least one retry is set
32
+ # We cannot use VP with DLQ without retries as we in order to provide ordering
33
+ # warranties on errors with VP, we need to collapse the VPs concurrency and retry
34
+ # without any indeterministic work
35
+ virtual do |data, errors|
36
+ next unless errors.empty?
37
+
38
+ dead_letter_queue = data[:dead_letter_queue]
39
+ virtual_partitions = data[:virtual_partitions]
40
+
41
+ next unless dead_letter_queue[:active]
42
+ next unless virtual_partitions[:active]
43
+ next if dead_letter_queue[:max_retries].positive?
44
+
45
+ [[%i[dead_letter_queue], :with_virtual_partitions]]
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class Delaying < Base
19
+ # Namespace for delaying feature contracts
20
+ module Contracts
21
+ # Contract to validate configuration of the expiring feature
22
+ class Topic < Karafka::Contracts::Base
23
+ configure do |config|
24
+ config.error_messages = YAML.safe_load(
25
+ File.read(
26
+ File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
27
+ )
28
+ ).fetch('en').fetch('validations').fetch('topic')
29
+ end
30
+
31
+ nested(:delaying) do
32
+ required(:active) { |val| [true, false].include?(val) }
33
+ required(:delay) { |val| val.nil? || (val.is_a?(Integer) && val.positive?) }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class Expiring < Base
19
+ # Namespace for expiring messages contracts
20
+ module Contracts
21
+ # Contract to validate configuration of the expiring feature
22
+ class Topic < Karafka::Contracts::Base
23
+ configure do |config|
24
+ config.error_messages = YAML.safe_load(
25
+ File.read(
26
+ File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
27
+ )
28
+ ).fetch('en').fetch('validations').fetch('topic')
29
+ end
30
+
31
+ nested(:expiring) do
32
+ required(:active) { |val| [true, false].include?(val) }
33
+ required(:ttl) { |val| val.nil? || (val.is_a?(Integer) && val.positive?) }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class Filtering < Base
19
+ # Namespace for filtering feature contracts
20
+ module Contracts
21
+ # Contract to validate configuration of the filtering feature
22
+ class Topic < Karafka::Contracts::Base
23
+ configure do |config|
24
+ config.error_messages = YAML.safe_load(
25
+ File.read(
26
+ File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
27
+ )
28
+ ).fetch('en').fetch('validations').fetch('topic')
29
+ end
30
+
31
+ nested(:filtering) do
32
+ required(:active) { |val| [true, false].include?(val) }
33
+
34
+ required(:factories) do |val|
35
+ val.is_a?(Array) && val.all? { |factory| factory.respond_to?(:call) }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -16,18 +16,21 @@ module Karafka
16
16
  module Routing
17
17
  module Features
18
18
  class LongRunningJob < Base
19
- # Rules around long-running job settings
20
- class Contract < Contracts::Base
21
- configure do |config|
22
- config.error_messages = YAML.safe_load(
23
- File.read(
24
- File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
25
- )
26
- ).fetch('en').fetch('validations').fetch('topic')
27
- end
19
+ # Namespace for LRJ contracts
20
+ module Contracts
21
+ # Rules around long-running job settings
22
+ class Topic < Karafka::Contracts::Base
23
+ configure do |config|
24
+ config.error_messages = YAML.safe_load(
25
+ File.read(
26
+ File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
27
+ )
28
+ ).fetch('en').fetch('validations').fetch('topic')
29
+ end
28
30
 
29
- nested(:long_running_job) do
30
- required(:active) { |val| [true, false].include?(val) }
31
+ nested(:long_running_job) do
32
+ required(:active) { |val| [true, false].include?(val) }
33
+ end
31
34
  end
32
35
  end
33
36
  end
@@ -15,22 +15,19 @@ module Karafka
15
15
  module Pro
16
16
  module Routing
17
17
  module Features
18
- class Filtering < Base
19
- # Contract to validate configuration of the filtering feature
20
- class Contract < Contracts::Base
21
- configure do |config|
22
- config.error_messages = YAML.safe_load(
23
- File.read(
24
- File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
25
- )
26
- ).fetch('en').fetch('validations').fetch('topic')
27
- end
28
-
29
- nested(:filtering) do
30
- required(:active) { |val| [true, false].include?(val) }
31
-
32
- required(:factories) do |val|
33
- val.is_a?(Array) && val.all? { |factory| factory.respond_to?(:call) }
18
+ class Patterns < Base
19
+ # Expansions for the routing builder
20
+ module Builder
21
+ # Allows us to define the simple routing pattern matching
22
+ #
23
+ # @param regexp_or_name [Symbol, String, Regexp] name of the pattern or regexp for
24
+ # automatic-based named patterns
25
+ # @param regexp [Regexp, nil] nil if we use auto-generated name based on the regexp or
26
+ # the regexp if we used named patterns
27
+ # @param block [Proc]
28
+ def pattern(regexp_or_name, regexp = nil, &block)
29
+ consumer_group('app') do
30
+ pattern(regexp_or_name, regexp, &block)
34
31
  end
35
32
  end
36
33
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ # Holds pattern info reference
19
+ # Type is set to:
20
+ # `:regular` - in case patterns are not used and topic is just a regular existing topic
21
+ # matched directly based on the name
22
+ # `:discovered` - in case it is a real topic on which we started to listed
23
+ # `:matcher` - represents a regular expression used by librdkafka
24
+ class Patterns < Base
25
+ # Config for pattern based topic
26
+ # Only pattern related topics are active in this context
27
+ Config = Struct.new(
28
+ :active,
29
+ :type,
30
+ :pattern,
31
+ keyword_init: true
32
+ ) do
33
+ alias_method :active?, :active
34
+
35
+ # @return [Boolean] is this a matcher topic
36
+ def matcher?
37
+ type == :matcher
38
+ end
39
+
40
+ # @return [Boolean] is this a discovered topic
41
+ def discovered?
42
+ type == :discovered
43
+ end
44
+
45
+ # @return [Boolean] is this a regular topic
46
+ def regular?
47
+ type == :regular
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class Patterns < Base
19
+ # Expansion of the consumer groups routing component to work with patterns
20
+ module ConsumerGroup
21
+ # @param args [Object] whatever consumer group accepts
22
+ def initialize(*args)
23
+ super
24
+ @patterns = Patterns.new([])
25
+ end
26
+
27
+ # @return [::Karafka::Pro::Routing::Features::Patterns::Patterns] created patterns
28
+ def patterns
29
+ @patterns
30
+ end
31
+
32
+ # Creates the pattern for topic matching with appropriate virtual topic
33
+ # @param regexp_or_name [Symbol, String, Regexp] name of the pattern or regexp for
34
+ # automatic-based named patterns
35
+ # @param regexp [Regexp, nil] nil if we use auto-generated name based on the regexp or
36
+ # the regexp if we used named patterns
37
+ # @param block [Proc] appropriate underlying topic settings
38
+ def pattern=(regexp_or_name, regexp = nil, &block)
39
+ # This code allows us to have a nice nameless (automatic-named) patterns that do not
40
+ # have to be explicitly named. However if someone wants to use names for exclusions
41
+ # it can be done by providing both
42
+ if regexp_or_name.is_a?(Regexp)
43
+ name = nil
44
+ regexp = regexp_or_name
45
+ else
46
+ name = regexp_or_name
47
+ end
48
+
49
+ pattern = Pattern.new(name, regexp, block)
50
+ virtual_topic = public_send(:topic=, pattern.name, &block)
51
+ # Indicate the nature of this topic (matcher)
52
+ virtual_topic.patterns(active: true, type: :matcher, pattern: pattern)
53
+ pattern.topic = virtual_topic
54
+ @patterns << pattern
55
+ end
56
+
57
+ # @return [Hash] consumer group with patterns injected
58
+ def to_h
59
+ super.merge(
60
+ patterns: patterns.map(&:to_h)
61
+ ).freeze
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class Patterns < Base
19
+ module Contracts
20
+ # Contract to validate configuration of the filtering feature
21
+ class ConsumerGroup < Karafka::Contracts::Base
22
+ configure do |config|
23
+ config.error_messages = YAML.safe_load(
24
+ File.read(
25
+ File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
26
+ )
27
+ ).fetch('en').fetch('validations').fetch('consumer_group')
28
+
29
+ required(:patterns) { |val| val.is_a?(Array) && val.all? { |el| el.is_a?(Hash) } }
30
+
31
+ virtual do |data, errors|
32
+ next unless errors.empty?
33
+
34
+ validator = Pattern.new
35
+
36
+ data[:patterns].each do |pattern|
37
+ validator.validate!(pattern)
38
+ end
39
+
40
+ nil
41
+ end
42
+
43
+ # Make sure, that there are no same regular expressions with different names
44
+ # in a single consumer group
45
+ virtual do |data, errors|
46
+ next unless errors.empty?
47
+
48
+ regexp_strings = data[:patterns].map { |pattern| pattern.fetch(:regexp_string) }
49
+
50
+ next if regexp_strings.empty?
51
+ next if regexp_strings.uniq.size == regexp_strings.size
52
+
53
+ [[%i[patterns], :regexps_not_unique]]
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end