natsy 0.3.0 → 0.4.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/.github/workflows/main.yml +8 -0
- data/.rubocop.yml +17 -1
- data/README.md +104 -26
- data/lib/natsy.rb +4 -0
- data/lib/natsy/client.rb +53 -72
- data/lib/natsy/config.rb +255 -0
- data/lib/natsy/controller.rb +6 -7
- data/lib/natsy/utils.rb +14 -0
- data/lib/natsy/version.rb +1 -1
- data/natsy.gemspec +1 -1
- metadata +18 -18
- data/CHANGELOG.md +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e8c3433d746ea11eacc51f7e602ab6a0d0816a69c5ef7db7e802847cf5b931fd
|
4
|
+
data.tar.gz: 79dc340ffefbe6371558fa1cd901977e55215f8cbda71024fcd930dda94eab2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e7c12f04962acfb2e36bfd1ad580da552678dd26a6c5c51b7b373ef69f816a2018bc72a1c2ad2278b394f0e9e7d18f3d942dd46883a01630b1e30384e21fcc9a
|
7
|
+
data.tar.gz: af46e5498949a3806972d9a5a26fdadfc275e3d8914338c634a3cc01ab33c45b26fb48849103ec8a786903d580685912b418df099ace08f9aa5fbdcc9b05dfbb
|
data/.github/workflows/main.yml
CHANGED
data/.rubocop.yml
CHANGED
@@ -26,13 +26,20 @@ Layout/FirstArrayElementIndentation:
|
|
26
26
|
# Metrics
|
27
27
|
|
28
28
|
Metrics/AbcSize:
|
29
|
-
Max:
|
29
|
+
Max: 35
|
30
30
|
CountRepeatedAttributes: false
|
31
31
|
Exclude:
|
32
32
|
- 'spec/**/*_spec.rb'
|
33
33
|
- '*.gemspec'
|
34
34
|
|
35
35
|
Metrics/BlockLength:
|
36
|
+
Max: 50
|
37
|
+
Exclude:
|
38
|
+
- 'spec/**/*_spec.rb'
|
39
|
+
- '*.gemspec'
|
40
|
+
|
41
|
+
Metrics/CyclomaticComplexity:
|
42
|
+
Max: 15
|
36
43
|
Exclude:
|
37
44
|
- 'spec/**/*_spec.rb'
|
38
45
|
- '*.gemspec'
|
@@ -55,6 +62,9 @@ Metrics/MethodLength:
|
|
55
62
|
- array
|
56
63
|
- hash
|
57
64
|
- heredoc
|
65
|
+
Exclude:
|
66
|
+
- 'spec/**/*_spec.rb'
|
67
|
+
- '*.gemspec'
|
58
68
|
|
59
69
|
Metrics/ModuleLength:
|
60
70
|
Max: 150
|
@@ -67,6 +77,12 @@ Metrics/ModuleLength:
|
|
67
77
|
- 'spec/**/*_spec.rb'
|
68
78
|
- '*.gemspec'
|
69
79
|
|
80
|
+
Metrics/PerceivedComplexity:
|
81
|
+
Max: 20
|
82
|
+
Exclude:
|
83
|
+
- 'spec/**/*_spec.rb'
|
84
|
+
- '*.gemspec'
|
85
|
+
|
70
86
|
# Rspec
|
71
87
|
|
72
88
|
RSpec/ExampleLength:
|
data/README.md
CHANGED
@@ -5,14 +5,15 @@ The `natsy` gem allows you to listen for (and reply to) NATS messages asynchrono
|
|
5
5
|
## TODO
|
6
6
|
|
7
7
|
- [x] docs
|
8
|
-
- [
|
8
|
+
- [x] tests
|
9
9
|
- [x] "controller"-style classes for reply organization
|
10
10
|
- [x] runtime subscription additions
|
11
11
|
- [x] multiple queues
|
12
|
-
- [
|
13
|
-
- [ ] config options for URL/host/port/etc.
|
12
|
+
- [x] config options for URL/host/port/etc.
|
14
13
|
- [ ] config for restart behavior (default is to restart listening on any `StandardError`)
|
15
|
-
- [ ]
|
14
|
+
- [ ] `on_error` handler so you can send a response (what's standard?)
|
15
|
+
- [ ] support lifecycle callbacks (like `on_connect`, `on_disconnect`, etc.) provided by the `nats` gem
|
16
|
+
- [ ] ability to _request_ (not just reply)
|
16
17
|
|
17
18
|
## Installation
|
18
19
|
|
@@ -44,6 +45,8 @@ This gem also requires a NATS server to be installed and running before use. See
|
|
44
45
|
|
45
46
|
## Usage
|
46
47
|
|
48
|
+
<a id="starting-the-nats-server-section">
|
49
|
+
|
47
50
|
### Starting the NATS server
|
48
51
|
|
49
52
|
You'll need to start a NATS server before running your Ruby application. If you installed it via Docker, you might start it like so:
|
@@ -56,6 +59,57 @@ docker run -p 4222:4222 -p 8222:8222 -p 6222:6222 -ti nats:latest
|
|
56
59
|
|
57
60
|
> **NOTE:** For other methods of running a NATS server, see [the NATS documentation](https://docs.nats.io/nats-server/installation).
|
58
61
|
|
62
|
+
### Configuration
|
63
|
+
|
64
|
+
Use `Natsy::Config::set` to set configuration options. These options can either be set via a `Hash`/keyword arguments passed to the `::set` method, or set by invoking the method with a block and assigning your options to the yielded `Natsy::Config::Options` instance.
|
65
|
+
|
66
|
+
This README will use the following two syntaxes interchangably; remember that they do **exactly the same thing:**
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
Natsy::Config.set(
|
70
|
+
urls: ["nats://foo.bar:4567", "nats://foo.bar:5678"],
|
71
|
+
default_queue: "foobar",
|
72
|
+
logger: Rails.logger,
|
73
|
+
)
|
74
|
+
```
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
Natsy::Config.set do |options|
|
78
|
+
options.urls = ["nats://foo.bar:4567", "nats://foo.bar:5678"]
|
79
|
+
options.default_queue = "foobar"
|
80
|
+
options.logger = Rails.logger
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
The following options are available:
|
85
|
+
|
86
|
+
- `url`: A single URL string (including protocol, domain, and port) which points to the relevant NATS server (see [here](#setting-nats-server-url-section) for more info)
|
87
|
+
- `urls`: An array of URL strings in case you need to listen to multiple NATS servers (see [here](#setting-nats-server-url-section) for more info)
|
88
|
+
- `logger`: A logger where `natsy` can write helpful information (see [here](#logging-section) for more info)
|
89
|
+
- `default_queue`: The default queue that your application should fall back to if none is given in a more specific context (see [here](#default-queue-section) for more info)
|
90
|
+
|
91
|
+
<a id="setting-nats-server-url-section"></a>
|
92
|
+
|
93
|
+
### Setting the NATS server URL(s)
|
94
|
+
|
95
|
+
Set the URL/URLs at which your NATS server mediates messages.
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
Natsy::Config.set do |options|
|
99
|
+
options.url = "nats://foo.bar:4567"
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
Natsy::Config.set do |options|
|
105
|
+
options.urls = ["nats://foo.bar:4567", "nats://foo.bar:5678"]
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
> **NOTE:** If no `url`/`urls` option is specified, `natsy` will fall back on the default NATS server URL, which is `nats://localhost:4222`.
|
110
|
+
|
111
|
+
<a id="logging-section"></a>
|
112
|
+
|
59
113
|
### Logging
|
60
114
|
|
61
115
|
#### Attaching a logger
|
@@ -66,23 +120,24 @@ Attach a logger to have `natsy` write out logs for messages received, responses
|
|
66
120
|
require 'natsy'
|
67
121
|
require 'logger'
|
68
122
|
|
69
|
-
|
70
|
-
nats_logger
|
71
|
-
|
72
|
-
|
123
|
+
Natsy::Config.set do |options|
|
124
|
+
nats_logger = Logger.new(STDOUT)
|
125
|
+
nats_logger.level = Logger::INFO
|
126
|
+
options.logger = nats_logger
|
127
|
+
end
|
73
128
|
```
|
74
129
|
|
75
130
|
In a Rails application, you might do this instead:
|
76
131
|
|
77
132
|
```ruby
|
78
|
-
Natsy::
|
133
|
+
Natsy::Config.set(logger: Rails.logger)
|
79
134
|
```
|
80
135
|
|
81
136
|
#### Log levels
|
82
137
|
|
83
138
|
The following will be logged at the specified log levels
|
84
139
|
|
85
|
-
- `DEBUG`: Lifecycle events (starting NATS listeners, stopping NATS, reply registration,
|
140
|
+
- `DEBUG`: Lifecycle events (starting NATS listeners, stopping NATS, reply registration, etc.), as well as everything under `INFO`, `WARN`, and `ERROR`
|
86
141
|
- `INFO`: Message activity over NATS (received a message, replied with a message, etc.), as well as everything under `WARN` and `ERROR`
|
87
142
|
- `WARN`: Error handled gracefully (listening restarted due to some exception, etc.), as well as everything under `ERROR`
|
88
143
|
- `ERROR`: Some exception was raised in-thread (error in handler, error in subscription, etc.)
|
@@ -94,13 +149,13 @@ The following will be logged at the specified log levels
|
|
94
149
|
Set a default queue for subscriptions.
|
95
150
|
|
96
151
|
```ruby
|
97
|
-
Natsy::
|
152
|
+
Natsy::Config.set(default_queue: "foobar")
|
98
153
|
```
|
99
154
|
|
100
|
-
Leave the
|
155
|
+
Leave the `default_queue` blank (or assign `nil`) to use no default queue.
|
101
156
|
|
102
157
|
```ruby
|
103
|
-
Natsy::
|
158
|
+
Natsy::Config.set(default_queue: nil)
|
104
159
|
```
|
105
160
|
|
106
161
|
<a id="reply-to-section"></a>
|
@@ -153,15 +208,29 @@ The following should be enough to start a `natsy` setup in your Ruby application
|
|
153
208
|
require 'natsy'
|
154
209
|
require 'logger'
|
155
210
|
|
156
|
-
|
157
|
-
nats_logger
|
211
|
+
Natsy::Config.set do |options|
|
212
|
+
nats_logger = Logger.new(STDOUT)
|
213
|
+
nats_logger.level = Logger::DEBUG
|
158
214
|
|
159
|
-
|
160
|
-
|
215
|
+
options.logger = nats_logger
|
216
|
+
options.urls = ["nats://foo.bar:4567", "nats://foo.bar:5678"]
|
217
|
+
options.default_queue = "foobar"
|
218
|
+
end
|
161
219
|
|
162
|
-
Natsy::Client.reply_to("some.subject")
|
163
|
-
|
164
|
-
|
220
|
+
Natsy::Client.reply_to("some.subject") do |data|
|
221
|
+
"Got it! #{data.inspect}"
|
222
|
+
end
|
223
|
+
|
224
|
+
Natsy::Client.reply_to("some.*.pattern") do |data, subject|
|
225
|
+
"Got #{data} on #{subject}"
|
226
|
+
end
|
227
|
+
|
228
|
+
Natsy::Client.reply_to("subject.in.queue", queue: "barbaz") do
|
229
|
+
{
|
230
|
+
msg: "My turn!",
|
231
|
+
turn: 5,
|
232
|
+
}
|
233
|
+
end
|
165
234
|
|
166
235
|
Natsy::Client.start!
|
167
236
|
```
|
@@ -172,7 +241,7 @@ Natsy::Client.start!
|
|
172
241
|
|
173
242
|
Create controller classes which inherit from `Natsy::Controller` in order to give your message listeners some structure.
|
174
243
|
|
175
|
-
Use the `::default_queue` macro to set a default queue string. If omitted, the controller will fall back on the global default queue assigned
|
244
|
+
Use the `::default_queue` macro to set a default queue string. If omitted, the controller will fall back on the global default queue assigned to `Natsy::Config::default_queue` (as described [here](#default-queue-section)). If no default queue is set in either the controller or globally, then the default queue will be blank. Set the default queue to `nil` in a controller to fall back to the global default queue.
|
176
245
|
|
177
246
|
Use the `::subject` macro to create a block for listening to that subject segment. Nested calls to `::subject` will append each subsequent subject/pattern string to the last (joined by a periods). There is no limit to the level of nesting.
|
178
247
|
|
@@ -208,9 +277,13 @@ class HelloController < Natsy::Controller
|
|
208
277
|
end
|
209
278
|
|
210
279
|
subject "hows" do
|
211
|
-
|
280
|
+
# The queue at this point is "foobar"
|
281
|
+
subject "*", queue: "barbaz" do # Override the default queue at any point
|
282
|
+
# The queue at this point is "barbaz" (!)
|
212
283
|
subject "doing" do
|
213
|
-
|
284
|
+
# The queue at this point is "barbaz"
|
285
|
+
response queue: "bazbam" do |data, subject|
|
286
|
+
# The queue at this point is "bazbam" (!)
|
214
287
|
# The subject at this point is "hows.<wildcard>.doing" (i.e., the
|
215
288
|
# subjects "hows.jack.doing" and "hows.jill.doing" will both match)
|
216
289
|
sender_name = data["name"]
|
@@ -229,8 +302,7 @@ end
|
|
229
302
|
> For example: in a Rails project (assuming you have your NATS controllers in a directory called `app/nats/`), you may want to put something like the following in an initializer (such as `config/initializers/nats.rb`):
|
230
303
|
>
|
231
304
|
> ```ruby
|
232
|
-
> Natsy::
|
233
|
-
> Natsy::Client.default_queue = "foobar"
|
305
|
+
> Natsy::Config.set(logger: Rails.logger, default_queue: "foobar")
|
234
306
|
>
|
235
307
|
> # ...
|
236
308
|
>
|
@@ -267,7 +339,7 @@ bin/console
|
|
267
339
|
|
268
340
|
### Run the tests
|
269
341
|
|
270
|
-
To run the RSpec test suites, run:
|
342
|
+
To run the RSpec test suites, first [start the NATS server](#starting-the-nats-server-section). Then, run the tests:
|
271
343
|
|
272
344
|
```bash
|
273
345
|
bundle exec rake spec
|
@@ -285,6 +357,12 @@ rake spec
|
|
285
357
|
bundle exec rubocop
|
286
358
|
```
|
287
359
|
|
360
|
+
...or (if your Ruby setup has good defaults) just this:
|
361
|
+
|
362
|
+
```bash
|
363
|
+
rubocop
|
364
|
+
```
|
365
|
+
|
288
366
|
### Create a release
|
289
367
|
|
290
368
|
Bump the `Natsy::VERSION` value in `lib/natsy/version.rb`, commit, and then run:
|
data/lib/natsy.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require "nats/client"
|
4
4
|
require_relative "natsy/version"
|
5
5
|
require_relative "natsy/utils"
|
6
|
+
require_relative "natsy/config"
|
6
7
|
require_relative "natsy/client"
|
7
8
|
require_relative "natsy/controller"
|
8
9
|
|
@@ -14,4 +15,7 @@ module Natsy
|
|
14
15
|
|
15
16
|
# New subscription has been added at runtime
|
16
17
|
class NewSubscriptionsError < Natsy::Error; end
|
18
|
+
|
19
|
+
# Invalid options have been provided to +Natsy::Config+
|
20
|
+
class InvalidConfigError < Natsy::Error; end
|
17
21
|
end
|
data/lib/natsy/client.rb
CHANGED
@@ -10,50 +10,6 @@ module Natsy
|
|
10
10
|
# most functionality if desired.
|
11
11
|
class Client
|
12
12
|
class << self
|
13
|
-
# Optional logger for lifecycle events, messages received, etc.
|
14
|
-
attr_reader :logger
|
15
|
-
|
16
|
-
# Optional default queue for message subscription and replies.
|
17
|
-
attr_reader :default_queue
|
18
|
-
|
19
|
-
# Attach a logger to have +natsy+ write out logs for messages
|
20
|
-
# received, responses sent, errors raised, lifecycle events, etc.
|
21
|
-
#
|
22
|
-
# @example
|
23
|
-
# require 'natsy'
|
24
|
-
# require 'logger'
|
25
|
-
#
|
26
|
-
# nats_logger = Logger.new(STDOUT)
|
27
|
-
# nats_logger.level = Logger::INFO
|
28
|
-
#
|
29
|
-
# Natsy::Client.logger = nats_logger
|
30
|
-
#
|
31
|
-
# In a Rails application, you might do this instead:
|
32
|
-
#
|
33
|
-
# @example
|
34
|
-
# Natsy::Client.logger = Rails.logger
|
35
|
-
#
|
36
|
-
def logger=(some_logger)
|
37
|
-
@logger = some_logger
|
38
|
-
log("Set the logger to #{@logger.inspect}")
|
39
|
-
end
|
40
|
-
|
41
|
-
# Set a default queue for subscriptions.
|
42
|
-
#
|
43
|
-
# @example
|
44
|
-
# Natsy::Client.default_queue = "foobar"
|
45
|
-
#
|
46
|
-
# Leave the +::default_queue+ blank (or assign +nil+) to use no default
|
47
|
-
# queue.
|
48
|
-
#
|
49
|
-
# @example
|
50
|
-
# Natsy::Client.default_queue = nil
|
51
|
-
#
|
52
|
-
def default_queue=(some_queue)
|
53
|
-
@default_queue = Utils.presence(some_queue.to_s)
|
54
|
-
log("Setting the default queue to #{@default_queue || '(none)'}", level: :debug)
|
55
|
-
end
|
56
|
-
|
57
13
|
# Returns +true+ if +::start!+ has already been called (meaning the client
|
58
14
|
# is listening to NATS messages). Returns +false+ if it has not yet been
|
59
15
|
# called, or if it has been stopped.
|
@@ -72,8 +28,9 @@ module Natsy
|
|
72
28
|
# method. Pass a subject string as the first argument (either a static
|
73
29
|
# subject string or a pattern to match more than one subject). Specify a
|
74
30
|
# queue (or don't) with the +queue:+ option. If you don't provide the
|
75
|
-
# +queue:+ option, it will be set to the value of
|
76
|
-
# +nil+ (no queue) if a default
|
31
|
+
# +queue:+ option, it will be set to the value of
|
32
|
+
# +Natsy::Config::default_queue+, or to +nil+ (no queue) if a default
|
33
|
+
# queue hasn't been set.
|
77
34
|
#
|
78
35
|
# The result of the given block will be published in reply to the message.
|
79
36
|
# The block is passed two arguments when a message matching the subject is
|
@@ -101,7 +58,7 @@ module Natsy
|
|
101
58
|
# end
|
102
59
|
#
|
103
60
|
def reply_to(subject, queue: nil, &block)
|
104
|
-
queue = Utils.presence(queue) || default_queue
|
61
|
+
queue = Utils.presence(queue) || Config.default_queue
|
105
62
|
queue_desc = " in queue '#{queue}'" if queue
|
106
63
|
log("Registering a reply handler for subject '#{subject}'#{queue_desc}", level: :debug)
|
107
64
|
register_reply!(subject: subject.to_s, handler: block, queue: queue.to_s)
|
@@ -145,44 +102,54 @@ module Natsy
|
|
145
102
|
|
146
103
|
started!
|
147
104
|
|
148
|
-
|
105
|
+
thread = Thread.new do
|
149
106
|
Thread.handle_interrupt(StandardError => :never) do
|
150
107
|
Thread.handle_interrupt(StandardError => :immediate) { listen }
|
151
108
|
rescue NATS::ConnectError => e
|
152
109
|
log("Could not connect to NATS server:", level: :error)
|
153
110
|
log(e.full_message, level: :error, indent: 2)
|
154
111
|
Thread.current.exit
|
155
|
-
rescue NewSubscriptionsError =>
|
112
|
+
rescue NewSubscriptionsError => _e
|
156
113
|
log("New subscriptions! Restarting...", level: :info)
|
157
114
|
restart!
|
158
|
-
|
115
|
+
Thread.current.exit
|
116
|
+
# raise e # TODO: there has to be a better way
|
159
117
|
rescue StandardError => e
|
160
118
|
log("Encountered an error:", level: :error)
|
161
119
|
log(e.full_message, level: :error, indent: 2)
|
162
120
|
restart!
|
163
|
-
|
121
|
+
Thread.current.exit
|
122
|
+
# raise e
|
164
123
|
end
|
165
124
|
end
|
125
|
+
|
126
|
+
threads << thread
|
166
127
|
end
|
167
128
|
|
168
|
-
|
129
|
+
# **USE WITH CAUTION:** This method (+::reset!+) clears all subscriptions,
|
130
|
+
# stops listening (if started), and kills any active threads.
|
131
|
+
def reset!
|
132
|
+
replies.clear
|
133
|
+
stop!
|
134
|
+
kill!
|
135
|
+
end
|
169
136
|
|
170
|
-
|
137
|
+
private
|
171
138
|
|
172
|
-
def
|
173
|
-
|
139
|
+
def threads
|
140
|
+
@threads ||= []
|
141
|
+
end
|
174
142
|
|
175
|
-
|
176
|
-
|
177
|
-
|
143
|
+
def current_thread
|
144
|
+
threads.last
|
145
|
+
end
|
178
146
|
|
179
|
-
|
180
|
-
|
181
|
-
end
|
147
|
+
def log(text, level: :info, indent: 0)
|
148
|
+
Utils.log(Config.logger, text, level: level, indent: indent)
|
182
149
|
end
|
183
150
|
|
184
151
|
def kill!
|
185
|
-
|
152
|
+
threads.each { |thread| thread.kill if thread.alive? }
|
186
153
|
end
|
187
154
|
|
188
155
|
def stop!
|
@@ -200,6 +167,7 @@ module Natsy
|
|
200
167
|
def restart!
|
201
168
|
log("Restarting NATS", level: :warn)
|
202
169
|
stop!
|
170
|
+
kill!
|
203
171
|
start!
|
204
172
|
end
|
205
173
|
|
@@ -228,7 +196,7 @@ module Natsy
|
|
228
196
|
reply = {
|
229
197
|
subject: subject,
|
230
198
|
handler: handler,
|
231
|
-
queue: Utils.presence(queue) || default_queue,
|
199
|
+
queue: Utils.presence(queue) || Config.default_queue,
|
232
200
|
}
|
233
201
|
|
234
202
|
replies << reply
|
@@ -237,30 +205,43 @@ module Natsy
|
|
237
205
|
end
|
238
206
|
|
239
207
|
def listen
|
240
|
-
NATS.start do
|
208
|
+
NATS.start(servers: Natsy::Config.urls) do
|
241
209
|
replies.each do |replier|
|
242
210
|
queue_desc = " in queue '#{replier[:queue]}'" if replier[:queue]
|
243
211
|
log("Subscribing to subject '#{replier[:subject]}'#{queue_desc}", level: :debug)
|
244
212
|
|
245
|
-
NATS.subscribe(replier[:subject], queue: replier[:queue]) do |message,
|
246
|
-
parsed_message =
|
247
|
-
|
213
|
+
NATS.subscribe(replier[:subject], queue: replier[:queue]) do |message, reply_subject, subject|
|
214
|
+
parsed_message = begin
|
215
|
+
JSON.parse(message)
|
216
|
+
rescue StandardError
|
217
|
+
message
|
218
|
+
end
|
219
|
+
|
220
|
+
id, data, pattern = if parsed_message.is_a?(Hash)
|
221
|
+
parsed_message.values_at("id", "data", "pattern")
|
222
|
+
else
|
223
|
+
[nil, parsed_message, nil]
|
224
|
+
end
|
225
|
+
|
226
|
+
message_data = id && data && pattern ? data : parsed_message
|
248
227
|
|
249
228
|
log("Received a message!")
|
250
229
|
message_desc = <<~LOG_MESSAGE
|
251
230
|
id: #{id || '(none)'}
|
252
231
|
pattern: #{pattern || '(none)'}
|
253
232
|
subject: #{subject || '(none)'}
|
254
|
-
data: #{
|
255
|
-
inbox: #{
|
233
|
+
data: #{message_data.to_json}
|
234
|
+
inbox: #{reply_subject || '(none)'}
|
235
|
+
queue: #{replier[:queue] || '(none)'}
|
236
|
+
message: #{message}
|
256
237
|
LOG_MESSAGE
|
257
238
|
log(message_desc, indent: 2)
|
258
239
|
|
259
|
-
|
240
|
+
raw_response = replier[:handler].call(message_data, subject)
|
260
241
|
|
261
|
-
log("Responding with '#{
|
242
|
+
log("Responding with '#{raw_response}'")
|
262
243
|
|
263
|
-
NATS.publish(
|
244
|
+
NATS.publish(reply_subject, raw_response.to_json) if Utils.present?(reply_subject)
|
264
245
|
end
|
265
246
|
end
|
266
247
|
end
|
data/lib/natsy/config.rb
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ostruct"
|
4
|
+
require_relative "./utils"
|
5
|
+
|
6
|
+
module Natsy
|
7
|
+
# Represents the configuration options for +Natsy+. Configuration options are
|
8
|
+
# set using the `Natsy::Config::set` method, either as arguments or by using
|
9
|
+
# the appropriate setters on the object passed to the block.
|
10
|
+
class Config
|
11
|
+
# A +Natsy::Config::Options+ object is passed as a single argument to the
|
12
|
+
# block (if provided) for the +Natsy::Config::set+ method. This class should
|
13
|
+
# probably *NOT* be instantiated directly; instead, set the relevant options
|
14
|
+
# using +Natsy::Config.set(some_option: "...", some_other_option: "...")+.
|
15
|
+
# If you find yourself instantiating this class directly, there's probably a
|
16
|
+
# better way to do what you're trying to do.
|
17
|
+
class Options
|
18
|
+
# Specify a NATS server URL (or multiple URLs)
|
19
|
+
#
|
20
|
+
# **NOTE:** The following two examples do exactly the same thing.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# Natsy::Config.set(url: "nats://foo.bar:4567"))
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# Natsy::Config.set do |options|
|
27
|
+
# options.url = "nats://foo.bar:4567"
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# **NOTE:** The following two examples do exactly the same thing.
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# Natsy::Config.set(urls: ["nats://foo.bar:4567", "nats://foo.bar:5678"])
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# Natsy::Config.set do |options|
|
37
|
+
# options.urls = ["nats://foo.bar:4567", "nats://foo.bar:5678"]
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# If left blank/omitted, +natsy+ will fall back on the default URL, which
|
41
|
+
# is +nats://localhost:4222+.
|
42
|
+
#
|
43
|
+
attr_accessor :url, :urls
|
44
|
+
|
45
|
+
# Attach a logger to have +natsy+ write out logs for messages
|
46
|
+
# received, responses sent, errors raised, lifecycle events, etc.
|
47
|
+
#
|
48
|
+
# **NOTE:** The following two examples do exactly the same thing.
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# require 'natsy'
|
52
|
+
# require 'logger'
|
53
|
+
#
|
54
|
+
# nats_logger = Logger.new(STDOUT)
|
55
|
+
# nats_logger.level = Logger::INFO
|
56
|
+
#
|
57
|
+
# Natsy::Config.set(logger: nats_logger)
|
58
|
+
#
|
59
|
+
# @example
|
60
|
+
# require 'natsy'
|
61
|
+
# require 'logger'
|
62
|
+
#
|
63
|
+
# Natsy::Config.set do |options|
|
64
|
+
# nats_logger = Logger.new(STDOUT)
|
65
|
+
# nats_logger.level = Logger::INFO
|
66
|
+
#
|
67
|
+
# options.logger = nats_logger
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
#
|
71
|
+
# In a Rails application, you might do this instead:
|
72
|
+
#
|
73
|
+
# @example
|
74
|
+
# Natsy::Config.set(logger: Rails.logger)
|
75
|
+
#
|
76
|
+
attr_reader :logger
|
77
|
+
|
78
|
+
# {include:Natsy::Config::Options#logger}
|
79
|
+
def logger=(new_logger)
|
80
|
+
@logger = new_logger
|
81
|
+
Utils.log(@logger, "Set the logger to #{@logger.inspect}", level: :debug)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Set a default queue for subscriptions.
|
85
|
+
#
|
86
|
+
# **NOTE:** The following two examples do exactly the same thing.
|
87
|
+
#
|
88
|
+
# @example
|
89
|
+
# Natsy::Config.set(default_queue: "foobar")
|
90
|
+
#
|
91
|
+
# @example
|
92
|
+
# Natsy::Config.set do |options|
|
93
|
+
# options.default_queue = "foobar"
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# Leave the +::default_queue+ blank (or assign +nil+) to use no default
|
97
|
+
# queue.
|
98
|
+
#
|
99
|
+
# **NOTE:** The following two examples do exactly the same thing.
|
100
|
+
#
|
101
|
+
# @example
|
102
|
+
# Natsy::Config.set(default_queue: nil)
|
103
|
+
#
|
104
|
+
# @example
|
105
|
+
# Natsy::Config.set do |options|
|
106
|
+
# options.default_queue = nil
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
attr_reader :default_queue
|
110
|
+
|
111
|
+
# {include:Natsy::Config::Options#default_queue}
|
112
|
+
def default_queue=(new_queue)
|
113
|
+
@default_queue = Utils.presence(new_queue.to_s)
|
114
|
+
Utils.log(logger, "Setting the default queue to #{@default_queue || '(none)'}", level: :debug)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns ONLY the config options THAT HAVE BEEN SET as a +Hash+. Will not
|
118
|
+
# have keys for properties that are unassigned, but will have keys for
|
119
|
+
# properties assigned +nil+.
|
120
|
+
def to_h
|
121
|
+
hash = {}
|
122
|
+
hash[:url] = url if defined?(@url)
|
123
|
+
hash[:urls] = urls if defined?(@urls)
|
124
|
+
hash[:logger] = logger if defined?(@logger)
|
125
|
+
hash[:default_queue] = default_queue if defined?(@default_queue)
|
126
|
+
hash
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Valid option keys that can be given to +Natsy::Config::set+, either in a
|
131
|
+
# +Hash+ passed to the method, keyword arguments passed to the method, or by
|
132
|
+
# using setters on the +Natsy::Config::Options+ object passed to the block.
|
133
|
+
VALID_OPTIONS = %i[
|
134
|
+
url
|
135
|
+
urls
|
136
|
+
logger
|
137
|
+
default_queue
|
138
|
+
].freeze
|
139
|
+
|
140
|
+
# The default NATS server URL (used if none is configured)
|
141
|
+
DEFAULT_URL = "nats://localhost:4222"
|
142
|
+
|
143
|
+
class << self
|
144
|
+
# Specify configuration options, either by providing them as keyword
|
145
|
+
# arguments or by using a block. Should you choose to set options using
|
146
|
+
# a block, it will be passed a single argument (an instance of
|
147
|
+
# +Natsy::Config::Options+). You can set any options on the instance that
|
148
|
+
# you see fit.
|
149
|
+
#
|
150
|
+
# **NOTE:** The following two examples do exactly the same thing.
|
151
|
+
#
|
152
|
+
# @example
|
153
|
+
# Natsy::Config.set(
|
154
|
+
# urls: ["nats://foo.bar:4567", "nats://foo.bar:5678"],
|
155
|
+
# default_queue: "foobar",
|
156
|
+
# logger: Rails.logger,
|
157
|
+
# )
|
158
|
+
#
|
159
|
+
# @example
|
160
|
+
# Natsy::Config.set do |options|
|
161
|
+
# options.urls = ["nats://foo.bar:4567", "nats://foo.bar:5678"]
|
162
|
+
# options.default_queue = "foobar"
|
163
|
+
# options.logger = Rails.logger
|
164
|
+
# end
|
165
|
+
#
|
166
|
+
def set(keyword_options = {})
|
167
|
+
new_hash_options = (keyword_options || {}).transform_keys(&:to_sym)
|
168
|
+
|
169
|
+
invalid_config = lambda do |detail, keys|
|
170
|
+
raise InvalidConfigError, "Invalid options provided #{detail}: #{keys.join(', ')}"
|
171
|
+
end
|
172
|
+
|
173
|
+
invalid_keys = invalid_option_keys(new_hash_options)
|
174
|
+
invalid_config.call("as arguments", invalid_keys) if invalid_keys.any?
|
175
|
+
|
176
|
+
# Want to take advantage of the setters on +Natsy::Config::Options+...
|
177
|
+
new_hash_options_object = new_hash_options.each_with_object(Options.new) do |(key, value), options|
|
178
|
+
options.send(:"#{key}=", value)
|
179
|
+
end
|
180
|
+
|
181
|
+
given_options.merge!(new_hash_options_object.to_h)
|
182
|
+
|
183
|
+
new_block_options_object = Options.new
|
184
|
+
yield(new_block_options_object) if block_given?
|
185
|
+
|
186
|
+
invalid_keys = invalid_option_keys(new_block_options_object)
|
187
|
+
invalid_config.call("in block", invalid_keys) if invalid_keys.any?
|
188
|
+
|
189
|
+
given_options.merge!(new_block_options_object.to_h)
|
190
|
+
end
|
191
|
+
|
192
|
+
# The NATS server URLs that +natsy+ should listen on.
|
193
|
+
#
|
194
|
+
# See also: {Natsy::Config::Options#urls=}
|
195
|
+
#
|
196
|
+
def urls
|
197
|
+
given_url_list = [given_options[:url]].flatten
|
198
|
+
given_urls_list = [given_options[:urls]].flatten
|
199
|
+
all_given_urls = [*given_url_list, *given_urls_list].compact.uniq
|
200
|
+
Utils.presence(all_given_urls) || [DEFAULT_URL]
|
201
|
+
end
|
202
|
+
|
203
|
+
# The logger that +natsy+ should use to write out logs for messages
|
204
|
+
# received, responses sent, errors raised, lifecycle events, etc.
|
205
|
+
#
|
206
|
+
# See also: {Natsy::Config::Options#logger=}
|
207
|
+
#
|
208
|
+
def logger
|
209
|
+
Utils.presence(given_options[:logger])
|
210
|
+
end
|
211
|
+
|
212
|
+
# The default queue that +natsy+ should use for subscriptions.
|
213
|
+
#
|
214
|
+
# See also: {Natsy::Config::Options#default_queue=}
|
215
|
+
#
|
216
|
+
def default_queue
|
217
|
+
Utils.presence(given_options[:default_queue])
|
218
|
+
end
|
219
|
+
|
220
|
+
# Returns all config options as a +Hash+.
|
221
|
+
def to_h
|
222
|
+
{
|
223
|
+
urls: urls,
|
224
|
+
logger: logger,
|
225
|
+
default_queue: default_queue,
|
226
|
+
}
|
227
|
+
end
|
228
|
+
|
229
|
+
# Alias for {Natsy::Config::to_h}.
|
230
|
+
def as_json(*_args)
|
231
|
+
to_h
|
232
|
+
end
|
233
|
+
|
234
|
+
# Serialize the configuration into a JSON object string.
|
235
|
+
def to_json(*_args)
|
236
|
+
to_h.to_json
|
237
|
+
end
|
238
|
+
|
239
|
+
# Reset the configuration to default values.
|
240
|
+
def reset!
|
241
|
+
@given_options = nil
|
242
|
+
end
|
243
|
+
|
244
|
+
private
|
245
|
+
|
246
|
+
def given_options
|
247
|
+
@given_options ||= {}
|
248
|
+
end
|
249
|
+
|
250
|
+
def invalid_option_keys(options)
|
251
|
+
options.to_h.keys - VALID_OPTIONS
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
data/lib/natsy/controller.rb
CHANGED
@@ -25,18 +25,17 @@ module Natsy
|
|
25
25
|
# end
|
26
26
|
#
|
27
27
|
# If omitted, the controller will fall back on the global default queue
|
28
|
-
# assigned with +Natsy::
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
# blank for that controller.
|
28
|
+
# assigned with +Natsy::Config::set+. If no default queue is set in either
|
29
|
+
# the controller or globally, then the default queue will be blank. Set
|
30
|
+
# the default queue to +nil+ in a controller to fall back to the global
|
31
|
+
# default queue.
|
33
32
|
#
|
34
33
|
def default_queue(some_queue = NO_QUEUE_GIVEN)
|
35
34
|
# +NO_QUEUE_GIVEN+ is a special symbol (rather than +nil+) so that the
|
36
35
|
# default queue can be "unset" to +nil+ (given a non-+nil+ global
|
37
|
-
# default set with +Natsy::Client::
|
36
|
+
# default set with +Natsy::Client::set+).
|
38
37
|
if some_queue == NO_QUEUE_GIVEN
|
39
|
-
@default_queue ||
|
38
|
+
@default_queue || Config.default_queue
|
40
39
|
else
|
41
40
|
@default_queue = Utils.presence(some_queue.to_s)
|
42
41
|
end
|
data/lib/natsy/utils.rb
CHANGED
@@ -15,6 +15,20 @@ module Natsy
|
|
15
15
|
def presence(value)
|
16
16
|
present?(value) ? value : nil
|
17
17
|
end
|
18
|
+
|
19
|
+
def log(logger, text, level: :info, indent: 0)
|
20
|
+
return unless logger
|
21
|
+
|
22
|
+
timestamp = Time.now.to_s
|
23
|
+
text_lines = text.split("\n")
|
24
|
+
indentation = indent.is_a?(String) ? indent : (" " * indent)
|
25
|
+
|
26
|
+
text_lines.each do |line|
|
27
|
+
logger.send(level, "[#{timestamp}] Natsy | #{indentation}#{line}")
|
28
|
+
end
|
29
|
+
|
30
|
+
nil
|
31
|
+
end
|
18
32
|
end
|
19
33
|
end
|
20
34
|
end
|
data/lib/natsy/version.rb
CHANGED
data/natsy.gemspec
CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
|
30
30
|
spec.add_development_dependency "bundler", "~> 2.2"
|
31
|
+
spec.add_development_dependency "pry"
|
31
32
|
spec.add_development_dependency "rake", "~> 13.0"
|
32
33
|
spec.add_development_dependency "rspec", "~> 3.0"
|
33
34
|
spec.add_development_dependency "rubocop", "~> 1.10"
|
@@ -35,7 +36,6 @@ Gem::Specification.new do |spec|
|
|
35
36
|
spec.add_development_dependency "rubocop-rake", "~> 0.5"
|
36
37
|
spec.add_development_dependency "rubocop-rspec", "~> 2.2"
|
37
38
|
spec.add_development_dependency "solargraph"
|
38
|
-
spec.add_development_dependency "pry"
|
39
39
|
|
40
40
|
spec.add_runtime_dependency "nats", "~> 0.11"
|
41
41
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: natsy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3
|
4
|
+
version: 0.4.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Keegan Leitz
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-05-
|
11
|
+
date: 2021-05-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '2.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rake
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,20 +136,6 @@ dependencies:
|
|
122
136
|
- - ">="
|
123
137
|
- !ruby/object:Gem::Version
|
124
138
|
version: '0'
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: pry
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - ">="
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: '0'
|
132
|
-
type: :development
|
133
|
-
prerelease: false
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - ">="
|
137
|
-
- !ruby/object:Gem::Version
|
138
|
-
version: '0'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: nats
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -161,7 +161,6 @@ files:
|
|
161
161
|
- ".gitignore"
|
162
162
|
- ".rspec"
|
163
163
|
- ".rubocop.yml"
|
164
|
-
- CHANGELOG.md
|
165
164
|
- Gemfile
|
166
165
|
- LICENSE.txt
|
167
166
|
- README.md
|
@@ -170,6 +169,7 @@ files:
|
|
170
169
|
- bin/setup
|
171
170
|
- lib/natsy.rb
|
172
171
|
- lib/natsy/client.rb
|
172
|
+
- lib/natsy/config.rb
|
173
173
|
- lib/natsy/controller.rb
|
174
174
|
- lib/natsy/utils.rb
|
175
175
|
- lib/natsy/version.rb
|
@@ -178,7 +178,7 @@ homepage: https://github.com/openbay/natsy
|
|
178
178
|
licenses:
|
179
179
|
- MIT
|
180
180
|
metadata:
|
181
|
-
documentation_uri: https://www.rubydoc.info/gems/natsy/0.3
|
181
|
+
documentation_uri: https://www.rubydoc.info/gems/natsy/0.4.3
|
182
182
|
post_install_message:
|
183
183
|
rdoc_options: []
|
184
184
|
require_paths:
|