surfliner-metadata_consumer 0.1.0.pre.alpha.6 → 0.1.0.pre.alpha.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25566009d8ccce03904d9020a0340511bbeb2fac08b5151c65705cd2d6117096
4
- data.tar.gz: 24136a3ca25672bf7fcc8670ea61f11a34c547b515022f92e46b440fe1d315cc
3
+ metadata.gz: f209e4cf680ce23da67c4caa19fd9448fed117ec03e38d54b50a382b1b6b72a0
4
+ data.tar.gz: 07a901ae1f7edda577c3cbd272aac1d68e7a31876ee536661b8586c55a0623d4
5
5
  SHA512:
6
- metadata.gz: fef5c2196922e8f5476422ffd83400bc17edc957b0822a323628dd9cd525440e231e1d7402b761c71894806282c2ad11c8c450eb6dc37a6a6e99dac7d3adc9c5
7
- data.tar.gz: 5014bdadb04910ca9888d8fe445342641462e33f6ec445333c4502dd322e428df5c5ecd01710018d7f905e80a889ce84815f7ff59b1d3908c00b405d5f02c048
6
+ metadata.gz: 5531263f0ddcac33d04d8ba55d9008b2e1562eda7288f49273631661038d1f61c858030bad5d6ec5dc8f51bd24f7e7ad34be5430eceba021ccfa21013771cece
7
+ data.tar.gz: 0f9d4c233b2b4be9410509772715c742e76717dbd5f124ca1b693965548d169f033734d262b67dfaa117246e6101a238f0b4894e20b5cfdfbc787c5865e18147
data/CHANGES.md CHANGED
@@ -1,3 +1,17 @@
1
+ # 0.1.0.pre.alpha.8 (2025-04-29)
2
+
3
+ - Support comparing configuration objects by value equality, so they can
4
+ be used as hash keys.
5
+
6
+ # 0.1.0.pre.alpha.7 (2025-04-28)
7
+
8
+ - Fix issue where `RABBITMQ_AWAIT_ON_CLOSE` environment variable could
9
+ not be omitted.
10
+ - Remove `:routing_key` from `Mq::QueueConfig`, to allow using multiple
11
+ routing keys with the same queue.
12
+ - Add `Mq::Router` for routing messages from a single queue to multiple
13
+ handlers by routing key.
14
+
1
15
  # 0.1.0.pre.alpha.6 (2025-04-25)
2
16
 
3
17
  - Update to Bunny 2.24
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- surfliner-metadata_consumer (0.1.0.pre.alpha.6)
4
+ surfliner-metadata_consumer (0.1.0.pre.alpha.8)
5
5
  bunny (~> 2.24)
6
6
  opentelemetry-exporter-otlp (~> 0.26.3)
7
7
  opentelemetry-instrumentation-all (~> 0.60.0)
@@ -294,7 +294,7 @@ GEM
294
294
  diff-lcs (>= 1.2.0, < 2.0)
295
295
  rspec-support (~> 3.13.0)
296
296
  rspec-support (3.13.2)
297
- rubocop (1.75.3)
297
+ rubocop (1.75.4)
298
298
  json (~> 2.3)
299
299
  language_server-protocol (~> 3.17.0.2)
300
300
  lint_roller (~> 1.1.0)
data/README.md CHANGED
@@ -71,6 +71,13 @@ connection.with_topic(topic_config) do |topic|
71
71
  end
72
72
  ```
73
73
 
74
+ `TopicConfig#publish` can either accept an explicit routing key, or read a default value
75
+ from the environment:
76
+
77
+ | Variable | Sample value | Description |
78
+ |---------------------------------|-------------------------------|----------------------|
79
+ | `RABBITMQ_PLATFORM_ROUTING_KEY` | `surfliner.metadata.daylight` | RabbitMQ routing key |
80
+
74
81
  Similarly, `Mq::Topic#bind_queue` can either take an explicit `QueueConfig`, or implicitly
75
82
  read a default with `QueConfig#from_env`. `QueueConfig#from_env` expects the following environment
76
83
  variables:
@@ -78,14 +85,13 @@ variables:
78
85
  | Variable | Sample value | Description |
79
86
  |---------------------------------|-------------------------------|----------------------|
80
87
  | `RABBITMQ_QUEUE` | `surfliner.metadata` | RabbitMQ queue name |
81
- | `RABBITMQ_PLATFORM_ROUTING_KEY` | `surfliner.metadata.daylight` | RabbitMQ routing key |
82
88
 
83
89
  And `QueueConfig#from_env` similarly accepts keyword options, which are forwarded to
