jetstream_bridge 4.0.2 → 4.0.3
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 +31 -0
- data/lib/jetstream_bridge/consumer/inbox/inbox_message.rb +0 -4
- data/lib/jetstream_bridge/core/connection.rb +139 -5
- data/lib/jetstream_bridge/core/logging.rb +0 -2
- data/lib/jetstream_bridge/core/model_utils.rb +0 -2
- data/lib/jetstream_bridge/models/inbox_event.rb +0 -2
- data/lib/jetstream_bridge/models/outbox_event.rb +0 -2
- data/lib/jetstream_bridge/version.rb +1 -1
- data/lib/jetstream_bridge.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fdb73e243bddbd9812d6d454d1975d15f6f8d98325c7759853d32a01c830e27e
|
|
4
|
+
data.tar.gz: 8c570f62d4e4b0efddf59e6a4033026b5cabaad83fe808ec0942ad57f3874d5e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c9b95208ecf116e52daa75e77d3edb62899e6724bec67c72c4322aab31778392906053f77e9eb420c855605ee6aca41eacad9c509b47e5123e710436d201718c
|
|
7
|
+
data.tar.gz: 588603c1946d72946c9ba990869869e5932c3f04e265cefbbc2d6f0c3829467e1e498eab307b84770b7d88ed134184d6c8f07596968d0b30da107204fa3bca5f
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [4.0.3] - 2025-11-23
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Connection Validation** - Comprehensive NATS URL validation on initialization
|
|
13
|
+
- Validates URL format, scheme (nats/nats+tls), host presence, and port range (1-65535)
|
|
14
|
+
- Verifies NATS connection established after connect
|
|
15
|
+
- Verifies JetStream availability with account info check
|
|
16
|
+
- Helpful error messages for common misconfigurations
|
|
17
|
+
- All validation errors include specific guidance for fixes
|
|
18
|
+
|
|
19
|
+
- **Connection Logging** - Detailed logging throughout connection lifecycle
|
|
20
|
+
- Debug logs for URL validation progress and verification steps
|
|
21
|
+
- Info logs for successful validation and JetStream stats (streams, consumers, memory, storage)
|
|
22
|
+
- Error logs for all validation failures with specific details
|
|
23
|
+
- Automatic credential sanitization in all log messages
|
|
24
|
+
- Human-readable resource usage formatting (bytes to KB/MB/GB)
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **Health Check** - Fixed `healthy` field returning `nil` instead of `false` when disconnected
|
|
29
|
+
- Changed `stream_info[:exists]` to `stream_info&.fetch(:exists, false)` for proper nil handling
|
|
30
|
+
- Ensures boolean values always returned for monitoring systems
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **Code Organization** - Moved all inline RuboCop rules to centralized `.rubocop.yml`
|
|
35
|
+
- Removed inline comments from 5 files (logging.rb, model_utils.rb, inbox_event.rb, outbox_event.rb, inbox_message.rb)
|
|
36
|
+
- Added exclusions to `.rubocop.yml` for metrics and naming rules
|
|
37
|
+
- Cleaner codebase with all style exceptions in one location
|
|
38
|
+
|
|
8
39
|
## [4.0.2] - 2025-11-23
|
|
9
40
|
|
|
10
41
|
### Fixed
|
|
@@ -8,7 +8,6 @@ module JetstreamBridge
|
|
|
8
8
|
class InboxMessage
|
|
9
9
|
attr_reader :msg, :seq, :deliveries, :stream, :subject, :headers, :body, :raw, :event_id, :now
|
|
10
10
|
|
|
11
|
-
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
12
11
|
def self.from_nats(msg)
|
|
13
12
|
meta = (msg.respond_to?(:metadata) && msg.metadata) || nil
|
|
14
13
|
seq = meta.respond_to?(:stream_sequence) ? meta.stream_sequence : nil
|
|
@@ -33,9 +32,7 @@ module JetstreamBridge
|
|
|
33
32
|
|
|
34
33
|
new(msg, seq, deliveries, stream, subject, headers, body, raw, id, Time.now.utc, consumer)
|
|
35
34
|
end
|
|
36
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
37
35
|
|
|
38
|
-
# rubocop:disable Metrics/ParameterLists
|
|
39
36
|
def initialize(msg, seq, deliveries, stream, subject, headers, body, raw, event_id, now, consumer = nil)
|
|
40
37
|
@msg = msg
|
|
41
38
|
@seq = seq
|
|
@@ -49,7 +46,6 @@ module JetstreamBridge
|
|
|
49
46
|
@now = now
|
|
50
47
|
@consumer = consumer
|
|
51
48
|
end
|
|
52
|
-
# rubocop:enable Metrics/ParameterLists
|
|
53
49
|
|
|
54
50
|
def body_for_store
|
|
55
51
|
body.empty? ? raw : body
|
|
@@ -34,6 +34,8 @@ module JetstreamBridge
|
|
|
34
34
|
connect_timeout: 5
|
|
35
35
|
}.freeze
|
|
36
36
|
|
|
37
|
+
VALID_NATS_SCHEMES = %w[nats nats+tls].freeze
|
|
38
|
+
|
|
37
39
|
class << self
|
|
38
40
|
# Thread-safe delegator to the singleton instance.
|
|
39
41
|
# Returns a live JetStream context.
|
|
@@ -106,11 +108,14 @@ module JetstreamBridge
|
|
|
106
108
|
end
|
|
107
109
|
|
|
108
110
|
def nats_servers
|
|
109
|
-
JetstreamBridge.config.nats_urls
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
servers = JetstreamBridge.config.nats_urls
|
|
112
|
+
.to_s
|
|
113
|
+
.split(',')
|
|
114
|
+
.map(&:strip)
|
|
115
|
+
.reject(&:empty?)
|
|
116
|
+
|
|
117
|
+
validate_nats_urls!(servers)
|
|
118
|
+
servers
|
|
114
119
|
end
|
|
115
120
|
|
|
116
121
|
def establish_connection(servers)
|
|
@@ -141,9 +146,15 @@ module JetstreamBridge
|
|
|
141
146
|
|
|
142
147
|
@nc.connect({ servers: servers }.merge(DEFAULT_CONN_OPTS))
|
|
143
148
|
|
|
149
|
+
# Verify connection is established
|
|
150
|
+
verify_connection!
|
|
151
|
+
|
|
144
152
|
# Create JetStream context
|
|
145
153
|
@jts = @nc.jetstream
|
|
146
154
|
|
|
155
|
+
# Verify JetStream is available
|
|
156
|
+
verify_jetstream!
|
|
157
|
+
|
|
147
158
|
# Ensure JetStream responds to #nc
|
|
148
159
|
return if @jts.respond_to?(:nc)
|
|
149
160
|
|
|
@@ -151,6 +162,129 @@ module JetstreamBridge
|
|
|
151
162
|
@jts.define_singleton_method(:nc) { nc_ref }
|
|
152
163
|
end
|
|
153
164
|
|
|
165
|
+
def validate_nats_urls!(servers)
|
|
166
|
+
Logging.debug(
|
|
167
|
+
"Validating #{servers.size} NATS URL(s): #{sanitize_urls(servers).join(', ')}",
|
|
168
|
+
tag: 'JetstreamBridge::Connection'
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
servers.each do |url|
|
|
172
|
+
# Check for basic URL format (scheme://host)
|
|
173
|
+
unless url.include?('://')
|
|
174
|
+
Logging.error(
|
|
175
|
+
"Invalid URL format (missing scheme): #{url}",
|
|
176
|
+
tag: 'JetstreamBridge::Connection'
|
|
177
|
+
)
|
|
178
|
+
raise ConnectionError, "Invalid NATS URL format: #{url}. Expected format: nats://host:port"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
uri = URI.parse(url)
|
|
182
|
+
|
|
183
|
+
# Validate scheme
|
|
184
|
+
scheme = uri.scheme&.downcase
|
|
185
|
+
unless VALID_NATS_SCHEMES.include?(scheme)
|
|
186
|
+
Logging.error(
|
|
187
|
+
"Invalid URL scheme '#{uri.scheme}': #{Logging.sanitize_url(url)}",
|
|
188
|
+
tag: 'JetstreamBridge::Connection'
|
|
189
|
+
)
|
|
190
|
+
raise ConnectionError, "Invalid NATS URL scheme '#{uri.scheme}' in: #{url}. Expected 'nats' or 'nats+tls'"
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Validate host is present
|
|
194
|
+
if uri.host.nil? || uri.host.empty?
|
|
195
|
+
Logging.error(
|
|
196
|
+
"Missing host in URL: #{Logging.sanitize_url(url)}",
|
|
197
|
+
tag: 'JetstreamBridge::Connection'
|
|
198
|
+
)
|
|
199
|
+
raise ConnectionError, "Invalid NATS URL - missing host: #{url}"
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Validate port if present
|
|
203
|
+
if uri.port && (uri.port < 1 || uri.port > 65_535)
|
|
204
|
+
Logging.error(
|
|
205
|
+
"Invalid port #{uri.port} in URL: #{Logging.sanitize_url(url)}",
|
|
206
|
+
tag: 'JetstreamBridge::Connection'
|
|
207
|
+
)
|
|
208
|
+
raise ConnectionError, "Invalid NATS URL - port must be 1-65535: #{url}"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
Logging.debug(
|
|
212
|
+
"URL validated: #{Logging.sanitize_url(url)}",
|
|
213
|
+
tag: 'JetstreamBridge::Connection'
|
|
214
|
+
)
|
|
215
|
+
rescue URI::InvalidURIError => e
|
|
216
|
+
Logging.error(
|
|
217
|
+
"Malformed URL: #{url} (#{e.message})",
|
|
218
|
+
tag: 'JetstreamBridge::Connection'
|
|
219
|
+
)
|
|
220
|
+
raise ConnectionError, "Invalid NATS URL format: #{url} (#{e.message})"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
Logging.info(
|
|
224
|
+
'All NATS URLs validated successfully',
|
|
225
|
+
tag: 'JetstreamBridge::Connection'
|
|
226
|
+
)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def verify_connection!
|
|
230
|
+
Logging.debug(
|
|
231
|
+
'Verifying NATS connection...',
|
|
232
|
+
tag: 'JetstreamBridge::Connection'
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
unless @nc.connected?
|
|
236
|
+
Logging.error(
|
|
237
|
+
'NATS connection verification failed - client not connected',
|
|
238
|
+
tag: 'JetstreamBridge::Connection'
|
|
239
|
+
)
|
|
240
|
+
raise ConnectionError, 'Failed to establish connection to NATS server(s)'
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
Logging.info(
|
|
244
|
+
'NATS connection verified successfully',
|
|
245
|
+
tag: 'JetstreamBridge::Connection'
|
|
246
|
+
)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def verify_jetstream!
|
|
250
|
+
Logging.debug(
|
|
251
|
+
'Verifying JetStream availability...',
|
|
252
|
+
tag: 'JetstreamBridge::Connection'
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Verify JetStream is enabled by checking account info
|
|
256
|
+
account_info = @jts.account_info
|
|
257
|
+
|
|
258
|
+
Logging.info(
|
|
259
|
+
"JetStream verified - Streams: #{account_info.streams}, " \
|
|
260
|
+
"Consumers: #{account_info.consumers}, " \
|
|
261
|
+
"Memory: #{format_bytes(account_info.memory)}, " \
|
|
262
|
+
"Storage: #{format_bytes(account_info.storage)}",
|
|
263
|
+
tag: 'JetstreamBridge::Connection'
|
|
264
|
+
)
|
|
265
|
+
rescue NATS::IO::NoRespondersError
|
|
266
|
+
Logging.error(
|
|
267
|
+
'JetStream not available - no responders (JetStream not enabled)',
|
|
268
|
+
tag: 'JetstreamBridge::Connection'
|
|
269
|
+
)
|
|
270
|
+
raise ConnectionError, 'JetStream not enabled on NATS server. Please enable JetStream with -js flag'
|
|
271
|
+
rescue StandardError => e
|
|
272
|
+
Logging.error(
|
|
273
|
+
"JetStream verification failed: #{e.class} - #{e.message}",
|
|
274
|
+
tag: 'JetstreamBridge::Connection'
|
|
275
|
+
)
|
|
276
|
+
raise ConnectionError, "JetStream verification failed: #{e.message}"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def format_bytes(bytes)
|
|
280
|
+
return 'N/A' if bytes.nil? || bytes.zero?
|
|
281
|
+
|
|
282
|
+
units = %w[B KB MB GB TB]
|
|
283
|
+
exp = (Math.log(bytes) / Math.log(1024)).to_i
|
|
284
|
+
exp = [exp, units.length - 1].min
|
|
285
|
+
"#{(bytes / (1024.0**exp)).round(2)} #{units[exp]}"
|
|
286
|
+
end
|
|
287
|
+
|
|
154
288
|
def refresh_jetstream_context
|
|
155
289
|
@jts = @nc.jetstream
|
|
156
290
|
nc_ref = @nc
|
|
@@ -40,7 +40,6 @@ module JetstreamBridge
|
|
|
40
40
|
log(:error, msg, tag: tag)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
44
43
|
def sanitize_url(url)
|
|
45
44
|
uri = URI.parse(url)
|
|
46
45
|
return url unless uri.user || uri.password
|
|
@@ -67,6 +66,5 @@ module JetstreamBridge
|
|
|
67
66
|
"#{scheme}://#{masked}@"
|
|
68
67
|
end
|
|
69
68
|
end
|
|
70
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
71
69
|
end
|
|
72
70
|
end
|
|
@@ -14,13 +14,11 @@ module JetstreamBridge
|
|
|
14
14
|
defined?(ActiveRecord::Base) && klass <= ActiveRecord::Base
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
# rubocop:disable Naming/PredicatePrefix
|
|
18
17
|
def has_columns?(klass, *cols)
|
|
19
18
|
return false unless ar_class?(klass)
|
|
20
19
|
|
|
21
20
|
cols.flatten.all? { |c| klass.column_names.include?(c.to_s) }
|
|
22
21
|
end
|
|
23
|
-
# rubocop:enable Naming/PredicatePrefix
|
|
24
22
|
|
|
25
23
|
def assign_known_attrs(record, attrs)
|
|
26
24
|
attrs.each do |k, v|
|
|
@@ -15,7 +15,6 @@ module JetstreamBridge
|
|
|
15
15
|
|
|
16
16
|
class << self
|
|
17
17
|
# Safe column presence check that never boots a connection during class load.
|
|
18
|
-
# rubocop:disable Naming/PredicatePrefix
|
|
19
18
|
def has_column?(name)
|
|
20
19
|
return false unless ar_connected?
|
|
21
20
|
|
|
@@ -23,7 +22,6 @@ module JetstreamBridge
|
|
|
23
22
|
rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::NoDatabaseError
|
|
24
23
|
false
|
|
25
24
|
end
|
|
26
|
-
# rubocop:enable Naming/PredicatePrefix
|
|
27
25
|
|
|
28
26
|
def ar_connected?
|
|
29
27
|
ActiveRecord::Base.connected? && connection_pool.active_connection?
|
|
@@ -15,7 +15,6 @@ module JetstreamBridge
|
|
|
15
15
|
|
|
16
16
|
class << self
|
|
17
17
|
# Safe column presence check that never boots a connection during class load.
|
|
18
|
-
# rubocop:disable Naming/PredicatePrefix
|
|
19
18
|
def has_column?(name)
|
|
20
19
|
return false unless ar_connected?
|
|
21
20
|
|
|
@@ -23,7 +22,6 @@ module JetstreamBridge
|
|
|
23
22
|
rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::NoDatabaseError
|
|
24
23
|
false
|
|
25
24
|
end
|
|
26
|
-
# rubocop:enable Naming/PredicatePrefix
|
|
27
25
|
|
|
28
26
|
def ar_connected?
|
|
29
27
|
# Avoid creating a connection; rescue if pool isn't set yet.
|
data/lib/jetstream_bridge.rb
CHANGED
|
@@ -126,7 +126,7 @@ module JetstreamBridge
|
|
|
126
126
|
stream_info = fetch_stream_info if connected
|
|
127
127
|
|
|
128
128
|
{
|
|
129
|
-
healthy: connected && stream_info
|
|
129
|
+
healthy: connected && stream_info&.fetch(:exists, false),
|
|
130
130
|
nats_connected: connected,
|
|
131
131
|
connected_at: connected_at&.iso8601,
|
|
132
132
|
stream: stream_info,
|