routemaster-client 2.1.0 → 3.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4ab7db72b3f45ca016af6ddfcd31727c08448d04
4
- data.tar.gz: 864cc421186071bf194c01c230f778016ab4011c
3
+ metadata.gz: 17d6f2b6dd28fd082771df65f57fd41f8cbbc2f6
4
+ data.tar.gz: a97e8c84d7c1a193e64fefbf2a55a18805d65aec
5
5
  SHA512:
6
- metadata.gz: d48921d988693488e7aed1b03b22526a88cd320eb85ce97976c32b0b0458fd79703043a0ef9f3e82971a9afcc3712f6427ce55c7aacc3ce5b00af81dd15ca24e
7
- data.tar.gz: 856f4e0a2f340dbbe9d5c8b13e39fea9026b94ce48f53ec81bbca6da1ba5d860fd4460339cbd5fefdf3f43129cc51bc63130b81aa5e4132a2a84ee350bd34573
6
+ metadata.gz: 2b8fa2d106cc69d3cb0edc75eb430d8c659a81b2686b92b6501b3789f006dbf0e92a7e4900361f2461090cb4f2560267db2d016b0f6c3d554c1171e94127e897
7
+ data.tar.gz: 66d7c256ae0ada32ea8afed4590bcd4a99df183791a8193a08249ea97f76d9586be067bc322f47b435bde09f90d5f88779a711121c1dc29b1df84746cec532ef
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- routemaster-client (2.1.0)
4
+ routemaster-client (3.0.0)
5
5
  faraday (>= 0.9.0)
6
6
  oj (~> 2.17)
7
7
  typhoeus (~> 1.1)
data/README.md CHANGED
@@ -25,27 +25,42 @@ Or install it yourself as:
25
25
 
26
26
  ```ruby
27
27
  require 'routemaster/client'
28
- client = Routemaster::Client.new(url: 'https://bus.example.com', uuid: 'demo')
28
+ Routemaster::Client.configure do |config|
29
+ config.url = 'https://bs.example.com'
30
+ config.uuid = 'demo'
31
+ end
29
32
  ```
30
33
 
31
34
  You can also specify a timeout value in seconds if you like with the ```timeout``` option.
32
35
 
33
36
  ```ruby
34
- Routemaster::Client.new(url: 'https://bus.example.com', uuid: 'demo', timeout: 2)
37
+ Routemaster::Client.configure do |config|
38
+ config.url = 'https://bs.example.com'
39
+ config.uuid = 'demo'
40
+ config.timeout = 2
41
+ end
35
42
  ```
36
43
 
37
44
  If you are using Sidekiq in your project, you can specify the usage of a Sidekiq backend, where event sending will be processed asynchronously.
38
45
 
39
46
  ```ruby
40
- Routemaster::Client.new(url, 'https://bus.example.com', uuid: 'demo', backend_type: Routemaster::Client::Backends::Sidekiq)
47
+ Routemaster::Client.configure do |config|
48
+ config.url = 'https://bs.example.com'
49
+ config.uuid = 'demo'
50
+ config.timeout = 2
51
+ config.async_backend = Routemaster::Client::Backends::Sidekiq.configure do |sidekiq|
52
+ sidekiq.queue = :realtime
53
+ sidekiq.retry = false
54
+ end
55
+ end
41
56
  ```
42
57
 
43
58
  **Push** an event about an entity in the topic `widgets` with a callback URL:
44
59
 
45
60
  ```ruby
46
- client.created('widgets', 'https://app.example.com/widgets/1')
47
- client.updated('widgets', 'https://app.example.com/widgets/2')
48
- client.noop('widgets', 'https://app.example.com/widgets/3')
61
+ Routemaster::Client.created('widgets', 'https://app.example.com/widgets/1')
62
+ Routemaster::Client.updated('widgets', 'https://app.example.com/widgets/2')
63
+ Routemaster::Client.noop('widgets', 'https://app.example.com/widgets/3')
49
64
  ```
50
65
 
51
66
  There are methods for the four canonical event types: `created`, `updated`,
@@ -59,14 +74,24 @@ A timestamp argument may be passed (it will be set by the bus automatically
59
74
  otherwise); it must be an integer number of milliseconds since the UNIX Epoch:
60
75
 
61
76
  ```ruby
62
- client.created('widgets', 'https://app.example.com/widgets/1', 1473080555409)
77
+ Routemaster::Client.created('widgets', 'https://app.example.com/widgets/1', 1473080555409)
63
78
  ```
64
79
 
