ably 1.0.6 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +14 -0
  3. data/.travis.yml +10 -8
  4. data/CHANGELOG.md +75 -3
  5. data/LICENSE +1 -3
  6. data/README.md +12 -7
  7. data/Rakefile +32 -0
  8. data/SPEC.md +1277 -835
  9. data/ably.gemspec +14 -9
  10. data/lib/ably/auth.rb +30 -4
  11. data/lib/ably/exceptions.rb +10 -4
  12. data/lib/ably/logger.rb +7 -1
  13. data/lib/ably/models/channel_state_change.rb +1 -1
  14. data/lib/ably/models/connection_state_change.rb +1 -1
  15. data/lib/ably/models/device_details.rb +87 -0
  16. data/lib/ably/models/device_push_details.rb +86 -0
  17. data/lib/ably/models/error_info.rb +23 -2
  18. data/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -4
  19. data/lib/ably/models/protocol_message.rb +32 -2
  20. data/lib/ably/models/push_channel_subscription.rb +89 -0
  21. data/lib/ably/modules/conversions.rb +1 -1
  22. data/lib/ably/modules/encodeable.rb +1 -1
  23. data/lib/ably/modules/exception_codes.rb +128 -0
  24. data/lib/ably/modules/model_common.rb +15 -2
  25. data/lib/ably/modules/state_machine.rb +2 -2
  26. data/lib/ably/realtime.rb +1 -0
  27. data/lib/ably/realtime/auth.rb +1 -1
  28. data/lib/ably/realtime/channel.rb +24 -102
  29. data/lib/ably/realtime/channel/channel_manager.rb +2 -6
  30. data/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
  31. data/lib/ably/realtime/channel/publisher.rb +74 -0
  32. data/lib/ably/realtime/channel/push_channel.rb +62 -0
  33. data/lib/ably/realtime/client.rb +91 -3
  34. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +6 -2
  35. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
  36. data/lib/ably/realtime/connection.rb +34 -20
  37. data/lib/ably/realtime/connection/connection_manager.rb +25 -9
  38. data/lib/ably/realtime/connection/websocket_transport.rb +1 -1
  39. data/lib/ably/realtime/presence.rb +4 -4
  40. data/lib/ably/realtime/presence/members_map.rb +3 -3
  41. data/lib/ably/realtime/push.rb +40 -0
  42. data/lib/ably/realtime/push/admin.rb +61 -0
  43. data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
  44. data/lib/ably/realtime/push/device_registrations.rb +105 -0
  45. data/lib/ably/rest.rb +1 -0
  46. data/lib/ably/rest/channel.rb +53 -17
  47. data/lib/ably/rest/channel/push_channel.rb +62 -0
  48. data/lib/ably/rest/client.rb +154 -32
  49. data/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
  50. data/lib/ably/rest/presence.rb +1 -0
  51. data/lib/ably/rest/push.rb +42 -0
  52. data/lib/ably/rest/push/admin.rb +54 -0
  53. data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
  54. data/lib/ably/rest/push/device_registrations.rb +103 -0
  55. data/lib/ably/version.rb +7 -2
  56. data/spec/acceptance/realtime/auth_spec.rb +245 -17
  57. data/spec/acceptance/realtime/channel_history_spec.rb +26 -20
  58. data/spec/acceptance/realtime/channel_spec.rb +177 -59
  59. data/spec/acceptance/realtime/client_spec.rb +153 -0
  60. data/spec/acceptance/realtime/connection_failures_spec.rb +72 -6
  61. data/spec/acceptance/realtime/connection_spec.rb +129 -18
  62. data/spec/acceptance/realtime/message_spec.rb +36 -34
  63. data/spec/acceptance/realtime/presence_spec.rb +201 -167
  64. data/spec/acceptance/realtime/push_admin_spec.rb +736 -0
  65. data/spec/acceptance/realtime/push_spec.rb +27 -0
  66. data/spec/acceptance/rest/auth_spec.rb +41 -3
  67. data/spec/acceptance/rest/base_spec.rb +2 -2
  68. data/spec/acceptance/rest/channel_spec.rb +79 -4
  69. data/spec/acceptance/rest/channels_spec.rb +6 -0
  70. data/spec/acceptance/rest/client_spec.rb +129 -10
  71. data/spec/acceptance/rest/message_spec.rb +158 -6
  72. data/spec/acceptance/rest/push_admin_spec.rb +952 -0
  73. data/spec/acceptance/rest/push_spec.rb +25 -0
  74. data/spec/acceptance/rest/time_spec.rb +1 -1
  75. data/spec/run_parallel_tests +33 -0
  76. data/spec/spec_helper.rb +1 -1
  77. data/spec/support/debug_failure_helper.rb +9 -5
  78. data/spec/support/test_app.rb +2 -2
  79. data/spec/unit/logger_spec.rb +10 -3
  80. data/spec/unit/models/device_details_spec.rb +102 -0
  81. data/spec/unit/models/device_push_details_spec.rb +101 -0
  82. data/spec/unit/models/error_info_spec.rb +51 -3
  83. data/spec/unit/models/message_spec.rb +17 -2
  84. data/spec/unit/models/presence_message_spec.rb +1 -1
  85. data/spec/unit/models/push_channel_subscription_spec.rb +86 -0
  86. data/spec/unit/modules/enum_spec.rb +1 -1
  87. data/spec/unit/realtime/client_spec.rb +13 -1
  88. data/spec/unit/realtime/connection_spec.rb +1 -1
  89. data/spec/unit/realtime/push_channel_spec.rb +36 -0
  90. data/spec/unit/rest/channel_spec.rb +8 -1
  91. data/spec/unit/rest/client_spec.rb +30 -0
  92. data/spec/unit/rest/push_channel_spec.rb +36 -0
  93. metadata +95 -26
