tapi 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +34 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +152 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/lib/tapi.rb +9 -0
- data/lib/tapi/v3/client.rb +261 -0
- data/lib/tapi/v3/configurable.rb +27 -0
- data/lib/tapi/v3/errors.rb +26 -0
- data/lib/tapi/v3/flights/search.rb +124 -0
- data/lib/tapi/v3/generic_search.rb +97 -0
- data/lib/tapi/v3/hotels/search.rb +213 -0
- data/lib/tapi/v3/utils.rb +64 -0
- data/lib/tapi/v3/validations.rb +58 -0
- data/spec/client_spec.rb +320 -0
- data/spec/configurable_spec.rb +27 -0
- data/spec/flights/search_spec.rb +102 -0
- data/spec/hotels/search_spec.rb +143 -0
- data/spec/spec_helper.rb +12 -0
- metadata +201 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module TAPI
|
3
|
+
module V3
|
4
|
+
module Configurable
|
5
|
+
|
6
|
+
def self.included(klass)
|
7
|
+
klass.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
def config
|
11
|
+
Thread.current[:tapi_config] ||= {}
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
def config
|
17
|
+
Thread.current[:tapi_config] ||= {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def config=(config)
|
21
|
+
Thread.current[:tapi_config] = config
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module TAPI
|
2
|
+
|
3
|
+
class Error < RuntimeError
|
4
|
+
attr_accessor :response_code, :request_url, :response_body
|
5
|
+
|
6
|
+
def to_s
|
7
|
+
"Status code: #{response_code}\nRequest url: #{request_url}\n\n\n#{response_body}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class BadRequestError < Error
|
12
|
+
end
|
13
|
+
|
14
|
+
class NotFoundError < Error
|
15
|
+
end
|
16
|
+
|
17
|
+
class ExpiredError < Error
|
18
|
+
end
|
19
|
+
|
20
|
+
class MovedError < Error
|
21
|
+
end
|
22
|
+
|
23
|
+
class InternalServerError < Error
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module TAPI
|
3
|
+
module V3
|
4
|
+
module Flights
|
5
|
+
class Search < TAPI::V3::GenericSearch
|
6
|
+
|
7
|
+
AIRPORTCODEEXPR = /^[\w]{3}$/
|
8
|
+
|
9
|
+
attr_accessor :origin, :destination, :comfort
|
10
|
+
attr_reader :leaves_on, :returns_on, :one_way, :adults, :children, :infants
|
11
|
+
|
12
|
+
def leaves_on=(date)
|
13
|
+
@leaves_on = coerce_date(date) rescue nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def returns_on=(date)
|
17
|
+
@returns_on = coerce_date(date) rescue nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def one_way=(value)
|
21
|
+
@one_way = value.to_s == 'true'
|
22
|
+
end
|
23
|
+
|
24
|
+
def adults=(value)
|
25
|
+
@adults = value.to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
def children=(value)
|
29
|
+
@children = value.to_i
|
30
|
+
end
|
31
|
+
|
32
|
+
def infants=(value)
|
33
|
+
@infants = value.to_i
|
34
|
+
end
|
35
|
+
|
36
|
+
validates_numericality_of :adults, "Bitte wählen Sie die Anzahl der Erwachsenen."
|
37
|
+
validates_numericality_of :children, "Bitte wählen Sie die Anzahl der Kinder."
|
38
|
+
validates_numericality_of :infants, "Bitte wählen Sie die Anzahl der Babies."
|
39
|
+
|
40
|
+
validate do
|
41
|
+
if leaves_on and leaves_on < Date.today
|
42
|
+
add_error :leaves_on, "Abflug muss in der Zukunft liegen."
|
43
|
+
end
|
44
|
+
|
45
|
+
if returns_on and leaves_on and returns_on < leaves_on
|
46
|
+
add_error :returns_on, "Rückflug muss später als der Hinflug sein."
|
47
|
+
end
|
48
|
+
|
49
|
+
if leaves_on.nil?
|
50
|
+
add_error :leaves_on, "Bitte wählen Sie ein gültiges Abflugdatum aus."
|
51
|
+
end
|
52
|
+
|
53
|
+
if one_way.to_s == 'true' and not returns_on.to_s.empty?
|
54
|
+
add_error :returns_on, "Bitte wählen Sie entweder One-Way oder ein Rückflugdatum aus."
|
55
|
+
end
|
56
|
+
|
57
|
+
if one_way.to_s != 'true' and returns_on.to_s.empty?
|
58
|
+
add_error :returns_on, "Bitte wählen Sie ein gültiges Datum aus."
|
59
|
+
end
|
60
|
+
|
61
|
+
if adults.to_i < infants.to_i
|
62
|
+
add_error :passengers, "Die Anzahl der Erwachsenen muss mindestens gleich der Babies sein."
|
63
|
+
end
|
64
|
+
|
65
|
+
if adults.to_i < 1
|
66
|
+
add_error :passengers, "Es muss mindestens ein Erwachsener fliegen."
|
67
|
+
end
|
68
|
+
|
69
|
+
unless AIRPORTCODEEXPR =~ origin.to_s
|
70
|
+
add_error :origin, "Bitte wählen Sie einen Startflughafen."
|
71
|
+
end
|
72
|
+
|
73
|
+
unless AIRPORTCODEEXPR =~ destination.to_s
|
74
|
+
add_error :destination, "Bitte wählen Sie einen Zielflughafen."
|
75
|
+
end
|
76
|
+
|
77
|
+
unless /^(E|B|EB|BE)$/ =~ comfort
|
78
|
+
add_error :passengers, "Bitte geben Die einen gültigen Code für den Comfort an."
|
79
|
+
end
|
80
|
+
|
81
|
+
unless origin.to_s.empty? or destination.to_s.empty?
|
82
|
+
if origin == destination
|
83
|
+
add_error :destination, "Bitte wählen Sie unterschiedliche Start- und Zielflughäfen."
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def item_path
|
89
|
+
'flights'
|
90
|
+
end
|
91
|
+
|
92
|
+
def initialize(options = {})
|
93
|
+
@errors = {}
|
94
|
+
self.origin = options[:origin]
|
95
|
+
self.destination = options[:destination]
|
96
|
+
self.leaves_on = options[:leaves_on]
|
97
|
+
self.returns_on = options[:returns_on]
|
98
|
+
self.one_way = options[:one_way]
|
99
|
+
self.adults = options[:adults] || 1
|
100
|
+
self.children = options[:children] || 0
|
101
|
+
self.infants = options[:infants] || 0
|
102
|
+
self.comfort = (options[:comfort] || "E").upcase
|
103
|
+
end
|
104
|
+
|
105
|
+
def parameters
|
106
|
+
{
|
107
|
+
:key => config[:key],
|
108
|
+
:format => 'json',
|
109
|
+
:origin => origin,
|
110
|
+
:destination => destination,
|
111
|
+
:leaves_on => leaves_on.to_s,
|
112
|
+
:returns_on => returns_on.to_s,
|
113
|
+
:one_way => one_way,
|
114
|
+
:adults => adults,
|
115
|
+
:children => children,
|
116
|
+
:infants => infants,
|
117
|
+
:comfort => comfort
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module TAPI
|
3
|
+
module V3
|
4
|
+
class GenericSearch
|
5
|
+
|
6
|
+
extend Validations
|
7
|
+
|
8
|
+
attr_reader :client, :client_urls, :started_at, :start_time
|
9
|
+
attr_accessor :errors
|
10
|
+
|
11
|
+
def id
|
12
|
+
if @id
|
13
|
+
@id
|
14
|
+
elsif @client and @client.respond_to?(:id)
|
15
|
+
@client.id
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def id=(id)
|
20
|
+
@id = id
|
21
|
+
end
|
22
|
+
|
23
|
+
include Configurable
|
24
|
+
include Utils
|
25
|
+
|
26
|
+
def add_error(name, message)
|
27
|
+
(errors[name] ||= []) << message
|
28
|
+
end
|
29
|
+
|
30
|
+
def valid?
|
31
|
+
errors.clear
|
32
|
+
|
33
|
+
self.class.inherited_validations.each do |validation|
|
34
|
+
instance_eval(&validation)
|
35
|
+
end
|
36
|
+
|
37
|
+
errors.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
def has_errors?
|
41
|
+
errors.any?
|
42
|
+
end
|
43
|
+
|
44
|
+
def reload
|
45
|
+
load_client
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def api_url
|
50
|
+
port = config[:port] == 80 ? '' : ":#{config[:port]}"
|
51
|
+
"http://#{ config[:host] }#{ port }#{ config[:path] }"
|
52
|
+
end
|
53
|
+
|
54
|
+
def api_key
|
55
|
+
config[:key]
|
56
|
+
end
|
57
|
+
|
58
|
+
def post_url
|
59
|
+
api_url + '/' + item_path + '/searches.json'
|
60
|
+
end
|
61
|
+
|
62
|
+
def get(path, options={})
|
63
|
+
TAPI::V3::Client.new_from_get(api_url + path + '.json', options.merge(:key => api_key)).first
|
64
|
+
end
|
65
|
+
|
66
|
+
def start!
|
67
|
+
@started_at = Time.now
|
68
|
+
@client = TAPI::V3::Client.new_from_post(post_url, parameters).search
|
69
|
+
@start_time = Time.now - @started_at
|
70
|
+
@client_urls = @client.urls
|
71
|
+
end
|
72
|
+
|
73
|
+
def load_client
|
74
|
+
if @client_urls
|
75
|
+
client, etag = TAPI::V3::Client.new_from_get(@client_urls[:search_url], {}, @etag)
|
76
|
+
@client, @etag = client.search, etag unless @etag == etag
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def restart!
|
81
|
+
if @client_urls
|
82
|
+
TAPI::V3::Client.new_from_post(@client_urls[:restart_url], {})
|
83
|
+
load_client
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def method_missing(key, *args)
|
88
|
+
begin
|
89
|
+
@client.send(key, *args)
|
90
|
+
rescue NoMethodError
|
91
|
+
raise NoMethodError, "undefined method `#{key}' for #{self.class}", caller
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module TAPI
|
3
|
+
module V3
|
4
|
+
module Hotels
|
5
|
+
class Search < TAPI::V3::GenericSearch
|
6
|
+
attr_accessor :expired
|
7
|
+
|
8
|
+
attr_reader :arrival_date, :departure_date, :city_id, :region_id, :hotel_id
|
9
|
+
attr_reader :room_configuration, :single_rooms_count, :double_rooms_count
|
10
|
+
|
11
|
+
validates_numericality_of :single_rooms_count, "Bitte wählen Sie die Anzahl der Einzelzimmer."
|
12
|
+
validates_numericality_of :double_rooms_count, "Bitte wählen Sie die Anzahl der Doppelzimmer."
|
13
|
+
|
14
|
+
validates_presence_of :arrival_date, "Bitte wählen Sie ein gültiges Datum."
|
15
|
+
validates_presence_of :departure_date, "Bitte wählen Sie ein gültiges Datum."
|
16
|
+
|
17
|
+
validate do
|
18
|
+
if arrival_date and arrival_date < Date.today
|
19
|
+
add_error :arrival_date, "Das Anreisedatum darf nicht in der Vergangenheit liegen."
|
20
|
+
end
|
21
|
+
|
22
|
+
if single_rooms_count == 0 and double_rooms_count == 0
|
23
|
+
add_error :room_configuration, "Bitte mindestens ein Zimmer angeben."
|
24
|
+
end
|
25
|
+
|
26
|
+
if (single_rooms_count + double_rooms_count) > 3
|
27
|
+
add_error :room_configuration, "Bitte nicht mehr als 3 Zimmer angeben."
|
28
|
+
end
|
29
|
+
|
30
|
+
if arrival_date and departure_date and departure_date <= arrival_date
|
31
|
+
add_error :departure_date, "Das Abreisedatum muss mindestens einen Tag nach der Ankunft liegen."
|
32
|
+
end
|
33
|
+
|
34
|
+
if city_id.blank? and region_id.blank? and hotel_id.blank?
|
35
|
+
add_error :city_id, "Bitte geben Sie den gewünschten Aufenthaltsort an."
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
class << self
|
41
|
+
|
42
|
+
def parse_room_configuration(str)
|
43
|
+
rooms_strings = str.split('][').map {|r| r.gsub(/\]|\[/, '').split('|')}
|
44
|
+
raise ArgumentError, "#{str} is not a valid room configuration hash !" if rooms_strings.length > 3 || rooms_strings.empty?
|
45
|
+
room_collection = {}
|
46
|
+
rooms = rooms_strings.each_with_index do |guests, i|
|
47
|
+
room_collection[i + 1] ||= {}
|
48
|
+
guests.each do |g|
|
49
|
+
case g
|
50
|
+
when 'A'
|
51
|
+
room_collection[i + 1]['adults'] ||= 0
|
52
|
+
room_collection[i + 1]['adults'] += 1
|
53
|
+
when /\d/
|
54
|
+
room_collection[i + 1]['child'] ||= {}
|
55
|
+
last_key = room_collection[i + 1]['child'].keys.min || 0
|
56
|
+
room_collection[i + 1]['child'][last_key + 1] = g.to_i
|
57
|
+
else
|
58
|
+
raise ArgumentError, "#{str} is not a valid room configuration hash !"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
room_collection
|
63
|
+
end
|
64
|
+
|
65
|
+
end # class
|
66
|
+
|
67
|
+
def initialize(options = {})
|
68
|
+
@errors = {}
|
69
|
+
@single_rooms_count = 0
|
70
|
+
@double_rooms_count = 0
|
71
|
+
|
72
|
+
self.arrival_date = options[:arrival_date]
|
73
|
+
self.departure_date = options[:departure_date]
|
74
|
+
|
75
|
+
self.city_id = options[:city_id]
|
76
|
+
self.region_id = options[:region_id]
|
77
|
+
self.hotel_id = options[:hotel_id]
|
78
|
+
|
79
|
+
if options[:room_configuration].blank?
|
80
|
+
self.single_rooms_count = options[:single_rooms_count] || 1
|
81
|
+
self.double_rooms_count = options[:double_rooms_count] || 0
|
82
|
+
else
|
83
|
+
self.room_configuration = options[:room_configuration]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def item_path
|
88
|
+
'hotels'
|
89
|
+
end
|
90
|
+
|
91
|
+
def state
|
92
|
+
@client.status_detailed.state if @client
|
93
|
+
end
|
94
|
+
|
95
|
+
def location
|
96
|
+
case
|
97
|
+
when city_id then city
|
98
|
+
when region_id then region
|
99
|
+
when hotel_id then hotel
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def location_type
|
104
|
+
case
|
105
|
+
when city_id then :city
|
106
|
+
when region_id then :region
|
107
|
+
when hotel_id then :hotel
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def location_id
|
112
|
+
city_id || region_id || hotel_id
|
113
|
+
end
|
114
|
+
|
115
|
+
def arrival_date=(date)
|
116
|
+
@arrival_date = coerce_date(date)
|
117
|
+
rescue TypeError => e
|
118
|
+
Client.logger.debug e.message
|
119
|
+
Client.logger.debug e.backtrace.join("\n")
|
120
|
+
end
|
121
|
+
|
122
|
+
def departure_date=(date)
|
123
|
+
@departure_date = coerce_date(date)
|
124
|
+
rescue TypeError => e
|
125
|
+
Client.logger.debug e.message
|
126
|
+
Client.logger.debug e.backtrace.join("\n")
|
127
|
+
end
|
128
|
+
|
129
|
+
def city_id=(id)
|
130
|
+
@city_id = id.to_i unless id.to_s.empty?
|
131
|
+
end
|
132
|
+
|
133
|
+
def region_id=(id)
|
134
|
+
@region_id = id.to_i unless id.to_s.empty?
|
135
|
+
end
|
136
|
+
|
137
|
+
def hotel_id=(id)
|
138
|
+
@hotel_id = id.to_i unless id.to_s.empty?
|
139
|
+
end
|
140
|
+
|
141
|
+
def city
|
142
|
+
@city ||= get("/locations/cities/#{city_id}").city unless city_id.to_s.empty?
|
143
|
+
end
|
144
|
+
|
145
|
+
def region
|
146
|
+
@region ||= get("/locations/regions/#{region_id}").region unless region_id.to_s.empty?
|
147
|
+
end
|
148
|
+
|
149
|
+
def hotel
|
150
|
+
@hotel ||= get("/locations/hotels/#{hotel_id}").hotel unless hotel_id.to_s.empty?
|
151
|
+
end
|
152
|
+
|
153
|
+
def room_configuration=(config)
|
154
|
+
@room_configuration = config
|
155
|
+
rooms = Search.parse_room_configuration(config).values
|
156
|
+
|
157
|
+
@single_rooms_count = rooms.count { |room| room['adults'] == 1 }
|
158
|
+
@double_rooms_count = rooms.count { |room| room['adults'] == 2 }
|
159
|
+
end
|
160
|
+
|
161
|
+
def single_rooms_count=(count)
|
162
|
+
@single_rooms_count = Integer(count)
|
163
|
+
update_room_configuration!
|
164
|
+
end
|
165
|
+
|
166
|
+
def double_rooms_count=(count)
|
167
|
+
@double_rooms_count = Integer(count)
|
168
|
+
update_room_configuration!
|
169
|
+
end
|
170
|
+
|
171
|
+
def update_room_configuration!
|
172
|
+
@room_configuration = ('[A]' * single_rooms_count) + ('[A|A]' * double_rooms_count)
|
173
|
+
end
|
174
|
+
|
175
|
+
def to_param
|
176
|
+
{
|
177
|
+
:location_type => location_type,
|
178
|
+
:location_id => location_id,
|
179
|
+
:arrival_date => arrival_date.to_s,
|
180
|
+
:departure_date => departure_date.to_s,
|
181
|
+
:single_rooms_count => single_rooms_count,
|
182
|
+
:double_rooms_count => double_rooms_count
|
183
|
+
}
|
184
|
+
end
|
185
|
+
|
186
|
+
def parameters
|
187
|
+
{
|
188
|
+
:key => config[:key],
|
189
|
+
:format => 'json',
|
190
|
+
:city_id => city_id,
|
191
|
+
:region_id => region_id,
|
192
|
+
:hotel_id => hotel_id,
|
193
|
+
:arrival_date => arrival_date.to_s,
|
194
|
+
:departure_date => departure_date.to_s,
|
195
|
+
:room_configuration => room_configuration
|
196
|
+
}
|
197
|
+
end
|
198
|
+
|
199
|
+
# create a hotel search with the same parameters
|
200
|
+
# for a given hotel
|
201
|
+
def hotel_search(result_or_hotel_id)
|
202
|
+
hotel_id = result_or_hotel_id.is_a?(Fixnum) ? result_or_hotel_id : result_or_hotel_id.hotel_id
|
203
|
+
params = parameters
|
204
|
+
params.delete(:city_id)
|
205
|
+
params.delete(:region_id)
|
206
|
+
params[:hotel_id] = hotel_id
|
207
|
+
TAPI::V3::Hotels::Search.new(params)
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|