80
+ **Async**
81
+ You can also push events asynchronously if you have an async backend defined, for each
82
+ event method there is a corresponding `event_async` method. eg
83
+ ```ruby
84
+ Routemaster::Client.updated_async('widgets', 'https://app.example.com/widgets/2')
85
+ ```
86
+
87
+ You cannot use these methods without defining an async backend, if you try then an error will
88
+ be raised.
89
+
65
90
  **Subscribe** to be notified about `widgets` and `kitten` at most 60 seconds after
66
91
  events, in batches of at most 500 events, to a given callback URL:
67
92
 
68
93
  ```ruby
69
- client.subscribe(
94
+ Routemaster::Client.subscribe(
70
95
  topics: ['widgets', 'kitten'],
71
96
  callback: 'https://app.example.com/events',
72
97
  uuid: 'demo',
@@ -102,29 +127,29 @@ gem](https://github.com/krisleech/wisper#wisper).
102
127
  **Unsubscribe** from a single topic:
103
128
 
104
129
  ```ruby
105
- client.unsubscribe('widgets')
130
+ Routemaster::Client.unsubscribe('widgets')
106
131
  ```
107
132
 
108
133
  **Unsubscribe** from all topics:
109
134
 
110
135
  ```ruby
111
- client.unsubscribe_all
136
+ Routemaster::Client.unsubscribe_all
112
137
  ```
113
138
 
114
139
  **Delete** a topic (only possible if you're the emitter for this topic):
115
140
 
116
141
  ```ruby
117
- client.delete_topic('widgets')
142
+ Routemaster::Client.delete_topic('widgets')
118
143
  ```
119
144
 
120
145
 
121
146
  **Monitor** the status of topics and subscriptions:
122
147
 
123
148
  ```ruby
124
- client.monitor_topics
149
+ Routemaster::Client.monitor_topics
125
150
  #=> [ #<Routemaster::Topic:XXXX @name="widgets", @publisher="demo", @events=12589>, ...]
126
151
 
127
- client.monitor_subscriptions
152
+ Routemaster::Client.monitor_subscriptions
128
153
  #=> [ {
129
154
  # subscriber: 'bob',
130
155
  # callback: 'https://app.example.com/events',
@@ -1,6 +1,9 @@
1
- require 'routemaster/client/backends'
1
+ require 'routemaster/client/backends/synchronous'
2
2
  require 'routemaster/client/connection'
3
+ require 'routemaster/client/configuration'
3
4
  require 'routemaster/client/version'
5
+ require 'routemaster/client/errors'
6
+ require 'routemaster/client/assertion_helpers'
4
7
  require 'routemaster/topic'
5
8
  require 'uri'
6
9
  require 'json'
@@ -10,150 +13,174 @@ require 'typhoeus/adapters/faraday'
10
13
  require 'oj'
11
14
 
12
15
  module Routemaster
13
- class Client
16
+ module Client
17
+ class << self
18
+ extend Forwardable
19
+ include AssertionHelpers
20
+
21
+ def_delegator :'Routemaster::Client::Configuration', :async_backend
22
+ def_delegator :'Routemaster::Client::Configuration', :lazy
23
+
24
+ def configure
25
+ self.tap do
26
+ Configuration.configure do |c|
27
+ yield c
28
+ end
29
+ _check_pulse! unless lazy
30
+ end
31
+ end
14
32
 
15
- def initialize(options = {})
16
- @_options = options.tap do |o|
17
- o[:timeout] ||= 1
18
- o[:backend_type] ||= Backends::Synchronous
33
+ def created(topic, callback, timestamp = nil)
34
+ _send_event('create', topic, callback, timestamp)
19
35
  end
20
36
 
21
- @_backend_type = @_options.fetch(:backend_type)
37
+ def created_async(topic, callback, timestamp = nil)
38
+ _send_event('create', topic, callback, timestamp, async: true)
39
+ end
22
40
 
23
- _assert_valid_url(@_options[:url])
24
- _assert_valid_backend_type(@_backend_type)
25
- _assert (@_options[:uuid] =~ /^[a-z0-9_-]{1,64}$/), 'uuid should be alpha'
26
- _assert_valid_timeout(@_options[:timeout])
41
+ def updated(topic, callback, timestamp = nil)
42
+ _send_event('update', topic, callback, timestamp)
43
+ end
27
44
 
45
+ def updated_async(topic, callback, timestamp = nil)
46
+ _send_event('update', topic, callback, timestamp, async: true)
47
+ end
28
48
 
29
- unless @_options[:lazy]
30
- _conn.get('/pulse').tap do |response|
31
- raise 'cannot connect to bus' unless response.success?
32
- end
49
+ def deleted(topic, callback, timestamp = nil)
50
+ _send_event('delete', topic, callback, timestamp)
33
51
  end