@@ -21,32 +21,37 @@ Gem::Specification.new do |spec|
21
21
  spec.add_runtime_dependency 'eventmachine', '~> 1.2.6'
22
22
  spec.add_runtime_dependency 'em-http-request', '~> 1.1'
23
23
  spec.add_runtime_dependency 'statesman', '~> 1.0.0'
24
- spec.add_runtime_dependency 'faraday', '~> 0.12'
24
+ spec.add_runtime_dependency 'faraday', '>= 0.12', '< 2.0.0'
25
25
  spec.add_runtime_dependency 'excon', '~> 0.55'
26
26
 
27
- if RUBY_VERSION.match(/^1/)
27
+ if RUBY_VERSION.match(/^1\./)
28
28
  spec.add_runtime_dependency 'json', '< 2.0'
29
29
  else
30
30
  spec.add_runtime_dependency 'json'
31
31
  end
32
32
  spec.add_runtime_dependency 'websocket-driver', '~> 0.7'
33
- spec.add_runtime_dependency 'msgpack', '>= 0.6.2'
33
+ spec.add_runtime_dependency 'msgpack', '>= 1.3.0'
34
34
  spec.add_runtime_dependency 'addressable', '>= 2.0.0'
35
35
 
36
- spec.add_development_dependency 'bundler', '~> 1.3'
37
36
  spec.add_development_dependency 'rake', '~> 11.3'
38
37
  spec.add_development_dependency 'redcarpet', '~> 3.3'
39
- spec.add_development_dependency 'rspec', '~> 3.2.0' # version lock, see config.around(:example, :event_machine) in event_machine_helper.rb
40
- spec.add_development_dependency 'rspec-retry', '~> 0.4'
38
+ spec.add_development_dependency 'rspec', '~> 3.3.0' # version lock, see config.around(:example, :event_machine) in event_machine_helper.rb
39
+ spec.add_development_dependency 'rspec-retry', '~> 0.6'
41
40
  spec.add_development_dependency 'yard', '~> 0.9'
41
+ spec.add_development_dependency 'rspec-instafail', '~> 1.0'
42
+ spec.add_development_dependency 'bundler', '>= 1.3.0'
42
43
 
