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,94 @@
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
+ #
10
+ def DevicePushDetails(device_push_details)
11
+ case device_push_details
12
+ when Ably::Models::DevicePushDetails
13
+ device_push_details
14
+ else
15
+ Ably::Models::DevicePushDetails.new(device_push_details)
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ module Ably::Models
22
+ # An object with the push notification details for {DeviceDetails} object
23
+ #
24
+ class DevicePushDetails < Ably::Exceptions::BaseAblyException
25
+ include Ably::Modules::ModelCommon
26
+
27
+ # @param hash_object [Hash,nil] Device push detail attributes
28
+ #
29
+ def initialize(hash_object = {})
30
+ @raw_hash_object = hash_object || {}
31
+ @hash_object = IdiomaticRubyWrapper(@raw_hash_object)
32
+ end
33
+
34
+ # The current state of the push registration.
35
+ #
36
+ # @spec PCP4
37
+ #
38
+ # @return [Symbol]
39
+ #
40
+ def state
41
+ attributes[:state]
42
+ end
43
+
44
+ def state=(val)
45
+ unless val.nil? || val.kind_of?(String)
46
+ raise ArgumentError, "state must be nil or a string value"
47
+ end
48
+ attributes[:state] = val
49
+ end
50
+
51
+ # A JSON object of key-value pairs that contains of the push transport and address.
52
+ #
53
+ # @spec PCP3
54
+ #
55
+ # @return [Hash, nil]
56
+ #
57
+ def recipient
58
+ attributes[:recipient] || {}
59
+ end
60
+
61
+ def recipient=(val)
62
+ unless val.nil? || val.kind_of?(Hash)
63
+ raise ArgumentError, "recipient must be nil or a Hash value"
64
+ end
65
+ attributes[:recipient] = val
66
+ end
67
+
68
+ # An {Ably::Models::ErrorInfo} object describing the most recent error when the state is Failing or Failed.
69
+ #
70
+ # @spec PCP2
71
+ #
72
+ # @return [Ably::Models::ErrorInfo]
73
+ #
74
+ def error_reason
75
+ attributes[:error_reason]
76
+ end
77
+
78
+ def error_reason=(val)
79
+ unless val.nil? || val.kind_of?(Hash) || val.kind_of?(Ably::Models::ErrorInfo)
80
+ raise ArgumentError, "error_reason must be nil, a Hash value or a ErrorInfo object"
81
+ end
82
+
83
+ attributes[:error_reason] = if val.nil?
84
+ nil
85
+ else
86
+ ErrorInfo(val)
87
+ end
88
+ end
89
+
90
+ def attributes
91
+ @hash_object
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,104 @@
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
+ #
10
+ def ErrorInfo(error_details)
11
+ case error_details
12
+ when Ably::Models::ErrorInfo
13
+ error_details
14
+ else
15
+ Ably::Models::ErrorInfo.new(error_details)
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ module Ably::Models
22
+ # An exception type encapsulating error information containing
23
+ # an Ably-specific error code and generic status code.
24
+ #
25
+ class ErrorInfo < Ably::Exceptions::BaseAblyException
26
+ include Ably::Modules::ModelCommon
27
+
28
+ def initialize(hash_object)
29
+ @raw_hash_object = hash_object
30
+ @hash_object = IdiomaticRubyWrapper(hash_object.clone.freeze)
31
+ end
32
+
33
+ # Ably error code.
34
+ #
35
+ # @spec TI1
36
+ #
37
+ # @return [Integer]
38
+ #
39
+ def code
40
+ attributes[:code]
41
+ end
42
+
43
+ # This is included for REST responses to provide a URL for additional help on the error code.
44
+ #
45
+ # @spec TI4
46
+ #
47
+ # @return [String]
48
+ #
49
+ def href
50
+ attributes[:href]
51
+ end
52
+
53
+ # Additional message information, where available.
54
+ #
55
+ # @spec TI1
56
+ #
57
+ # @return [String]
58
+ #
59
+ def message
60
+ attributes[:message]
61
+ end
62
+
63
+ # Information pertaining to what caused the error where available.
64
+ #
65
+ # @spec TI1
66
+ #
67
+ # @return [Ably::Models::ErrorInfo]
68
+ #
69
+ def cause
70
+ attributes[:cause]
71
+ end
72
+
73
+ # HTTP Status Code corresponding to this error, where applicable.
74
+ #
75
+ # @spec TI1
76
+ #
77
+ # @return [Integer]
78
+ #
79
+ def status_code
80
+ attributes[:status_code]
81
+ end
82
+
83
+ # If a request fails, the request ID must be included in the ErrorInfo returned to the user.
84
+ #
85
+ # @spec RSC7c
86
+ #
87
+ # @return [String]
88
+ #
89
+ def request_id
90
+ attributes[:request_id]
91
+ end
92
+ alias_method :status, :status_code
93
+
94
+ def attributes
95
+ @hash_object
96
+ end
97
+
98
+ def to_s
99
+ error_href = href || (code ? "https://help.ably.io/error/#{code}" : '')
100
+ see_msg = " -> see #{error_href} for help" unless message.to_s.include?(error_href.to_s)
101
+ "<Error: #{message} (code: #{code}, http status: #{status} request_id: #{request_id} cause: #{cause})>#{see_msg}"
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,112 @@
1
+ require 'ably/models/paginated_result'
2
+
3
+ module Ably::Models
4
+ # HTTP respones object from Rest#request object
5
+ # Wraps any Ably HTTP response that supports paging and provides methods to iterate through
6
+ # the pages using {#first}, {#next}, {#has_next?} and {#last?}
7
+
8
+ class HttpPaginatedResponse < PaginatedResult
9
+ # Retrieve the first page of results.
10
+ # When used as part of the {Ably::Realtime} library, it will return a {Ably::Util::SafeDeferrable} object,
11
+ # and allows an optional success callback block to be provided.
12
+ #
13
+ # @return [HttpPaginatedResponse,Ably::Util::SafeDeferrable]
14
+ #
15
+ def first(&success_callback)
16
+ async_wrap_if_realtime(success_callback) do
17
+ return nil unless supports_pagination?
18
+ HttpPaginatedResponse.new(client.get(pagination_url('first')), base_url, client, pagination_options, &each_block)
19
+ end
20
+ end
21
+
22
+ # Retrieve the next page of results.
23
+ # When used as part of the {Ably::Realtime} library, it will return a {Ably::Util::SafeDeferrable} object,
24
+ # and allows an optional success callback block to be provided.
25
+ #
26
+ # @return [HttpPaginatedResponse,Ably::Util::SafeDeferrable]
27
+ #
28
+ def next(&success_callback)
29
+ async_wrap_if_realtime(success_callback) do
30
+ return nil unless has_next?
31
+ HttpPaginatedResponse.new(client.get(pagination_url('next')), base_url, client, pagination_options, &each_block)
32
+ end
33
+ end
34
+
35
+ # The HTTP status code of the response.
36
+ #
37
+ # @spec HP4
38
+ #
39
+ # @return [Integer]
40
+ #
41
+ def status_code
42
+ http_response.status.to_i
43
+ end
44
+
45
+ # Whether statusCode indicates success. This is equivalent to 200 <= statusCode < 300.
46
+ #
47
+ # @spec HP5
48
+ #
49
+ # @return [Boolean]
50
+ #
51
+ def success?
52
+ (200..299).include?(http_response.status.to_i)
53
+ end
54
+
55
+ # The error code if the X-Ably-Errorcode HTTP header is sent in the response.
56
+ #
57
+ # @spec HP6
58
+ #
59
+ # @return [Integer]
60
+ #
61
+ def error_code
62
+ if http_response.headers['X-Ably-Errorcode']
63
+ http_response.headers['X-Ably-Errorcode'].to_i
64
+ end
65
+ end
66
+
67
+ # The error message if the X-Ably-Errormessage HTTP header is sent in the response.
68
+ #
69
+ # @spec HP7
70
+ #
71
+ # @return [String]
72
+ #
73
+ def error_message
74
+ http_response.headers['X-Ably-Errormessage']
75
+ end
76
+
77
+ # The headers of the response.
78
+ #
79
+ # @spec HP8
80
+ #
81
+ # @return [Hash<String, String>]
82
+ #
83
+ def headers
84
+ http_response.headers || {}
85
+ end
86
+
87
+ # Farady compatible response object used when an exception is raised
88
+ # @api private
89
+ class ErrorResponse
90
+ def initialize(status, error_code, error_message)
91
+ @status = status
92
+ @error_code = error_code
93
+ @error_message = error_message
94
+ end
95
+
96
+ def status
97
+ @status
98
+ end
99
+
100
+ def headers
101
+ {
102
+ 'X-Ably-Errorcode' => @error_code,
103
+ 'X-Ably-Errormessage' => @error_message
104
+ }
105
+ end
106
+
107
+ def body
108
+ nil
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,235 @@
1
+ require 'logger'
2
+
3
+ module Ably::Modules
4
+ module Conversions
5
+ private
6
+ # Creates or returns an {IdiomaticRubyWrapper} ensuring it never wraps itself
7
+ #
8
+ # @return [IdiomaticRubyWrapper]
9
+ #
10
+ def IdiomaticRubyWrapper(object, options = {})
11
+ case object
12
+ when Ably::Models::IdiomaticRubyWrapper
13
+ object
14
+ else
15
+ Ably::Models::IdiomaticRubyWrapper.new(object, options)
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ module Ably::Models
22
+ # Wraps Hash objects returned by Ably service to appear as Idiomatic Ruby Hashes with symbol keys
23
+ # It recursively wraps containing Hashes, but will stop wrapping at arrays, any other non Hash object, or any key matching the `:stops_at` options
24
+ # It also provides methods matching the symbolic keys for convenience
25
+ #
26
+ # @example
27
+ # ruby_hash = IdiomaticRubyWrapper.new({ 'keyValue' => 'true' })
28
+ # # or recommended to avoid wrapping wrapped objects
29
+ # ruby_hash = IdiomaticRubyWrapper({ 'keyValue' => 'true' })
30
+ #
31
+ # ruby_hash[:key_value] # => 'true'
32
+ # ruby_hash.key_value # => 'true'
33
+ # ruby_hash[:key_value] = 'new_value'
34
+ # ruby_hash.key_value # => 'new_value'
35
+ #
36
+ # ruby_hash[:none] # => nil
37
+ # ruby_hash.none # => nil
38
+ #
39
+ # @!attribute [r] stop_at
40
+ # @return [Array<Symbol,String>] array of keys that this wrapper should stop wrapping at to preserve the underlying Hash as is
41
+ #
42
+ class IdiomaticRubyWrapper
43
+ include Enumerable
44
+ include Ably::Modules::Conversions
45
+ include Ably::Modules::MessagePack
46
+
47
+ attr_reader :stop_at
48
+
49
+ # Creates an IdiomaticRubyWrapper around the mixed case Hash object
50
+ #
51
+ # @attribute [Hash] mixedCaseHashObject mixed case Hash object
52
+ # @attribute [Array<Symbol,String>] stop_at array of keys that this wrapper should stop wrapping at to preserve the underlying Hash as is
53
+ #
54
+ def initialize(mixedCaseHashObject, options = {})
55
+ stop_at = options.fetch(:stop_at, [])
56
+
57
+ if mixedCaseHashObject.kind_of?(IdiomaticRubyWrapper)
58
+ $stderr.puts "<IdiomaticRubyWrapper#initialize> WARNING: Wrapping a IdiomaticRubyWrapper with another IdiomaticRubyWrapper"
59
+ end
60
+
61
+ @attributes = mixedCaseHashObject
62
+ @stop_at = Array(stop_at).each_with_object({}) do |key, object|
63
+ object[convert_to_snake_case_symbol(key)] = true
64
+ end.freeze
65
+ end
66
+
67
+ def [](key)
68
+ value = attributes[source_key_for(key)]
69
+ if stop_at?(key) || !value.kind_of?(Hash)
70
+ value
71
+ else
72
+ IdiomaticRubyWrapper.new(value, stop_at: stop_at)
73
+ end
74
+ end
75
+
76
+ def []=(key, value)
77
+ attributes[source_key_for(key)] = value
78
+ end
79
+
80
+ def fetch(key, default = nil)
81
+ if has_key?(key)
82
+ self[key]
83
+ else
84
+ if default
85
+ default
86
+ elsif block_given?
87
+ yield key
88
+ else
89
+ raise KeyError, "key not found: #{key}"
90
+ end
91
+ end
92
+ end
93
+
94
+ def size
95
+ attributes.size
96
+ end
97
+
98
+ def empty?
99
+ attributes.empty?
100
+ end
101
+
102
+ def keys
103
+ map { |key, value| key }
104
+ end
105
+
106
+ def values
107
+ map { |key, value| value }
108
+ end
109
+
110
+ def has_key?(key)
111
+ attributes.has_key?(source_key_for(key))
112
+ end
113
+
114
+ # Method ensuring this {IdiomaticRubyWrapper} is {http://ruby-doc.org/core-2.1.3/Enumerable.html Enumerable}
115
+ def each
116
+ return to_enum(:each) unless block_given?
117
+
118
+ attributes.each do |key, value|
119
+ key = convert_to_snake_case_symbol(key)
120
+ value = self[key]
121
+ yield key, value
122
+ end
123
+ end
124
+
125
+ # Compare object based on Hash equivalent
126
+ def ==(other)
127
+ return false unless other.kind_of?(self.class) || other.kind_of?(Hash)
128
+
129
+ other = other.to_hash if other.kind_of?(self.class)
130
+ to_hash == other
131
+ end
132
+
133
+ def method_missing(method_sym, *arguments)
134
+ key = method_sym.to_s.gsub(%r{=$}, '')
135
+ return super if !has_key?(key)
136
+
137
+ if method_sym.to_s.match(%r{=$})
138
+ raise ArgumentError, "Cannot set #{method_sym} with more than one argument" unless arguments.length == 1
139
+ self[key] = arguments.first
140
+ else
141
+ raise ArgumentError, "Cannot pass an argument to #{method_sym} when retrieving its value" unless arguments.empty?
142
+ self[method_sym]
143
+ end
144
+ end
145
+
146
+ # @!attribute [r] Hash
147
+ # @return [Hash] Access to the raw Hash object provided to the constructor of this wrapper
148
+ def attributes
149
+ @attributes
150
+ end
151
+
152
+ # Takes the underlying Hash object and returns it in as a JSON ready Hash object using camelCase for compability with the Ably service.
153
+ # Note name clashes are ignored and will result in loss of one or more values
154
+ # @example
155
+ # wrapper = IdiomaticRubyWrapper({ 'mixedCase': true, mixed_case: false, 'snake_case': 1 })
156
+ # wrapper.as_json => { 'mixedCase': true, 'snakeCase': 1 }
157
+ def as_json(*args)
158
+ attributes.each_with_object({}) do |key_val, new_hash|
159
+ key = key_val[0]
160
+ mixed_case_key = convert_to_mixed_case(key)
161
+ wrapped_val = self[key]
162
+ wrapped_val = wrapped_val.as_json(args) if wrapped_val.kind_of?(IdiomaticRubyWrapper)
163
+
164
+ new_hash[mixed_case_key] = wrapped_val
165
+ end
166
+ end
167
+
168
+ # Converts the current wrapped mixedCase object to JSON
169
+ # using snakedCase syntax as expected by the Realtime API
170
+ def to_json(*args)
171
+ as_json(args).to_json
172
+ end
173
+
174
+ # Generate a symbolized Hash object representing the underlying Hash in a Ruby friendly format.
175
+ # Note name clashes are ignored and will result in loss of one or more values
176
+ # @example
177
+ # wrapper = IdiomaticRubyWrapper({ 'mixedCase': true, mixed_case: false, 'snake_case': 1 })
178
+ # wrapper.to_hash => { mixed_case: true, snake_case: 1 }
179
+ def to_hash(*args)
180
+ each_with_object({}) do |key_val, object|
181
+ key, val = key_val
182
+ val = val.to_hash(args) if val.kind_of?(IdiomaticRubyWrapper)
183
+ object[key] = val
184
+ end
185
+ end
186
+
187
+ # Method to create a duplicate of the underlying Hash object
188
+ # Useful when underlying Hash is frozen
189
+ def dup
190
+ Ably::Models::IdiomaticRubyWrapper.new(attributes.dup, stop_at: stop_at.keys)
191
+ end
192
+
193
+ # Freeze the underlying data
194
+ def freeze
195
+ attributes.freeze
196
+ end
197
+
198
+ def to_s
199
+ attributes.to_s
200
+ end
201
+
202
+ # @!attribute [r] hash
203
+ # @return [Integer] Compute a hash-code for this hash. Two hashes with the same content will have the same hash code
204
+ def hash
205
+ attributes.hash
206
+ end
207
+
208
+ private
209
+ def stop_at?(key)
210
+ @stop_at.has_key?(key)
211
+ end
212
+
213
+ # We assume by default all keys are interchangeable between :this_format and 'thisFormat'
214
+ # However, this method will find other fallback formats such as CamelCase or :symbols if a matching
215
+ # key is not found in mixedCase.
216
+ def source_key_for(symbolized_key)
217
+ format_preferences = [
218
+ lambda { |key_sym| convert_to_mixed_case(key_sym) },
219
+ lambda { |key_sym| key_sym.to_sym },
220
+ lambda { |key_sym| key_sym.to_s },
221
+ lambda { |key_sym| convert_to_mixed_case(key_sym).to_sym },
222
+ lambda { |key_sym| convert_to_lower_case(key_sym) },
223
+ lambda { |key_sym| convert_to_lower_case(key_sym).to_sym },
224
+ lambda { |key_sym| convert_to_mixed_case(key_sym, force_camel: true) },
225
+ lambda { |key_sym| convert_to_mixed_case(key_sym, force_camel: true).to_sym }
226
+ ]
227
+
228
+ preferred_format = format_preferences.detect do |format|
229
+ attributes.has_key?(format.call(symbolized_key))
230
+ end || format_preferences.first
231
+
232
+ preferred_format.call(symbolized_key)
233
+ end
234
+ end
235
+ end