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,147 @@
1
+ module Ably::Models
2
+ # Convert presence_messsage argument to a {PresenceMessage} object and associate with a protocol message if provided
3
+ #
4
+ # @param presence_message [PresenceMessage,Hash] A presence message object or Hash of presence message properties
5
+ # @param protocol_message [ProtocolMessage] An optional protocol message to assocate the presence message with
6
+ #
7
+ # @return [PresenceMessage]
8
+ def self.PresenceMessage(presence_message, protocol_message = nil)
9
+ case presence_message
10
+ when PresenceMessage
11
+ presence_message.tap do
12
+ presence_message.assign_to_protocol_message protocol_message
13
+ end
14
+ else
15
+ PresenceMessage.new(presence_message, protocol_message)
16
+ end
17
+ end
18
+
19
+ # A class representing an individual presence message to be sent or received
20
+ # via the Ably Realtime service.
21
+ #
22
+ # @!attribute [r] action
23
+ # @return [STATE] the state change event signified by a PresenceMessage
24
+ # @!attribute [r] client_id
25
+ # @return [String] The client_id associated with this presence state
26
+ # @!attribute [r] connection_id
27
+ # @return [String] A unique member identifier, disambiguating situations where a given client_id is present on multiple connections simultaneously
28
+ # @!attribute [r] member_key
29
+ # @return [String] A unique connection and client_id identifier ensuring multiple connected clients with the same client_id are unique
30
+ # @!attribute [r] data
31
+ # @return [Object] Optional client-defined status or other event payload associated with this state
32
+ # @!attribute [r] encoding
33
+ # @return [Object] The encoding for the message data. Encoding and decoding of messages is handled automatically by the client library.
34
+ # Therefore, the `encoding` attribute should always be nil unless an Ably library decoding error has occurred.
35
+ # @!attribute [r] timestamp
36
+ # @return [Time] Timestamp when the message was received by the Ably the real-time service
37
+ # @!attribute [r] hash
38
+ # @return [Hash] Access the protocol message Hash object ruby'fied to use symbolized keys
39
+ #
40
+ class PresenceMessage
41
+ include Ably::Modules::Conversions
42
+ include Ably::Modules::Encodeable
43
+ include Ably::Modules::ModelCommon
44
+ include EventMachine::Deferrable
45
+ extend Ably::Modules::Enum
46
+
47
+ ACTION = ruby_enum('ACTION',
48
+ :absent,
49
+ :present,
50
+ :enter,
51
+ :leave,
52
+ :update
53
+ )
54
+
55
+ # {Message} initializer
56
+ #
57
+ # @param hash_object [Hash] object with the underlying message details
58
+ # @param protocol_message [ProtocolMessage] if this message has been published, then it is associated with a {ProtocolMessage}
59
+ #
60
+ def initialize(hash_object, protocol_message = nil)
61
+ @protocol_message = protocol_message
62
+ @raw_hash_object = hash_object
63
+
64
+ set_hash_object hash_object
65
+
66
+ ensure_utf_8 :client_id, client_id, allow_nil: true
67
+ ensure_utf_8 :connection_id, connection_id, allow_nil: true
68
+ ensure_utf_8 :encoding, encoding, allow_nil: true
69
+ end
70
+
71
+ %w( client_id data encoding ).each do |attribute|
72
+ define_method attribute do
73
+ hash[attribute.to_sym]
74
+ end
75
+ end
76
+
77
+ def id
78
+ hash.fetch(:id) { "#{protocol_message.id!}:#{protocol_message_index}" }
79
+ end
80
+
81
+ def connection_id
82
+ hash.fetch(:connection_id) { protocol_message.connection_id if assigned_to_protocol_message? }
83
+ end
84
+
85
+ def member_key
86
+ "#{connection_id}:#{client_id}"
87
+ end
88
+
89
+ def timestamp
90
+ if hash[:timestamp]
91
+ as_time_from_epoch(hash[:timestamp])
92
+ else
93
+ protocol_message.timestamp
94
+ end
95
+ end
96
+
97
+ def action
98
+ ACTION(hash[:action])
99
+ end
100
+
101
+ def hash
102
+ @hash_object
103
+ end
104
+
105
+ # Return a JSON ready object from the underlying #hash using Ably naming conventions for keys
106
+ def as_json(*args)
107
+ hash.dup.tap do |presence_message|
108
+ presence_message['action'] = action.to_i
109
+ decode_binary_data_before_to_json presence_message
110
+ end.as_json
111
+ rescue KeyError
112
+ raise KeyError, ':action is missing or invalid, cannot generate a valid Hash for ProtocolMessage'
113
+ end
114
+
115
+ # Assign this presence message to a ProtocolMessage before delivery to the Ably system
116
+ # @api private
117
+ def assign_to_protocol_message(protocol_message)
118
+ @protocol_message = protocol_message
119
+ end
120
+
121
+ # True if this presence message is assigned to a ProtocolMessage for delivery to Ably, or received from Ably
122
+ # @return [Boolean]
123
+ # @api private
124
+ def assigned_to_protocol_message?
125
+ !!@protocol_message
126
+ end
127
+
128
+ # The optional ProtocolMessage this presence message is assigned to. If ProtocolMessage is nil, an error will be raised.
129
+ # @return [Ably::Models::ProtocolMessage]
130
+ # @api private
131
+ def protocol_message
132
+ raise RuntimeError, 'Presence Message is not yet published with a ProtocolMessage. ProtocolMessage is nil' if @protocol_message.nil?
133
+ @protocol_message
134
+ end
135
+
136
+ private
137
+ attr_reader :raw_hash_object
138
+
139
+ def protocol_message_index
140
+ protocol_message.presence.index(self)
141
+ end
142
+
143
+ def set_hash_object(hash)
144
+ @hash_object = IdiomaticRubyWrapper(hash.clone.freeze, stop_at: [:data])
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,210 @@
1
+ module Ably::Models
2
+ # A message sent and received over the Realtime protocol.
3
+ # A ProtocolMessage always relates to a single channel only, but
4
+ # can contain multiple individual Messages or PresenceMessages.
5
+ # ProtocolMessages are serially numbered on a connection.
6
+ # See the {http://docs.ably.io/client-lib-development-guide/protocol/ Ably client library developer documentation}
7
+ # for further details on the members of a ProtocolMessage
8
+ #
9
+ # @!attribute [r] action
10
+ # @return [ACTION] Protocol Message action {Ably::Modules::Enum} from list of {ACTION}. Returns nil if action is unsupported by protocol
11
+ # @!attribute [r] count
12
+ # @return [Integer] The count field is used for ACK and NACK actions. See {http://docs.ably.io/client-lib-development-guide/protocol/#message-acknowledgement message acknowledgement protocol}
13
+ # @!attribute [r] error_info
14
+ # @return [ErrorInfo] Contains error information
15
+ # @!attribute [r] channel
16
+ # @return [String] Channel name for messages
17
+ # @!attribute [r] channel_serial
18
+ # @return [String] Contains a serial number for a message on the current channel
19
+ # @!attribute [r] connection_id
20
+ # @return [String] Contains a string public identifier for the connection
21
+ # @!attribute [r] connection_key
22
+ # @return [String] Contains a string private connection key used to recover this connection
23
+ # @!attribute [r] connection_serial
24
+ # @return [Bignum] Contains a serial number for a message sent from the server to the client
25
+ # @!attribute [r] message_serial
26
+ # @return [Bignum] Contains a serial number for a message sent from the client to the server
27
+ # @!attribute [r] timestamp
28
+ # @return [Time] An optional timestamp, applied by the service in messages sent to the client, to indicate the system time at which the message was sent (milliseconds past epoch)
29
+ # @!attribute [r] messages
30
+ # @return [Message] A {ProtocolMessage} with a `:message` action contains one or more messages belonging to a channel
31
+ # @!attribute [r] presence
32
+ # @return [PresenceMessage] A {ProtocolMessage} with a `:presence` action contains one or more presence updates belonging to a channel
33
+ # @!attribute [r] flags
34
+ # @return [Integer] Flags inidicating special ProtocolMessage states
35
+ # @!attribute [r] hash
36
+ # @return [Hash] Access the protocol message Hash object ruby'fied to use symbolized keys
37
+ #
38
+ class ProtocolMessage
39
+ include Ably::Modules::ModelCommon
40
+ include EventMachine::Deferrable if defined?(Ably::Realtime)
41
+ extend Ably::Modules::Enum
42
+
43
+ # Actions which are sent by the Ably Realtime API
44
+ #
45
+ # The values correspond to the ints which the API
46
+ # understands.
47
+ ACTION = ruby_enum('ACTION',
48
+ heartbeat: 0,
49
+ ack: 1,
50
+ nack: 2,
51
+ connect: 3,
52
+ connected: 4,
53
+ disconnect: 5,
54
+ disconnected: 6,
55
+ close: 7,
56
+ closed: 8,
57
+ error: 9,
58
+ attach: 10,
59
+ attached: 11,
60
+ detach: 12,
61
+ detached: 13,
62
+ presence: 14,
63
+ message: 15,
64
+ sync: 16
65
+ )
66
+
67
+ # Indicates this protocol message action will generate an ACK response such as :message or :presence
68
+ def self.ack_required?(for_action)
69
+ [ACTION.Presence, ACTION.Message].include?(ACTION(for_action))
70
+ end
71
+
72
+ def initialize(hash_object)
73
+ @raw_hash_object = hash_object
74
+ @hash_object = IdiomaticRubyWrapper(@raw_hash_object.clone)
75
+
76
+ raise ArgumentError, 'Invalid ProtocolMessage, action cannot be nil' if @hash_object[:action].nil?
77
+ @hash_object[:action] = ACTION(@hash_object[:action]).to_i unless @hash_object[:action].kind_of?(Integer)
78
+
79
+ @hash_object.freeze
80
+ end
81
+
82
+ %w(id channel channel_serial connection_id connection_key).each do |attribute|
83
+ define_method attribute do
84
+ hash[attribute.to_sym]
85
+ end
86
+ end
87
+
88
+ def id!
89
+ raise RuntimeError, 'ProtocolMessage #id is nil' unless id
90
+ id
91
+ end
92
+
93
+ def action
94
+ ACTION(hash[:action])
95
+ rescue KeyError
96
+ raise KeyError, "Action '#{hash[:action]}' is not supported by ProtocolMessage"
97
+ end
98
+
99
+ def error
100
+ @error_info ||= ErrorInfo.new(hash[:error]) if hash[:error]
101
+ end
102
+
103
+ def timestamp
104
+ as_time_from_epoch(hash[:timestamp]) if hash[:timestamp]
105
+ end
106
+
107
+ def message_serial
108
+ Integer(hash[:msg_serial])
109
+ rescue TypeError
110
+ raise TypeError, "msg_serial '#{hash[:msg_serial]}' is invalid, a positive Integer is expected for a ProtocolMessage"
111
+ end
112
+
113
+ def connection_serial
114
+ Integer(hash[:connection_serial])
115
+ rescue TypeError
116
+ raise TypeError, "connection_serial '#{hash[:connection_serial]}' is invalid, a positive Integer is expected for a ProtocolMessage"
117
+ end
118
+
119
+ def count
120
+ [1, hash[:count].to_i].max
121
+ end
122
+
123
+ def has_message_serial?
124
+ message_serial && true
125
+ rescue TypeError
126
+ false
127
+ end
128
+
129
+ def has_connection_serial?
130
+ connection_serial && true
131
+ rescue TypeError
132
+ false
133
+ end
134
+
135
+ def serial
136
+ if has_connection_serial?
137
+ connection_serial
138
+ else
139
+ message_serial
140
+ end
141
+ end
142
+
143
+ def has_serial?
144
+ has_connection_serial? || has_message_serial?
145
+ end
146
+
147
+ def messages
148
+ @messages ||=
149
+ Array(hash[:messages]).map do |message|
150
+ Ably::Models.Message(message, self)
151
+ end
152
+ end
153
+
154
+ def add_message(message)
155
+ messages << message
156
+ end
157
+
158
+ def presence
159
+ @presence ||=
160
+ Array(hash[:presence]).map do |message|
161
+ Ably::Models.PresenceMessage(message, self)
162
+ end
163
+ end
164
+
165
+ # Flags as bits
166
+ def flags
167
+ Integer(hash[:flags])
168
+ rescue TypeError
169
+ 0
170
+ end
171
+
172
+ def has_presence_flag?
173
+ flags & 1 == 1
174
+ end
175
+
176
+ # Indicates this protocol message will generate an ACK response when sent
177
+ # Examples of protocol messages required ACK include :message and :presence
178
+ def ack_required?
179
+ self.class.ack_required?(action)
180
+ end
181
+
182
+ def hash
183
+ @hash_object
184
+ end
185
+
186
+ # Return a JSON ready object from the underlying #hash using Ably naming conventions for keys
187
+ def as_json(*args)
188
+ raise TypeError, ':action is missing, cannot generate a valid Hash for ProtocolMessage' unless action
189
+ raise TypeError, ':msg_serial or :connection_serial is missing, cannot generate a valid Hash for ProtocolMessage' if ack_required? && !has_serial?
190
+
191
+ hash.dup.tap do |hash_object|
192
+ hash_object['action'] = action.to_i
193
+ hash_object['messages'] = messages.map(&:as_json) unless messages.empty?
194
+ hash_object['presence'] = presence.map(&:as_json) unless presence.empty?
195
+ end.as_json
196
+ end
197
+
198
+ def to_s
199
+ to_json
200
+ end
201
+
202
+ # True if the ProtocolMessage appears to be invalid, however this is not a guarantee
203
+ # @return [Boolean]
204
+ # @api private
205
+ def invalid?
206
+ action_enum = action rescue nil
207
+ !action_enum || (ack_required? && !has_serial?)
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,161 @@
1
+ module Ably::Models
2
+ # Convert stat argument to a {Stat} object
3
+ #
4
+ # @param stat [Stat,Hash] A Stat object or Hash of stat properties
5
+ #
6
+ # @return [Stat]
7
+ def self.Stat(stat)
8
+ case stat
9
+ when Stat
10
+ stat
11
+ else
12
+ Stat.new(stat)
13
+ end
14
+ end
15
+
16
+ # A class representing an individual statistic for a specified {#interval_id}
17
+ #
18
+ # @!attribute [r] all
19
+ # @return [Hash] Breakdown of summary stats for all message types
20
+ # @!attribute [r] inbound
21
+ # @return [Hash] Breakdown of summary stats for traffic over various transport types for all inbound messages
22
+ # @!attribute [r] outbound
23
+ # @return [Hash] Breakdown of summary stats for traffic over various transport types for all outbound messages
24
+ # @!attribute [r] persisted
25
+ # @return [Hash] Breakdown of summary stats for all persisted messages
26
+ # @!attribute [r] connections
27
+ # @return [Hash] A breakdown of summary stats data for different (TLS vs non-TLS) connection types.
28
+ # @!attribute [r] channels
29
+ # @return [Hash] Aggregate data for usage of Channels
30
+ # @!attribute [r] api_requests
31
+ # @return [Hash] Aggregate data for numbers of API requests
32
+ # @!attribute [r] token_requests
33
+ # @return [Hash] Aggregate data for numbers of Token requests
34
+ #
35
+ class Stat
36
+ include Ably::Modules::ModelCommon
37
+ extend Ably::Modules::Enum
38
+
39
+ GRANULARITY = ruby_enum('GRANULARITY',
40
+ :minute,
41
+ :hour,
42
+ :day,
43
+ :month
44
+ )
45
+
46
+ INTERVAL_FORMAT_STRING = [
47
+ '%Y-%m-%d:%H:%M',
48
+ '%Y-%m-%d:%H',
49
+ '%Y-%m-%d',
50
+ '%Y-%m'
51
+ ]
52
+
53
+ class << self
54
+ # Convert a Time with the specified Granularity into an interval ID based on UTC 0 time
55
+ # @example
56
+ # Stat.to_interval_id(Time.now, :hour) # => '2015-01-01:10'
57
+ #
58
+ # @param time [Time] Time used to determine the interval
59
+ # @param granularity [GRANULARITY] Granularity of the metrics such as :hour, :day
60
+ #
61
+ # @return [String] interval ID used for stats
62
+ #
63
+ def to_interval_id(time, granularity)
64
+ raise ArgumentError, 'Time object required as first argument' unless time.kind_of?(Time)
65
+
66
+ granularity = GRANULARITY(granularity)
67
+ format = INTERVAL_FORMAT_STRING[granularity.to_i]
68
+
69
+ time.utc.strftime(format)
70
+ end
71
+
72
+ # Returns the UTC 0 start Time of an interval_id
73
+ # @example
74
+ # Stat.from_interval_id('2015-01-01:10') # => 2015-01-01 10:00:00 +0000
75
+ #
76
+ # @param interval_id [String]
77
+ #
78
+ # @return [Time] start time of the provided interval_id
79
+ #
80
+ def from_interval_id(interval_id)
81
+ raise ArgumentError, 'Interval ID must be a string' unless interval_id.kind_of?(String)
82
+
83
+ format = INTERVAL_FORMAT_STRING.find { |format| expected_length(format) == interval_id.length }
84
+ raise ArgumentError, 'Interval ID is an invalid length' unless format
85
+
86
+ Time.strptime("#{interval_id} +0000", "#{format} %z").utc
87
+ end
88
+
89
+ # Returns the {GRANULARITY} determined from the interval_id
90
+ # @example
91
+ # Stat.granularity_from_interval_id('2015-01-01:10') # => :hour
92
+ #
93
+ # @param interval_id [String]
94
+ #
95
+ # @return [GRANULARITY] Granularity Enum for the interval_id
96
+ #
97
+ def granularity_from_interval_id(interval_id)
98
+ raise ArgumentError, 'Interval ID must be a string' unless interval_id.kind_of?(String)
99
+
100
+ format = INTERVAL_FORMAT_STRING.find { |format| expected_length(format) == interval_id.length }
101
+ raise ArgumentError, 'Interval ID is an invalid length' unless format
102
+
103
+ GRANULARITY[INTERVAL_FORMAT_STRING.index(format)]
104
+ end
105
+
106
+ private
107
+ def expected_length(format)
108
+ format.gsub('%Y', 'YYYY').length
109
+ end
110
+ end
111
+
112
+ # {Stat} initializer
113
+ #
114
+ # @param hash_object [Hash] object with the underlying stat details
115
+ #
116
+ def initialize(hash_object)
117
+ @raw_hash_object = hash_object
118
+
119
+ set_hash_object hash_object
120
+ end
121
+
122
+ %w( all inbound outbound persisted connections channels api_requests token_requests ).each do |attribute|
123
+ define_method attribute do
124
+ hash[attribute.to_sym]
125
+ end
126
+ end
127
+
128
+ # @!attribute [r] interval_id
129
+ # @return [String] The interval that this statistic applies to, see {GRANULARITY} and {INTERVAL_FORMAT_STRING}
130
+ def interval_id
131
+ hash.fetch(:interval_id)
132
+ end
133
+
134
+ # @!attribute [r] interval_time
135
+ # @return [Time] A Time object representing the start of the interval
136
+ def interval_time
137
+ self.class.from_interval_id(interval_id)
138
+ end
139
+
140
+ # @!attribute [r] interval_granularity
141
+ # @return [GRANULARITY] The granularity of the interval for the stat such as :day, :hour, :minute, see {GRANULARITY}
142
+ def interval_granularity
143
+ self.class.granularity_from_interval_id(interval_id)
144
+ end
145
+
146
+ def hash
147
+ @hash_object
148
+ end
149
+
150
+ def as_json(*args)
151
+ hash.as_json(*args)
152
+ end
153
+
154
+ private
155
+ attr_reader :raw_hash_object
156
+
157
+ def set_hash_object(hash)
158
+ @hash_object = IdiomaticRubyWrapper(hash.clone.freeze)
159
+ end
160
+ end
161
+ end