43
- if RUBY_VERSION.match(/^1/)
44
+ if RUBY_VERSION.match(/^1\./)
44
45
  spec.add_development_dependency 'public_suffix', '~> 1.4.6' # Later versions do not support Ruby 1.9
45
46
  spec.add_development_dependency 'webmock', '2.2'
47
+ spec.add_development_dependency 'parallel_tests', '~> 2.9.0'
46
48
  else
47
49
  spec.add_development_dependency 'webmock', '~> 2.2'
48
50
  spec.add_development_dependency 'coveralls'
49
- spec.add_development_dependency 'pry'
50
- spec.add_development_dependency 'pry-byebug'
51
+ spec.add_development_dependency 'parallel_tests', '~> 2.22'
52
+ if !RUBY_VERSION.match(/^2\.[0123]/)
53
+ spec.add_development_dependency 'pry'
54
+ spec.add_development_dependency 'pry-byebug'
55
+ end
51
56
  end
52
57
  end
@@ -35,6 +35,28 @@ module Ably
35
35
 
36
36
  API_KEY_REGEX = /^[\w-]{2,}\.[\w-]{2,}:[\w-]{2,}$/
37
37
 
38
+ # Supported AuthOption keys, see https://www.ably.io/documentation/realtime/types#auth-options
39
+ # TODO: Review client_id usage embedded incorrectly within AuthOptions.
40
+ # This is legacy code to configure a client with a client_id from the ClientOptions
41
+ # TODO: Review inclusion of use_token_auth, ttl, token_params in auth options
42
+ AUTH_OPTIONS_KEYS = %w(
43
+ auth_callback
44
+ auth_url
45
+ auth_method
46
+ auth_headers
47
+ auth_params
48
+ client_id
49
+ key
50
+ key_name
51
+ key_secret
52
+ query_time
53
+ token
54
+ token_details
55
+ token_params
56
+ ttl
57
+ use_token_auth
58
+ )
59
+
38
60
  attr_reader :options, :token_params, :current_token_details
39
61
  alias_method :auth_options, :options
40
62
 
@@ -231,7 +253,7 @@ module Ably
231
253
  auth_callback.call(token_params)
232
254
  end
233
255
  rescue StandardError => err
234
- raise Ably::Exceptions::AuthenticationFailed.new("auth_callback failed: #{err.message}", nil, nil, err, fallback_status: 500, fallback_code: 80019)
256
+ raise Ably::Exceptions::AuthenticationFailed.new("auth_callback failed: #{err.message}", nil, nil, err, fallback_status: 500, fallback_code: Ably::Exceptions::Codes::CONNECTION_NOT_ESTABLISHED_NO_TRANSPORT_HANDLE)
235
257
  end
236
258
  elsif auth_url = auth_options.delete(:auth_url)
237
259
  begin
@@ -239,7 +261,7 @@ module Ably
239
261
  token_request_from_auth_url(auth_url, auth_options, token_params)
240
262
  end
241
263
  rescue StandardError => err
242
- raise Ably::Exceptions::AuthenticationFailed.new("auth_url failed: #{err.message}", nil, nil, err, fallback_status: 500, fallback_code: 80019)
264
+ raise Ably::Exceptions::AuthenticationFailed.new("auth_url failed: #{err.message}", nil, nil, err, fallback_status: 500, fallback_code: Ably::Exceptions::Codes::CONNECTION_NOT_ESTABLISHED_NO_TRANSPORT_HANDLE)
243
265
  end
244
266
  else
245
267
  create_token_request(token_params, auth_options)
@@ -504,6 +526,10 @@ module Ably
504
526
  end
505
527
 
506
528
  def ensure_valid_auth_attributes(attributes)
529
+ (attributes.keys.map(&:to_s) - AUTH_OPTIONS_KEYS).tap do |unsupported_keys|
530
+ raise ArgumentError, "The key(s) #{unsupported_keys.map { |k| ":#{k}" }.join(', ')} are not valid AuthOptions" unless unsupported_keys.empty?
531
+ end
532
+
507
533
  if attributes[:timestamp]
