ably-rest 0.7.1 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +13 -5
  2. data/.gitmodules +1 -1
  3. data/.rspec +1 -0
  4. data/.travis.yml +7 -3
  5. data/SPEC.md +495 -419
  6. data/ably-rest.gemspec +19 -5
  7. data/lib/ably-rest.rb +9 -1
  8. data/lib/submodules/ably-ruby/.gitignore +6 -0
  9. data/lib/submodules/ably-ruby/.rspec +1 -0
  10. data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
  11. data/lib/submodules/ably-ruby/.travis.yml +10 -0
  12. data/lib/submodules/ably-ruby/Gemfile +4 -0
  13. data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
  14. data/lib/submodules/ably-ruby/README.md +122 -0
  15. data/lib/submodules/ably-ruby/Rakefile +34 -0
  16. data/lib/submodules/ably-ruby/SPEC.md +1794 -0
  17. data/lib/submodules/ably-ruby/ably.gemspec +36 -0
  18. data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
  19. data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
  20. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
  21. data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
  22. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
  23. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
  24. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
  25. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
  26. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
  27. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
  28. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
  29. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
  30. data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
  31. data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
  32. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
  33. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
  34. data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
  35. data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
  36. data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
  37. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
  38. data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
  39. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
  40. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
  41. data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
  42. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
  43. data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
  44. data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
  45. data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
  46. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
  47. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
  48. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
  49. data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  50. data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
  51. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
  52. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
  53. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
  54. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
  55. data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
  56. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
  57. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
  58. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
  59. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
  60. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
  61. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
  62. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
  63. data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
  64. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
  65. data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
  66. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
  67. data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
  68. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
  69. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
  70. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
  71. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
  72. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  73. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
  74. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
  75. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
  76. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
  77. data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
  78. data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
  79. data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
  80. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
  81. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
  82. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
  83. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
  84. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
  85. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
  86. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
  87. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
  88. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
  89. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
  90. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
  91. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
  92. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
  93. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
  94. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
  95. data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
  96. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
  97. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
  98. data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
  99. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
  100. data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
  101. data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
  102. data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
  103. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
  104. data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
  105. data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
  106. data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
  107. data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
  108. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
  109. data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
  110. data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
  111. data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
  112. data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
  113. data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
  114. data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
  115. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
  116. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
  117. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
  118. data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
  119. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
  120. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
  121. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
  122. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
  123. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
  124. data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
  125. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
  126. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
  127. data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
  128. data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
  129. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
  130. data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
  131. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
  132. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
  133. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
  134. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
  135. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
  136. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
  137. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
  138. data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
  139. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
  140. data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
  141. data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
  142. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
  143. data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
  144. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
  145. data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
  146. data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
  147. data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
  148. metadata +182 -27