34
- end
35
52
 
36
- def created(topic, callback, timestamp = nil)
37
- _send_event('create', topic, callback, timestamp)
38
- end
53
+ def deleted_async(topic, callback, timestamp = nil)
54
+ _send_event('delete', topic, callback, timestamp, async: true)
55
+ end
39
56
 
40
- def updated(topic, callback, timestamp = nil)
41
- _send_event('update', topic, callback, timestamp)
42
- end
57
+ def noop(topic, callback, timestamp = nil)
58
+ _send_event('noop', topic, callback, timestamp)
59
+ end
43
60
 
44
- def deleted(topic, callback, timestamp = nil)
45
- _send_event('delete', topic, callback, timestamp)
46
- end
61
+ def noop_async(topic, callback, timestamp = nil)
62
+ _send_event('noop', topic, callback, timestamp, async: true)
63
+ end
47
64
 
48
- def noop(topic, callback, timestamp = nil)
49
- _send_event('noop', topic, callback, timestamp)
50
- end
65
+ def subscribe(topics:, callback:, **options)
66
+ _assert_valid_topics!(topics)
67
+ _assert_valid_url!(callback)
68
+ _assert_valid_max_events! options[:max] if options[:max]
69
+ _assert_valid_timeout! options[:timeout] if options[:timeout]
51
70
 
52
- def subscribe(options = {})
53
- if (options.keys - [:topics, :callback, :timeout, :max, :uuid]).any?
54
- raise ArgumentError.new('bad options')
71
+ _conn.subscribe(topics: topics, callback: callback, **options)
55
72
  end
56
- _assert options[:topics].kind_of?(Enumerable), 'topics required'
57
- _assert options[:callback], 'callback required'
58
- _assert_valid_timeout options[:timeout] if options[:timeout]
59
- _assert_valid_max_events options[:max] if options[:max]
60
73
 
61
- options[:topics].each { |t| _assert_valid_topic(t) }
62
- _assert_valid_url(options[:callback])
63
- _conn.subscribe(options)
64
- end
74
+ def unsubscribe(*topics)
75
+ _assert_valid_topics!(topics)
65
76
 
66
- def unsubscribe(*topics)
67
- topics.each { |t| _assert_valid_topic(t) }
77
+ topics.each do |t|
78
+ response = _conn.delete("/subscriber/topics/#{t}")
68
79
 
69
- topics.each do |t|
70
- response = _conn.delete("/subscriber/topics/#{t}")
80
+ unless response.success?
81
+ raise 'unsubscribe rejected'
82
+ end
83
+ end
84
+ end
85
+
86
+ def unsubscribe_all
87
+ response = _conn.delete('/subscriber')
71
88
 
72
89
  unless response.success?
73
- raise 'unsubscribe rejected'
90
+ raise 'unsubscribe all rejected'
74
91
  end
75
92
  end
76
- end
77
93
 
78
- def unsubscribe_all
79
- response = _conn.delete('/subscriber')
94
+ def delete_topic(topic)
95
+ _assert_valid_topic!(topic)
96
+
97
+ response = _conn.delete("/topics/#{topic}")
80
98
 
81
- unless response.success?
82
- raise 'unsubscribe all rejected'
99
+ unless response.success?
100
+ raise 'failed to delete topic'
101
+ end
83
102
  end
84
- end
85
103
 
86
- def delete_topic(topic)
87
- _assert_valid_topic(topic)
104
+ def monitor_topics
105
+ response = _conn.get('/topics') do |r|
106
+ r.headers['Content-Type'] = 'application/json'
107
+ end
88
108
 
89
- response = _conn.delete("/topics/#{topic}")
109
+ unless response.success?
110
+ raise 'failed to connect to /topics'
111
+ end
90
112
 
91
- unless response.success?
92
- raise 'failed to delete topic'
113
+ Oj.load(response.body).map do |raw_topic|
114
+ Topic.new raw_topic
115
+ end
93
116
  end
94
- end
95
117
 
96
- def monitor_topics
97
- response = _conn.get('/topics') do |r|
98
- r.headers['Content-Type'] = 'application/json'
118
+ private
119
+
120
+ def _conn
121
+ Connection
99
122
  end
100
123
 
101
- unless response.success?
102
- raise 'failed to connect to /topics'
124
+ def _synchronous_backend
125
+ Routemaster::Client::Backends::Synchronous
103
126
  end
104
127
 
105
- Oj.load(response.body).map do |raw_topic|
106
- Topic.new raw_topic
128
+ def _assert_valid_url!(url)
129
+ assert_valid_url_throwing_error!(url, InvalidArgumentError)
107
130
  end