508
534
  unless attributes[:timestamp].kind_of?(Time) || attributes[:timestamp].kind_of?(Numeric)
509
535
  raise ArgumentError, ':timestamp must be a Time or positive Integer value of seconds since epoch'
@@ -631,7 +657,7 @@ module Ably
631
657
  uri = URI.parse(auth_url)
632
658
  connection = Faraday.new("#{uri.scheme}://#{uri.host}", connection_options)
633
659
  method = auth_options[:auth_method] || options[:auth_method] || :get
634
- params = (auth_options[:auth_params] || options[:auth_method] || {}).merge(token_params)
660
+ params = (auth_options[:auth_params] || options[:auth_params] || {}).merge(token_params)
635
661
 
636
662
  response = connection.public_send(method) do |request|
637
663
  request.url uri.path
@@ -643,7 +669,7 @@ module Ably
643
669
  end
644
670
  end
645
671
 
646
- if !response.body.kind_of?(Hash) && !response.headers['Content-Type'].to_s.match(%r{text/plain}i)
672
+ if !response.body.kind_of?(Hash) && !response.headers['Content-Type'].to_s.match(%r{text/plain|application/jwt}i)
647
673
  raise Ably::Exceptions::InvalidResponseBody,
648
674
  "Content Type #{response.headers['Content-Type']} is not supported by this client library"
649
675
  end
@@ -1,10 +1,11 @@
1
+ require 'ably/modules/exception_codes'
2
+
1
3
  module Ably
2
4
  module Exceptions
3
5
  TOKEN_EXPIRED_CODE = 40140..40149
4
- INVALID_CLIENT_ID = 40012
5
6
 
6
7
  # Base Ably exception class that contains status and code values used by Ably
7
- # Refer to https://github.com/ably/ably-common/blob/master/protocol/errors.json
8
+ # Refer to https://github.com/ably/ably-common/blob/main/protocol/errors.json
8
9
  #
9
10
  # @!attribute [r] message
10
11
  # @return [String] Error message from Ably
@@ -37,6 +38,7 @@ module Ably
37
38
  additional_info << "base exception: #{@base_exception.class}" if @base_exception
38
39
  additional_info << "request_id: #{request_id}" if request_id
39
40
  message << "(#{additional_info.join(', ')})"
41
+ message << "-> see https://help.ably.io/error/#{code} for help" if code
40
42
  end
41
43
  message.join(' ')
42
44
  end
@@ -53,6 +55,8 @@ module Ably
53
55
  # An invalid request was received by Ably
54
56
  class InvalidRequest < BaseAblyException; end
55
57
 
58
+ class InvalidCredentials < BaseAblyException; end
59
+
56
60
  # Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided
57
61
  class UnauthorizedRequest < BaseAblyException; end
58
62
 
@@ -112,7 +116,7 @@ module Ably
112
116
  class InvalidState < BaseAblyException; end
113
117
 
114
118
  # A generic Ably exception taht supports a status & code.
115
- # See https://github.com/ably/ably-common/blob/master/protocol/errors.json for a list of Ably errors
119
+ # See https://github.com/ably/ably-common/blob/main/protocol/errors.json for a list of Ably errors
116
120
  class Standard < BaseAblyException; end
117
121
 
118
122
  # The HTTP request has returned a 500 error
@@ -146,12 +150,14 @@ module Ably
146
150
  class ChannelInactive < BaseAblyException; end
147
151
 
148
152
  class IncompatibleClientId < BaseAblyException
149
- def initialize(messages, status = 400, code = INVALID_CLIENT_ID, *args)
153
+ def initialize(messages, status = 400, code = Ably::Exceptions::Codes::INVALID_CLIENT_ID, *args)
150
154
  super(message, status, code, *args)
151
155
  end
152
156
  end
153
157
 
154
158
  # Token request has missing or invalid attributes