84
90
  [`Bunny::Channel#queue`](https://api.rubybunny.info/Bunny/Channel.html#queue-instance_method):
85
91
 
86
92
  ```ruby
87
93
  connection.with_topic(topic_config) do |topic|
88
- queue_config = QueueConfig.from_env(exclusive: true, durable: true)
94
+ queue_config = QueueConfig.from_env(exclusive: true, durable: false)
89
95
  queue = topic.bind_queue(queue_config)
90
96
  # ...
91
97
  end
@@ -2,6 +2,6 @@
2
2
  module Surfliner
3
3
  module MetadataConsumer
4
4
  # The gem version
5
- VERSION = "0.1.0.pre.alpha.6"
5
+ VERSION = "0.1.0.pre.alpha.8"
6
6
  end
7
7
  end
@@ -2,6 +2,7 @@ module Surfliner
2
2
  module Mq
3
3
  # An object encapsulating RabbitMQ configuration.
4
4
  class ConnectionConfig
5
+ # @return [Array<String>] values of RABBITMQ_AWAIT_ON_CLOSE interpreted as false
5
6
  FALSE_VALUES = ([""] + %w[0 off Off OFF f false False FALSE F n no No NO N]).freeze
6
7
 
7
8
  # @return [String] The RabbitMQ hostname
@@ -27,7 +28,7 @@ module Surfliner
27
28
  # @param port [String] RabbitMQ AMQP port
28
29
  # @param username [String] RabbitMQ username
29
30
  # @param password [String] RabbitMQ passsword
30
- # @param await_response_on_close [Boolean] whether the RabbitMQ client should wait for a response when closing the connection
31
+ # @param await_response_on_close [Boolean, String] whether the RabbitMQ client should wait for a response when closing the connection
31
32
  # @param opts [Hash] additional RabbitMQ conection options (see Bunny::Session#initialize)
32
33
  def initialize(host:, port:, username:, password:, await_response_on_close: true, **opts)
33
34
  @host = host
@@ -57,7 +58,7 @@ module Surfliner
57
58
  port: ENV.fetch("RABBITMQ_NODE_PORT_NUMBER"),
58
59
  username: ENV.fetch("RABBITMQ_USERNAME"),
59
60
  password: ENV.fetch("RABBITMQ_PASSWORD"),
60
- await_response_on_close: ENV.fetch("RABBITMQ_AWAIT_ON_CLOSE") || true,
61
+ await_response_on_close: ENV["RABBITMQ_AWAIT_ON_CLOSE"] || true,
61
62
  **opts
62
63
  )
63
64
  end
@@ -73,7 +74,34 @@ module Surfliner
73
74
  @redacted_url ||= session_url.sub(password, "REDACTED")
74
75
  end
75
76
 
76
- private
77
+ # Whether `other` represents the same configuration as this object.
78
+ # Note that if any attributes or connection options are modified, equality becomes unstable.
79
+ # @return [Boolean] true if `other` represents the same configuration as this object, false otherwise
80
+ def eql?(other)
81
+ self == other
82
+ end
83
+
84
+ # Whether `other` represents the same configuration as this object.
85
+ # Note that if any attributes or connection options are modified, equality becomes unstable.
86
+ # @return [Boolean] true if `other` represents the same configuration as this object, false otherwise
87
+ def ==(other)
88
+ return unless other.class == self.class
89
+ other.options == options
90
+ end
91
+
92
+ # The hash value of this object. Equal configurations will have equal hash values.
93
+ # Note that if any attributes or connection options are modified, the hash value becomes unstable.
94
+ # @return [Integer] a hash value suitable for using equal configs as hash keys
95
+ def hash
96
+ options.hash
97
+ end
98
+
99
+ protected
100
+
101
+ # @return [Hash] all keyword arguments and connection options, for purposes of equality checking
102
+ def options
103
+ {host:, port:, username:, password:, await_response_on_close:, opts:}
104
+ end
77
105
 
78
106
  def parse_boolean(v)
79
107
  !FALSE_VALUES.include?(v.to_s)
@@ -5,21 +5,40 @@ module Surfliner
5
5
  # @return [String] The queue to listen to
6
6
  attr_reader :name
7
7
 
8
- # @return [String] platform routing key to listen to
9
- attr_reader :routing_key
10
-
11
8
  # @return [Hash] RabbitMQ queue options. (See Bunny::Channel#queue)
12
9
  attr_reader :options
13
10
 
14
11
  # @param name [String] queue exchange to listen to
15
- # @param routing_key [String] platform routing key to listen to
16
12
  # @param options [Hash] RabbitMQ queue options. (See Bunny::Channel#queue)
17
- def initialize(name:, routing_key:, options: {})
13
+ def initialize(name:, options: {})
18
14
  @name = name
19
- @routing_key = routing_key
20
15
  @options = options
21
16
  end
22
17
 
18
+ # Whether `other` represents the same configuration as this object.
19
+ # Note that if `name` or `options` are modified, equality becomes unstable.
20
+ # @param other [Object, nil] the object to compare
21
+ # @return [Boolean] true if `other` represents the same configuration as this object, false otherwise
22
+ def eql?(other)
23
+ self == other
24
+ end
25
+
26
+ # Whether `other` represents the same configuration as this object.
27
+ # Note that if `name` or `options` are modified, equality becomes unstable.
28
+ # @param other [Object, nil] the object to compare
29
+ # @return [Boolean] true if `other` represents the same configuration as this object, false otherwise
30
+ def ==(other)
31
+ return unless other.class == self.class
32
+ other.name == name && other.options == options
33
+ end
34
+
35
+ # The hash value of this object. Equal configurations will have equal hash values.
36
+ # Note that if `name` or `options` are modified, the hash value becomes unstable.
37
+ # @return [Integer] a hash value suitable for using equal configs as hash keys
38
+ def hash
39
+ [name, options].hash
40
+ end
41
+
23
42
  class << self
24
43
  # Returns a default (environment-variable-based) configuration with the
25
44
  # specified options.
@@ -27,22 +46,15 @@ module Surfliner
27
46
  # | Variable | Sample value | Description |
28
47
  # |---------------------------------|-------------------------------|----------------------|
29
48
  # | `RABBITMQ_QUEUE` | `surfliner.metadata` | RabbitMQ queue name |
30
- # | `RABBITMQ_PLATFORM_ROUTING_KEY` | `surfliner.metadata.daylight` | RabbitMQ routing key |
31
49
  #
32
50
  # @param options [Hash] RabbitMQ queue options. (See Bunny::Channel#queue)
33
51
  # @return [QueueConfig] The configuration.
34
52
  def from_env(**options)
35
53
  QueueConfig.new(
36
54
  name: ENV.fetch("RABBITMQ_QUEUE"),
37
- routing_key: default_routing_key,
38
55
  options:
39
56
  )
40
57
  end
41
-
42
- # @return [String] The default routing key from `ENV["RABBITMQ_PLATFORM_ROUTING_KEY"]`
43
- def default_routing_key
44
- ENV.fetch("RABBITMQ_PLATFORM_ROUTING_KEY")
45
- end
46
58
  end
47
59
  end
48
60
  end
@@ -0,0 +1,78 @@
1
+ module Surfliner
2
+ module Mq
3
+ # Queue subscriber that routes messages by routing key to multiple handlers
4
+ class Router
5
+ # @return [Topic] the topic
6
+ attr_reader :topic
7
+
8
+ # @return [Bunny::Queue] the queue
9
+ attr_reader :queue
10
+
11
+ # @return [Bunny::Consumer] the consumer (used to unsubscribe)
12
+ attr_reader :consumer
13
+
14
+ # Initializes a new Router and subscribes it to the specified queue
15
+ # on the specified topic.
16
+ #
17
+ # @param topic [Mq::Topic] The topic to listen on
18
+ # @param queue_config [Mq::QueueConfig] The queue to subscribe to
19
+ def initialize(topic, queue_config: QueueConfig.from_env)
20
+ @topic = topic
21
+ @queue = topic.queue(queue_config)
22
+ @consumer = queue.subscribe(&method(:on_delivery))
23
+ end
24
+
25
+ # Adds a handler for the specified routing key.
26
+ # Handlers will receive only messages with the specified routing key.
27
+ # Multiple handler blocks can be added to the same queue and/or routing key.
28
+ #
29
+ # @param routing_key [String] the routing key to filter messages by
30
+ # @yieldparam payload_json [String] each payload
31
+ def add_handler(routing_key, &block)
32
+ raise "Can't add handlers after router shutdown" if consumer.nil?
33
+
34
+ log("adding handler #{block}")
35
+ handlers = handlers_for(routing_key)
36
+ queue.bind(topic.exchange, routing_key:) if handlers.empty?
37
+ handlers << block
38
+ end
39
+
40
+ # Removes all handlers and unsubscribes from the queue.
41
+ def shutdown
42
+ log("shutting down")
43
+ handlers_by_routing_key.clear
44
+ consumer.cancel
45
+ @consumer = nil
46
+ end
47
+
48
+ private
49
+
50
+ # Receives messages and routes them to the appropriate handler
51
+ #
52
+ # @param delivery_info [Bunny::DeliveryInfo] the delivery info
53
+ # @param _metadata [Bunny::MessageProperties] the message properties
54
+ # @param payload_json [String] the payload as a string
55
+ def on_delivery(delivery_info, _metadata, payload_json)
56
+ routing_key = delivery_info.routing_key
57
+ handlers_for(routing_key).each do |handler|
58
+ log("delivering #{payload_json} to handler #{handler} for #{routing_key}")
59
+ handler.call(payload_json)
60
+ end
61
+ end
62
+
63
+ # @return [Hash{String => Array<Proc>}] all handlers by routing key
64
+ def handlers_by_routing_key
65
+ @handlers_by_routing_key ||= {}
66
+ end
67
+
68
+ # @return [Array<Proc>] the handlers for the specified key, if any
69
+ def handlers_for(routing_key)
70
+ (handlers_by_routing_key[routing_key] ||= [])
71
+ end
72
+
73
+ def log(msg)
74
+ topic.logger.debug("#{self.class}(#{topic.name}, #{queue.name}): #{msg}")
75
+ end
76
+ end
77
+ end
78
+ end
@@ -38,17 +38,36 @@ module Surfliner
38
38
  # @param payload [String] the payload to publish
39
39
  # @param routing_key [String] platform routing key to publish to
40
40
  # @return [Bunny::Exchange] see #exchange
41
- def publish(payload, routing_key: QueueConfig.default_routing_key)
41
+ def publish(payload, routing_key: default_routing_key)
42
42
  logger.info "Publishing to #{routing_key} with payload: #{payload}"
43
43
  exchange.publish(payload, routing_key:)
44
44
  end
45
45
 
46
+ # Creates or looks up the specified queue and binds it to receive
47
+ # messages with the specified routing key.
46
48
  # @param config [QueueConfig] queue configuration
47
- # @return [Bunny::Queue] the queue
48
- def bind_queue(config = QueueConfig.from_env)
49
- channel.queue(config.name, config.options).tap do |q|
50
- q.bind(exchange, routing_key: config.routing_key)
51
- end
49
+ # @param routing_key [String] platform routing key to bind on
50
+ # @return [Bunny::Queue] the queue
51
+ def bind_queue(config = QueueConfig.from_env, routing_key: default_routing_key)
52
+ queue(config).tap { |q| q.bind(exchange, routing_key:) }
53
+ end
54
+
55
+ # Creates or looks up the specified queue.
56
+ # @param config [QueueConfig] queue configuration
57
+ # @return [Bunny::Queue] the queue
58
+ def queue(config)
59
+ channel.queue(config.name, config.options)
60
+ end
61
+
62
+ # Returns the default (environment-variable-based) routing key
63
+ #
64
+ # | Variable | Sample value | Description |
65
+ # |---------------------------------|-------------------------------|----------------------|
66
+ # | `RABBITMQ_PLATFORM_ROUTING_KEY` | `surfliner.metadata.daylight` | RabbitMQ routing key |
67
+ #
68
+ # @return [String, nil] the configured default routing key
69
+ def default_routing_key
70
+ ENV.fetch("RABBITMQ_PLATFORM_ROUTING_KEY")
52
71
  end
53
72
 
54
73
  class << self
@@ -15,6 +15,30 @@ module Surfliner
15
15
  @options = options
16
16
  end
17
17
 
18
+ # Whether `other` represents the same configuration as this object.
19
+ # Note that if `name` or `options` are modified, equality becomes unstable.
20
+ # @param other [Object, nil] the object to compare
21
+ # @return [Boolean] true if `other` represents the same configuration as this object, false otherwise
22
+ def eql?(other)
23
+ self == other
24
+ end
25
+
26
+ # Whether `other` represents the same configuration as this object.
27
+ # Note that if `name` or `options` are modified, equality becomes unstable.
28
+ # @param other [Object, nil] the object to compare
29
+ # @return [Boolean] true if `other` represents the same configuration as this object, false otherwise
30
+ def ==(other)
31
+ return unless other.class == self.class
32
+ other.name == name && other.options == options
33
+ end
34
+
35
+ # The hash value of this object. Equal configurations will have equal hash values.
36
+ # Note that if `name` or `options` are modified, the hash value becomes unstable.
37
+ # @return [Integer] a hash value suitable for using equal configs as hash keys
38
+ def hash
39
+ [name, options].hash
40
+ end
41
+
18
42
  class << self
19
43
  # Returns a default (environment-variable-based) configuration with the
20
44
  # specified options.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: surfliner-metadata_consumer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre.alpha.6
4
+ version: 0.1.0.pre.alpha.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Project Surfliner
@@ -295,6 +295,7 @@ files:
295
295
  - lib/surfliner/mq/connection.rb
296
296
  - lib/surfliner/mq/connection_config.rb
297
297
  - lib/surfliner/mq/queue_config.rb
298
+ - lib/surfliner/mq/router.rb
298
299
  - lib/surfliner/mq/topic.rb
299
300
  - lib/surfliner/mq/topic_config.rb
300
301
  - lib/surfliner/util.rb