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.
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