155
159
  class InvalidTokenRequest < BaseAblyException; end
160
+
161
+ class PushNotificationsNotSupported < BaseAblyException; end
156
162
  end
157
163
  end
@@ -20,6 +20,8 @@ module Ably
20
20
  ensure_logger_interface_is_valid
21
21
 
22
22
  @logger.level = log_level
23
+
24
+ @log_mutex = Mutex.new
23
25
  end
24
26
 
25
27
  # The logger used by this class, defaults to {http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html Ruby Logger}
@@ -38,7 +40,9 @@ module Ably
38
40
  %w(fatal error warn info debug).each do |method_name|
39
41
  define_method(method_name) do |*args, &block|
40
42
  begin
41
- logger.public_send(method_name, *args, &block)
43
+ log_mutex.synchronize do
44
+ logger.public_send(method_name, *args, &block)
45
+ end
42
46
  rescue StandardError => e
43
47
  logger.error "Logger: Failed to log #{method_name} block - #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
44
48
  end
@@ -46,6 +50,8 @@ module Ably
46
50
  end
47
51
 
48
52
  private
53
+ attr_reader :log_mutex
54
+
49
55
  def client
50
56
  @client
51
57
  end
@@ -48,7 +48,7 @@ module Ably::Models
48
48
  end
49
49
 
50
50
  def to_s
51
- "ChannelStateChange: current state #{current}, previous state #{previous}"
51
+ "<ChannelStateChange: current state #{current}, previous state #{previous}>"
52
52
  end
53
53
  end
54
54
  end
@@ -38,7 +38,7 @@ module Ably::Models
38
38
  end
39
39
 
40
40
  def to_s
41
- "ConnectionStateChange: current state #{current}, previous state #{previous}"
41
+ "<ConnectionStateChange: current state #{current}, previous state #{previous}>"
42
42
  end
43
43
  end
44
44
  end