108
- end
109
131
 
110
- private
132
+ def _assert_valid_max_events!(max)
133
+ unless (1..10_000).include?(max)
134
+ raise InvalidArgumentError, "max events '#{max}' is invalid, must be between 1 and 10,000"
135
+ end
136
+ end
111
137
 
112
- def _assert_valid_timeout(timeout)
113
- _assert (0..3_600_000).include?(timeout), 'bad timeout'
114
- end
138
+ def _assert_valid_topics!(topics)
139
+ unless topics.kind_of? Enumerable
140
+ raise InvalidArgumentError, "topics must be a list"
141
+ end
115
142
 
116
- def _assert_valid_max_events(max)
117
- _assert (0..10_000).include?(max), 'bad max # events'
118
- end
143
+ unless topics.length > 0
144
+ raise InvalidArgumentError "topics must contain at least one element"
145
+ end
119
146
 
120
- def _assert_valid_url(url)
121
- uri = URI.parse(url)
122
- _assert (uri.scheme == 'https'), 'HTTPS required'
123
- return url
124
- end
147
+ topics.each { |t| _assert_valid_topic!(t) }
148
+ end
125
149
 
126
- def _assert_valid_topic(topic)
127
- _assert (topic =~ /^[a-z_]{1,64}$/), 'bad topic name: must only include letters and underscores'
128
- end
150
+ def _assert_valid_topic!(topic)
151
+ unless topic =~ /^[a-z_]{1,64}$/
152
+ raise InvalidArgumentError, 'bad topic name: must only include letters and underscores'
153
+ end
154
+ end
129
155
 
130
- def _assert_valid_timestamp(timestamp)
131
- _assert timestamp.kind_of?(Integer), 'not an integer'
132
- end
156
+ def _assert_valid_timestamp!(timestamp)
157
+ unless timestamp.kind_of? Integer
158
+ raise InvalidArgumentError, "timestamp '#{timestamp}' is invalid, must be an integer"
159
+ end
160
+ end
133
161
 
134
- def _assert_valid_backend_type(backend_type)
135
- backends = Backends::NAMES
136
- _assert backends.include?(backend_type.to_s), "unknown backend type, must be one of #{backends.map{ |w| "Routemaster::Backends::#{w}" }.join(", ")}"
137
- backend_type
138
- end
162
+ def _assert_valid_timeout!(timeout)
163
+ unless (0..3_600_000).include? timeout
164
+ raise InvalidArgumentError, "timeout '#{timeout}' is invalid, must be an integer between 0 and 3,600,000"
165
+ end
166
+ end
139
167
 
140
- def _send_event(event, topic, callback, timestamp = nil)
141
- _assert_valid_url(callback)
142
- _assert_valid_topic(topic)
143
- _assert_valid_timestamp(timestamp) if timestamp
144
- _backend.send_event(event, topic, callback, timestamp)
145
- end
168
+ def _send_event(event, topic, callback, timestamp = nil, async: false)
169
+ _assert_valid_url!(callback)
170
+ _assert_valid_topic!(topic)
171
+ _assert_valid_timestamp!(timestamp) if timestamp
146
172
 
147
- def _assert(condition, message)
148
- condition or raise ArgumentError.new(message)
149
- end
173
+ backend = async ? async_backend : _synchronous_backend
174
+ backend.send_event(event, topic, callback, timestamp)
175
+ end
150
176
 
151
- def _conn
152
- @_conn ||= Client::Connection.new(@_options)
153
- end
177
+ def _check_pulse!
178
+ _conn.get('/pulse').tap do |response|
179
+ raise 'cannot connect to bus' unless response.success?
180
+ end
181
+ end
154
182
 
155
- def _backend
156
- @_worker ||= @_backend_type.configure(@_options)
183
+ private :async_backend, :lazy
157
184
  end
158
185
  end
159
186
  end
@@ -0,0 +1,18 @@
1
+ require 'uri'
2
+
3
+ module Routemaster
4
+ module Client
5
+ module AssertionHelpers
6
+ def assert_valid_url_throwing_error!(url, error_class)
7
+ begin
8
+ uri = URI.parse(url)
9
+ unless uri.is_a? URI::HTTPS
10
+ raise error_class, "url '#{url}' is invalid, must be an https url"
11
+ end
12
+ rescue URI::InvalidURIError
13
+ raise error_class, "url '#{url}' is invalid, must be an https url"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ require 'routemaster/client/errors'
2
+
3
+ module Routemaster
4
+ module Client
5
+ module Backends
6
+ class MissingAsynchronous
7
+ class << self
8
+ def send_event(*)
9
+ raise MissingAsyncBackendError
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end