ably-rest 0.7.1 → 0.7.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 +13 -5
- data/.gitmodules +1 -1
- data/.rspec +1 -0
- data/.travis.yml +7 -3
- data/SPEC.md +495 -419
- data/ably-rest.gemspec +19 -5
- data/lib/ably-rest.rb +9 -1
- data/lib/submodules/ably-ruby/.gitignore +6 -0
- data/lib/submodules/ably-ruby/.rspec +1 -0
- data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
- data/lib/submodules/ably-ruby/.travis.yml +10 -0
- data/lib/submodules/ably-ruby/Gemfile +4 -0
- data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
- data/lib/submodules/ably-ruby/README.md +122 -0
- data/lib/submodules/ably-ruby/Rakefile +34 -0
- data/lib/submodules/ably-ruby/SPEC.md +1794 -0
- data/lib/submodules/ably-ruby/ably.gemspec +36 -0
- data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
- data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
- data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
- data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
- data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
- data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
- data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
- data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
- data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
- data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
- data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
- data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
- data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
- data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
- data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
- data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
- data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
- data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
- data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
- data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
- data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
- data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
- data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
- data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
- data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
- data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
- data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
- data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
- data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
- metadata +182 -27
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module Ably
|
|
2
|
+
module Exceptions
|
|
3
|
+
# Base Ably exception class that contains status and code values used by Ably
|
|
4
|
+
# Refer to https://github.com/ably/ably-common/blob/master/protocol/errors.json
|
|
5
|
+
#
|
|
6
|
+
# @!attribute [r] message
|
|
7
|
+
# @return [String] Error message from Ably
|
|
8
|
+
# @!attribute [r] status
|
|
9
|
+
# @return [String] HTTP status code of error
|
|
10
|
+
# @!attribute [r] code
|
|
11
|
+
# @return [String] Ably specific error code
|
|
12
|
+
class BaseAblyException < StandardError
|
|
13
|
+
attr_reader :status, :code
|
|
14
|
+
def initialize(message, status = nil, code = nil)
|
|
15
|
+
super message
|
|
16
|
+
@status = status
|
|
17
|
+
@code = code
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# An invalid request was received by Ably
|
|
22
|
+
class InvalidRequest < BaseAblyException; end
|
|
23
|
+
|
|
24
|
+
# The token is invalid
|
|
25
|
+
class InvalidToken < BaseAblyException; end
|
|
26
|
+
|
|
27
|
+
# Ably Protocol message received that is invalid
|
|
28
|
+
class ProtocolError < BaseAblyException; end
|
|
29
|
+
|
|
30
|
+
# Encryption or Decryption failure
|
|
31
|
+
class CipherError < BaseAblyException; end
|
|
32
|
+
|
|
33
|
+
# Encoding or decoding failure
|
|
34
|
+
class EncoderError < BaseAblyException; end
|
|
35
|
+
|
|
36
|
+
# Connection error from Realtime or REST service
|
|
37
|
+
class ConnectionError < BaseAblyException
|
|
38
|
+
def initialize(message, status = nil, code = nil, base_error = nil)
|
|
39
|
+
super message, status, code
|
|
40
|
+
@base_error = base_error
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Connection Timeout accessing Realtime or REST service
|
|
45
|
+
class ConnectionTimeoutError < ConnectionError; end
|
|
46
|
+
|
|
47
|
+
# Invalid State Change error on a {https://github.com/gocardless/statesman Statesman State Machine}
|
|
48
|
+
class StateChangeError < BaseAblyException; end
|
|
49
|
+
|
|
50
|
+
# A generic Ably exception taht supports a status & code.
|
|
51
|
+
# See https://github.com/ably/ably-common/blob/master/protocol/errors.json for a list of Ably errors
|
|
52
|
+
class Standard < BaseAblyException; end
|
|
53
|
+
|
|
54
|
+
# The HTTP request has returned a 500 error
|
|
55
|
+
class ServerError < BaseAblyException; end
|
|
56
|
+
|
|
57
|
+
# PaginatedResource cannot retrieve the page
|
|
58
|
+
class InvalidPageError < BaseAblyException; end
|
|
59
|
+
|
|
60
|
+
# The expected response from the server was invalid
|
|
61
|
+
class InvalidResponseBody < BaseAblyException; end
|
|
62
|
+
|
|
63
|
+
# The request cannot be performed because it is insecure
|
|
64
|
+
class InsecureRequestError < BaseAblyException; end
|
|
65
|
+
|
|
66
|
+
# The token request could not be created
|
|
67
|
+
class TokenRequestError < BaseAblyException; end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
module Ably
|
|
2
|
+
# Logger unifies logging for #debug, #info, #warn, #error, and #fatal messages.
|
|
3
|
+
# A new Ably client uses this Logger and sets the appropriate log level.
|
|
4
|
+
# A custom Logger can be configured when instantiating the client, refer to the {Ably::Rest::Client} and {Ably::Realtime::Client} documentation
|
|
5
|
+
#
|
|
6
|
+
class Logger
|
|
7
|
+
extend Forwardable
|
|
8
|
+
|
|
9
|
+
# @param client [Ably::Rest::Client,Ably::Realtime::Client] Rest or Realtime Ably client
|
|
10
|
+
# @param log_level [Integer] {http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html Ruby Logger} log level
|
|
11
|
+
# @param custom_logger [nil,Object] A custom logger can optionally be used instead of the,
|
|
12
|
+
# however it must provide a {http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html Ruby Logger} compatible interface.
|
|
13
|
+
#
|
|
14
|
+
def initialize(client, log_level, custom_logger = nil)
|
|
15
|
+
@client = client
|
|
16
|
+
@custom_logger = custom_logger
|
|
17
|
+
@logger = custom_logger || default_logger
|
|
18
|
+
@log_level = log_level
|
|
19
|
+
|
|
20
|
+
ensure_logger_interface_is_valid
|
|
21
|
+
|
|
22
|
+
@logger.level = log_level
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The logger used by this class, defaults to {http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html Ruby Logger}
|
|
26
|
+
# @return {Object,Logger}
|
|
27
|
+
attr_reader :logger
|
|
28
|
+
|
|
29
|
+
# If a custom logger is being used with this Logger, this property is not nil
|
|
30
|
+
# @return {nil,Object}
|
|
31
|
+
attr_reader :custom_logger
|
|
32
|
+
|
|
33
|
+
# The log level ranging from DEBUG to FATAL, refer to http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html
|
|
34
|
+
# @return {Integer}
|
|
35
|
+
attr_reader :log_level
|
|
36
|
+
|
|
37
|
+
def_delegators :logger, :fatal, :error, :warn, :info, :debug
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
attr_reader :client
|
|
41
|
+
|
|
42
|
+
def color(color_value, string)
|
|
43
|
+
"\033[#{color_value}m#{string}\033[0m"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def red(string)
|
|
47
|
+
color(31, string)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def magenta(string)
|
|
51
|
+
color(35, string)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def cyan(string)
|
|
55
|
+
color(36, string)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def connection_id
|
|
59
|
+
if realtime?
|
|
60
|
+
if client.connection.id
|
|
61
|
+
"[#{cyan(client.connection.id)}] "
|
|
62
|
+
else
|
|
63
|
+
"[ #{cyan('--')} ] "
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def realtime?
|
|
69
|
+
defined?(Ably::Realtime::Client) && client.kind_of?(Ably::Realtime::Client)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def default_logger
|
|
73
|
+
::Logger.new(STDOUT).tap do |logger|
|
|
74
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
|
75
|
+
severity = ::Logger::SEV_LABEL.index(severity) if severity.kind_of?(String)
|
|
76
|
+
|
|
77
|
+
formatted_date = if severity == ::Logger::DEBUG
|
|
78
|
+
datetime.strftime("%H:%M:%S.%L")
|
|
79
|
+
else
|
|
80
|
+
datetime.strftime("%Y-%m-%d %H:%M:%S.%L")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
severity_label = if severity <= ::Logger::INFO
|
|
84
|
+
magenta(::Logger::SEV_LABEL[severity])
|
|
85
|
+
else
|
|
86
|
+
red(::Logger::SEV_LABEL[severity])
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
"#{formatted_date} #{severity_label} #{connection_id}#{msg}\n"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def ensure_logger_interface_is_valid
|
|
95
|
+
%w(fatal error warn info debug level level=).each do |method|
|
|
96
|
+
unless logger.respond_to?(method)
|
|
97
|
+
raise ArgumentError, "The custom Logger's interface does not provide the method '#{method}'"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Ably::Models
|
|
2
|
+
# An exception type encapsulating error information containing
|
|
3
|
+
# an Ably-specific error code and generic status code.
|
|
4
|
+
#
|
|
5
|
+
# @!attribute [r] message
|
|
6
|
+
# @return [String] Additional reason information, where available
|
|
7
|
+
# @!attribute [r] code
|
|
8
|
+
# @return [Integer] Ably error code (see ably-common/protocol/errors.json)
|
|
9
|
+
# @!attribute [r] status
|
|
10
|
+
# @return [Integer] HTTP Status Code corresponding to this error, where applicable
|
|
11
|
+
# @!attribute [r] hash
|
|
12
|
+
# @return [Hash] Access the protocol message Hash object ruby'fied to use symbolized keys
|
|
13
|
+
#
|
|
14
|
+
class ErrorInfo
|
|
15
|
+
include Ably::Modules::ModelCommon
|
|
16
|
+
|
|
17
|
+
def initialize(hash_object)
|
|
18
|
+
@raw_hash_object = hash_object
|
|
19
|
+
@hash_object = IdiomaticRubyWrapper(hash_object.clone.freeze)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
%w(message code status_code).each do |attribute|
|
|
23
|
+
define_method attribute do
|
|
24
|
+
hash[attribute.to_sym]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
alias_method :status, :status_code
|
|
28
|
+
|
|
29
|
+
def hash
|
|
30
|
+
@hash_object
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_s
|
|
34
|
+
"Error: #{message} (code: #{code}, http status: #{status})"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
require 'logger'
|
|
2
|
+
|
|
3
|
+
module Ably::Modules
|
|
4
|
+
module Conversions
|
|
5
|
+
private
|
|
6
|
+
# Creates or returns an {IdiomaticRubyWrapper} ensuring it never wraps itself
|
|
7
|
+
#
|
|
8
|
+
# @return {IdiomaticRubyWrapper}
|
|
9
|
+
def IdiomaticRubyWrapper(object, options = {})
|
|
10
|
+
case object
|
|
11
|
+
when Ably::Models::IdiomaticRubyWrapper
|
|
12
|
+
object
|
|
13
|
+
else
|
|
14
|
+
Ably::Models::IdiomaticRubyWrapper.new(object, options)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module Ably::Models
|
|
21
|
+
# Wraps Hash objects returned by Ably service to appear as Idiomatic Ruby Hashes with symbol keys
|
|
22
|
+
# It recursively wraps containing Hashes, but will stop wrapping at arrays, any other non Hash object, or any key matching the `:stops_at` options
|
|
23
|
+
# It also provides methods matching the symbolic keys for convenience
|
|
24
|
+
#
|
|
25
|
+
# @example
|
|
26
|
+
# ruby_hash = IdiomaticRubyWrapper.new({ 'keyValue' => 'true' })
|
|
27
|
+
# # or recommended to avoid wrapping wrapped objects
|
|
28
|
+
# ruby_hash = IdiomaticRubyWrapper({ 'keyValue' => 'true' })
|
|
29
|
+
#
|
|
30
|
+
# ruby_hash[:key_value] # => 'true'
|
|
31
|
+
# ruby_hash.key_value # => 'true'
|
|
32
|
+
# ruby_hash[:key_value] = 'new_value'
|
|
33
|
+
# ruby_hash.key_value # => 'new_value'
|
|
34
|
+
#
|
|
35
|
+
# ruby_hash[:none] # => nil
|
|
36
|
+
# ruby_hash.none # => nil
|
|
37
|
+
#
|
|
38
|
+
# @!attribute [r] stop_at
|
|
39
|
+
# @return [Array<Symbol,String>] array of keys that this wrapper should stop wrapping at to preserve the underlying Hash as is
|
|
40
|
+
#
|
|
41
|
+
class IdiomaticRubyWrapper
|
|
42
|
+
include Enumerable
|
|
43
|
+
include Ably::Modules::Conversions
|
|
44
|
+
include Ably::Modules::MessagePack
|
|
45
|
+
|
|
46
|
+
attr_reader :stop_at
|
|
47
|
+
|
|
48
|
+
# Creates an IdiomaticRubyWrapper around the mixed case Hash object
|
|
49
|
+
#
|
|
50
|
+
# @attribute [Hash] mixedCaseHashObject mixed case Hash object
|
|
51
|
+
# @attribute [Array<Symbol,String>] stop_at array of keys that this wrapper should stop wrapping at to preserve the underlying Hash as is
|
|
52
|
+
#
|
|
53
|
+
def initialize(mixedCaseHashObject, options = {})
|
|
54
|
+
stop_at = options.fetch(:stop_at, [])
|
|
55
|
+
|
|
56
|
+
if mixedCaseHashObject.kind_of?(IdiomaticRubyWrapper)
|
|
57
|
+
$stderr.puts "<IdiomaticRubyWrapper#initialize> WARNING: Wrapping a IdiomaticRubyWrapper with another IdiomaticRubyWrapper"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
@hash = mixedCaseHashObject
|
|
61
|
+
@stop_at = Array(stop_at).each_with_object({}) do |key, hash|
|
|
62
|
+
hash[convert_to_snake_case_symbol(key)] = true
|
|
63
|
+
end.freeze
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def [](key)
|
|
67
|
+
value = hash[source_key_for(key)]
|
|
68
|
+
if stop_at?(key) || !value.kind_of?(Hash)
|
|
69
|
+
value
|
|
70
|
+
else
|
|
71
|
+
IdiomaticRubyWrapper.new(value, stop_at: stop_at)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def []=(key, value)
|
|
76
|
+
hash[source_key_for(key)] = value
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def fetch(key, default = nil)
|
|
80
|
+
if has_key?(key)
|
|
81
|
+
self[key]
|
|
82
|
+
else
|
|
83
|
+
if default
|
|
84
|
+
default
|
|
85
|
+
elsif block_given?
|
|
86
|
+
yield key
|
|
87
|
+
else
|
|
88
|
+
raise KeyError, "key not found: #{key}"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def size
|
|
94
|
+
hash.size
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def keys
|
|
98
|
+
map { |key, value| key }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def values
|
|
102
|
+
map { |key, value| value }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def has_key?(key)
|
|
106
|
+
hash.has_key?(source_key_for(key))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Method ensuring this {IdiomaticRubyWrapper} is {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
|
|
110
|
+
def each
|
|
111
|
+
return to_enum(:each) unless block_given?
|
|
112
|
+
|
|
113
|
+
hash.each do |key, value|
|
|
114
|
+
key = convert_to_snake_case_symbol(key)
|
|
115
|
+
value = self[key]
|
|
116
|
+
yield key, value
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Compare object based on Hash equivalent
|
|
121
|
+
def ==(other)
|
|
122
|
+
return false unless other.kind_of?(self.class) || other.kind_of?(Hash)
|
|
123
|
+
|
|
124
|
+
other = other.to_hash if other.kind_of?(self.class)
|
|
125
|
+
to_hash == other
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def method_missing(method_sym, *arguments)
|
|
129
|
+
key = method_sym.to_s.gsub(%r{=$}, '')
|
|
130
|
+
return super if !has_key?(key)
|
|
131
|
+
|
|
132
|
+
if method_sym.to_s.match(%r{=$})
|
|
133
|
+
raise ArgumentError, "Cannot set #{method_sym} with more than one argument" unless arguments.length == 1
|
|
134
|
+
self[key] = arguments.first
|
|
135
|
+
else
|
|
136
|
+
raise ArgumentError, "Cannot pass an argument to #{method_sym} when retrieving its value" unless arguments.empty?
|
|
137
|
+
self[method_sym]
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Access to the raw Hash object provided to the constructer of this wrapper
|
|
142
|
+
def hash
|
|
143
|
+
@hash
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Takes the underlying Hash object and returns it in as a JSON ready Hash object using snakeCase for compability with the Ably service.
|
|
147
|
+
# Note name clashes are ignored and will result in loss of one or more values
|
|
148
|
+
# @example
|
|
149
|
+
# wrapper = IdiomaticRubyWrapper({ 'mixedCase': true, mixed_case: false, 'snake_case': 1 })
|
|
150
|
+
# wrapper.as_json({ 'mixedCase': true, 'snakeCase': 1 })
|
|
151
|
+
def as_json(*args)
|
|
152
|
+
hash.each_with_object({}) do |key_val, new_hash|
|
|
153
|
+
key, val = key_val
|
|
154
|
+
mixed_case_key = convert_to_mixed_case(key)
|
|
155
|
+
wrapped_val = self[key]
|
|
156
|
+
wrapped_val = wrapped_val.as_json(args) if wrapped_val.kind_of?(IdiomaticRubyWrapper)
|
|
157
|
+
|
|
158
|
+
new_hash[mixed_case_key] = wrapped_val
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Converts the current wrapped mixedCase object to JSON
|
|
163
|
+
# using mixedCase syntax as expected by the Realtime API
|
|
164
|
+
def to_json(*args)
|
|
165
|
+
as_json(args).to_json
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Generate a symbolized Hash object representing the underlying Hash in a Ruby friendly format.
|
|
169
|
+
# Note name clashes are ignored and will result in loss of one or more values
|
|
170
|
+
# @example
|
|
171
|
+
# wrapper = IdiomaticRubyWrapper({ 'mixedCase': true, mixed_case: false, 'snake_case': 1 })
|
|
172
|
+
# wrapper.to_hash({ mixed_case: true, snake_case: 1 })
|
|
173
|
+
def to_hash(*args)
|
|
174
|
+
each_with_object({}) do |key_val, hash|
|
|
175
|
+
key, val = key_val
|
|
176
|
+
val = val.to_hash(args) if val.kind_of?(IdiomaticRubyWrapper)
|
|
177
|
+
hash[key] = val
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Method to create a duplicate of the underlying Hash object
|
|
182
|
+
# Useful when underlying Hash is frozen
|
|
183
|
+
def dup
|
|
184
|
+
Ably::Models::IdiomaticRubyWrapper.new(hash.dup)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Freeze the underlying data
|
|
188
|
+
def freeze
|
|
189
|
+
hash.freeze
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def to_s
|
|
193
|
+
hash.to_s
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
private
|
|
197
|
+
def stop_at?(key)
|
|
198
|
+
@stop_at.has_key?(key)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# We assume by default all keys are interchangeable between :this_format and 'thisFormat'
|
|
202
|
+
# However, this method will find other fallback formats such as CamelCase or :symbols if a matching
|
|
203
|
+
# key is not found in mixedCase.
|
|
204
|
+
def source_key_for(symbolized_key)
|
|
205
|
+
format_preferences = [
|
|
206
|
+
proc { |key_sym| convert_to_mixed_case(key_sym) },
|
|
207
|
+
proc { |key_sym| key_sym.to_sym },
|
|
208
|
+
proc { |key_sym| key_sym.to_s },
|
|
209
|
+
proc { |key_sym| convert_to_mixed_case(key_sym).to_sym },
|
|
210
|
+
proc { |key_sym| convert_to_lower_case(key_sym) },
|
|
211
|
+
proc { |key_sym| convert_to_lower_case(key_sym).to_sym },
|
|
212
|
+
proc { |key_sym| convert_to_mixed_case(key_sym, force_camel: true) },
|
|
213
|
+
proc { |key_sym| convert_to_mixed_case(key_sym, force_camel: true).to_sym }
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
preferred_format = format_preferences.detect do |format|
|
|
217
|
+
hash.has_key?(format.call(symbolized_key))
|
|
218
|
+
end || format_preferences.first
|
|
219
|
+
|
|
220
|
+
preferred_format.call(symbolized_key)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
module Ably::Models
|
|
2
|
+
# Convert messsage argument to a {Message} object and associate with a protocol message if provided
|
|
3
|
+
#
|
|
4
|
+
# @param message [Message,Hash] A message object or Hash of message properties
|
|
5
|
+
# @param protocol_message [ProtocolMessage] An optional protocol message to assocate the message with
|
|
6
|
+
#
|
|
7
|
+
# @return [Message]
|
|
8
|
+
def self.Message(message, protocol_message = nil)
|
|
9
|
+
case message
|
|
10
|
+
when Message
|
|
11
|
+
message.tap do
|
|
12
|
+
message.assign_to_protocol_message protocol_message
|
|
13
|
+
end
|
|
14
|
+
else
|
|
15
|
+
Message.new(message, protocol_message)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# A class representing an individual message to be sent or received
|
|
20
|
+
# via the Ably Realtime service.
|
|
21
|
+
#
|
|
22
|
+
# @!attribute [r] name
|
|
23
|
+
# @return [String] The event name, if available
|
|
24
|
+
# @!attribute [r] client_id
|
|
25
|
+
# @return [String] The id of the publisher of this message
|
|
26
|
+
# @!attribute [r] data
|
|
27
|
+
# @return [Object] The message payload. See the documentation for supported datatypes.
|
|
28
|
+
# @!attribute [r] encoding
|
|
29
|
+
# @return [Object] The encoding for the message data. Encoding and decoding of messages is handled automatically by the client library.
|
|
30
|
+
# Therefore, the `encoding` attribute should always be nil unless an Ably library decoding error has occurred.
|
|
31
|
+
# @!attribute [r] timestamp
|
|
32
|
+
# @return [Time] Timestamp when the message was received by the Ably the real-time service
|
|
33
|
+
# @!attribute [r] id
|
|
34
|
+
# @return [String] A globally unique message ID
|
|
35
|
+
# @!attribute [r] connection_id
|
|
36
|
+
# @return [String] The connection_id of the publisher of the message
|
|
37
|
+
# @!attribute [r] hash
|
|
38
|
+
# @return [Hash] Access the protocol message Hash object ruby'fied to use symbolized keys
|
|
39
|
+
#
|
|
40
|
+
class Message
|
|
41
|
+
include Ably::Modules::Conversions
|
|
42
|
+
include Ably::Modules::Encodeable
|
|
43
|
+
include Ably::Modules::ModelCommon
|
|
44
|
+
include EventMachine::Deferrable
|
|
45
|
+
|
|
46
|
+
# {Message} initializer
|
|
47
|
+
#
|
|
48
|
+
# @param hash_object [Hash] object with the underlying message details
|
|
49
|
+
# @param protocol_message [ProtocolMessage] if this message has been published, then it is associated with a {ProtocolMessage}
|
|
50
|
+
#
|
|
51
|
+
def initialize(hash_object, protocol_message = nil)
|
|
52
|
+
@protocol_message = protocol_message
|
|
53
|
+
@raw_hash_object = hash_object
|
|
54
|
+
|
|
55
|
+
set_hash_object hash_object
|
|
56
|
+
|
|
57
|
+
ensure_utf_8 :name, name, allow_nil: true
|
|
58
|
+
ensure_utf_8 :client_id, client_id, allow_nil: true
|
|
59
|
+
ensure_utf_8 :encoding, encoding, allow_nil: true
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
%w( name client_id encoding connection_id ).each do |attribute|
|
|
63
|
+
define_method attribute do
|
|
64
|
+
hash[attribute.to_sym]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def data
|
|
69
|
+
@data ||= hash[:data].freeze
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def id
|
|
73
|
+
hash.fetch(:id) { "#{protocol_message.id!}:#{protocol_message_index}" }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def connection_id
|
|
77
|
+
hash.fetch(:connection_id) { protocol_message.connection_id if assigned_to_protocol_message? }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def timestamp
|
|
81
|
+
if hash[:timestamp]
|
|
82
|
+
as_time_from_epoch(hash[:timestamp])
|
|
83
|
+
else
|
|
84
|
+
protocol_message.timestamp
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def hash
|
|
89
|
+
@hash_object
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def as_json(*args)
|
|
93
|
+
raise RuntimeError, ':name is missing, cannot generate a valid Hash for Message' unless name
|
|
94
|
+
|
|
95
|
+
hash.dup.tap do |message|
|
|
96
|
+
decode_binary_data_before_to_json message
|
|
97
|
+
end.as_json
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Assign this message to a ProtocolMessage before delivery to the Ably system
|
|
101
|
+
# @api private
|
|
102
|
+
def assign_to_protocol_message(protocol_message)
|
|
103
|
+
@protocol_message = protocol_message
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# True if this message is assigned to a ProtocolMessage for delivery to Ably, or received from Ably
|
|
107
|
+
# @return [Boolean]
|
|
108
|
+
# @api private
|
|
109
|
+
def assigned_to_protocol_message?
|
|
110
|
+
!!@protocol_message
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# The optional ProtocolMessage this message is assigned to. If ProtocolMessage is nil, an error will be raised.
|
|
114
|
+
# @return [Ably::Models::ProtocolMessage]
|
|
115
|
+
# @api private
|
|
116
|
+
def protocol_message
|
|
117
|
+
raise RuntimeError, 'Message is not yet published with a ProtocolMessage. ProtocolMessage is nil' if @protocol_message.nil?
|
|
118
|
+
@protocol_message
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
attr_reader :raw_hash_object
|
|
123
|
+
|
|
124
|
+
def protocol_message_index
|
|
125
|
+
protocol_message.messages.map(&:object_id).index(self.object_id)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def set_hash_object(hash)
|
|
129
|
+
@hash_object = IdiomaticRubyWrapper(hash.clone.freeze, stop_at: [:data])
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|