@@ -0,0 +1,87 @@
1
+ module Ably::Modules
2
+ module Conversions
3
+ private
4
+ # Convert device_details argument to a {Ably::Models::DeviceDetails} object
5
+ #
6
+ # @param device_details [Ably::Models::DeviceDetails,Hash,nil] A device details object
7
+ #
8
+ # @return [Ably::Models::DeviceDetails]
9
+ def DeviceDetails(device_details)
10
+ case device_details
11
+ when Ably::Models::DeviceDetails
12
+ device_details
13
+ else
14
+ Ably::Models::DeviceDetails.new(device_details)
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ module Ably::Models
21
+ # An object representing a devices details, used currently for push notifications
22
+ #
23
+ # @!attribute [r] id
24
+ # @return [String] Unique device identifier assigned randomly by the device
25
+ # @!attribute [r] platform
26
+ # @return [String] Device platform such as android, ios or browser
27
+ # @!attribute [r] form_factor
28
+ # @return [String] Device form factor such as phone, tablet, watch
29
+ # @!attribute [r] client_id
30
+ # @return [String] The authenticated client identifier for this device. See {https://www.ably.io/documentation/general/authentication#identified-clients auth documentation}.
31
+ # @!attribute [r] metadata
32
+ # @return [Hash] Arbitrary metadata that can be associated with a device
33
+ # @!attribute [r] device_secret
34
+ # @return [String] This secret is used internally by Ably client libraries to authenticate with Ably when push registration updates are required such as when the GCM token expires and needs renewing
35
+ # @!attribute [r] push
36
+ # @return [DevicePushDetails] The push notification specific properties for this device allowing push notifications to be delivered to the device
37
+ #
38
+ class DeviceDetails < Ably::Exceptions::BaseAblyException
39
+ include Ably::Modules::ModelCommon
40
+
41
+ # @param hash_object [Hash,nil] Device detail attributes
42
+ #a
43
+ def initialize(hash_object = {})
44
+ @raw_hash_object = hash_object || {}
45
+ @hash_object = IdiomaticRubyWrapper(hash_object)
46
+ end
47
+
48
+ %w(id platform form_factor client_id device_secret).each do |attribute|
49
+ define_method attribute do
50
+ attributes[attribute.to_sym]
51
+ end
52
+
53
+ define_method "#{attribute}=" do |val|
54
+ unless val.nil? || val.kind_of?(String)
55
+ raise ArgumentError, "#{attribute} must be nil or a string value"
56
+ end
57
+ attributes[attribute.to_sym] = val
58
+ end
59
+ end
60
+
61
+ def metadata
62
+ attributes[:metadata] || {}
63
+ end
64
+
65
+ def metadata=(val)
66
+ unless val.nil? || val.kind_of?(Hash)
67
+ raise ArgumentError, "metadata must be nil or a Hash value"
68
+ end
69
+ attributes[:metadata] = val
70
+ end
71
+
72
+ def push
73
+ DevicePushDetails(attributes[:push] || {})
74
+ end
75
+
76
+ def push=(val)
77
+ unless val.nil? || val.kind_of?(Hash) || val.kind_of?(Ably::Models::DevicePushDetails)
78
+ raise ArgumentError, "push must be nil, a Hash value or a DevicePushDetails object"
79
+ end
80
+ attributes[:push] = DevicePushDetails(val)
81
+ end
82
+
83
+ def attributes
84
+ @hash_object
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,86 @@
1
+ module Ably::Modules
2
+ module Conversions
3
+ private
4
+ # Convert device_push_details argument to a {Ably::Models::DevicePushDetails} object
5
+ #
6
+ # @param device_push_details [Ably::Models::DevicePushDetails,Hash,nil] A device push notification details object
7
+ #
8
+ # @return [Ably::Models::DevicePushDetails]
9
+ def DevicePushDetails(device_push_details)
10
+ case device_push_details
11
+ when Ably::Models::DevicePushDetails
12
+ device_push_details
13
+ else
14
+ Ably::Models::DevicePushDetails.new(device_push_details)
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ module Ably::Models
21
+ # An object with the push notification details for {DeviceDetails} object
22
+ #
23
+ # @!attribute [r] transport_type
24
+ # @return [String] Transport type for push notifications such as gcm, apns, web
25
+ # @!attribute [r] state
26
+ # @return [String] The current state of this push target such as Active, Failing or Failed
27
+ # @!attribute [r] error_reason
28
+ # @return [ErrorInfo] If the state is Failing of Failed, this field may optionally contain a reason
29
+ # @!attribute [r] metadata
30
+ # @return [Hash] Arbitrary metadata that can be associated with this object
31
+ #
32
+ class DevicePushDetails < Ably::Exceptions::BaseAblyException
33
+ include Ably::Modules::ModelCommon
34
+
35
+ # @param hash_object [Hash,nil] Device push detail attributes
36
+ #a
37
+ def initialize(hash_object = {})
38
+ @raw_hash_object = hash_object || {}
39
+ @hash_object = IdiomaticRubyWrapper(@raw_hash_object)
40
+ end
41
+
42
+ %w(state).each do |attribute|
43
+ define_method attribute do
44
+ attributes[attribute.to_sym]
45
+ end
46
+
47
+ define_method "#{attribute}=" do |val|
48
+ unless val.nil? || val.kind_of?(String)
49
+ raise ArgumentError, "#{attribute} must be nil or a string value"
50
+ end
51
+ attributes[attribute.to_sym] = val
52
+ end
53
+ end
54
+
55
+ def recipient
56
+ attributes[:recipient] || {}
57
+ end
58
+
59
+ def recipient=(val)
60
+ unless val.nil? || val.kind_of?(Hash)
61
+ raise ArgumentError, "recipient must be nil or a Hash value"
62
+ end
63
+ attributes[:recipient] = val
64
+ end
65
+
66
+ def error_reason
67
+ attributes[:error_reason]
68
+ end
69
+
70
+ def error_reason=(val)
71
+ unless val.nil? || val.kind_of?(Hash) || val.kind_of?(Ably::Models::ErrorInfo)
72
+ raise ArgumentError, "error_reason must be nil, a Hash value or a ErrorInfo object"
73
+ end
74
+
75
+ attributes[:error_reason] = if val.nil?
76
+ nil
77
+ else
78
+ ErrorInfo(val)
79
+ end
80
+ end
81
+
82
+ def attributes
83
+ @hash_object
84
+ end
85
+ end
86
+ end
@@ -1,3 +1,22 @@
1
+ module Ably::Modules
2
+ module Conversions
3
+ private
4
+ # Convert error_details argument to a {ErrorInfo} object
5
+ #
6
+ # @param error_details [ErrorInfo,Hash] Error info attributes
7
+ #
8
+ # @return [ErrorInfo]
9
+ def ErrorInfo(error_details)
10
+ case error_details
11
+ when Ably::Models::ErrorInfo
12
+ error_details
13
+ else
14
+ Ably::Models::ErrorInfo.new(error_details)
15
+ end
16
+ end
17
+ end
18
+ end
19
+
1
20
  module Ably::Models
