rsmp 0.51.4 → 0.52.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 653475280335ad2b867657cada46ef95d9cb61808cb12f3302762b0e2bcda790
4
- data.tar.gz: 2d8b6cfe3c5b46623329cb1dbcb1a01544dd293ab93f5398b650fea8121d31fd
3
+ metadata.gz: cfba3255efe256eae701725584e74d80a7ba66fe94708ef9bdc76cbd678c7700
4
+ data.tar.gz: b0aebcd26383d1ed77fbcdd18351fecc0a7afcc962d582f17a3f488a53aaeb22
5
5
  SHA512:
6
- metadata.gz: 82018c56d70a3cfa59db7d60b30c172f324765a01be8d650906d64c92109d68c52d80f75efdf388fd8332a9fa5028bc0ee1bed51e4293a1ffa6c5f441bfec8db
7
- data.tar.gz: a1b83604f3b516da1dc0e8a3071cddd71cd4cadbc78f22406fe5ea0f73d21c06663ef1739de1666818d88ea85445cc6cc045ee024987fe2120a4ddc6e179b539
6
+ metadata.gz: f7796d54dbed77a9d230a4e49f3ddadd22b3833b29fa05c882f9d7a6c540ed64ae55745f5b8dbe38a9fe095fa49b45684f9ebaf0a954d9fa021d892f2bdc5cec
7
+ data.tar.gz: 5bf194b21059fd35d82e41093c68658585fe083cc061f5759e98d95df1f45e5fbd0958051cb3dbac8b771f915fc0511c056f72c887339f1b5df6c60f73d6a57f
data/CHANGELOG.md CHANGED
@@ -729,3 +729,9 @@ Initial release.
729
729
  ## 0.51.4
730
730
  - don't update subscription list until unsubscribe is acknowledged
731
731
 
732
+ ## 0.52.0
733
+ - allow the proxy's `collect:` option to accept a Proc that builds a collector or queue before the handshake
734
+ - add optional `predicate:` filtering to collectors, including status collectors
735
+ - add `start` and `stop` aliases to queues for use with proxy collection factories
736
+ - fix status collectors to honor `component:` and `responses:`; the misspelled `reponses:` option is no longer used
737
+
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rsmp (0.51.4)
4
+ rsmp (0.52.0)
5
5
  async (~> 2.45)
6
6
  colorize (~> 1.1)
7
7
  io-endpoint (~> 0.17)
@@ -112,6 +112,118 @@ result = collector.collect do |message|
112
112
  end
