isotope11-suitcase 1.7.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +5 -0
  2. data/CHANGELOG.md +17 -0
  3. data/Gemfile +3 -0
  4. data/README.md +83 -0
  5. data/Rakefile +13 -0
  6. data/examples/hash_adapter.rb +15 -0
  7. data/examples/hotel_image_db.rb +24 -0
  8. data/examples/redis_adapter.rb +29 -0
  9. data/isotope11-suitcase.gemspec +30 -0
  10. data/lib/suitcase.rb +16 -0
  11. data/lib/suitcase/car_rental.rb +57 -0
  12. data/lib/suitcase/codes.rb +5 -0
  13. data/lib/suitcase/configuration.rb +29 -0
  14. data/lib/suitcase/core_ext/string.rb +13 -0
  15. data/lib/suitcase/hotel.rb +366 -0
  16. data/lib/suitcase/hotel/amenity.rb +46 -0
  17. data/lib/suitcase/hotel/bed_type.rb +21 -0
  18. data/lib/suitcase/hotel/cache.rb +52 -0
  19. data/lib/suitcase/hotel/cancellation.rb +55 -0
  20. data/lib/suitcase/hotel/ean_exception.rb +35 -0
  21. data/lib/suitcase/hotel/helpers.rb +210 -0
  22. data/lib/suitcase/hotel/image.rb +19 -0
  23. data/lib/suitcase/hotel/location.rb +67 -0
  24. data/lib/suitcase/hotel/nightly_rate.rb +14 -0
  25. data/lib/suitcase/hotel/payment_option.rb +41 -0
  26. data/lib/suitcase/hotel/reservation.rb +15 -0
  27. data/lib/suitcase/hotel/room.rb +139 -0
  28. data/lib/suitcase/hotel/session.rb +7 -0
  29. data/lib/suitcase/hotel/surcharge.rb +23 -0
  30. data/lib/suitcase/version.rb +3 -0
  31. data/test/car_rentals/car_rental_test.rb +30 -0
  32. data/test/hotels/amenity_test.rb +23 -0
  33. data/test/hotels/caching_test.rb +42 -0
  34. data/test/hotels/ean_exception_test.rb +24 -0
  35. data/test/hotels/helpers_test.rb +60 -0
  36. data/test/hotels/hotel_location_test.rb +23 -0
  37. data/test/hotels/hotel_test.rb +122 -0
  38. data/test/hotels/image_test.rb +20 -0
  39. data/test/hotels/payment_option_test.rb +15 -0
  40. data/test/hotels/reservation_test.rb +15 -0
  41. data/test/hotels/room_test.rb +53 -0
  42. data/test/hotels/session_test.rb +14 -0
  43. data/test/keys.rb +43 -0
  44. data/test/minitest_helper.rb +29 -0
  45. data/test/support/fake_response.rb +13 -0
  46. metadata +187 -0