2
21
  # An exception type encapsulating error information containing
3
22
  # an Ably-specific error code and generic status code.
@@ -19,7 +38,7 @@ module Ably::Models
19
38
  @hash_object = IdiomaticRubyWrapper(hash_object.clone.freeze)
20
39
  end
21
40
 
22
- %w(message code status_code).each do |attribute|
41
+ %w(message code href status_code).each do |attribute|
23
42
  define_method attribute do
24
43
  attributes[attribute.to_sym]
25
44
  end
@@ -31,7 +50,9 @@ module Ably::Models
31
50
  end
32
51
 
33
52
  def to_s
34
- "Error: #{message} (code: #{code}, http status: #{status})"
53
+ error_href = href || (code ? "https://help.ably.io/error/#{code}" : '')
54
+ see_msg = " -> see #{error_href} for help" unless message.to_s.include?(error_href.to_s)
55
+ "<Error: #{message} (code: #{code}, http status: #{status})>#{see_msg}"
35
56
  end
36
57
  end
37
58
  end
@@ -144,11 +144,11 @@ module Ably::Models
144
144
  @attributes
145
145
  end
146
146
 
147
- # Takes the underlying Hash object and returns it in as a JSON ready Hash object using snakeCase for compability with the Ably service.
147
+ # Takes the underlying Hash object and returns it in as a JSON ready Hash object using camelCase for compability with the Ably service.
148
148
  # Note name clashes are ignored and will result in loss of one or more values
149
149
  # @example
150
150
  # wrapper = IdiomaticRubyWrapper({ 'mixedCase': true, mixed_case: false, 'snake_case': 1 })
151
- # wrapper.as_json({ 'mixedCase': true, 'snakeCase': 1 })
151
+ # wrapper.as_json => { 'mixedCase': true, 'snakeCase': 1 }
152
152
  def as_json(*args)
153
153
  attributes.each_with_object({}) do |key_val, new_hash|
154
154
  key = key_val[0]
@@ -161,7 +161,7 @@ module Ably::Models
161
161
  end
162
162
 
163
163
  # Converts the current wrapped mixedCase object to JSON
164
- # using mixedCase syntax as expected by the Realtime API
164
+ # using snakedCase syntax as expected by the Realtime API
165
165
  def to_json(*args)
166
166
  as_json(args).to_json
167
167
  end
@@ -170,7 +170,7 @@ module Ably::Models
170
170
  # Note name clashes are ignored and will result in loss of one or more values
171
171
  # @example
172
172
  # wrapper = IdiomaticRubyWrapper({ 'mixedCase': true, mixed_case: false, 'snake_case': 1 })
173
- # wrapper.to_hash({ mixed_case: true, snake_case: 1 })
173
+ # wrapper.to_hash => { mixed_case: true, snake_case: 1 }
174
174
  def to_hash(*args)
175
175
  each_with_object({}) do |key_val, object|
176
176
  key, val = key_val