113
113
  ```
114
114
 
115
+ ### Message predicates
116
+
117
+ Use `predicate:` for conditions beyond the message type, direction, and component
118
+ handled by `RSMP::Filter`. The Proc receives a whole message after the filter
119
+ accepts it. Returning `false` or `nil` skips the message before collection
120
+ callbacks or state-item matching; earlier item matches remain intact.
121
+
122
+ ```ruby
123
+ collector = RSMP::Collector.new(
124
+ proxy,
125
+ filter: RSMP::Filter.new(type: 'StatusUpdate', ingoing: true, outgoing: false),
126
+ predicate: ->(message) { message.is_a?(RSMP::StatusUpdate) && message.attribute('sS').any? },
127
+ num: 1,
128
+ timeout: 5
129
+ )
130
+ ```
131
+
132
+ Predicates also work with `StatusCollector`. They do not suppress failures such
133
+ as a rejection of the initiating request or an invalid peer message. `Filter`
134
+ admits `MessageNotAck` even when a different message type is requested, so a
135
+ predicate that accesses type-specific attributes should check the message type.
136
+
137
+ ### Status collection
138
+
139
+ `StatusCollector` matches wanted status items within incoming messages. Both
140
+ `StatusUpdate` and `StatusResponse` are accepted by default. Set `responses: false`
141
+ to accept updates only, or `updates: false` to accept responses only.
142
+
143
+ ```ruby
144
+ want = [{ 'sCI' => 'S0001', 'n' => 'signalgroupstatus' }]
145
+ collector = RSMP::StatusCollector.new(
146
+ proxy, want, component: 'C1', responses: false, timeout: 5
147
+ )
148
+ ```
149
+
150
+ `component:` filters the message's `cId`; the wanted items describe entries in
151
+ `sS`, which do not have their own component ID. Collection completes when all
152
+ wanted items match, rather than after a fixed number of messages.
153
+
154
+ ### Collection during proxy setup
155
+
156
+ The proxy's `collect:` option accepts the existing collector options hash:
157
+
158
+ ```ruby
159
+ proxy_options = {
160
+ collect: {
161
+ filter: RSMP::Filter.new(type: 'StatusUpdate', ingoing: true, outgoing: false),
162
+ num: 1,
163
+ timeout: 5
164
+ }
165
+ }
166
+ ```
167
+
168
+ It also accepts a Proc that receives the new proxy and returns a collector or
169
+ queue. The proxy calls `start` on the returned object and exposes it through
170
+ `proxy.collector`. This starts receiving before the handshake can deliver
171
+ messages. Return an unstarted object; the factory runs before version negotiation
172
+ and should not rely on the proxy being fully initialized or connected.
173
+
174
+ ```ruby
175
+ proxy_options = {
176
+ collect: proc do |proxy|
177
+ RSMP::StatusCollector.new(
178
+ proxy,
179
+ [{ 'sCI' => 'S0001', 'n' => 'signalgroupstatus' }],
180
+ component: 'C1', responses: false, timeout: 5
181
+ )
182
+ end
183
+ }
184
+ ```
185
+
186
+ Pass these options when creating the proxy. Use `proxy.collector.wait` or
187
+ `wait!` for a collector, or `wait_for_message` or `wait_for_message!` for a queue.
188
+
189
+ ## Queues
190
+
191
+ `RSMP::Queue` retains accepted messages until consumed, including messages that
192
+ arrive before the caller starts waiting. Each wait returns one message, rather
193
+ than a collection. `start` and `stop` are aliases for `start_receiving` and
194
+ `stop_receiving`, so a queue can also be returned by a proxy's `collect:` factory.
195
+
196
+ ```ruby
197
+ queue = RSMP::Queue.new(proxy, filter: RSMP::Filter.new(type: 'StatusUpdate'))
198
+ queue.start
199
+ begin
200
+ result = queue.wait_for_message(timeout: 5)
201
+ puts result.value if result.success?
202
+ ensure
203
+ queue.stop
204
+ end
205
+ ```
206
+
207
+ A queue stays registered until stopped. Its `timeout:` applies to an individual
208
+ wait: expiry returns a failure with code `:timeout`, or raises
209
+ `RSMP::OperationError` through `wait_for_message!`.
210
+
211
+ An enclosing Async timeout raised while waiting in the queue is converted in
212
+ the same way, even when no queue timeout is supplied:
213
+
214
+ ```ruby
215
+ begin
216
+ message = Async::Task.current.with_timeout(5) { queue.wait_for_message! }
217
+ rescue RSMP::OperationError => error
218
+ raise unless error.failure.code == :timeout
219
+
220
+ warn 'The caller deadline expired'
221
+ end
222
+ ```
223
+
224
+ This example assumes the queue is already started. Both timeout sources use the
225
+ same failure code; `failure.cause` retains the original Async exception.
226
+
115
227
  ## Sending and collecting atomically
116
228
 
117
229
  High-level send methods follow the same non-bang/bang convention:
@@ -7,6 +7,9 @@ module RSMP
7
7
 
8
8
  attr_reader :messages, :m_id, :initiator
9
9
 
10
+ # :predicate optionally receives each message accepted by the filter. A falsey
11
+ # result skips the message before collection callbacks or item matching.
12
+ # Rejection and invalid-message failures are handled independently of it.
10
13
  def initialize(distributor, options = {})
11
14
  initialize_receiver distributor, filter: options[:filter]
12
15
  @options = {
@@ -17,6 +20,7 @@ module RSMP
17
20
  }.deep_merge(options)
18
21
  @timeout = options[:timeout]
19
22
  @num = options[:num]
23
+ @predicate = options[:predicate]
20
24
  @initiator = options[:initiator]
21
25
  @m_id = options[:m_id] || @initiator&.attributes&.dig('mId')
22
26
  make_title(options[:title])
@@ -131,6 +135,8 @@ module RSMP
131
135
  def perform_match?(message)
132
136
  return false if reject_not_ack?(message)
133
137
  return false unless acceptable?(message)
138
+ return false if @predicate && !@predicate.call(message)
139
+ return false unless active?
134
140
 
135
141
  if @block
136
142
  status = Array(@block.call(message))
@@ -7,6 +7,10 @@ module RSMP
7
7
 
8
8
  attr_reader :messages
9
9
 
10
+ # Allow queues to be started by a proxy's collect factory, like collectors.
11
+ alias start start_receiving
12
+ alias stop stop_receiving
13
+
10
14
  def initialize(distributor, filter: nil)
11
15
  initialize_receiver distributor, filter: filter
12
16
  @condition = Async::Notification.new
@@ -4,11 +4,11 @@ module RSMP
4
4
  def initialize(proxy, want, options = {})
5
5
  type = []
6
6
  type << 'StatusUpdate' unless options[:updates] == false
7
- type << 'StatusResponse' unless options[:reponses] == false
7
+ type << 'StatusResponse' unless options[:responses] == false
8
8
 
9
9
  super(proxy, want, options.merge(
10
10
  title: 'status response',
11
- filter: RSMP::Filter.new(ingoing: true, outgoing: false, type: type)
11
+ filter: RSMP::Filter.new(ingoing: true, outgoing: false, type: type, component: options[:component])
12
12
  ))
13
13
  end
14
14
 
@@ -106,9 +106,15 @@ module RSMP
106
106
  @rejected_sxls = []
107
107
  @receive_alarms = true
108
108
  @site_settings = nil # can't pick until we know the site id
109
- return unless options[:collect]
109
+ collect = options[:collect]
110
+ return unless collect
111
+
112
+ @collector = if collect.is_a?(Proc)
113
+ collect.call(self)
114
+ else
115
+ RSMP::Collector.new self, collect
116
+ end
110
117
 
111
- @collector = RSMP::Collector.new self, options[:collect]
112
118
  @collector.start
113
119
  end
114
120
 
data/lib/rsmp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module RSMP
2
- VERSION = '0.51.4'.freeze
2
+ VERSION = '0.52.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rsmp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.51.4
4
+ version: 0.52.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emil Tin