spacebunny 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +190 -0
- data/Rakefile +6 -0
- data/assets/logo.png +0 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/examples/device/auto_config_publish.rb +87 -0
- data/examples/device/manual_config.rb +83 -0
- data/examples/device/publish_with_confirm.rb +85 -0
- data/examples/device/receive_messages.rb +109 -0
- data/examples/device/tls_connection.rb +65 -0
- data/examples/live_stream/receive_messages.rb +128 -0
- data/examples/live_stream/tls_connection.rb +22 -0
- data/lib/spacebunny.rb +23 -0
- data/lib/spacebunny/device/amqp.rb +158 -0
- data/lib/spacebunny/device/base.rb +229 -0
- data/lib/spacebunny/device/message.rb +56 -0
- data/lib/spacebunny/endpoint_connection.rb +128 -0
- data/lib/spacebunny/exceptions.rb +143 -0
- data/lib/spacebunny/live_stream/amqp.rb +124 -0
- data/lib/spacebunny/live_stream/base.rb +198 -0
- data/lib/spacebunny/live_stream/message.rb +38 -0
- data/lib/spacebunny/logger.rb +35 -0
- data/lib/spacebunny/utils.rb +185 -0
- data/lib/spacebunny/version.rb +3 -0
- data/spacebunny.gemspec +27 -0
- metadata +157 -0
@@ -0,0 +1,229 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'http'
|
3
|
+
|
4
|
+
module Spacebunny
|
5
|
+
module Device
|
6
|
+
|
7
|
+
# Proxy to Base.new
|
8
|
+
def self.new(*args)
|
9
|
+
Amqp.new *args
|
10
|
+
end
|
11
|
+
|
12
|
+
class Base
|
13
|
+
attr_accessor :key, :api_endpoint, :auto_recover, :raise_on_error, :id, :name, :host, :secret, :vhost, :channels
|
14
|
+
attr_reader :log_to, :log_level, :logger, :custom_connection_configs, :auto_connection_configs,
|
15
|
+
:connection_configs, :auto_configs, :tls, :tls_cert, :tls_key, :tls_ca_certificates, :verify_peer
|
16
|
+
|
17
|
+
def initialize(protocol, *args)
|
18
|
+
@protocol = protocol
|
19
|
+
@custom_connection_configs = {}
|
20
|
+
@auto_connection_configs = {}
|
21
|
+
options = args.extract_options.deep_symbolize_keys
|
22
|
+
key = args.first
|
23
|
+
|
24
|
+
@key = key || options[:key]
|
25
|
+
@api_endpoint = options[:api_endpoint] || {}
|
26
|
+
|
27
|
+
extract_custom_connection_configs_from options
|
28
|
+
set_channels options[:channels]
|
29
|
+
|
30
|
+
@raise_on_error = options[:raise_on_error]
|
31
|
+
@log_to = options[:log_to] || STDOUT
|
32
|
+
@log_level = options[:log_level] || ::Logger::WARN
|
33
|
+
@logger = options[:logger] || build_logger
|
34
|
+
end
|
35
|
+
|
36
|
+
def api_endpoint=(options)
|
37
|
+
unless options.is_a? Hash
|
38
|
+
raise ArgumentError, 'api_endpoint must be an Hash. See doc for further info'
|
39
|
+
end
|
40
|
+
@api_endpoint = options.deep_symbolize_keys
|
41
|
+
end
|
42
|
+
|
43
|
+
def connection_configs
|
44
|
+
return @connection_configs if @connection_configs
|
45
|
+
if auto_configure?
|
46
|
+
# If key is specified, retrieve configs from APIs endpoint
|
47
|
+
@auto_configs = EndpointConnection.new(@api_endpoint.merge(key: @key)).configs
|
48
|
+
normalize_and_add_channels @auto_configs[:channels]
|
49
|
+
@auto_connection_configs = normalize_auto_connection_configs
|
50
|
+
end
|
51
|
+
# Build final connection_configs
|
52
|
+
@connection_configs = merge_connection_configs
|
53
|
+
# Check for required params presence
|
54
|
+
check_connection_configs
|
55
|
+
@connection_configs
|
56
|
+
end
|
57
|
+
|
58
|
+
def connect
|
59
|
+
logger.warn "connect method must be implemented on class responsibile to handle protocol '#{@protocol}'"
|
60
|
+
end
|
61
|
+
|
62
|
+
def connection_options=(options)
|
63
|
+
unless options.is_a? Hash
|
64
|
+
raise ArgumentError, 'connection_options must be an Hash. See doc for further info'
|
65
|
+
end
|
66
|
+
extract_custom_connection_configs_from options.with_indifferent_access
|
67
|
+
end
|
68
|
+
|
69
|
+
def disconnect
|
70
|
+
@connection_configs = nil
|
71
|
+
end
|
72
|
+
|
73
|
+
# Stub method: must be implemented on the class responsible to handle the protocol
|
74
|
+
def publish(channel, message, options = {})
|
75
|
+
logger.warn "publish method must be implemented on class responsibile to handle protocol '#{@protocol}'"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Stub method: must be implemented on the class responsible to handle the protocol
|
79
|
+
def on_receive(options = {}, &block)
|
80
|
+
logger.warn "on_receive method must be implemented on class responsibile to handle protocol '#{@protocol}'"
|
81
|
+
end
|
82
|
+
|
83
|
+
def auto_recover
|
84
|
+
connection_configs[:auto_recover]
|
85
|
+
end
|
86
|
+
|
87
|
+
def id
|
88
|
+
connection_configs[:device_id]
|
89
|
+
end
|
90
|
+
|
91
|
+
def name
|
92
|
+
connection_configs[:device_name]
|
93
|
+
end
|
94
|
+
|
95
|
+
def host
|
96
|
+
connection_configs[:host]
|
97
|
+
end
|
98
|
+
|
99
|
+
def secret
|
100
|
+
connection_configs[:secret]
|
101
|
+
end
|
102
|
+
|
103
|
+
def vhost
|
104
|
+
connection_configs[:vhost]
|
105
|
+
end
|
106
|
+
|
107
|
+
protected
|
108
|
+
|
109
|
+
# @protected
|
110
|
+
def auto_configure?
|
111
|
+
!@key.nil?
|
112
|
+
end
|
113
|
+
|
114
|
+
def with_channel_check(name)
|
115
|
+
unless res = channels.include?(name)
|
116
|
+
logger.warn <<-MSG
|
117
|
+
|
118
|
+
You're going to publish on channel '#{name}', but it does not appear a configured channel.
|
119
|
+
If using auto-configuration (device-key) associate the channel to device '#{@auto_configs[:connection][:name]}'
|
120
|
+
from web interface.
|
121
|
+
If providing manual configuration, please specify channels list through the :channels option
|
122
|
+
or through given setter, e.g. client.channels = [:first_channel, :second_channel, ... ])
|
123
|
+
|
124
|
+
MSG
|
125
|
+
end
|
126
|
+
if block_given?
|
127
|
+
yield
|
128
|
+
else
|
129
|
+
res
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
# @private
|
136
|
+
# Check if channels are an array
|
137
|
+
def set_channels(channels)
|
138
|
+
if channels && !channels.is_a?(Array)
|
139
|
+
raise ChannelsMustBeAnArray
|
140
|
+
end
|
141
|
+
normalize_and_add_channels(channels)
|
142
|
+
end
|
143
|
+
|
144
|
+
# @private
|
145
|
+
# Check for required params presence
|
146
|
+
def check_connection_configs
|
147
|
+
raise DeviceIdMissing unless @connection_configs[:device_id]
|
148
|
+
end
|
149
|
+
|
150
|
+
# @private
|
151
|
+
# Merge auto_connection_configs and custom_connection_configs
|
152
|
+
def merge_connection_configs
|
153
|
+
auto_connection_configs.merge(custom_connection_configs) do |key, old_val, new_val|
|
154
|
+
if new_val.nil?
|
155
|
+
old_val
|
156
|
+
else
|
157
|
+
new_val
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# @private
|
163
|
+
def build_logger
|
164
|
+
logger = ::Logger.new(@log_to)
|
165
|
+
logger.level = normalize_log_level
|
166
|
+
logger.progname = 'Spacebunny'
|
167
|
+
Spacebunny.logger = logger
|
168
|
+
end
|
169
|
+
|
170
|
+
# @private
|
171
|
+
# Copy options to custom_connection_configs and normalize some of the attributes overwriting it
|
172
|
+
def extract_custom_connection_configs_from(options)
|
173
|
+
@custom_connection_configs = options
|
174
|
+
# Auto_recover from connection.close by default
|
175
|
+
@custom_connection_configs[:auto_recover] = @custom_connection_configs.delete(:auto_recover) || true
|
176
|
+
@custom_connection_configs[:host] = @custom_connection_configs.delete :host
|
177
|
+
if @custom_connection_configs[:protocols] && custom_connection_configs[:protocols][@protocol]
|
178
|
+
@custom_connection_configs[:port] = @custom_connection_configs[:protocols][@protocol].delete :port
|
179
|
+
@custom_connection_configs[:tls_port] = @custom_connection_configs[:protocols][@protocol].delete :tls_port
|
180
|
+
end
|
181
|
+
@custom_connection_configs[:vhost] = @custom_connection_configs.delete :vhost
|
182
|
+
@custom_connection_configs[:device_id] = @custom_connection_configs.delete :device_id
|
183
|
+
@custom_connection_configs[:device_name] = @custom_connection_configs.delete :device_name
|
184
|
+
@custom_connection_configs[:secret] = @custom_connection_configs.delete :secret
|
185
|
+
end
|
186
|
+
|
187
|
+
# @private
|
188
|
+
def normalize_and_add_channels(chs)
|
189
|
+
@channels = [] unless @channels
|
190
|
+
return unless chs
|
191
|
+
chs.each do |ch|
|
192
|
+
case ch
|
193
|
+
when Hash
|
194
|
+
@channels << ch[:name].to_sym
|
195
|
+
else
|
196
|
+
ch.to_sym
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# @private
|
202
|
+
# Translate from auto configs given by APIs endpoint to a common format
|
203
|
+
def normalize_auto_connection_configs
|
204
|
+
{
|
205
|
+
host: @auto_configs[:connection][:host],
|
206
|
+
port: @auto_configs[:connection][:protocols][@protocol][:port],
|
207
|
+
tls_port: @auto_configs[:connection][:protocols][@protocol][:tls_port],
|
208
|
+
vhost: @auto_configs[:connection][:vhost],
|
209
|
+
device_id: @auto_configs[:connection][:device_id],
|
210
|
+
device_name: @auto_configs[:connection][:device_name],
|
211
|
+
secret: @auto_configs[:connection][:secret]
|
212
|
+
}
|
213
|
+
end
|
214
|
+
|
215
|
+
# @private
|
216
|
+
def normalize_log_level
|
217
|
+
case @log_level
|
218
|
+
when :debug, ::Logger::DEBUG, 'debug' then ::Logger::DEBUG
|
219
|
+
when :info, ::Logger::INFO, 'info' then ::Logger::INFO
|
220
|
+
when :warn, ::Logger::WARN, 'warn' then ::Logger::WARN
|
221
|
+
when :error, ::Logger::ERROR, 'error' then ::Logger::ERROR
|
222
|
+
when :fatal, ::Logger::FATAL, 'fatal' then ::Logger::FATAL
|
223
|
+
else
|
224
|
+
Logger::WARN
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Spacebunny
|
2
|
+
module Device
|
3
|
+
class Message
|
4
|
+
attr_reader :device, :sender_id, :channel_name, :delivery_info, :metadata, :payload
|
5
|
+
|
6
|
+
def initialize(device, options, delivery_info, metadata, payload)
|
7
|
+
@device = device
|
8
|
+
@options = options
|
9
|
+
@delivery_info = delivery_info
|
10
|
+
@metadata = metadata
|
11
|
+
@payload = payload
|
12
|
+
|
13
|
+
extract_options
|
14
|
+
set_sender_id_and_channel
|
15
|
+
end
|
16
|
+
|
17
|
+
def ack(options = {})
|
18
|
+
multiple = options.fetch :multiple, false
|
19
|
+
@device.input_channel.acknowledge @delivery_info.delivery_tag, multiple
|
20
|
+
end
|
21
|
+
|
22
|
+
def nack(options = {})
|
23
|
+
multiple = options.fetch :multiple, false
|
24
|
+
requeue = options.fetch :requeue, false
|
25
|
+
@device.input_channel.nack @delivery_info.delivery_tag, multiple, requeue
|
26
|
+
end
|
27
|
+
|
28
|
+
def blacklisted?
|
29
|
+
# Discard packet if it has been sent from me
|
30
|
+
if @discard_mine && @device.id.eql?(@sender_id) && !from_api?
|
31
|
+
return true
|
32
|
+
end
|
33
|
+
# Discard packet if has been published from APIs
|
34
|
+
if @discard_from_api && from_api?
|
35
|
+
return true
|
36
|
+
end
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
def from_api?
|
41
|
+
!@metadata[:headers].nil? && @metadata[:headers]['x-from-sb-api']
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def extract_options
|
47
|
+
@discard_mine = @options.fetch :discard_mine, false
|
48
|
+
@discard_from_api = @options.fetch :discard_from_api, false
|
49
|
+
end
|
50
|
+
|
51
|
+
def set_sender_id_and_channel
|
52
|
+
@sender_id, @channel_name = @delivery_info[:routing_key].split('.')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'uri/http'
|
2
|
+
require 'uri/https'
|
3
|
+
|
4
|
+
# Handle retrieve of Device and LiveStream configs from APIs endpoint
|
5
|
+
|
6
|
+
module Spacebunny
|
7
|
+
class EndpointConnection
|
8
|
+
DEFAULT_OPTIONS = {
|
9
|
+
scheme: 'https',
|
10
|
+
host: 'api.spacebunny.io',
|
11
|
+
port: 443,
|
12
|
+
api_version: '/v1',
|
13
|
+
configs_path: {
|
14
|
+
device: '/device_configurations',
|
15
|
+
live_stream: '/live_stream_key_configurations'
|
16
|
+
}
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
attr_accessor :scheme, :host, :port, :api_version, :configs_path
|
20
|
+
|
21
|
+
def initialize(options = {})
|
22
|
+
unless options.is_a? Hash
|
23
|
+
fail ArgumentError, 'connection options must be an Hash'
|
24
|
+
end
|
25
|
+
options = merge_with_default options
|
26
|
+
@key = options[:key]
|
27
|
+
@client = options[:client]
|
28
|
+
@secret = options[:secret]
|
29
|
+
|
30
|
+
ensure_credentials_have_been_provided
|
31
|
+
# API endpoint params
|
32
|
+
@scheme = options[:scheme]
|
33
|
+
@host = options[:host]
|
34
|
+
@port = options[:port]
|
35
|
+
@api_version = options[:api_version]
|
36
|
+
@configs_path = options[:configs_path]
|
37
|
+
end
|
38
|
+
|
39
|
+
def configs
|
40
|
+
unless @configs
|
41
|
+
@configs = fetch
|
42
|
+
end
|
43
|
+
@configs
|
44
|
+
end
|
45
|
+
|
46
|
+
def configs_path
|
47
|
+
if @configs_path.is_a? Hash
|
48
|
+
if device?
|
49
|
+
@configs_path[:device]
|
50
|
+
else
|
51
|
+
@configs_path[:live_stream]
|
52
|
+
end
|
53
|
+
else
|
54
|
+
@configs_path
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Contact APIs endpoint to retrieve configs
|
59
|
+
def fetch
|
60
|
+
uri_builder = case scheme
|
61
|
+
when 'http'
|
62
|
+
URI::HTTP
|
63
|
+
when 'https'
|
64
|
+
URI::HTTPS
|
65
|
+
end
|
66
|
+
|
67
|
+
unless uri = uri_builder.build(host: host, port: port, path: "#{api_version}#{configs_path}")
|
68
|
+
raise SchemeNotValid.new(scheme)
|
69
|
+
end
|
70
|
+
|
71
|
+
response = contact_endpoint_with uri
|
72
|
+
content = JSON.parse(response, symbolize_names: true) rescue nil
|
73
|
+
status = response.status
|
74
|
+
if status != 200
|
75
|
+
if content
|
76
|
+
phrase = "Auto-configuration failed: #{response.status} => #{content[:error]}"
|
77
|
+
if status == 401
|
78
|
+
if device?
|
79
|
+
phrase = "#{phrase}. Is Device Key correct?"
|
80
|
+
else
|
81
|
+
phrase = "#{phrase} Are Client and Secret correct?"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
else
|
85
|
+
phrase = "#{response.status}"
|
86
|
+
end
|
87
|
+
raise EndpointError, phrase
|
88
|
+
end
|
89
|
+
content
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def ensure_credentials_have_been_provided
|
95
|
+
if !@key && !(@client && @secret)
|
96
|
+
raise DeviceKeyOrClientAndSecretRequired
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def device?
|
101
|
+
!@key.nil?
|
102
|
+
end
|
103
|
+
|
104
|
+
def contact_endpoint_with(uri)
|
105
|
+
if device?
|
106
|
+
request = HTTP.headers('Device-Key' => @key)
|
107
|
+
else
|
108
|
+
request = HTTP.headers('Live-Stream-Key-Client' => @client, 'Live-Stream-Key-Secret' => @secret)
|
109
|
+
end
|
110
|
+
request = request.headers(content_type: 'application/json', accept: 'application/json')
|
111
|
+
begin
|
112
|
+
request.get(uri.to_s)
|
113
|
+
rescue => e
|
114
|
+
logger.error e.message
|
115
|
+
logger.error e.backtrace.join "\n"
|
116
|
+
raise EndPointNotReachable
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def logger
|
121
|
+
Spacebunny.logger
|
122
|
+
end
|
123
|
+
|
124
|
+
def merge_with_default(options)
|
125
|
+
DEFAULT_OPTIONS.merge(options) { |key, old_val, new_val| new_val.nil? ? old_val : new_val }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Spacebunny
|
2
|
+
class DeviceKeyOrClientAndSecretRequired < Exception
|
3
|
+
def initialize(message = nil)
|
4
|
+
message = message || "A valid 'Api Key' or valid 'Client' and 'Secret' are required for auto-configuration"
|
5
|
+
super(message)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class DeviceKeyOrConfigurationsRequired < Exception
|
10
|
+
def initialize(message = nil)
|
11
|
+
message = message || 'Neither key or connection options provided!'
|
12
|
+
super(message)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class AckTypeError < Exception
|
17
|
+
def initialize(message = nil)
|
18
|
+
message = message || "Ack type not valid. Use one of #{Spacebunny::AmqpClient::ACK_TYPES.map{ |t| ":#{t}" }.join(', ')}"
|
19
|
+
super(message)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class BlockRequired < Exception
|
24
|
+
def initialize(message = nil)
|
25
|
+
message = message || 'block missing. Please provide a block'
|
26
|
+
super(message)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class ChannelsMustBeAnArray < Exception
|
31
|
+
def initialize
|
32
|
+
message = "channels option must be an Array. E.g. [:data, :alarms]"
|
33
|
+
super(message)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class ChannelNotExists < Exception
|
38
|
+
def initialize(channel = nil)
|
39
|
+
message = if channel
|
40
|
+
"Channel '#{channel}' does not exists. Is this channel enabled for the device or did you specified it on client initialization?"
|
41
|
+
else
|
42
|
+
'Channel does not exists'
|
43
|
+
end
|
44
|
+
super(message)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class ClientRequired < Exception
|
49
|
+
def initialize(message = nil)
|
50
|
+
message = message || "Missing mandatory 'client'. Spacebunny::LiveStream.new(:client => 'a_valid_client', :secret: 'a_valid_secret')"
|
51
|
+
super(message)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class LiveStreamFormatError < Exception
|
56
|
+
def initialize
|
57
|
+
message = "Live Stream not correctly formatted. It must be an Hash with at least 'name' and 'id' attributes"
|
58
|
+
super(message)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class LiveStreamNotFound < Exception
|
63
|
+
def initialize(name = nil)
|
64
|
+
message = if name
|
65
|
+
"Live Stream '#{name}' not found. Did you created and configured it?"
|
66
|
+
else
|
67
|
+
'Live Stream not found'
|
68
|
+
end
|
69
|
+
super(message)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class LiveStreamParamError < Exception
|
74
|
+
def initialize(live_stream_name, param_name)
|
75
|
+
live_stream_name = live_stream_name || 'no-name-provided'
|
76
|
+
"Live Stream '#{live_stream_name}' misses mandatory '#{param_name}' param"
|
77
|
+
super(message)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class SecretRequired < Exception
|
82
|
+
def initialize(message = nil)
|
83
|
+
message = message || "Missing mandatory 'secret' Spacebunny::LiveStream.new(:client => 'a_valid_client', :secret: 'a_valid_secret')"
|
84
|
+
super(message)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class ClientNotConnected < Exception
|
89
|
+
def initialize(message = nil)
|
90
|
+
message = message || 'Client not connected! Check internet connection'
|
91
|
+
super(message)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class ClientNotSetup < Exception
|
96
|
+
def initialize(message = nil)
|
97
|
+
message = message || "'Client not setup. Did you call 'connect'?'"
|
98
|
+
super(message)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class DeviceIdMissing < Exception
|
103
|
+
def initialize(message = nil)
|
104
|
+
message = message || "missing mandatory 'device_id' parameter. Please provide it on client initialization (see doc) or use auto-configuration"
|
105
|
+
super(message)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class EndpointError < Exception
|
110
|
+
def initialize(message = nil)
|
111
|
+
message = message || 'Error while contacting endpoint for auto-configuration'
|
112
|
+
super(message)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class EndPointNotReachable < Exception
|
117
|
+
def initialize(message = nil)
|
118
|
+
message = message || 'Endpoint not reachable'
|
119
|
+
super(message)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class ProtocolNotRegistered < Exception
|
124
|
+
def initialize(protocol)
|
125
|
+
message = "protocol #{protocol} is not registered"
|
126
|
+
super(message)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class SchemeNotValid < Exception
|
131
|
+
def initialize(scheme)
|
132
|
+
message = "Provided scheme #{scheme} is not valid"
|
133
|
+
super(message)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class StreamsMustBeAnArray < Exception
|
138
|
+
def initialize
|
139
|
+
message = "streams option must be an Array"
|
140
|
+
super(message)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|