ably-pubsub-core 0.0.1.pre

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 (105) hide show
  1. checksums.yaml +7 -0
  2. data/lib/ably/agent.rb +8 -0
  3. data/lib/ably/auth.rb +791 -0
  4. data/lib/ably/exceptions.rb +169 -0
  5. data/lib/ably/logger.rb +119 -0
  6. data/lib/ably/models/auth_details.rb +45 -0
  7. data/lib/ably/models/channel_details.rb +69 -0
  8. data/lib/ably/models/channel_metrics.rb +107 -0
  9. data/lib/ably/models/channel_occupancy.rb +50 -0
  10. data/lib/ably/models/channel_options.rb +120 -0
  11. data/lib/ably/models/channel_state_change.rb +84 -0
  12. data/lib/ably/models/channel_status.rb +63 -0
  13. data/lib/ably/models/cipher_params.rb +137 -0
  14. data/lib/ably/models/connection_details.rb +149 -0
  15. data/lib/ably/models/connection_state_change.rb +83 -0
  16. data/lib/ably/models/delta_extras.rb +28 -0
  17. data/lib/ably/models/device_details.rb +126 -0
  18. data/lib/ably/models/device_push_details.rb +94 -0
  19. data/lib/ably/models/error_info.rb +104 -0
  20. data/lib/ably/models/http_paginated_response.rb +112 -0
  21. data/lib/ably/models/idiomatic_ruby_wrapper.rb +235 -0
  22. data/lib/ably/models/message.rb +223 -0
  23. data/lib/ably/models/message_encoders/base.rb +135 -0
  24. data/lib/ably/models/message_encoders/base64.rb +40 -0
  25. data/lib/ably/models/message_encoders/cipher.rb +84 -0
  26. data/lib/ably/models/message_encoders/json.rb +34 -0
  27. data/lib/ably/models/message_encoders/utf8.rb +26 -0
  28. data/lib/ably/models/nil_logger.rb +20 -0
  29. data/lib/ably/models/paginated_result.rb +201 -0
  30. data/lib/ably/models/presence_message.rb +244 -0
  31. data/lib/ably/models/protocol_message.rb +288 -0
  32. data/lib/ably/models/push_channel_subscription.rb +114 -0
  33. data/lib/ably/models/stats.rb +242 -0
  34. data/lib/ably/models/stats_types.rb +107 -0
  35. data/lib/ably/models/token_details.rb +148 -0
  36. data/lib/ably/models/token_request.rb +180 -0
  37. data/lib/ably/modules/ably.rb +26 -0
  38. data/lib/ably/modules/async_wrapper.rb +69 -0
  39. data/lib/ably/modules/channels_collection.rb +105 -0
  40. data/lib/ably/modules/conversions.rb +153 -0
  41. data/lib/ably/modules/encodeable.rb +111 -0
  42. data/lib/ably/modules/enum.rb +230 -0
  43. data/lib/ably/modules/event_emitter.rb +200 -0
  44. data/lib/ably/modules/event_machine_helpers.rb +26 -0
  45. data/lib/ably/modules/exception_codes.rb +128 -0
  46. data/lib/ably/modules/http_helpers.rb +41 -0
  47. data/lib/ably/modules/message_emitter.rb +78 -0
  48. data/lib/ably/modules/message_pack.rb +14 -0
  49. data/lib/ably/modules/model_common.rb +84 -0
  50. data/lib/ably/modules/safe_deferrable.rb +83 -0
  51. data/lib/ably/modules/safe_yield.rb +41 -0
  52. data/lib/ably/modules/state_emitter.rb +181 -0
  53. data/lib/ably/modules/state_machine.rb +59 -0
  54. data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  55. data/lib/ably/modules/uses_state_machine.rb +111 -0
  56. data/lib/ably/realtime/auth.rb +283 -0
  57. data/lib/ably/realtime/channel/channel_manager.rb +272 -0
  58. data/lib/ably/realtime/channel/channel_properties.rb +40 -0
  59. data/lib/ably/realtime/channel/channel_state_machine.rb +104 -0
  60. data/lib/ably/realtime/channel/publisher.rb +82 -0
  61. data/lib/ably/realtime/channel/push_channel.rb +69 -0
  62. data/lib/ably/realtime/channel.rb +440 -0
  63. data/lib/ably/realtime/channels.rb +71 -0
  64. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +225 -0
  65. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +87 -0
  66. data/lib/ably/realtime/client.rb +363 -0
  67. data/lib/ably/realtime/connection/connection_manager.rb +594 -0
  68. data/lib/ably/realtime/connection/connection_state_machine.rb +144 -0
  69. data/lib/ably/realtime/connection/websocket_transport.rb +287 -0
  70. data/lib/ably/realtime/connection.rb +738 -0
  71. data/lib/ably/realtime/models/nil_channel.rb +30 -0
  72. data/lib/ably/realtime/presence/members_map.rb +395 -0
  73. data/lib/ably/realtime/presence/presence_manager.rb +78 -0
  74. data/lib/ably/realtime/presence/presence_state_machine.rb +57 -0
  75. data/lib/ably/realtime/presence.rb +502 -0
  76. data/lib/ably/realtime/push/admin.rb +75 -0
  77. data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
  78. data/lib/ably/realtime/push/device_registrations.rb +105 -0
  79. data/lib/ably/realtime/push.rb +25 -0
  80. data/lib/ably/realtime/recovery_key_context.rb +36 -0
  81. data/lib/ably/realtime.rb +60 -0
  82. data/lib/ably/rest/channel/push_channel.rb +59 -0
  83. data/lib/ably/rest/channel.rb +194 -0
  84. data/lib/ably/rest/channels.rb +47 -0
  85. data/lib/ably/rest/client.rb +727 -0
  86. data/lib/ably/rest/middleware/encoder.rb +49 -0
  87. data/lib/ably/rest/middleware/exceptions.rb +53 -0
  88. data/lib/ably/rest/middleware/external_exceptions.rb +24 -0
  89. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +22 -0
  90. data/lib/ably/rest/middleware/logger.rb +58 -0
  91. data/lib/ably/rest/middleware/parse_json.rb +27 -0
  92. data/lib/ably/rest/middleware/parse_message_pack.rb +43 -0
  93. data/lib/ably/rest/presence.rb +113 -0
  94. data/lib/ably/rest/push/admin.rb +58 -0
  95. data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
  96. data/lib/ably/rest/push/device_registrations.rb +114 -0
  97. data/lib/ably/rest/push.rb +25 -0
  98. data/lib/ably/rest.rb +40 -0
  99. data/lib/ably/util/ably_extensions.rb +29 -0
  100. data/lib/ably/util/crypto.rb +134 -0
  101. data/lib/ably/util/pub_sub.rb +45 -0
  102. data/lib/ably/util/safe_deferrable.rb +44 -0
  103. data/lib/ably/version.rb +7 -0
  104. data/lib/ably.rb +16 -0
  105. metadata +497 -0
