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
|
@@ -3,9 +3,11 @@ module RSMP
|
|
|
3
3
|
module Modules
|
|
4
4
|
# Handles status requests, responses, subscriptions and updates
|
|
5
5
|
module Status
|
|
6
|
-
# Build and send a StatusRequest. Returns
|
|
6
|
+
# Build and send a StatusRequest. Returns Result<StatusRequest>.
|
|
7
7
|
def request_status(status_list, component: nil, m_id: nil, validate: true)
|
|
8
|
-
validate_ready 'request status'
|
|
8
|
+
readiness = validate_ready 'request status'
|
|
9
|
+
return readiness if readiness.failure?
|
|
10
|
+
|
|
9
11
|
component ||= main.c_id
|
|
10
12
|
m_id ||= RSMP::Message.make_m_id
|
|
11
13
|
|
|
@@ -21,14 +23,18 @@ module RSMP
|
|
|
21
23
|
'mId' => m_id
|
|
22
24
|
})
|
|
23
25
|
apply_nts_message_attributes message
|
|
24
|
-
send_message
|
|
25
|
-
|
|
26
|
+
send_message(message, validate: validate).map(&:message)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def request_status!(...)
|
|
30
|
+
request_status(...).value!
|
|
26
31
|
end
|
|
27
32
|
|
|
28
|
-
# Build, send a StatusRequest and collect the StatusResponse. Returns
|
|
29
|
-
# Call .ok! on the result to raise on NotAck or timeout.
|
|
33
|
+
# Build, send a StatusRequest and collect the StatusResponse. Returns Result<Exchange>.
|
|
30
34
|
def request_status_and_collect(status_list, within:, component: nil, m_id: nil, validate: true)
|
|
31
|
-
validate_ready 'request status'
|
|
35
|
+
readiness = validate_ready 'request status'
|
|
36
|
+
return readiness if readiness.failure?
|
|
37
|
+
|
|
32
38
|
component ||= main.c_id
|
|
33
39
|
m_id ||= RSMP::Message.make_m_id
|
|
34
40
|
|
|
@@ -45,7 +51,11 @@ module RSMP
|
|
|
45
51
|
})
|
|
46
52
|
apply_nts_message_attributes message
|
|
47
53
|
collector = StatusCollector.new(self, list.to_a, timeout: within, m_id: m_id)
|
|
48
|
-
send_message_and_collect(message, collector, validate: validate)
|
|
54
|
+
send_message_and_collect(message, collector, validate: validate)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def request_status_and_collect!(...)
|
|
58
|
+
request_status_and_collect(...).value!
|
|
49
59
|
end
|
|
50
60
|
|
|
51
61
|
def process_status_response(message)
|
|
@@ -71,9 +81,11 @@ module RSMP
|
|
|
71
81
|
end
|
|
72
82
|
end
|
|
73
83
|
|
|
74
|
-
# Build and send a StatusSubscribe. Returns
|
|
84
|
+
# Build and send a StatusSubscribe. Returns Result<StatusSubscribe>.
|
|
75
85
|
def subscribe_to_status(status_list, component: nil, m_id: nil, validate: true)
|
|
76
|
-
validate_ready 'subscribe to status'
|
|
86
|
+
readiness = validate_ready 'subscribe to status'
|
|
87
|
+
return readiness if readiness.failure?
|
|
88
|
+
|
|
77
89
|
component ||= main.c_id
|
|
78
90
|
m_id ||= RSMP::Message.make_m_id
|
|
79
91
|
|
|
@@ -89,14 +101,18 @@ module RSMP
|
|
|
89
101
|
'mId' => m_id
|
|
90
102
|
})
|
|
91
103
|
apply_nts_message_attributes message
|
|
92
|
-
send_message
|
|
93
|
-
{ sent: message }
|
|
104
|
+
send_message(message, validate: validate).map(&:message)
|
|
94
105
|
end
|
|
95
106
|
|
|
96
|
-
|
|
97
|
-
|
|
107
|
+
def subscribe_to_status!(...)
|
|
108
|
+
subscribe_to_status(...).value!
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Build, send a StatusSubscribe and collect the first matching status update. Returns Result<Exchange>.
|
|
98
112
|
def subscribe_to_status_and_collect(status_list, within:, component: nil, m_id: nil, validate: true)
|
|
99
|
-
validate_ready 'subscribe to status'
|
|
113
|
+
readiness = validate_ready 'subscribe to status'
|
|
114
|
+
return readiness if readiness.failure?
|
|
115
|
+
|
|
100
116
|
component ||= main.c_id
|
|
101
117
|
m_id ||= RSMP::Message.make_m_id
|
|
102
118
|
|
|
@@ -113,7 +129,11 @@ module RSMP
|
|
|
113
129
|
})
|
|
114
130
|
apply_nts_message_attributes message
|
|
115
131
|
collector = StatusCollector.new(self, list.to_a, timeout: within, m_id: m_id)
|
|
116
|
-
send_message_and_collect(message, collector, validate: validate)
|
|
132
|
+
send_message_and_collect(message, collector, validate: validate)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def subscribe_to_status_and_collect!(...)
|
|
136
|
+
subscribe_to_status_and_collect(...).value!
|
|
117
137
|
end
|
|
118
138
|
|
|
119
139
|
def remove_subscription_item(component_id, code, name)
|
|
@@ -131,15 +151,20 @@ module RSMP
|
|
|
131
151
|
remove_subscription_item(component, item['sCI'], item['n'])
|
|
132
152
|
end
|
|
133
153
|
|
|
134
|
-
|
|
154
|
+
# Local subscription state must be cleaned up even when the peer has
|
|
155
|
+
# already disconnected. In that case there is nothing left to send.
|
|
156
|
+
return Result.success(nil) unless ready?
|
|
135
157
|
|
|
136
158
|
message = RSMP::StatusUnsubscribe.new({
|
|
137
159
|
'cId' => component,
|
|
138
160
|
'sS' => status_list
|
|
139
161
|
})
|
|
140
162
|
apply_nts_message_attributes message
|
|
141
|
-
send_message
|
|
142
|
-
|
|
163
|
+
send_message(message, validate: validate).map(&:message)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def unsubscribe_to_status!(...)
|
|
167
|
+
unsubscribe_to_status(...).value!
|
|
143
168
|
end
|
|
144
169
|
|
|
145
170
|
# unsubscribes to all statuses (with all attributes) defined in the used SXL
|
|
@@ -56,10 +56,6 @@ module RSMP
|
|
|
56
56
|
else
|
|
57
57
|
super
|
|
58
58
|
end
|
|
59
|
-
rescue RSMP::RepeatedAlarmError, RSMP::RepeatedStatusError, RSMP::TimestampError => e
|
|
60
|
-
str = "Rejected #{message.type} message,"
|
|
61
|
-
dont_acknowledge message, str, e.to_s
|
|
62
|
-
distribute_error e.exception("#{str}#{e.message} #{message.json}")
|
|
63
59
|
end
|
|
64
60
|
|
|
65
61
|
def handled_by_parent?(message)
|
|
@@ -115,7 +111,18 @@ module RSMP
|
|
|
115
111
|
end
|
|
116
112
|
|
|
117
113
|
def validate_ready(action)
|
|
118
|
-
|
|
114
|
+
return Result.success(self) if ready?
|
|
115
|
+
|
|
116
|
+
Result.failure(
|
|
117
|
+
:not_ready,
|
|
118
|
+
message: "Can't #{action} because connection is not ready. (Currently #{@state})",
|
|
119
|
+
source: :connection,
|
|
120
|
+
context: { action: action, state: @state, session_id: session_id }
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def validate_ready!(action)
|
|
125
|
+
validate_ready(action).value!
|
|
119
126
|
end
|
|
120
127
|
|
|
121
128
|
def version_acknowledged; end
|
|
@@ -204,11 +211,6 @@ module RSMP
|
|
|
204
211
|
end
|
|
205
212
|
end
|
|
206
213
|
|
|
207
|
-
def receive_error(error, options = {})
|
|
208
|
-
@supervisor&.receive_error error, options
|
|
209
|
-
distribute_error error, options
|
|
210
|
-
end
|
|
211
|
-
|
|
212
214
|
def build_component(id:, type:, settings: {})
|
|
213
215
|
settings ||= {}
|
|
214
216
|
if type == 'main'
|
|
@@ -88,7 +88,7 @@ module RSMP
|
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
def buffer_message(message
|
|
91
|
+
def buffer_message(message)
|
|
92
92
|
prepared = prepare_message_for_buffer message, core_version: @core_version
|
|
93
93
|
if prepared
|
|
94
94
|
enqueue_buffered_message prepared
|
|
@@ -97,8 +97,6 @@ module RSMP
|
|
|
97
97
|
else
|
|
98
98
|
super
|
|
99
99
|
end
|
|
100
|
-
rescue NotReady, IOError
|
|
101
|
-
raise error if error
|
|
102
100
|
end
|
|
103
101
|
|
|
104
102
|
def enqueue_buffered_message(message)
|
|
@@ -118,19 +116,20 @@ module RSMP
|
|
|
118
116
|
@message_buffer = []
|
|
119
117
|
log "Sending #{queued.size} buffered message(s)", level: :info
|
|
120
118
|
queued.each_with_index do |message, index|
|
|
121
|
-
break unless flush_buffered_message(message, queued, index)
|
|
119
|
+
break unless flush_buffered_message?(message, queued, index)
|
|
122
120
|
end
|
|
123
121
|
end
|
|
124
122
|
|
|
125
|
-
def flush_buffered_message(message, queued, index)
|
|
123
|
+
def flush_buffered_message?(message, queued, index)
|
|
126
124
|
prepared = prepare_message_for_buffer message, core_version: @core_version, for_send: true
|
|
127
125
|
return true unless prepared
|
|
128
126
|
|
|
129
|
-
send_message
|
|
130
|
-
true
|
|
131
|
-
|
|
127
|
+
result = send_message(prepared, 'from buffer', buffer: false)
|
|
128
|
+
return true if result.success?
|
|
129
|
+
|
|
132
130
|
@message_buffer = queued[index..] + @message_buffer
|
|
133
|
-
log "Stopped sending buffered messages
|
|
131
|
+
log "Stopped sending buffered messages (#{result.failure.code}); " \
|
|
132
|
+
"#{message_buffer.size} message(s) remain queued", level: :warning
|
|
134
133
|
false
|
|
135
134
|
end
|
|
136
135
|
end
|
|
@@ -34,45 +34,39 @@ module RSMP
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
loop do
|
|
37
|
-
connect
|
|
37
|
+
connected = connect
|
|
38
|
+
unless connected.success?
|
|
39
|
+
publish_connection_attempt_failure(connected.failure)
|
|
40
|
+
break unless reconnect_delay?
|
|
41
|
+
|
|
42
|
+
next
|
|
43
|
+
end
|
|
38
44
|
start_reader
|
|
39
45
|
start_handshake
|
|
40
|
-
wait_for_reader
|
|
41
|
-
break unless reconnect_delay?
|
|
42
|
-
rescue Restart
|
|
43
|
-
@logger.mute @ip, @port
|
|
44
|
-
raise
|
|
45
|
-
rescue RSMP::ConnectionError => e
|
|
46
|
-
log e, level: :error
|
|
47
|
-
break unless reconnect_delay?
|
|
48
|
-
rescue StandardError => e
|
|
49
|
-
distribute_error e, level: :internal
|
|
46
|
+
close_from_result(wait_for_reader)
|
|
50
47
|
break unless reconnect_delay?
|
|
51
48
|
ensure
|
|
49
|
+
close(reason: :internal_failure) if $ERROR_INFO
|
|
52
50
|
close
|
|
53
|
-
stop_subtasks
|
|
54
51
|
end
|
|
55
52
|
end
|
|
56
53
|
|
|
57
54
|
def run_accepted_connection
|
|
55
|
+
begin_session
|
|
58
56
|
self.state = :connected
|
|
59
57
|
start_reader
|
|
60
58
|
start_handshake
|
|
61
|
-
wait_for_reader
|
|
62
|
-
rescue RSMP::ConnectionError => e
|
|
63
|
-
log e, level: :error
|
|
64
|
-
rescue StandardError => e
|
|
65
|
-
distribute_error e, level: :internal
|
|
59
|
+
close_from_result(wait_for_reader)
|
|
66
60
|
ensure
|
|
61
|
+
close(reason: :internal_failure) if $ERROR_INFO
|
|
67
62
|
close
|
|
68
|
-
stop_subtasks
|
|
69
63
|
end
|
|
70
64
|
|
|
71
65
|
def start_handshake
|
|
72
66
|
send_version_request @site_settings['site_id'], core_versions
|
|
73
67
|
end
|
|
74
68
|
|
|
75
|
-
def close
|
|
69
|
+
def close(...)
|
|
76
70
|
prune_unbuffered_status_subscriptions
|
|
77
71
|
super
|
|
78
72
|
end
|
|
@@ -80,14 +74,15 @@ module RSMP
|
|
|
80
74
|
# connect to the supervisor and initiate handshake supervisor
|
|
81
75
|
def connect
|
|
82
76
|
log "Connecting to supervisor at #{@ip}:#{@port}", level: :info
|
|
77
|
+
begin_session
|
|
83
78
|
self.state = :connecting
|
|
84
|
-
connect_tcp
|
|
79
|
+
opened = connect_tcp
|
|
80
|
+
return opened if opened.failure?
|
|
81
|
+
|
|
82
|
+
self.state = :connected
|
|
85
83
|
@logger.unmute @ip, @port
|
|
86
84
|
log "Connected to supervisor at #{@ip}:#{@port}", level: :info
|
|
87
|
-
|
|
88
|
-
raise ConnectionError, "Could not connect to supervisor at #{@ip}:#{@port}: Errno #{e.errno} #{e}"
|
|
89
|
-
rescue StandardError => e
|
|
90
|
-
raise ConnectionError, "Error while connecting to supervisor at #{@ip}:#{@port}: #{e}"
|
|
85
|
+
Result.success(self)
|
|
91
86
|
end
|
|
92
87
|
|
|
93
88
|
def stop_task
|
|
@@ -108,10 +103,22 @@ module RSMP
|
|
|
108
103
|
|
|
109
104
|
@stream = IO::Stream::Buffered.new(@socket)
|
|
110
105
|
@protocol = RSMP::Protocol.new(@stream) # rsmp messages are json terminated with a form-feed
|
|
111
|
-
self
|
|
106
|
+
Result.success(self)
|
|
112
107
|
rescue Errno::ECONNREFUSED => e # rescue to avoid log output
|
|
113
108
|
log 'Connection refused', level: :warning
|
|
114
|
-
|
|
109
|
+
failed_connection_result(e)
|
|
110
|
+
rescue SystemCallError, SocketError, IOError, Async::TimeoutError => e
|
|
111
|
+
failed_connection_result(e)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def failed_connection_result(error)
|
|
115
|
+
Result.failure(
|
|
116
|
+
:connection_failed,
|
|
117
|
+
message: "Could not connect to supervisor at #{@ip}:#{@port}: #{error.message}",
|
|
118
|
+
source: :transport,
|
|
119
|
+
context: { ip: @ip, port: @port, session_id: @session_id },
|
|
120
|
+
cause: error
|
|
121
|
+
)
|
|
115
122
|
end
|
|
116
123
|
|
|
117
124
|
def handshake_complete
|
|
@@ -142,9 +149,6 @@ module RSMP
|
|
|
142
149
|
else
|
|
143
150
|
super
|
|
144
151
|
end
|
|
145
|
-
rescue UnknownComponent, UnknownCommand, UnknownStatus,
|
|
146
|
-
MessageRejected, MissingAttribute => e
|
|
147
|
-
dont_acknowledge message, '', e.to_s
|
|
148
152
|
end
|
|
149
153
|
|
|
150
154
|
def handle_interface_request(message)
|
|
@@ -221,7 +225,7 @@ module RSMP
|
|
|
221
225
|
end
|
|
222
226
|
|
|
223
227
|
def send_component_list
|
|
224
|
-
|
|
228
|
+
send_generated_message ComponentList.new('components' => @site.component_list)
|
|
225
229
|
end
|
|
226
230
|
|
|
227
231
|
def component_list_acknowledged
|
data/lib/rsmp/result.rb
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
# Result values represent the expected outcome of finite operations.
|
|
3
|
+
module Result
|
|
4
|
+
# A successfully completed operation.
|
|
5
|
+
Success = Data.define(:value) do
|
|
6
|
+
def success?
|
|
7
|
+
true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def failure?
|
|
11
|
+
false
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def failure
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def value!
|
|
19
|
+
value
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def map
|
|
23
|
+
Result.success(yield(value))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def and_then
|
|
27
|
+
yield(value)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# An expected operational failure.
|
|
32
|
+
Failure = Data.define(:failure) do
|
|
33
|
+
def initialize(failure:)
|
|
34
|
+
raise ArgumentError, 'failure must be an RSMP::Failure' unless failure.is_a?(RSMP::Failure)
|
|
35
|
+
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def success?
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def failure?
|
|
44
|
+
true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def value
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def value!
|
|
52
|
+
exception = OperationError.new(failure)
|
|
53
|
+
raise exception, cause: failure.cause if failure.cause
|
|
54
|
+
|
|
55
|
+
raise exception
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def map
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def and_then
|
|
63
|
+
self
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
module_function
|
|
68
|
+
|
|
69
|
+
def success(value = nil)
|
|
70
|
+
Success.new(value)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def failure(code = nil, failure: nil, **details)
|
|
74
|
+
Failure.new(
|
|
75
|
+
failure: failure || RSMP::Failure.new(
|
|
76
|
+
code: code,
|
|
77
|
+
message: details.fetch(:message, code),
|
|
78
|
+
source: details.fetch(:source, :operation),
|
|
79
|
+
context: details.fetch(:context, {}),
|
|
80
|
+
cause: details[:cause]
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Raised by bang APIs when an expected failure result is explicitly unwrapped.
|
|
87
|
+
class OperationError < Error
|
|
88
|
+
attr_reader :failure
|
|
89
|
+
|
|
90
|
+
def initialize(failure)
|
|
91
|
+
@failure = failure
|
|
92
|
+
super(failure.message)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -41,6 +41,8 @@ module RSMP
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
# Core must pass. SXL-defined messages pass if at least one SXL schema passes.
|
|
44
|
+
# A message which does not satisfy its schemas is an ordinary validation
|
|
45
|
+
# outcome. Invalid API arguments and unavailable schemas still raise.
|
|
44
46
|
def self.validate(message, schemas, options = {})
|
|
45
47
|
raise ArgumentError, 'message missing' unless message
|
|
46
48
|
raise ArgumentError, 'schemas missing' unless schemas
|
|
@@ -49,9 +51,9 @@ module RSMP
|
|
|
49
51
|
|
|
50
52
|
errors = validate_core(message, schemas, options)
|
|
51
53
|
errors.concat validate_sxls(message, schemas, options) if errors.empty?
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
Validation.new(violations: errors)
|
|
55
|
+
rescue UnknownMessageCodeError => e
|
|
56
|
+
Validation.new(violations: [['', 'unknown_message_code', e.message]])
|
|
55
57
|
end
|
|
56
58
|
end
|
|
57
59
|
end
|
data/lib/rsmp/sxl/interface.rb
CHANGED
|
@@ -6,7 +6,14 @@ module RSMP
|
|
|
6
6
|
class Interface
|
|
7
7
|
extend Forwardable
|
|
8
8
|
|
|
9
|
-
def_delegators :proxy,
|
|
9
|
+
def_delegators :proxy,
|
|
10
|
+
:send_message,
|
|
11
|
+
:send_message!,
|
|
12
|
+
:send_message_and_collect,
|
|
13
|
+
:send_message_and_collect!,
|
|
14
|
+
:validate_ready,
|
|
15
|
+
:validate_ready!,
|
|
16
|
+
:log
|
|
10
17
|
|
|
11
18
|
attr_reader :proxy, :name, :version
|
|
12
19
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
module SXL
|
|
3
|
+
module Processing
|
|
4
|
+
# Indexes locally available SXL definitions by name and exact version.
|
|
5
|
+
class Catalogue
|
|
6
|
+
def initialize(paths = [])
|
|
7
|
+
@documents = Hash.new { |hash, key| hash[key] = {} }
|
|
8
|
+
Array(paths).each { |path| add_path(path) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add_path(path)
|
|
12
|
+
expanded_path = File.expand_path(path)
|
|
13
|
+
raise Error, "SXL source #{path} not found" unless File.exist?(expanded_path)
|
|
14
|
+
|
|
15
|
+
if File.directory?(expanded_path)
|
|
16
|
+
pattern = File.join(expanded_path, '**', '{sxl.yaml,sxl.yml}')
|
|
17
|
+
files = Dir.glob(pattern)
|
|
18
|
+
raise Error, "No sxl.yaml or sxl.yml files found under #{path}" if files.empty?
|
|
19
|
+
|
|
20
|
+
files.each { |file| add_file(file) }
|
|
21
|
+
else
|
|
22
|
+
add_file(expanded_path)
|
|
23
|
+
end
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def add_document(document)
|
|
28
|
+
existing = @documents[document.name][document.version_string]
|
|
29
|
+
if existing && existing.path != document.path
|
|
30
|
+
raise Error,
|
|
31
|
+
"Duplicate SXL #{document.name} #{document.version_string} in #{existing.path} and #{document.path}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@documents[document.name][document.version_string] = document
|
|
35
|
+
document
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def candidates(name, requirements = [])
|
|
39
|
+
versions = @documents.fetch(name, {}).values.sort_by(&:version).reverse
|
|
40
|
+
versions.select do |document|
|
|
41
|
+
requirements.all? { |requirement| requirement.satisfied_by?(document.version) }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def versions(name)
|
|
46
|
+
@documents.fetch(name, {}).values.map(&:version_string).sort_by { |version| Gem::Version.new(version) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def fetch(name, version)
|
|
50
|
+
@documents.fetch(name, {})[version.to_s]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def add_file(path)
|
|
56
|
+
add_document(Document.load(path))
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
module SXL
|
|
3
|
+
module Processing
|
|
4
|
+
# Rejects component types or message codes owned by more than one SXL.
|
|
5
|
+
module ConflictChecker
|
|
6
|
+
def self.check!(documents)
|
|
7
|
+
check_definitions!(documents, :component_types, 'component type')
|
|
8
|
+
check_definitions!(documents, :message_codes, 'message code')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.check_definitions!(documents, collection, label)
|
|
12
|
+
owners = {}
|
|
13
|
+
documents.each do |document|
|
|
14
|
+
document.public_send(collection).each do |definition|
|
|
15
|
+
previous = owners[definition.id]
|
|
16
|
+
if previous
|
|
17
|
+
raise Error,
|
|
18
|
+
"Conflicting #{label} #{definition.id.inspect} in " \
|
|
19
|
+
"#{previous[:document].name} and #{document.name}"
|
|
20
|
+
end
|
|
21
|
+
owners[definition.id] = { document: document, definition: definition }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private_class_method :check_definitions!
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module RSMP
|
|
2
|
+
module SXL
|
|
3
|
+
module Processing
|
|
4
|
+
# Dependency graph validation shared by resolution and manifest verification.
|
|
5
|
+
module DependencyGraph
|
|
6
|
+
def self.cycle(documents)
|
|
7
|
+
by_name = documents.to_h { |document| [document.name, document] }
|
|
8
|
+
visited = {}
|
|
9
|
+
stack = []
|
|
10
|
+
by_name.each_key do |name|
|
|
11
|
+
found = visit(name, by_name, visited, stack)
|
|
12
|
+
return found if found
|
|
13
|
+
end
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.visit(name, documents, visited, stack)
|
|
18
|
+
return stack[stack.index(name)..] + [name] if stack.include?(name)
|
|
19
|
+
return nil if visited[name]
|
|
20
|
+
|
|
21
|
+
visited[name] = true
|
|
22
|
+
stack << name
|
|
23
|
+
documents.fetch(name).dependencies.each_key do |dependency|
|
|
24
|
+
next unless documents.key?(dependency)
|
|
25
|
+
|
|
26
|
+
found = visit(dependency, documents, visited, stack)
|
|
27
|
+
return found if found
|
|
28
|
+
end
|
|
29
|
+
stack.pop
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private_class_method :visit
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|