@@ -0,0 +1,46 @@
1
+ module Suitcase
2
+ class Hotel
3
+ class Amenity
4
+ attr_accessor :id, :description
5
+
6
+ BITS = { business_services: 1,
7
+ fitness_center: 2,
8
+ hot_tub: 4,
9
+ internet_access: 8,
10
+ kids_activities: 16,
11
+ kitchen: 32,
12
+ pets_allowed: 64,
13
+ swimming_pool: 128,
14
+ restaurant: 256,
15
+ whirlpool_bath: 1024,
16
+ breakfast: 2048,
17
+ babysitting: 4096,
18
+ jacuzzi: 8192,
19
+ parking: 16384,
20
+ room_service: 32768,
21
+ accessible_path: 65536,
22
+ accessible_bathroom: 131072,
23
+ roll_in_shower: 262144,
24
+ handicapped_parking: 524288,
25
+ in_room_accessibility: 1048576,
26
+ deaf_accessiblity: 2097152,
27
+ braille_or_signage: 4194304,
28
+ free_airport_shuttle: 8388608,
29
+ indoor_pool: 16777216,
30
+ outdoor_pool: 33554432,
31
+ extended_parking: 67108864,
32
+ free_parking: 134217728
33
+ }
34
+
35
+ def initialize(info)
36
+ @id, @description = info[:id], info[:description]
37
+ end
38
+
39
+ def self.parse_mask(bitmask)
40
+ return nil unless bitmask
41
+
42
+ BITS.select { |amenity, bit| (bitmask & bit) > 0 }.keys
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,21 @@
1
+ module Suitcase
2
+ class Hotel
3
+ # Public: A BedType represents a bed configuration for a Room.
4
+ class BedType
5
+ # Internal: The ID of the BedType.
6
+ attr_accessor :id
7
+
8
+ # Internal: The description of the BedType.
9
+ attr_accessor :description
10
+
11
+ # Internal: Create a new BedType.
12
+ #
13
+ # info - A Hash from the parsed API response with the following keys:
14
+ # :id - The ID of the BedType.
15
+ # :description 3- The description of the BedType.
16
+ def initialize(info)
17
+ @id, @description = info[:id], info[:description]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,52 @@
1
+ module Suitcase
2
+ class Hotel
3
+ class Cache
4
+ attr_accessor :store
5
+
6
+ def initialize(store)
7
+ @store = store
8
+ end
9
+
10
+ def save_query(action, params, response)
11
+ %w(apiKey cid customerSessionId customerIpAddress locale
12
+ customerUserAgent).each do |param|
13
+ params.delete(param)
14
+ end
15
+ params.delete("currencyCode") unless action == :paymentInfo
16
+
17
+ string_params = keys_to_strings(params)
18
+
19
+ @store[action] ||= {}
20
+ @store[action] = @store[action].merge(string_params => response)
21
+ end
22
+
23
+ def get_query(action, params)
24
+ string_params = keys_to_strings(params)
25
+ @store[action] ? @store[action][string_params] : nil
26
+ end
27
+
28
+ def keys
29
+ @store.keys
30
+ end
31
+
32
+ def cached?(action, params)
33
+ string_params = keys_to_strings(params)
34
+ @store[action] && @store[action][string_params]
35
+ end
36
+
37
+ def undefine_query(action, params)
38
+ string_params = keys_to_strings(params)
39
+ @store[action].delete(string_params) if @store[action]
40
+ end
41
+
42
+ private
43
+
44
+ def keys_to_strings(hash)
45
+ hash.inject({}) do |memo, (k, v)|
46
+ memo[k.to_s] = v
47
+ memo
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,55 @@
1
+ module Suitcase
2
+ class Hotel
3
+ class Cancellation
4
+ extend Helpers
5
+ include Helpers
6
+
7
+ attr_accessor :itinerary_id, :email,
8
+ :confirmation_number,
9
+ :reason, :cancellation_number
10
+
11
+ # Accepts a hash with the itinerary_id, confirmation_number, email
12
+ # and optional reason code (COP ILL DEA OTH)
13
+ def initialize(info)
14
+ info.each do |k, v|
15
+ instance_variable_set("@" + k.to_s, v)
16
+ end
17
+ end
18
+
19
+ # Makes the call to EAN and if successful sets the
20
+ # cancellation_number attribute
21
+ def cancel_reservation!
22
+ params = {}
23
+ params["itineraryId"] = itinerary_id
24
+ params["email"] = email
25
+ params["confirmationNumber"] = confirmation_number
26
+ params["reason"] = reason if valid_reason?
27
+
28
+ uri = Cancellation.url(
29
+ method: 'cancel',
30
+ params: params,
31
+ include_key: true,
32
+ include_cid: true,
33
+ secure: true
34
+ )
35
+
36
+ session = Patron::Session.new
37
+ session.timeout = 30000
38
+ session.base_url = "https://" + uri.host
39
+ res = session.post uri.request_uri, {}
40
+ parsed = JSON.parse res.body
41
+ handle_errors(parsed)
42
+ @cancellation_number = parsed["HotelRoomCancellationResponse"]["cancellationNumber"]
43
+ end
44
+
45
+ def reservation_canceled?
46
+ !cancellation_number.nil?
47
+ end
48
+
49
+ def valid_reason?
50
+ %w(COP ILL DEA OTH).include?(reason)
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,35 @@
1
+ module Suitcase
2
+ class Hotel
3
+ # Public: An Exception to be raised from all EAN API-related errors.
4
+ class EANException < Exception
5
+ # Internal: Setter for the recovery information.
6
+ attr_writer :recovery
7
+
8
+ # Public: Getter for the recovery information.
9
+ attr_reader :recovery
10
+
11
+ # Internal: Setter for the error type.
12
+ attr_writer :type
13
+
14
+ # Public: Getter for the error type..
15
+ attr_reader :type
16
+
17
+ # Internal: Create a new EAN exception.
18
+ #
19
+ # message - The String error message returned by the API.
20
+ # type - The Symbol type of the error.
21
+ def initialize(message, type = nil)
22
+ @type = type
23
+ super(message)
24
+ end
25
+
26
+ # Public: Check if the error is recoverable. If it is, recovery information
27
+ # is in the attribute recovery.
28
+ #
29
+ # Returns a Boolean based on whether the error is recoverable.
30
+ def recoverable?
31
+ @recovery.is_a?(Hash)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,210 @@
1
+ module Suitcase
2
+ # Internal: Various methods for doing things that many files need to in the
3
+ # library.
4
+ #
5
+ # Examples
6
+ #
7
+ # parameterize(something: "else", another: "thing")
8
+ # # => "something=else&another=thing"
9
+ class Hotel
10
+ module Helpers
11
+ # Internal: Defaults for the builder options to Helpers#url.
12
+ URL_DEFAULTS = {
13
+ include_key: true,
14
+ include_cid: true,
15
+ secure: false,
16
+ as_form: false,
17
+ session: Session.new
18
+ }
19
+
20
+ # Internal: Parameterize a Hash.
21
+ #
22
+ # hash - The Hash to be parameterized.
23
+ #
24
+ # Examples
25
+ #
26
+ # parameterize(something: "else", another: "thing")
27
+ # # => "something=else&another=thing"
28
+ #
29
+ # Returns a parameterized String.
30
+ def parameterize(hash)
31
+ hash.map { |key, value| "#{key}=#{value}" }.join "&"
32
+ end
33
+
34
+ # Internal: Build an API URL.
35
+ #
36
+ # builder - A Hash with the following required keys:
37
+ # :method - The API method to be put in the URL.
38
+ # :params - The params to be put in the URL.
39
+ # And the following optional ones:
40
+ # :include_key - Whether to include the API key or not.
41
+ # :include_cid - Whether to include the API cid or not.
42
+ # :secure - Whether or not for the request to be sent over
43
+ # HTTPS.
44
+ # :as_form - Whether or not to include the parameters in the
45
+ # URL.
46
+ # :session - The Session associated with the request.
47
+ #
48
+ # Examples
49
+ #
50
+ # url(secure: true, as_form: true, method: "myMethod", params: {})
51
+ # # => #<URI::HTTPS URL:https://book.api.ean.com/.../rs/hotel/v3/myMethod>
52
+ #
53
+ # Returns the URI with the builder's information.
54
+ def url(builder)
55
+ builder = URL_DEFAULTS.merge(builder)
56
+ builder[:session] ||= URL_DEFAULTS[:session]
57
+ method, params = builder[:method], builder[:params]
58
+ params["apiKey"] = Configuration.hotel_api_key if builder[:include_key]
59
+ params["cid"] = (Configuration.hotel_cid ||= 55505) if builder[:include_cid]
60
+ params["sig"] = generate_signature if Configuration.use_signature_auth?
61
+ params["minorRev"] = Configuration.ean_revision
62
+
63
+ url = main_url(builder[:secure]) + method.to_s + (builder[:as_form] ? "" : "?")
64
+
65
+ params.merge!(build_session_params(builder[:session]))
66
+ url += parameterize(params) unless builder[:as_form]
67
+ URI.parse(URI.escape(url))
68
+ end
69
+
70
+ # Internal: Get the root URL for the Hotels API.
71
+ #
72
+ # secure - Whether or not the URL should be HTTPS or not.
73
+ #
74
+ # Returns the URL.
75
+ def main_url(secure)
76
+ url = "http#{secure ? "s" : ""}://#{secure ? "book." : ""}"
77
+ url += "api.ean.com/ean-services/rs/hotel/v3/"
78
+ url
79
+ end
80
+
81
+ # Internal: Parse the JSON response at the given URL and handle errors.
82
+ #
83
+ # uri - The URI to parse the JSON from.
84
+ #
85
+ # Returns the parsed JSON.
86
+ def parse_response(uri)
87
+ response = Net::HTTP.get_response(uri)
88
+
89
+ if response.code.to_i == 403
90
+ if response.body.include?("Forbidden")
91
+ e = EANException.new("You have not been granted permission to access the requested method or object.")
92
+ e.type = :forbidden
93
+ elsif response.body.include?("Not Authorized")
94
+ e = EANException.new("The API key associated with your request was not recognized, or the digital signature was incorrect.")
95
+ e.type = :not_authorized
96
+ elsif response.body.include?("Developer Inactive")
97
+ e = EANException.new("The API key you are using to access the API has not been approved, is not correct, or has been disabled. If using SIG Authentication, your digital signature is incorrect and does not match the one generated when receiving your request.")
98
+ e.type = :developer_inactive
99
+ elsif response.body.include?("Queries Per Second Limit")
100
+ e = EANException.new("The API key you are using has attempted to access the API too many times in one second.")
101
+ e.type = :query_limit
102
+ elsif response.body.include?("Account Over Rate Limit")
103
+ e = EANException.new("The API key you are using has attempted to access the API too many times in the rate limiting period.")
104
+ e.type = :rate_limit
105
+ elsif response.body.include?("Rate Limit Exceeded")
106
+ e = EANException.new("The service you have requested is over capacity.")
107
+ e.type = :over_capacity
108
+ elsif response.body.include?("Authentication Failure")
109
+ e = EANException.new("The combination of authentication checks failed.")
110
+ e.type = :authentication_failure
111
+ else
112
+ e = EANException.new("An unknown error occured: #{response.body}.")
113
+ e.type = :unknown
114
+ end
115
+
116
+ raise e
117
+ end
118
+
119
+ JSON.parse(Net::HTTP.get_response(uri).body)
120
+ end
121
+
122
+ # Internal: Raise the errors returned from the response.
123
+ #
124
+ # info - The parsed JSON to get the errors from.
125
+ #
126
+ # Returns nothing.
127
+ def handle_errors(info)
128
+ key = info.keys.first
129
+ if info[key] && info[key]["EanWsError"]
130
+ message = info[key]["EanWsError"]["presentationMessage"]
131
+ exception = EANException.new(message)
132
+ if message =~ /Multiple locations/ && (info = info[key]["LocationInfos"])
133
+ exception.type = :multiple_locations
134
+ exception.recovery = {
135
+ alternate_locations: info["LocationInfo"].map do |info|
136
+ Location.new(
137
+ destination_id: info["destinationId"],
138
+ type: info["type"],
139
+ city: info["city"],
140
+ province: info["stateProvinceCode"]
141
+ )
142
+ end
143
+ }
144
+ elsif message =~ /Data in this request could not be validated/
145
+ exception.type = :invalid_data
146
+ end
147
+
148
+ raise exception
149
+ end
150
+ end
151
+
152
+ # Internal: Get the base URL based on a Hash of info.
153
+ #
154
+ # info - A Hash with only one required key:
155
+ # :booking - Whether or not it is a booking request.
156
+ #
157
+ # Returns the base URL.
158
+ def base_url(info)
159
+ main_url(info[:booking])
160
+ end
161
+
162
+ # Internal: Build a Hash of params from a Session.
163
+ #
164
+ # session - The Session to generate params from.
165
+ #
166
+ # Returns the Hash of params.
167
+ def build_session_params(session)
168
+ session_info = {}
169
+ session_info["customerSessionId"] = session.id if session.id
170
+ session_info["customerIpAddress"] = session.ip_address if session.ip_address
171
+ session_info["locale"] = session.locale if session.locale
172
+ session_info["currencyCode"] = session.currency_code if session.currency_code
173
+ session_info["customerUserAgent"] = session.user_agent if session.user_agent
174
+ session_info
175
+ end
176
+
177
+ # Internal: Update the Session's ID with the parsed JSON.
178
+ #
179
+ # parsed - The parsed JSON to fetch the ID from.
180
+ # session - The Session to update.
181
+ #
182
+ # Returns nothing.
183
+ def update_session(parsed, session)
184
+ session ||= Session.new
185
+ session.id = parsed[parsed.keys.first]["customerSessionId"] if parsed[parsed.keys.first]
186
+ end
187
+
188
+ # Internal: Generate a digital signature to authenticate with.
189
+ #
190
+ # Returns the generated signature.
191
+ def generate_signature
192
+ Digest::MD5.hexdigest(Configuration.hotel_api_key +
193
+ Configuration.hotel_shared_secret +
194
+ Time.now.to_i.to_s)
195
+ end
196
+
197
+ # Internal: Build a rooms parameter given an array of hashes
198
+ #
199
+ # Returns the rooms parameters hash
200
+ def parameterize_rooms(rooms)
201
+ params = {}
202
+ rooms.each_with_index do |room, n|
203
+ params["room#{n+1}"] = room[:adults].to_s
204
+ params["room#{n+1}"] += "," + room[:children_ages].join(",") if room[:children_ages]
205
+ end
206
+ params
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,19 @@
1
+ module Suitcase
2
+ class Image
3
+ attr_accessor :id, :url, :caption, :width, :height, :thumbnail_url, :name
4
+
5
+ def initialize(data)
6
+ @id = data["hotelImageId"]
7
+ @name = data["name"]
8
+ @caption = data["caption"]
9
+ @url = data["url"]
10
+ @thumbnail_url = data["thumbnailURL"]
11
+ @width = data["width"]
12
+ @height = data["height"]
13
+ end
14
+
15
+ def size
16
+ width.to_s + "x" + height.to_s
17
+ end
18
+ end
19
+ end