@@ -0,0 +1,288 @@
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://ably.com/docs/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] auth
12
+ # @return [Ably::Models::AuthDetails] Authentication details used to perform authentication upgrades over an existing transport
13
+ # @!attribute [r] count
14
+ # @return [Integer] The count field is used for ACK and NACK actions. See {http://ably.com/docs/client-lib-development-guide/protocol/#message-acknowledgement message acknowledgement protocol}
15
+ # @!attribute [r] error
16
+ # @return [ErrorInfo] Contains error information
17
+ # @!attribute [r] channel
18
+ # @return [String] Channel name for messages
19
+ # @!attribute [r] channel_serial
20
+ # @return [String] Contains a serial number for a message on the current channel
21
+ # @!attribute [r] connection_id
22
+ # @return [String] Contains a string private connection key used to recover this connection
23
+ # @!attribute [r] message_serial
24
+ # @return [Bignum] Contains a serial number for a message sent from the client to the server
25
+ # @!attribute [r] timestamp
26
+ # @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)
27
+ # @!attribute [r] messages
28
+ # @return [Array<Message>] A {ProtocolMessage} with a `:message` action contains one or more messages belonging to the channel
29
+ # @!attribute [r] presence
30
+ # @return [Array<PresenceMessage>] A {ProtocolMessage} with a `:presence` action contains one or more presence updates belonging to the channel
31
+ # @!attribute [r] flags
32
+ # @return [Integer] Flags indicating special ProtocolMessage states
33
+ # @!attribute [r] attributes
34
+ # @return [Hash] Access the protocol message Hash object ruby'fied to use symbolized keys
35
+ #
36
+ class ProtocolMessage
37
+ include Ably::Modules::ModelCommon
38
+ include Ably::Modules::Encodeable
39
+ include Ably::Modules::SafeDeferrable if defined?(Ably::Realtime)
40
+ extend Ably::Modules::Enum
41
+
42
+ # Actions which are sent by the Ably Realtime API
43
+ #
44
+ # The values correspond to the ints which the API
45
+ # understands.
46
+ ACTION = ruby_enum('ACTION',
47
+ heartbeat: 0,
48
+ ack: 1,
49
+ nack: 2,
50
+ connect: 3,
51
+ connected: 4,
52
+ disconnect: 5,
53
+ disconnected: 6,
54
+ close: 7,
55
+ closed: 8,
56
+ error: 9,
57
+ attach: 10,
58
+ attached: 11,
59
+ detach: 12,
60
+ detached: 13,
61
+ presence: 14,
62
+ message: 15,
63
+ sync: 16,
64
+ auth: 17
65
+ )
66
+
67
+ ATTACH_FLAGS_MAPPING = {
68
+ resume: 32, # 2^5
69
+ presence: 65536, # 2^16
70
+ publish: 131072, # 2^17
71
+ subscribe: 262144, # 2^18
72
+ presence_subscribe: 524288, # 2^19
73
+ }
74
+
75
+ # Indicates this protocol message action will generate an ACK response such as :message or :presence
76
+ # @api private
77
+ def self.ack_required?(for_action)
78
+ ACTION(for_action).match_any?(ACTION.Presence, ACTION.Message)
79
+ end
80
+
81
+ # {ProtocolMessage} initializer
82
+ #
83
+ # @param hash_object [Hash] object with the underlying protocol message data
84
+ # @param [Hash] options an options Hash for this initializer
85
+ # @option options [Logger] :logger An optional Logger to be used by {Ably::Modules::SafeDeferrable} if an exception is caught in a callback
86
+ #
87
+ def initialize(hash_object, options = {})
88
+ @logger = options[:logger] # Logger expected for SafeDeferrable
89
+
90
+ @raw_hash_object = hash_object
91
+ @hash_object = IdiomaticRubyWrapper(@raw_hash_object.clone)
92
+
93
+ raise ArgumentError, 'Invalid ProtocolMessage, action cannot be nil' if @hash_object[:action].nil?
94
+ @hash_object[:action] = ACTION(@hash_object[:action]).to_i unless @hash_object[:action].kind_of?(Integer)
95
+
96
+ @hash_object.freeze
97
+ end
98
+
99
+ %w(id channel channel_serial connection_id).each do |attribute|
100
+ define_method attribute do
101
+ attributes[attribute.to_sym]
102
+ end
103
+ end
104
+
105
+ def id!
106
+ raise RuntimeError, 'ProtocolMessage #id is nil' unless id
107
+ id
108
+ end
109
+
110
+ def action
111
+ ACTION(attributes[:action])
112
+ rescue KeyError
113
+ raise KeyError, "Action '#{attributes[:action]}' is not supported by ProtocolMessage"
114
+ end
115
+
116
+ def error
117
+ @error ||= ErrorInfo.new(attributes[:error]) if attributes[:error]
118
+ end
119
+
120
+ def timestamp
121
+ as_time_from_epoch(attributes[:timestamp]) if attributes[:timestamp]
122
+ end
123
+
124
+ def message_serial
125
+ Integer(attributes[:msg_serial])
126
+ rescue TypeError
127
+ raise TypeError, "msg_serial '#{attributes[:msg_serial]}' is invalid, a positive Integer is expected for a ProtocolMessage"
128
+ end
129
+
130
+ def count
131
+ [1, attributes[:count].to_i].max
132
+ end
133
+
134
+ # @api private
135
+ def has_message_serial?
136
+ message_serial && true
137
+ rescue TypeError
138
+ false
139
+ end
140
+
141
+ def has_channel_serial?
142
+ channel_serial && true
143
+ rescue TypeError
144
+ false
145
+ end
146
+
147
+ def messages
148
+ @messages ||=
149
+ Array(attributes[:messages]).map do |message|
150
+ Ably::Models.Message(message, protocol_message: self)
151
+ end
152
+ end
153
+
154
+ # @api private
155
+ def add_message(message)
156
+ messages << message
157
+ end
158
+
159
+ def presence
160
+ @presence ||=
161
+ Array(attributes[:presence]).map do |message|
162
+ Ably::Models.PresenceMessage(message, protocol_message: self)
163
+ end
164
+ end
165
+
166
+ def message_size
167
+ presence.map(&:size).sum + messages.map(&:size).sum
168
+ end
169
+
170
+ def params
171
+ @params ||= attributes[:params].to_h
172
+ end
173
+
174
+ def flags
175
+ Integer(attributes[:flags])
176
+ rescue TypeError
177
+ 0
178
+ end
179
+
180
+ # @api private
181
+ def has_presence_flag?
182
+ flags & 1 == 1
183
+ end
184
+
185
+ # @api private
186
+ def has_backlog_flag?
187
+ flags & 2 == 2 # 2^1
188
+ end
189
+
190
+ # @api private
191
+ def has_channel_resumed_flag?
192
+ flags & 4 == 4 # 2^2
193
+ end
194
+
195
+ # @api private
196
+ def has_local_presence_flag?
197
+ flags & 8 == 8 # 2^3
198
+ end
199
+
200
+ # @api private
201
+ def has_transient_flag?
202
+ flags & 16 == 16 # 2^4
203
+ end
204
+
205
+ # @api private
206
+ def has_attach_resume_flag?
207
+ flags & ATTACH_FLAGS_MAPPING[:resume] == ATTACH_FLAGS_MAPPING[:resume] # 2^5
208
+ end
209
+
210
+ # @api private
211
+ def has_attach_presence_flag?
212
+ flags & ATTACH_FLAGS_MAPPING[:presence] == ATTACH_FLAGS_MAPPING[:presence] # 2^16
213
+ end
214
+
215
+ # @api private
216
+ def has_attach_publish_flag?
217
+ flags & ATTACH_FLAGS_MAPPING[:publish] == ATTACH_FLAGS_MAPPING[:publish] # 2^17
218
+ end
219
+
220
+ # @api private
221
+ def has_attach_subscribe_flag?
222
+ flags & ATTACH_FLAGS_MAPPING[:subscribe] == ATTACH_FLAGS_MAPPING[:subscribe] # 2^18
223
+ end
224
+
225
+ # @api private
226
+ def has_attach_presence_subscribe_flag?
227
+ flags & ATTACH_FLAGS_MAPPING[:presence_subscribe] == ATTACH_FLAGS_MAPPING[:presence_subscribe] # 2^19
228
+ end
229
+
230
+ def connection_details
231
+ @connection_details ||= Ably::Models::ConnectionDetails(attributes[:connection_details])
232
+ end
233
+
234
+ def auth
235
+ @auth ||= Ably::Models::AuthDetails(attributes[:auth])
236
+ end
237
+
238
+ # Indicates this protocol message will generate an ACK response when sent
239
+ # Examples of protocol messages required ACK include :message and :presence
240
+ # @api private
241
+ def ack_required?
242
+ self.class.ack_required?(action)
243
+ end
244
+
245
+ def attributes
246
+ @hash_object
247
+ end
248
+
249
+ # Return a JSON ready object from the underlying #attributes using Ably naming conventions for keys
250
+ def as_json(*args)
251
+ raise TypeError, ':action is missing, cannot generate a valid Hash for ProtocolMessage' unless action
252
+
253
+ attributes.dup.tap do |hash_object|
254
+ hash_object['action'] = action.to_i
255
+ hash_object['messages'] = messages.map(&:as_json) unless messages.empty?
256
+ hash_object['presence'] = presence.map(&:as_json) unless presence.empty?
257
+ end.as_json
258
+ end
259
+
260
+ def to_s
261
+ json_hash = as_json
262
+
263
+ # Decode any binary data to before converting to a JSON string representation
264
+ %w(messages presence).each do |message_type|
265
+ if json_hash[message_type] && !json_hash[message_type].empty?
266
+ json_hash[message_type].each do |message|
267
+ decode_binary_data_before_to_json message
268
+ end
269
+ end
270
+ end
271
+
272
+ json_hash.to_json
273
+ end
274
+
275
+ # True if the ProtocolMessage appears to be invalid, however this is not a guarantee
276
+ # Used for validating incoming protocol messages, so no need to add unnecessary checks
277
+ # @return [Boolean]
278
+ # @api private
279
+ def invalid?
280
+ action_enum = action rescue nil
281
+ !action_enum
282
+ end
283
+
284
+ # @!attribute [r] logger
285
+ # @api private
286
+ attr_reader :logger
287
+ end
288
+ end
@@ -0,0 +1,114 @@
1
+ module Ably::Modules
2
+ module Conversions
3
+ private
4
+ # Convert push_channel_subscription argument to a {Ably::Models::PushChannelSubscription} object
5
+ #
6
+ # @param push_channel_subscription [Ably::Models::PushChannelSubscription,Hash,nil] A device details object
7
+ #
8
+ # @return [Ably::Models::PushChannelSubscription]
9
+ #
10
+ def PushChannelSubscription(push_channel_subscription)
11
+ case push_channel_subscription
12
+ when Ably::Models::PushChannelSubscription
13
+ push_channel_subscription
14
+ else
15
+ Ably::Models::PushChannelSubscription.new(push_channel_subscription)
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ module Ably::Models
22
+ # Contains the subscriptions of a device, or a group of devices sharing the same clientId,
23
+ # has to a channel in order to receive push notifications.
24
+ #
25
+ class PushChannelSubscription < Ably::Exceptions::BaseAblyException
26
+ include Ably::Modules::ModelCommon
27
+
28
+ # @param hash_object [Hash,nil] Device detail attributes
29
+ #
30
+ def initialize(hash_object = {})
31
+ @raw_hash_object = hash_object || {}
32
+ @hash_object = IdiomaticRubyWrapper(hash_object)
33
+
34
+ if !attributes[:client_id] && !attributes[:device_id]
35
+ raise ArgumentError, 'Either client_id or device_id must be provided'
36
+ end
37
+ if attributes[:client_id] && attributes[:device_id]
38
+ raise ArgumentError, 'client_id and device_id cannot both be provided, they are mutually exclusive'
39
+ end
40
+ if !attributes[:channel]
41
+ raise ArgumentError, 'channel is required'
42
+ end
43
+ end
44
+
45
+ # A static factory method to create a PushChannelSubscription object for a channel and single device.
46
+ #
47
+ # @spec PSC5
48
+ #
49
+ # @param channel [String] the realtime pub/sub channel this subscription is registered to
50
+ # @param device_id [String] Unique device identifier assigned to the push device
51
+ #
52
+ # @return [PushChannelSubscription]
53
+ #
54
+ def self.for_device(channel, device_id)
55
+ PushChannelSubscription.new(channel: channel, device_id: device_id)
56
+ end
57
+
58
+ # A static factory method to create a PushChannelSubscription object for a channel and group of devices sharing the same clientId.
59
+ #
60
+ # @spec PSC5
61
+ #
62
+ # @param channel [String] the realtime pub/sub channel this subscription is registered to
63
+ # @param client_id [String] Client ID that is assigned to one or more registered push devices
64
+ #
65
+ # @return [PushChannelSubscription]
66
+ #
67
+ def self.for_client_id(channel, client_id)
68
+ PushChannelSubscription.new(channel: channel, client_id: client_id)
69
+ end
70
+
71
+ # The channel the push notification subscription is for.
72
+ #
73
+ # @spec PCS4
74
+ #
75
+ # @return [String]
76
+ #
77
+ def channel
78
+ attributes[:channel]
79
+ end
80
+
81
+ # The ID of the client the device, or devices are associated to.
82
+ #
83
+ # @spec PCS3, PCS6
84
+ #
85
+ # @return [String]
86
+ #
87
+ def client_id
88
+ attributes[:client_id]
89
+ end
90
+
91
+ # The unique ID of the device.
92
+ #
93
+ # @spec PCS2, PCS5, PCS6
94
+ #
95
+ # @return [String]
96
+ #
97
+ def device_id
98
+ attributes[:device_id]
99
+ end
100
+
101
+ %w(channel client_id device_id).each do |attribute|
102
+ define_method "#{attribute}=" do |val|
103
+ unless val.nil? || val.kind_of?(String)
104
+ raise ArgumentError, "#{attribute} must be nil or a string value"
105
+ end
106
+ attributes[attribute.to_sym] = val
107
+ end
108
+ end
109
+
110
+ def attributes
111
+ @hash_object
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,242 @@
1
+ require 'ably/models/stats_types'
2
+
3
+ module Ably::Models
4
+ # Convert stat argument to a {Stats} object
5
+ #
6
+ # @param stat [Stats,Hash] A Stats object or Hash of stat properties
7
+ #
8
+ # @return [Stats]
9
+ def self.Stats(stat)
10
+ case stat
11
+ when Stats
12
+ stat
13
+ else
14
+ Stats.new(stat)
15
+ end
16
+ end
17
+
18
+ # A class representing an individual statistic for a specified {#interval_id}
19
+ #
20
+ class Stats
21
+ include Ably::Modules::ModelCommon
22
+ extend Ably::Modules::Enum
23
+
24
+ # Describes the interval unit over which statistics are gathered.
25
+ #
26
+ # MINUTE Interval unit over which statistics are gathered as minutes.
27
+ # HOUR Interval unit over which statistics are gathered as hours.
28
+ # DAY Interval unit over which statistics are gathered as days.
29
+ # MONTH Interval unit over which statistics are gathered as months.
30
+ #
31
+ GRANULARITY = ruby_enum('GRANULARITY',
32
+ :minute,
33
+ :hour,
34
+ :day,
35
+ :month
36
+ )
37
+
38
+ INTERVAL_FORMAT_STRING = [
39
+ '%Y-%m-%d:%H:%M',
40
+ '%Y-%m-%d:%H',
41
+ '%Y-%m-%d',
42
+ '%Y-%m'
43
+ ]
44
+
45
+ class << self
46
+ # Convert a Time with the specified Granularity into an interval ID based on UTC 0 time
47
+ # @example
48
+ # Stats.to_interval_id(Time.now, :hour) # => '2015-01-01:10'
49
+ #
50
+ # @param time [Time] Time used to determine the interval
51
+ # @param granularity [GRANULARITY] Granularity of the metrics such as :hour, :day
52
+ #
53
+ # @return [String] interval ID used for stats
54
+ #
55
+ def to_interval_id(time, granularity)
56
+ raise ArgumentError, 'Time object required as first argument' unless time.kind_of?(Time)
57
+
58
+ granularity = GRANULARITY(granularity)
59
+ format = INTERVAL_FORMAT_STRING[granularity.to_i]
60
+
61
+ time.utc.strftime(format)
62
+ end
63
+
64
+ # Returns the UTC 0 start Time of an interval_id
65
+ # @example
66
+ # Stats.from_interval_id('2015-01-01:10') # => 2015-01-01 10:00:00 +0000
67
+ #
68
+ # @param interval_id [String]
69
+ #
70
+ # @return [Time] start time of the provided interval_id
71
+ #
72
+ def from_interval_id(interval_id)
73
+ raise ArgumentError, 'Interval ID must be a string' unless interval_id.kind_of?(String)
74
+
75
+ format = INTERVAL_FORMAT_STRING.find { |fmt| expected_length(fmt) == interval_id.length }
76
+ raise ArgumentError, 'Interval ID is an invalid length' unless format
77
+
78
+ Time.strptime("#{interval_id} +0000", "#{format} %z").utc
79
+ end
80
+
81
+ # Returns the {Symbol} determined from the interval_id
82
+ # @example
83
+ # Stats.granularity_from_interval_id('2015-01-01:10') # => :hour
84
+ #
85
+ # @param interval_id [String]
86
+ #
87
+ # @return [Symbol]
88
+ #
89
+ def granularity_from_interval_id(interval_id)
90
+ raise ArgumentError, 'Interval ID must be a string' unless interval_id.kind_of?(String)
91
+
92
+ format = INTERVAL_FORMAT_STRING.find { |fmt| expected_length(fmt) == interval_id.length }
93
+ raise ArgumentError, 'Interval ID is an invalid length' unless format
94
+
95
+ GRANULARITY[INTERVAL_FORMAT_STRING.index(format)]
96
+ end
97
+
98
+ private
99
+ def expected_length(format)
100
+ format.gsub('%Y', 'YYYY').length
101
+ end
102
+ end
103
+
104
+ # {Stats} initializer
105
+ #
106
+ # @param hash_object [Hash] object with the underlying stat details
107
+ #
108
+ def initialize(hash_object)
109
+ @raw_hash_object = hash_object
110
+ set_attributes_object hash_object
111
+ end
112
+
113
+ # A {Ably::Models::Stats::MessageTypes} object containing the aggregate count of all message stats.
114
+ #
115
+ # @spec TS12e
116
+ #
117
+ # @return [Stats::MessageTypes]
118
+ #
119
+ def all
120
+ @all ||= Stats::MessageTypes.new(attributes[:all])
121
+ end
122
+
123
+ # A {Ably::Models::Stats::MessageTraffic} object containing the aggregate count of inbound message stats.
124
+ #
125
+ # @spec TS12f
126
+ #
127
+ # @return [Ably::Models::Stats::MessageTraffic]
128
+ #
129
+ def inbound
130
+ @inbound ||= Stats::MessageTraffic.new(attributes[:inbound])
131
+ end
132
+
133
+ # A {Ably::Models::Stats::MessageTraffic} object containing the aggregate count of outbound message stats.
134
+ #
135
+ # @spec TS12g
136
+ #
137
+ # @return [Ably::Models::Stats::MessageTraffic]
138
+ #
139
+ def outbound
140
+ @outbound ||= Stats::MessageTraffic.new(attributes[:outbound])
141
+ end
142
+
143
+ # A {Ably::Models::Stats::MessageTraffic} object containing the aggregate count of persisted message stats.
144
+ #
145
+ # @spec TS12h
146
+ #
147
+ # @return [Ably::Models::Stats::MessageTraffic]
148
+ #
149
+ def persisted
150
+ @persisted ||= Stats::MessageTypes.new(attributes[:persisted])
151
+ end
152
+
153
+ # A {Ably::Models::Stats::ConnectionTypes} object containing a breakdown of connection related stats, such as min, mean and peak connections.
154
+ #
155
+ # @spec TS12i
156
+ #
157
+ # @return [Ably::Models::Stats::ConnectionTypes]
158
+ #
159
+ def connections
160
+ @connections ||= Stats::ConnectionTypes.new(attributes[:connections])
161
+ end
162
+
163
+ # A {Ably::Models::Stats::ResourceCount} object containing a breakdown of connection related stats, such as min, mean and peak connections.
164
+ #
165
+ # @spec TS12j
166
+ #
167
+ # @return [Ably::Models::Stats::ResourceCount]
168
+ #
169
+ def channels
170
+ @channels ||= Stats::ResourceCount.new(attributes[:channels])
171
+ end
172
+
173
+ # A {Ably::Models::Stats::RequestCount} object containing a breakdown of API Requests.
174
+ #
175
+ # @spec TS12k
176
+ #
177
+ # @return [Ably::Models::Stats::RequestCount]
178
+ #
179
+ def api_requests
180
+ @api_requests ||= Stats::RequestCount.new(attributes[:api_requests])
181
+ end
182
+
183
+ # A {Ably::Models::Stats::RequestCount} object containing a breakdown of Ably Token requests.
184
+ #
185
+ # @spec TS12l
186
+ #
187
+ # @return [Ably::Models::Stats::RequestCount]
188
+ #
189
+ def token_requests
190
+ @token_requests ||= Stats::RequestCount.new(attributes[:token_requests])
191
+ end
192
+
193
+ # The UTC time at which the time period covered begins. If unit is set to minute this will be in
194
+ # the format YYYY-mm-dd:HH:MM, if hour it will be YYYY-mm-dd:HH, if day it will be YYYY-mm-dd:00
195
+ # and if month it will be YYYY-mm-01:00.
196
+ #
197
+ # @spec TS12a
198
+ #
199
+ # @return [String]
200
+ #
201
+ def interval_id
202
+ attributes.fetch(:interval_id)
203
+ end
204
+
205
+ # Represents the intervalId as a time object.
206
+ #
207
+ # @spec TS12b
208
+ #
209
+ # @return [Time]
210
+ #
211
+ def interval_time
212
+ self.class.from_interval_id(interval_id)
213
+ end
214
+
215
+ # The length of the interval the stats span. Values will be a [StatsIntervalGranularity]{@link StatsIntervalGranularity}.
216
+ #
217
+ # @spec TS12c
218
+ #
219
+ # @return [GRANULARITY] The granularity of the interval for the stat such as :day, :hour, :minute, see {GRANULARITY}
220
+ #
221
+ def interval_granularity
222
+ self.class.granularity_from_interval_id(interval_id)
223
+ end
224
+
225
+ def attributes
226
+ @attributes
227
+ end
228
+
229
+ def as_json(*args)
230
+ attributes.as_json(*args).reject { |key, val| val.nil? }
231
+ end
232
+
233
+ private
234
+ def raw_hash_object
235
+ @raw_hash_object
236
+ end
237
+
238
+ def set_attributes_object(new_attributes)
239
+ @attributes = IdiomaticRubyWrapper(new_attributes.clone.freeze)
240
+ end
241
+ end
242
+ end