rsmp 0.48.4 → 0.51.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 +6 -0
- data/Gemfile.lock +19 -20
- data/README.md +27 -3
- data/Rakefile +3 -1
- data/documentation/cli.md +79 -4
- data/documentation/collecting_message.md +90 -170
- data/documentation/message_distribution.md +17 -3
- data/documentation/tasks.md +65 -117
- data/lib/rsmp/cli.rb +93 -5
- data/lib/rsmp/collect/collector.rb +117 -134
- data/lib/rsmp/collect/distributor.rb +16 -3
- data/lib/rsmp/collect/queue.rb +14 -6
- data/lib/rsmp/collect/receiver.rb +1 -1
- data/lib/rsmp/collect/state_collector.rb +11 -2
- data/lib/rsmp/collection.rb +12 -0
- data/lib/rsmp/completion.rb +49 -0
- data/lib/rsmp/component/component_base.rb +3 -3
- data/lib/rsmp/convert/export/json_schema/index.rb +8 -3
- data/lib/rsmp/convert/import/yaml.rb +12 -8
- data/lib/rsmp/delivery.rb +18 -0
- data/lib/rsmp/event.rb +19 -0
- data/lib/rsmp/event_source.rb +36 -0
- data/lib/rsmp/exchange.rb +10 -0
- data/lib/rsmp/failure.rb +23 -0
- data/lib/rsmp/helpers/error.rb +14 -23
- data/lib/rsmp/message.rb +9 -7
- data/lib/rsmp/node/node.rb +3 -21
- data/lib/rsmp/node/site/connections.rb +4 -9
- data/lib/rsmp/node/site/site.rb +31 -16
- data/lib/rsmp/node/supervisor/modules/connection.rb +19 -5
- data/lib/rsmp/node/supervisor/modules/sites.rb +16 -5
- data/lib/rsmp/node/supervisor/supervisor.rb +13 -14
- data/lib/rsmp/node/task.rb +59 -53
- data/lib/rsmp/proxy/modules/acknowledgements.rb +22 -9
- data/lib/rsmp/proxy/modules/lifecycle.rb +134 -0
- data/lib/rsmp/proxy/modules/receive.rb +109 -66
- data/lib/rsmp/proxy/modules/send.rb +109 -51
- data/lib/rsmp/proxy/modules/state.rb +15 -5
- data/lib/rsmp/proxy/modules/tasks.rb +96 -70
- data/lib/rsmp/proxy/modules/versions.rb +8 -8
- data/lib/rsmp/proxy/modules/watchdogs.rb +15 -2
- data/lib/rsmp/proxy/proxy.rb +4 -66
- data/lib/rsmp/proxy/site/connection.rb +27 -22
- data/lib/rsmp/proxy/site/modules/aggregated_status.rb +18 -8
- data/lib/rsmp/proxy/site/modules/alarms.rb +45 -38
- data/lib/rsmp/proxy/site/modules/commands.rb +18 -8
- data/lib/rsmp/proxy/site/modules/status.rb +44 -19
- data/lib/rsmp/proxy/site/site_proxy.rb +12 -10
- data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +1 -1
- data/lib/rsmp/proxy/supervisor/modules/alarms.rb +1 -1
- data/lib/rsmp/proxy/supervisor/modules/commands.rb +1 -1
- data/lib/rsmp/proxy/supervisor/modules/message_buffer.rb +8 -9
- data/lib/rsmp/proxy/supervisor/modules/status.rb +1 -1
- data/lib/rsmp/proxy/supervisor/modules/status_updates.rb +1 -1
- data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +34 -30
- data/lib/rsmp/result.rb +95 -0
- data/lib/rsmp/schema/validation.rb +5 -3
- data/lib/rsmp/sxl/interface.rb +8 -1
- data/lib/rsmp/sxl/processing/catalogue.rb +61 -0
- data/lib/rsmp/sxl/processing/conflict_checker.rb +30 -0
- data/lib/rsmp/sxl/processing/dependency_graph.rb +37 -0
- data/lib/rsmp/sxl/processing/document.rb +158 -0
- data/lib/rsmp/sxl/processing/error.rb +8 -0
- data/lib/rsmp/sxl/processing/manifest.rb +144 -0
- data/lib/rsmp/sxl/processing/natural_sort.rb +22 -0
- data/lib/rsmp/sxl/processing/resolver.rb +94 -0
- data/lib/rsmp/sxl/processing/version_requirement.rb +86 -0
- data/lib/rsmp/sxl/processing.rb +18 -0
- data/lib/rsmp/sxl/supervisor_interface.rb +8 -1
- data/lib/rsmp/termination.rb +10 -0
- data/lib/rsmp/tlc/modules/plans.rb +0 -2
- data/lib/rsmp/tlc/proxy/control.rb +50 -16
- data/lib/rsmp/tlc/proxy/detectors.rb +15 -4
- data/lib/rsmp/tlc/proxy/io.rb +46 -31
- data/lib/rsmp/tlc/proxy/plans.rb +36 -19
- data/lib/rsmp/tlc/proxy/plans_bang.rb +44 -0
- data/lib/rsmp/tlc/proxy/status.rb +47 -23
- data/lib/rsmp/tlc/proxy/system.rb +16 -4
- data/lib/rsmp/tlc/supervisor_interface.rb +7 -1
- data/lib/rsmp/tlc/traffic_controller_site.rb +6 -9
- data/lib/rsmp/validation.rb +22 -0
- data/lib/rsmp/version.rb +1 -1
- data/lib/rsmp.rb +19 -4
- data/rsmp.gemspec +1 -1
- metadata +25 -4
- data/lib/rsmp/collect/collector/status.rb +0 -34
|
@@ -1,37 +1,25 @@
|
|
|
1
1
|
module RSMP
|
|
2
|
-
# Collects messages
|
|
3
|
-
# Can filter by message type, componet and direction.
|
|
4
|
-
# Wakes up the once the desired number of messages has been collected.
|
|
2
|
+
# Collects matching messages and resolves once with an immutable Result.
|
|
5
3
|
class Collector
|
|
6
4
|
include Receiver
|
|
7
|
-
include Status
|
|
8
5
|
include Reporting
|
|
9
6
|
include Logging
|
|
10
7
|
|
|
11
|
-
attr_reader :
|
|
8
|
+
attr_reader :messages, :m_id, :initiator
|
|
12
9
|
|
|
13
10
|
def initialize(distributor, options = {})
|
|
14
11
|
initialize_receiver distributor, filter: options[:filter]
|
|
15
12
|
@options = {
|
|
16
13
|
cancel: {
|
|
17
|
-
|
|
18
|
-
disconnect:
|
|
14
|
+
invalid_message: true,
|
|
15
|
+
disconnect: true
|
|
19
16
|
}
|
|
20
|
-
}.deep_merge
|
|
17
|
+
}.deep_merge(options)
|
|
21
18
|
@timeout = options[:timeout]
|
|
22
19
|
@num = options[:num]
|
|
23
20
|
@initiator = options[:initiator]
|
|
24
21
|
@m_id = options[:m_id] || @initiator&.attributes&.dig('mId')
|
|
25
|
-
|
|
26
|
-
make_title options[:title]
|
|
27
|
-
|
|
28
|
-
if task
|
|
29
|
-
@task = task
|
|
30
|
-
elsif distributor.respond_to? 'task'
|
|
31
|
-
# if distributor is a Proxy, or some other object that implements task(),
|
|
32
|
-
# then try to get the task that way
|
|
33
|
-
@task = distributor.task
|
|
34
|
-
end
|
|
22
|
+
make_title(options[:title])
|
|
35
23
|
reset
|
|
36
24
|
end
|
|
37
25
|
|
|
@@ -45,207 +33,202 @@ module RSMP
|
|
|
45
33
|
end
|
|
46
34
|
end
|
|
47
35
|
|
|
48
|
-
def use_task(task)
|
|
49
|
-
@task = task
|
|
50
|
-
end
|
|
51
|
-
|
|
52
36
|
def reset
|
|
53
37
|
@messages = []
|
|
54
|
-
@
|
|
55
|
-
@
|
|
38
|
+
@completion = Completion.new
|
|
39
|
+
@active = false
|
|
56
40
|
end
|
|
57
41
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"#<#{self.class.name}:#{object_id}, message:#{@messages}>"
|
|
42
|
+
def active?
|
|
43
|
+
@active
|
|
61
44
|
end
|
|
62
45
|
|
|
63
|
-
|
|
64
|
-
# return self, so this can be tucked on to calls that return a collector
|
|
65
|
-
def ok!
|
|
66
|
-
raise @error if @error
|
|
46
|
+
alias collecting? active?
|
|
67
47
|
|
|
68
|
-
|
|
48
|
+
def inspect
|
|
49
|
+
"#<#{self.class.name}:#{object_id}, message:#{@messages}>"
|
|
69
50
|
end
|
|
70
51
|
|
|
71
|
-
# Collect message
|
|
72
|
-
# Will return once all messages have been collected, or timeout is reached
|
|
73
52
|
def collect(&)
|
|
74
53
|
start(&)
|
|
75
54
|
wait
|
|
76
|
-
@status
|
|
77
55
|
ensure
|
|
78
|
-
|
|
56
|
+
stop
|
|
79
57
|
end
|
|
80
58
|
|
|
81
|
-
# Collect message
|
|
82
|
-
# Returns the collected messages, or raise an exception in case of a time out.
|
|
83
59
|
def collect!(&)
|
|
84
|
-
collect(&)
|
|
85
|
-
ok!
|
|
86
|
-
@messages
|
|
60
|
+
collect(&).value!.messages
|
|
87
61
|
end
|
|
88
62
|
|
|
89
|
-
#
|
|
90
|
-
#
|
|
63
|
+
# The configured timeout terminates this collection. Completion retains the
|
|
64
|
+
# result, so every current or future waiter observes the same value.
|
|
91
65
|
def wait
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
66
|
+
raise 'collector has not been started' unless active? || @completion.resolved?
|
|
67
|
+
|
|
68
|
+
observed = @completion.wait(timeout: @timeout)
|
|
69
|
+
if timeout_result?(observed)
|
|
70
|
+
finish_failure(observed.failure.with(message: describe_progress))
|
|
71
|
+
observed = @completion.wait
|
|
98
72
|
end
|
|
99
|
-
|
|
100
|
-
rescue Async::TimeoutError
|
|
101
|
-
@error = RSMP::TimeoutError.new describe_progress
|
|
102
|
-
@status = :timeout
|
|
73
|
+
observed
|
|
103
74
|
end
|
|
104
75
|
|
|
105
|
-
# If collection is not active, raise an error. Otherwise wait until
|
|
106
|
-
# the desired messages have been collected.
|
|
107
|
-
# If timeout is reached or collection was cancelled with an error, an exception is raised.
|
|
108
76
|
def wait!
|
|
109
|
-
wait
|
|
110
|
-
raise @error if timeout? || (cancelled? && @error)
|
|
111
|
-
|
|
112
|
-
@messages
|
|
77
|
+
wait.value!.messages
|
|
113
78
|
end
|
|
114
79
|
|
|
115
|
-
# Start collection and return immediately
|
|
116
|
-
# You can later use wait() to wait for completion
|
|
117
80
|
def start(&block)
|
|
118
|
-
raise
|
|
81
|
+
raise 'collector is already active' if active?
|
|
119
82
|
|
|
120
83
|
@block = block
|
|
121
|
-
raise ArgumentError, '
|
|
84
|
+
raise ArgumentError, 'num, timeout or block must be provided' unless @num || @timeout || @block
|
|
122
85
|
|
|
123
86
|
reset
|
|
124
|
-
@
|
|
87
|
+
@active = true
|
|
125
88
|
log_start
|
|
126
|
-
@distributor
|
|
89
|
+
@distributor.add_receiver(self)
|
|
90
|
+
self
|
|
127
91
|
end
|
|
128
92
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return unless @m_id
|
|
93
|
+
def stop
|
|
94
|
+
return self unless @active
|
|
132
95
|
|
|
133
|
-
|
|
134
|
-
|
|
96
|
+
@active = false
|
|
97
|
+
@distributor.remove_receiver(self)
|
|
98
|
+
self
|
|
99
|
+
end
|
|
135
100
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
101
|
+
# Check for a NotAck related to the initiating request.
|
|
102
|
+
def reject_not_ack?(message)
|
|
103
|
+
return false unless @m_id
|
|
104
|
+
return false unless message.is_a?(MessageNotAck)
|
|
105
|
+
return false unless message.attribute('oMId') == @m_id
|
|
106
|
+
|
|
107
|
+
m_id_short = RSMP::Message.shorten_m_id(@m_id, 8)
|
|
108
|
+
finish_failure(
|
|
109
|
+
Failure.new(
|
|
110
|
+
code: :message_rejected,
|
|
111
|
+
message: "#{@title} #{m_id_short} was rejected with '#{message.attribute('rea')}'",
|
|
112
|
+
source: :peer,
|
|
113
|
+
context: { message: message, original_message_id: @m_id }
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
@distributor.log "#{identifier}: rejected by a NotAck", level: :debug
|
|
139
117
|
true
|
|
140
118
|
end
|
|
141
119
|
|
|
142
|
-
# Handle message and return true if we're done collecting
|
|
143
120
|
def receive(message)
|
|
144
|
-
|
|
145
|
-
unless ready? || collecting?
|
|
146
|
-
raise "can't process message when status is :#{@status}, title: #{@title}, desc: #{describe}"
|
|
147
|
-
end
|
|
121
|
+
return unless active?
|
|
148
122
|
|
|
149
|
-
if perform_match
|
|
150
|
-
|
|
151
|
-
complete
|
|
152
|
-
else
|
|
153
|
-
incomplete
|
|
154
|
-
end
|
|
123
|
+
if perform_match?(message)
|
|
124
|
+
done? ? complete : incomplete
|
|
155
125
|
end
|
|
156
|
-
|
|
126
|
+
active?
|
|
157
127
|
end
|
|
158
128
|
|
|
159
129
|
def describe; end
|
|
160
130
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return false if reject_not_ack(message)
|
|
131
|
+
def perform_match?(message)
|
|
132
|
+
return false if reject_not_ack?(message)
|
|
164
133
|
return false unless acceptable?(message)
|
|
165
134
|
|
|
166
135
|
if @block
|
|
167
|
-
status =
|
|
168
|
-
return unless
|
|
136
|
+
status = Array(@block.call(message))
|
|
137
|
+
return false unless active?
|
|
169
138
|
|
|
170
|
-
keep
|
|
139
|
+
keep(message) if status.include?(:keep)
|
|
171
140
|
else
|
|
172
|
-
keep
|
|
141
|
+
keep(message)
|
|
173
142
|
end
|
|
143
|
+
true
|
|
174
144
|
end
|
|
175
145
|
|
|
176
|
-
# Have we collected the required number of messages?
|
|
177
146
|
def done?
|
|
178
147
|
@num && @messages.size >= @num
|
|
179
148
|
end
|
|
180
149
|
|
|
181
|
-
# Called when we're done collecting. Remove ourself as a receiver,
|
|
182
|
-
# se we don't receive message notifications anymore
|
|
183
150
|
def complete
|
|
184
|
-
|
|
185
|
-
do_stop
|
|
151
|
+
finish_success(build_collection)
|
|
186
152
|
log_complete
|
|
187
153
|
end
|
|
188
154
|
|
|
189
|
-
# called when we received a message, but are not done yet
|
|
190
155
|
def incomplete
|
|
191
156
|
log_incomplete
|
|
192
157
|
end
|
|
193
158
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
def do_stop
|
|
197
|
-
@distributor.remove_receiver self
|
|
198
|
-
@condition.signal
|
|
199
|
-
end
|
|
159
|
+
def receive_event(event)
|
|
160
|
+
return unless active?
|
|
200
161
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
when
|
|
205
|
-
|
|
206
|
-
when RSMP::DisconnectError
|
|
207
|
-
receive_disconnect error, options
|
|
162
|
+
case event.type
|
|
163
|
+
when :invalid_message
|
|
164
|
+
receive_invalid_message(event)
|
|
165
|
+
when :connection_ended
|
|
166
|
+
receive_connection_ended(event)
|
|
208
167
|
end
|
|
209
168
|
end
|
|
210
169
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
return unless
|
|
170
|
+
def receive_invalid_message(event)
|
|
171
|
+
return unless @options.dig(:cancel, :invalid_message)
|
|
172
|
+
return unless event.message
|
|
173
|
+
return unless acceptable?(event.message)
|
|
214
174
|
|
|
215
|
-
|
|
216
|
-
|
|
175
|
+
finish_failure(event.failure)
|
|
176
|
+
end
|
|
217
177
|
|
|
218
|
-
|
|
219
|
-
return unless @
|
|
178
|
+
def receive_connection_ended(event)
|
|
179
|
+
return unless @options.dig(:cancel, :disconnect)
|
|
220
180
|
|
|
221
|
-
|
|
222
|
-
cancel error
|
|
181
|
+
finish_failure(event.failure)
|
|
223
182
|
end
|
|
224
183
|
|
|
225
|
-
#
|
|
226
|
-
def
|
|
227
|
-
|
|
184
|
+
# Explicit cancellation is an expected caller-controlled result.
|
|
185
|
+
def cancel(reason = 'Collection cancelled')
|
|
186
|
+
finish_failure(
|
|
187
|
+
Failure.new(code: :cancelled, message: reason.to_s, source: :local)
|
|
188
|
+
)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def fail(failure)
|
|
192
|
+
raise ArgumentError, 'failure must be an RSMP::Failure' unless failure.is_a?(Failure)
|
|
228
193
|
|
|
229
|
-
|
|
230
|
-
cancel error
|
|
194
|
+
finish_failure(failure)
|
|
231
195
|
end
|
|
232
196
|
|
|
233
|
-
#
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
197
|
+
# Unexpected receiver/callback failures reject the completion so waiters see
|
|
198
|
+
# the original exception and stack trace.
|
|
199
|
+
def crash(error)
|
|
200
|
+
return unless active?
|
|
201
|
+
|
|
202
|
+
@completion.crash(error)
|
|
203
|
+
stop
|
|
238
204
|
end
|
|
239
205
|
|
|
240
|
-
# Store a message in the result array
|
|
241
206
|
def keep(message)
|
|
242
207
|
@messages << message
|
|
243
208
|
end
|
|
244
209
|
|
|
245
|
-
# Check a message against our match criteria
|
|
246
|
-
# Return true if there's a match, false if not
|
|
247
210
|
def acceptable?(message)
|
|
248
211
|
@filter.nil? || @filter.accept?(message)
|
|
249
212
|
end
|
|
213
|
+
|
|
214
|
+
def build_collection
|
|
215
|
+
Collection.new(messages: @messages)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
private
|
|
219
|
+
|
|
220
|
+
def timeout_result?(result)
|
|
221
|
+
result.failure? && result.failure.code == :timeout && !@completion.resolved?
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def finish_success(collection)
|
|
225
|
+
@completion.succeed(collection)
|
|
226
|
+
stop
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def finish_failure(failure)
|
|
230
|
+
@completion.fail(failure)
|
|
231
|
+
stop
|
|
232
|
+
end
|
|
250
233
|
end
|
|
251
234
|
end
|
|
@@ -56,11 +56,24 @@ module RSMP
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
def distribute_immediately(message)
|
|
59
|
-
@receivers.each { |receiver| receiver
|
|
59
|
+
@receivers.dup.each { |receiver| deliver(receiver, :receive, message) }
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
def
|
|
63
|
-
|
|
62
|
+
def distribute_event(event)
|
|
63
|
+
raise ArgumentError, 'event must be an RSMP::Event' unless event.is_a?(RSMP::Event)
|
|
64
|
+
|
|
65
|
+
@receivers.dup.each { |receiver| deliver(receiver, :receive_event, event) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def deliver(receiver, method, value)
|
|
71
|
+
receiver.public_send(method, value)
|
|
72
|
+
rescue StandardError => e
|
|
73
|
+
remove_receiver(receiver)
|
|
74
|
+
raise unless receiver.respond_to?(:crash)
|
|
75
|
+
|
|
76
|
+
receiver.crash(e)
|
|
64
77
|
end
|
|
65
78
|
end
|
|
66
79
|
end
|
data/lib/rsmp/collect/queue.rb
CHANGED
|
@@ -7,10 +7,9 @@ module RSMP
|
|
|
7
7
|
|
|
8
8
|
attr_reader :messages
|
|
9
9
|
|
|
10
|
-
def initialize(distributor,
|
|
10
|
+
def initialize(distributor, filter: nil)
|
|
11
11
|
initialize_receiver distributor, filter: filter
|
|
12
12
|
@condition = Async::Notification.new
|
|
13
|
-
@task = task
|
|
14
13
|
clear
|
|
15
14
|
end
|
|
16
15
|
|
|
@@ -21,14 +20,23 @@ module RSMP
|
|
|
21
20
|
def wait_for_message(timeout: nil)
|
|
22
21
|
if @messages.empty?
|
|
23
22
|
if timeout
|
|
24
|
-
|
|
23
|
+
Async::Task.current.with_timeout(timeout) { @condition.wait }
|
|
25
24
|
else
|
|
26
25
|
@condition.wait
|
|
27
26
|
end
|
|
28
27
|
end
|
|
29
|
-
@messages.shift
|
|
30
|
-
rescue Async::TimeoutError
|
|
31
|
-
|
|
28
|
+
Result.success(@messages.shift)
|
|
29
|
+
rescue Async::TimeoutError => e
|
|
30
|
+
Result.failure(
|
|
31
|
+
:timeout,
|
|
32
|
+
message: "No message was received within #{timeout}s",
|
|
33
|
+
source: :timeout,
|
|
34
|
+
cause: e
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def wait_for_message!(...)
|
|
39
|
+
wait_for_message(...).value!
|
|
32
40
|
end
|
|
33
41
|
|
|
34
42
|
def handle_message(message)
|
|
@@ -106,9 +106,9 @@ module RSMP
|
|
|
106
106
|
end
|
|
107
107
|
end
|
|
108
108
|
|
|
109
|
-
def perform_match(message)
|
|
109
|
+
def perform_match?(message)
|
|
110
110
|
return false if super == false
|
|
111
|
-
return unless collecting?
|
|
111
|
+
return false unless collecting?
|
|
112
112
|
|
|
113
113
|
@matchers.each do |matcher|
|
|
114
114
|
break unless collecting?
|
|
@@ -120,6 +120,7 @@ module RSMP
|
|
|
120
120
|
handle_match_result(matched, matcher, message, item)
|
|
121
121
|
end
|
|
122
122
|
end
|
|
123
|
+
true
|
|
123
124
|
end
|
|
124
125
|
|
|
125
126
|
# don't collect anything. Matcher will collect them instead
|
|
@@ -248,5 +249,13 @@ module RSMP
|
|
|
248
249
|
def log_complete
|
|
249
250
|
@distributor.log "#{identifier}: Completed with #{matcher_got_hash}", level: :collect
|
|
250
251
|
end
|
|
252
|
+
|
|
253
|
+
def build_collection
|
|
254
|
+
Collection.new(
|
|
255
|
+
messages: messages,
|
|
256
|
+
reached: reached,
|
|
257
|
+
matcher_status: matcher_status
|
|
258
|
+
)
|
|
259
|
+
end
|
|
251
260
|
end
|
|
252
261
|
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
# Immutable snapshot returned by a completed collector.
|
|
3
|
+
Collection = Data.define(:messages, :reached, :matcher_status) do
|
|
4
|
+
def initialize(messages:, reached: [], matcher_status: {})
|
|
5
|
+
super(
|
|
6
|
+
messages: messages.dup.freeze,
|
|
7
|
+
reached: reached.dup.freeze,
|
|
8
|
+
matcher_status: matcher_status.dup.freeze
|
|
9
|
+
)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
# A resolve-once Async completion. Expected failures resolve to Result::Failure;
|
|
3
|
+
# unexpected exceptions reject the promise and retain their original backtrace.
|
|
4
|
+
class Completion
|
|
5
|
+
def initialize
|
|
6
|
+
@promise = Async::Promise.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def resolved?
|
|
10
|
+
@promise.resolved?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def succeed(value = nil)
|
|
14
|
+
@promise.resolve(Result.success(value))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def fail(failure = nil, **)
|
|
18
|
+
result = if failure.is_a?(RSMP::Failure)
|
|
19
|
+
Result.failure(failure: failure)
|
|
20
|
+
else
|
|
21
|
+
Result.failure(failure, **)
|
|
22
|
+
end
|
|
23
|
+
@promise.resolve(result)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def crash(exception)
|
|
27
|
+
@promise.reject(exception)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# A wait timeout bounds this caller's wait; it does not resolve or cancel the
|
|
31
|
+
# underlying operation.
|
|
32
|
+
def wait(timeout: nil)
|
|
33
|
+
@promise.wait(timeout: timeout)
|
|
34
|
+
rescue Async::TimeoutError => e
|
|
35
|
+
return @promise.wait if @promise.resolved?
|
|
36
|
+
|
|
37
|
+
Result.failure(
|
|
38
|
+
:timeout,
|
|
39
|
+
message: "Operation did not complete within #{timeout}s",
|
|
40
|
+
source: :timeout,
|
|
41
|
+
cause: e
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def wait!(timeout: nil)
|
|
46
|
+
wait(timeout: timeout).value!
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -70,7 +70,7 @@ module RSMP
|
|
|
70
70
|
|
|
71
71
|
def set_aggregated_status(status, options = {})
|
|
72
72
|
status = [status] if status.is_a? Symbol
|
|
73
|
-
raise
|
|
73
|
+
raise ArgumentError, 'aggregated status must be an Array' unless status.is_a? Array
|
|
74
74
|
|
|
75
75
|
input = status & AGGREGATED_STATUS_KEYS
|
|
76
76
|
return unless input != @aggregated_status
|
|
@@ -82,8 +82,8 @@ module RSMP
|
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
def aggregated_status_bools=(status)
|
|
85
|
-
raise
|
|
86
|
-
raise
|
|
85
|
+
raise ArgumentError, 'aggregated status must be an Array' unless status.is_a? Array
|
|
86
|
+
raise ArgumentError, 'aggregated status must contain 8 elements' unless status.size == 8
|
|
87
87
|
|
|
88
88
|
return unless status != @aggregated_status_bools
|
|
89
89
|
|
|
@@ -6,8 +6,10 @@ module RSMP
|
|
|
6
6
|
def self.output_sxl_index(out, sxl)
|
|
7
7
|
out['sxl_index.json'] = output_json({
|
|
8
8
|
'meta' => sxl[:meta],
|
|
9
|
-
'statuses' => index_items(sxl[:statuses],
|
|
10
|
-
|
|
9
|
+
'statuses' => index_items(sxl[:statuses],
|
|
10
|
+
legacy_types: legacy_types?(sxl)),
|
|
11
|
+
'commands' => index_items(sxl[:commands],
|
|
12
|
+
legacy_types: legacy_types?(sxl)),
|
|
11
13
|
'alarms' => index_items(sxl[:alarms], legacy_types: legacy_types?(sxl))
|
|
12
14
|
})
|
|
13
15
|
end
|
|
@@ -69,7 +71,10 @@ module RSMP
|
|
|
69
71
|
|
|
70
72
|
def self.argument_array_descriptor(argument, legacy_types:)
|
|
71
73
|
descriptor = { 'type' => argument['type'] }
|
|
72
|
-
|
|
74
|
+
if argument['items'].is_a?(Hash)
|
|
75
|
+
descriptor['items'] =
|
|
76
|
+
typed_arguments(argument['items'], legacy_types: legacy_types)
|
|
77
|
+
end
|
|
73
78
|
descriptor
|
|
74
79
|
end
|
|
75
80
|
|
|
@@ -20,7 +20,7 @@ module RSMP
|
|
|
20
20
|
def self.convert(yaml)
|
|
21
21
|
sxl = build_empty_sxl
|
|
22
22
|
sxl[:meta] = yaml['meta']
|
|
23
|
-
|
|
23
|
+
merge_components(sxl, component_definitions(yaml))
|
|
24
24
|
sxl
|
|
25
25
|
end
|
|
26
26
|
|
|
@@ -28,16 +28,20 @@ module RSMP
|
|
|
28
28
|
{ meta: {}, alarms: {}, statuses: {}, commands: {} }
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
def self.
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
def self.component_definitions(yaml)
|
|
32
|
+
yaml.fetch('components') { yaml.fetch('objects') }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.merge_components(sxl, components)
|
|
36
|
+
components.each_pair do |_type, component|
|
|
37
|
+
merge_component_items(sxl, component)
|
|
34
38
|
end
|
|
35
39
|
end
|
|
36
40
|
|
|
37
|
-
def self.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
def self.merge_component_items(sxl, component)
|
|
42
|
+
component['alarms']&.each { |id, item| sxl[:alarms][id] = item }
|
|
43
|
+
component['statuses']&.each { |id, item| sxl[:statuses][id] = item }
|
|
44
|
+
component['commands']&.each { |id, item| sxl[:commands][id] = item }
|
|
41
45
|
end
|
|
42
46
|
end
|
|
43
47
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
# The disposition of a successfully submitted outbound message.
|
|
3
|
+
Delivery = Data.define(:message, :state) do
|
|
4
|
+
def initialize(message:, state:)
|
|
5
|
+
raise ArgumentError, 'delivery state must be :sent or :buffered' unless %i[sent buffered].include?(state)
|
|
6
|
+
|
|
7
|
+
super
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def sent?
|
|
11
|
+
state == :sent
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def buffered?
|
|
15
|
+
state == :buffered
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/rsmp/event.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
# A typed occurrence produced by a long-running node or connection.
|
|
3
|
+
Event = Data.define(:type, :source, :session_id, :message, :failure, :context, :at) do
|
|
4
|
+
def initialize(type:, source:, session_id: nil, message: nil, failure: nil, context: {}, at: Time.now)
|
|
5
|
+
raise ArgumentError, 'event type must be a Symbol' unless type.is_a?(Symbol)
|
|
6
|
+
raise ArgumentError, 'event context must be a Hash' unless context.is_a?(Hash)
|
|
7
|
+
|
|
8
|
+
super(
|
|
9
|
+
type: type,
|
|
10
|
+
source: source,
|
|
11
|
+
session_id: session_id,
|
|
12
|
+
message: message,
|
|
13
|
+
failure: failure,
|
|
14
|
+
context: context.dup.freeze,
|
|
15
|
+
at: at
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|