natsy 0.3.0 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|