@@ -0,0 +1,74 @@
1
+ module Ably::Models
2
+ # Authentication token issued by Ably in response to an token request
3
+ class Token
4
+ include Ably::Modules::ModelCommon
5
+
6
+ DEFAULTS = {
7
+ capability: { '*' => ['*'] },
8
+ ttl: 60 * 60 # 1 hour
9
+ }
10
+
11
+ # Buffer in seconds given to the use of a token prior to it being considered unusable
12
+ # For example, if buffer is 10s, the token can no longer be used for new requests 9s before it expires
13
+ TOKEN_EXPIRY_BUFFER = 5
14
+
15
+ def initialize(attributes)
16
+ @hash_object = IdiomaticRubyWrapper(attributes.clone.freeze)
17
+ end
18
+
19
+ # @!attribute [r] id
20
+ # @return [String] Unique token ID used to authenticate requests
21
+ def id
22
+ hash.fetch(:id)
23
+ end
24
+
25
+ # @!attribute [r] key_id
26
+ # @return [String] Key ID used to create this token
27
+ def key_id
28
+ hash.fetch(:key)
29
+ end
30
+
31
+ # @!attribute [r] issued_at
32
+ # @return [Time] Time the token was issued
33
+ def issued_at
34
+ as_time_from_epoch(hash.fetch(:issued_at), granularity: :s)
35
+ end
36
+
37
+ # @!attribute [r] expires_at
38
+ # @return [Time] Time the token expires
39
+ def expires_at
40
+ as_time_from_epoch(hash.fetch(:expires), granularity: :s)
41
+ end
42
+
43
+ # @!attribute [r] capability
44
+ # @return [Hash] Capabilities assigned to this token
45
+ def capability
46
+ hash.fetch(:capability)
47
+ end
48
+
49
+ # @!attribute [r] client_id
50
+ # @return [String] Optional client ID assigned to this token
51
+ def client_id
52
+ hash[:client_id]
53
+ end
54
+
55
+ # @!attribute [r] nonce
56
+ # @return [String] unique nonce used to generate Token and ensure token generation cannot be replayed
57
+ def nonce
58
+ hash.fetch(:nonce)
59
+ end
60
+
61
+ # Returns true if token is expired or about to expire
62
+ #
63
+ # @return [Boolean]
64
+ def expired?
65
+ expires_at < Time.now + TOKEN_EXPIRY_BUFFER
66
+ end
67
+
68
+ # @!attribute [r] hash
69
+ # @return [Hash] Access the token Hash object ruby'fied to use symbolized keys
70
+ def hash
71
+ @hash_object
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,15 @@
1
+ # Ably is the base namespace for the Ably {Ably::Realtime Realtime} & {Ably::Rest Rest} client libraries.
2
+ #
3
+ # Please refer to the {file:README.md Readme} on getting started.
4
+ #
5
+ # @see file:README.md README
6
+ module Ably
7
+ # Fallback hosts to use when a connection to rest/realtime.ably.io is not possible due to
8
+ # network failures either at the client, between the client and Ably, within an Ably data center, or at the IO domain registrar
9
+ #
10
+ FALLBACK_HOSTS = %w(A.ably-realtime.com B.ably-realtime.com C.ably-realtime.com D.ably-realtime.com E.ably-realtime.com)
11
+ INTERNET_CHECK = {
12
+ url: '//internet-up.ably-realtime.com/is-the-internet-up.txt',
13
+ ok_text: 'yes'
14
+ }
15
+ end
@@ -0,0 +1,62 @@
1
+ require 'eventmachine'
2
+
3
+ module Ably::Modules
4
+ # Provides methods to convert synchronous operations into async operations through the use of
5
+ # {EventMachine#defer http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine#defer-class_method}.
6
+ # The async_wrap method can only be called from within an EventMachine reactor, and must be thread safe.
7
+ #
8
+ # @note using this AsyncWrapper should only be used for methods that are used less frequently and typically
9
+ # not run with levels of concurrency due to the limited number of threads available to EventMachine by default.
10
+ #
11
+ # @example
12
+ # class BlockingOperation
13
+ # include Aby::Modules::AsyncWrapper
14
+ #
15
+ # def operation(&success_callback)
16
+ # async_wrap(success_callback) do
17
+ # sleep 1
18
+ # 'slept'
19
+ # end
20
+ # end
21
+ # end
22
+ #
23
+ # blocking_object = BlockingOperation.new
24
+ # deferrable = blocking_object.operation do |result|
25
+ # puts "Done with result: #{result}"
26
+ # end
27
+ # puts "Starting"
28
+ #
29
+ # # => 'Starting'
30
+ # # => 'Done with result: slept'
31
+ #
32
+ module AsyncWrapper
33
+ private
34
+
35
+ # Will yield the provided block in a new thread and return an {EventMachine::Deferrable http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Deferrable}
36
+ #
37
+ # @yield [Object] operation block that is run in a thread
38
+ # @return [EventMachine::Deferrable]
39
+ #
40
+ def async_wrap(success_callback = nil)
41
+ raise ArgumentError, 'Block required' unless block_given?
42
+
43
+ EventMachine::DefaultDeferrable.new.tap do |deferrable|
44
+ deferrable.callback &success_callback if success_callback
45
+
46
+ operation_with_exception_handling = proc do
47
+ begin
48
+ yield
49
+ rescue StandardError => e
50
+ deferrable.fail e
51
+ end
52
+ end
53
+
54
+ complete_callback = proc do |result|
55
+ deferrable.succeed result
56
+ end
57
+
58
+ EventMachine.defer operation_with_exception_handling, complete_callback
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,69 @@
1
+ module Ably::Modules
2
+ # ChannelsCollection module provides common functionality to the Rest and Realtime Channels objects
3
+ # such as #get, #[], #fetch, and #release
4
+ module ChannelsCollection
5
+ include Enumerable
6
+
7
+ def initialize(client, channel_klass)
8
+ @client = client
9
+ @channel_klass = channel_klass
10
+ @channels = {}
11
+ end
12
+
13
+ # Return a Channel for the given name
14
+ #
15
+ # @param name [String] The name of the channel
16
+ # @param channel_options [Hash] Channel options including the encryption options
17
+ #
18
+ # @return [Channel]
19
+ #
20
+ def get(name, channel_options = {})
21
+ channels[name] ||= channel_klass.new(client, name, channel_options)
22
+ end
23
+ alias_method :[], :get
24
+
25
+ # Return a Channel for the given name if it exists, else the block will be called.
26
+ # This method is intentionally similar to {http://ruby-doc.org/core-2.1.3/Hash.html#method-i-fetch Hash#fetch} providing a simple way to check if a channel exists or not without creating one
27
+ #
28
+ # @param name [String] The name of the channel
29
+ #
30
+ # @yield [options] (optional) if a missing_block is passed to this method and no channel exists matching the name, this block is called
31
+ # @yieldparam [String] name of the missing channel
32
+ #
33
+ # @return [Channel]
34
+ #
35
+ def fetch(name, &missing_block)
36
+ channels.fetch(name, &missing_block)
37
+ end
38
+
39
+ # Destroy the Channel and releases the associated resources.
40
+ #
41
+ # Releasing a Channel is not typically necessary as a channel consumes no resources other than the memory footprint of the
42
+ # Channel object. Explicitly release channels to free up resources if required
43
+ #
44
+ # @param name [String] The name of the channel
45
+ #
46
+ # @return [void]
47
+ #
48
+ def release(name)
49
+ channels.delete(name)
50
+ end
51
+
52
+ # @!attribute [r] length
53
+ # @return [Integer] number of channels created
54
+ def length
55
+ channels.length
56
+ end
57
+ alias_method :count, :length
58
+ alias_method :size, :length
59
+
60
+ # Method to allow {ChannelsCollection} to be {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
61
+ def each(&block)
62
+ return to_enum(:each) unless block_given?
63
+ channels.values.each(&block)
64
+ end
65
+
66
+ private
67
+ attr_reader :client, :channel_klass, :channels
68
+ end
69
+ end
@@ -0,0 +1,100 @@
1
+ module Ably::Modules
2
+ # Conversions module provides common timestamp and variable naming conversions to Ably classes.
3
+ # All methods are private
4
+ module Conversions
5
+ extend self
6
+
7
+ private
8
+ def as_since_epoch(time, options = {})
9
+ granularity = options.fetch(:granularity, :ms)
10
+
11
+ case time
12
+ when Time
13
+ time.to_f * multiplier_from_granularity(granularity)
14
+ when Numeric
15
+ time
16
+ else
17
+ raise ArgumentError, 'time argument must be a Numeric or Time object'
18
+ end.to_i
19
+ end
20
+
21
+ def as_time_from_epoch(time, options = {})
22
+ granularity = options.fetch(:granularity, :ms)
23
+
24
+ case time
25
+ when Numeric
26
+ Time.at(time / multiplier_from_granularity(granularity))
27
+ when Time
28
+ time
29
+ else
30
+ raise ArgumentError, 'time argument must be a Numeric or Time object'
31
+ end
32
+ end
33
+
34
+ def multiplier_from_granularity(granularity)
35
+ case granularity
36
+ when :ms # milliseconds
37
+ 1_000.0
38
+ when :s # seconds
39
+ 1.0
40
+ else
41
+ raise ArgumentError, 'invalid granularity'
42
+ end
43
+ end
44
+
45
+ # Convert key to mixedCase from mixed_case
46
+ def convert_to_mixed_case(key, options = {})
47
+ force_camel = options.fetch(:force_camel, false)
48
+
49
+ key.to_s.
50
+ split('_').
51
+ each_with_index.map do |str, index|
52
+ if index > 0 || force_camel
53
+ str.capitalize
54
+ else
55
+ str
56
+ end
57
+ end.
58
+ join
59
+ end
60
+
61
+ # Convert a Hash into a mixed case Hash objet
62
+ # i.e. { client_id: 1 } becomes { 'clientId' => 1 }
63
+ def convert_to_mixed_case_hash(hash, options = {})
64
+ raise ArgumentError, 'Hash expected' unless hash.kind_of?(Hash)
65
+ hash.each_with_object({}) do |pair, new_hash|
66
+ key, val = pair
67
+ new_hash[convert_to_mixed_case(key, options)] = val
68
+ end
69
+ end
70
+
71
+ # Convert key to :snake_case from snakeCase
72
+ def convert_to_snake_case_symbol(key)
73
+ key.to_s.gsub(/::/, '/').
74
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
75
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
76
+ gsub(/([a-zA-Z])(\d)/,'\1_\2').
77
+ tr("-", "_").
78
+ downcase.
79
+ to_sym
80
+ end
81
+
82
+ def convert_to_lower_case(key)
83
+ key.to_s.gsub('_', '')
84
+ end
85
+
86
+ # Ensures that the string value is converted to UTF-8 encoding
87
+ # Unless option allow_nil: true, an {ArgumentError} is raised if the string_value is not a string
88
+ #
89
+ # @return <void>
90
+ #
91
+ def ensure_utf_8(field_name, string_value, options = {})
92
+ unless options[:allow_nil] && string_value.nil?
93
+ raise ArgumentError, "#{field_name} must be a String" unless string_value.kind_of?(String)
94
+ end
95
+ string_value.encode!(Encoding::UTF_8) if string_value
96
+ rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e
97
+ raise ArgumentError, "#{field_name} could not be converted to UTF-8: #{e.message}"
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,69 @@
1
+ require 'base64'
2
+ require 'ably/exceptions'
3
+
4
+ module Ably::Modules
5
+ # Provides methods to allow this model's `data` property to be encoded and decoded based on the `encoding` property.
6
+ #
7
+ # This module expects the following:
8
+ # - A #hash method that returns the underlying hash object
9
+ # - A #set_hash_object(hash) method that updates the underlying hash object
10
+ # - A #raw_hash_object attribute that returns the original hash used to create this object
11
+ #
12
+ module Encodeable
13
+ # Encode a message using the channel options and register encoders for the client
14
+ # @param channel [Ably::Realtime::Channel]
15
+ # @return [void]
16
+ # @api private
17
+ def encode(channel)
18
+ apply_encoders :encode, channel
19
+ end
20
+
21
+ # Decode a message using the channel options and registered encoders for the client
22
+ # @param channel [Ably::Realtime::Channel]
23
+ # @return [void]
24
+ # @api private
25
+ def decode(channel)
26
+ apply_encoders :decode, channel
27
+ end
28
+
29
+ # The original encoding of this message when it was received as a raw message from the Ably service
30
+ # @return [String,nil]
31
+ # @api private
32
+ def original_encoding
33
+ raw_hash_object['encoding']
34
+ end
35
+
36
+ private
37
+ def decode_binary_data_before_to_json(message)
38
+ if message[:data].kind_of?(String) && message[:data].encoding == ::Encoding::ASCII_8BIT
39
+ message[:data] = ::Base64.encode64(message[:data])
40
+ message[:encoding] = [message[:encoding], 'base64'].compact.join('/')
41
+ end
42
+ end
43
+
44
+ def apply_encoders(method, channel)
45
+ max_encoding_length = 512
46
+ message_hash = hash.dup
47
+
48
+ begin
49
+ if message_hash[:encoding].to_s.length > max_encoding_length
50
+ raise Ably::Exceptions::EncoderError("Encoding error, encoding value is too long: '#{message_hash[:encoding]}'", nil, 92100)
51
+ end
52
+
53
+ previous_encoding = message_hash[:encoding]
54
+ channel.client.encoders.each do |encoder|
55
+ encoder.send method, message_hash, channel.options
56
+ end
57
+ end until previous_encoding == message_hash[:encoding]
58
+
59
+ set_hash_object message_hash
60
+ rescue Ably::Exceptions::CipherError => cipher_error
61
+ if channel.respond_to?(:trigger)
62
+ channel.client.logger.error "Encoder error #{cipher_error.code} trying to #{method} message: #{cipher_error.message}"
63
+ channel.trigger :error, cipher_error
64
+ else
65
+ raise cipher_error
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,202 @@
1
+ require 'ably/modules/conversions'
2
+
3
+ module Ably::Modules
4
+ # Enum brings Enum like functionality used in other languages to Ruby
5
+ #
6
+ # @example
7
+ # class House
8
+ # extend Ably::Moduels::Enum
9
+ # CONSTRUCTION = ruby_enum('CONSTRUCTION',
10
+ # :brick,
11
+ # :steel,
12
+ # :wood
13
+ # )
14
+ # end
15
+ #
16
+ # House::CONSTRUCTION(:brick).to_i # => 0
17
+ # House::CONSTRUCTION('Wood').to_i # => 2
18
+ # House::CONSTRUCTION.Wood == :wood # => true
19
+ #
20
+ module Enum
21
+ private
22
+
23
+ class Base; end
24
+
25
+ # ruby_enum returns an Enum-like class that should be assigned to a constant in your class
26
+ # The first `enum_name` argument must match the constant name so that the coercion method is available
27
+ #
28
+ # @example
29
+ # class House
30
+ # extend Ably::Moduels::Enum
31
+ # CONSTRUCTION = ruby_enum('CONSTRUCTION', :brick)
32
+ # end
33
+ #
34
+ # # ensures the following coercion method is available
35
+ # House::CONSTRUCTION(:brick) # => CONSTRUCTION.Brick
36
+ #
37
+ def ruby_enum(enum_name, *values)
38
+ enum_class = Class.new(Enum::Base) do
39
+ include Conversions
40
+ extend Conversions
41
+
42
+ @enum_name = enum_name
43
+ @by_index = {}
44
+ @by_symbol = {}
45
+
46
+ class << self
47
+ include Enumerable
48
+
49
+ def get(identifier)
50
+ case identifier
51
+ when Symbol
52
+ by_symbol.fetch(identifier)
53
+ when String
54
+ by_symbol.fetch(convert_to_snake_case_symbol(identifier))
55
+ when Numeric
56
+ by_index.fetch(identifier)
57
+ when ancestors.first
58
+ identifier
59
+ else
60
+ if identifier.class.ancestors.include?(Enum::Base)
61
+ by_symbol.fetch(identifier.to_sym)
62
+ else
63
+ raise KeyError, "Cannot find Enum matching identifier '#{identifier}' argument as it is an unacceptable type: #{identifier.class}"
64
+ end
65
+ end
66
+ end
67
+
68
+ def [](*args)
69
+ get(*args)
70
+ end
71
+
72
+ def to_s
73
+ name
74
+ end
75
+
76
+ def size
77
+ by_symbol.keys.length
78
+ end
79
+ alias_method :length, :size
80
+
81
+ # Method ensuring this {Enum} is {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
82
+ def each(&block)
83
+ return to_enum(:each) unless block_given?
84
+ by_symbol.values.each(&block)
85
+ end
86
+
87
+ # The name provided in the constructor for this Enum
88
+ def name
89
+ @enum_name
90
+ end
91
+
92
+ private
93
+ attr_reader :by_index, :by_symbol
94
+
95
+ # Define constants for each of the Enum values
96
+ # e.g. define_constants(:dog) creates Enum::Dog
97
+ def define_values(values)
98
+ raise RuntimeError, "#{name} Enum cannot be modified" if by_index.frozen?
99
+
100
+ # Allow another Enum to be used as a set of values
101
+ if values.length == 1 && klass = values.first
102
+ if klass.kind_of?(Class) && klass.ancestors.include?(Enum::Base)
103
+ values = values.first.map(&:to_sym)
104
+ end
105
+ end
106
+
107
+ values.map do |value|
108
+ # Convert any key => index_value pairs into array pairs
109
+ Array(value)
110
+ end.flatten(1).each_with_index do |name, index|
111
+ name, index = name if name.kind_of?(Array) # name is key => index_value pair
112
+ raise ArgumentError, "Index value '#{index}' is invalid" unless index.kind_of?(Numeric)
113
+
114
+ camel_name = convert_to_mixed_case(name, force_camel: true)
115
+ name_symbol = convert_to_snake_case_symbol(name)
116
+ enum = new(camel_name, name_symbol, index.to_i)
117
+
118
+ by_index[index.to_i] = enum
119
+ by_symbol[name_symbol] = enum
120
+
121
+ define_singleton_method camel_name do
122
+ enum
123
+ end
124
+ end
125
+
126
+ by_index.freeze
127
+ by_symbol.freeze
128
+ end
129
+ end
130
+
131
+ def initialize(name, symbol, index)
132
+ @name = name
133
+ @index = index
134
+ @symbol = symbol
135
+ end
136
+
137
+ def to_s
138
+ "#{self.class}.#{name}"
139
+ end
140
+
141
+ def to_i
142
+ index
143
+ end
144
+
145
+ def to_sym
146
+ symbol
147
+ end
148
+
149
+ def to_json(*args)
150
+ %{"#{symbol}"}
151
+ end
152
+
153
+ # Allow comparison of Enum objects based on:
154
+ #
155
+ # * Other equivalent Enum objects
156
+ # * Symbol
157
+ # * String
158
+ # * Integer index of Enum
159
+ #
160
+ def ==(other)
161
+ case other
162
+ when Symbol
163
+ self.to_sym == convert_to_snake_case_symbol(other)
164
+ when String
165
+ self.to_sym == convert_to_snake_case_symbol(other)
166
+ when Numeric
167
+ self.to_i == other.to_i
168
+ when self.class
169
+ self.to_i == other.to_i
170
+ else
171
+ false
172
+ end
173
+ end
174
+
175
+ private
176
+ attr_reader :name, :index, :symbol
177
+
178
+ define_values values
179
+ end
180
+
181
+ # Convert any comparable object into this Enum
182
+ # @example
183
+ # class Example
184
+ # DOGS = ruby_enum('DOGS', :terrier, :labrador, :great_dane)
185
+ # end
186
+ #
187
+ # Example.DOGS(:great_dane) # => <DOGS.GreatDane>
188
+ # Example.DOGS(0) # => <DOGS.Terrier>
189
+ # Example.new.DOGS(0) # => <DOGS.Terrier>
190
+ #
191
+ define_singleton_method enum_name do |val|
192
+ enum_class.get(val)
193
+ end
194
+
195
+ define_method enum_name do |val|
196
+ enum_class.get(val)
197
+ end
198
+
199
+ enum_class
200
+ end
201
+ end
202
+ end