routemaster-client 2.1.0 → 3.0.0

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