tapi 0.2.0
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.
- 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
|