flightstats-flex 0.1.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/README.md +252 -0
- data/lib/flightstats.rb +109 -0
- data/lib/flightstats/abstract_date.rb +6 -0
- data/lib/flightstats/actual_gate_departure.rb +4 -0
- data/lib/flightstats/actual_runway_departure.rb +4 -0
- data/lib/flightstats/airline.rb +10 -0
- data/lib/flightstats/airport.rb +88 -0
- data/lib/flightstats/airport_resources.rb +7 -0
- data/lib/flightstats/api.rb +73 -0
- data/lib/flightstats/api/errors.rb +120 -0
- data/lib/flightstats/api/net_http_adapter.rb +114 -0
- data/lib/flightstats/appendix.rb +6 -0
- data/lib/flightstats/arrival_date.rb +4 -0
- data/lib/flightstats/codeshare.rb +7 -0
- data/lib/flightstats/departure_date.rb +4 -0
- data/lib/flightstats/equipment.rb +10 -0
- data/lib/flightstats/estimated_gate_arrival.rb +4 -0
- data/lib/flightstats/estimated_gate_departure.rb +4 -0
- data/lib/flightstats/estimated_runway_arrival.rb +4 -0
- data/lib/flightstats/estimated_runway_departure.rb +4 -0
- data/lib/flightstats/extended_options.rb +6 -0
- data/lib/flightstats/flight.rb +61 -0
- data/lib/flightstats/flight_durations.rb +9 -0
- data/lib/flightstats/flight_equipment.rb +6 -0
- data/lib/flightstats/flight_id.rb +6 -0
- data/lib/flightstats/flight_leg.rb +25 -0
- data/lib/flightstats/flight_plan_planned_arrival.rb +4 -0
- data/lib/flightstats/flight_plan_planned_departure.rb +4 -0
- data/lib/flightstats/flight_status.rb +50 -0
- data/lib/flightstats/helper.rb +52 -0
- data/lib/flightstats/operational_times.rb +16 -0
- data/lib/flightstats/operator.rb +5 -0
- data/lib/flightstats/published_arrival.rb +4 -0
- data/lib/flightstats/published_departure.rb +4 -0
- data/lib/flightstats/resource.rb +108 -0
- data/lib/flightstats/schedule.rb +7 -0
- data/lib/flightstats/scheduled_gate_arrival.rb +4 -0
- data/lib/flightstats/scheduled_gate_departure.rb +4 -0
- data/lib/flightstats/version.rb +17 -0
- metadata +141 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
module FlightStats
|
2
|
+
class API
|
3
|
+
require 'flightstats/api/errors'
|
4
|
+
require 'flightstats/resource'
|
5
|
+
|
6
|
+
@@base_uri = "https://api.flightstats.com"
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# Additional HTTP headers sent with each API call
|
10
|
+
# @return [Hash{String => String}]
|
11
|
+
def headers
|
12
|
+
@headers ||= { 'Accept' => accept, 'User-Agent' => user_agent }
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [String, nil] Accept-Language header value
|
16
|
+
def accept_language
|
17
|
+
headers['Accept-Language']
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param [String] language Accept-Language header value
|
21
|
+
def accept_language=(language)
|
22
|
+
headers['Accept-Language'] = language
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Net::HTTPOK, Net::HTTPResponse]
|
26
|
+
# @raise [ResponseError] With a non-2xx status code.
|
27
|
+
def head uri, params = {}, options = {}
|
28
|
+
request :head, uri, { :params => params }.merge(options)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Net::HTTPOK, Net::HTTPResponse]
|
32
|
+
# @raise [ResponseError] With a non-2xx status code.
|
33
|
+
def get uri, params = {}, options = {}
|
34
|
+
request :get, uri, { :params => params }.merge(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Net::HTTPCreated, Net::HTTPResponse]
|
38
|
+
# @raise [ResponseError] With a non-2xx status code.
|
39
|
+
def post uri, body = nil, options = {}
|
40
|
+
request :post, uri, { :body => body.to_s }.merge(options)
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Net::HTTPOK, Net::HTTPResponse]
|
44
|
+
# @raise [ResponseError] With a non-2xx status code.
|
45
|
+
def put uri, body = nil, options = {}
|
46
|
+
request :put, uri, { :body => body.to_s }.merge(options)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Net::HTTPNoContent, Net::HTTPResponse]
|
50
|
+
# @raise [ResponseError] With a non-2xx status code.
|
51
|
+
def delete uri, options = {}
|
52
|
+
request :delete, uri, options
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [URI::Generic]
|
56
|
+
def base_uri
|
57
|
+
URI.parse @@base_uri
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [String]
|
61
|
+
def user_agent
|
62
|
+
"FlightStats/#{FlightStats::Version}; #{RUBY_DESCRIPTION}"
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def accept
|
68
|
+
'application/json'
|
69
|
+
end
|
70
|
+
alias content_type accept
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module FlightStats
|
5
|
+
class API
|
6
|
+
# The superclass to all errors that occur when making an API request.
|
7
|
+
class ResponseError < Error
|
8
|
+
attr_reader :request
|
9
|
+
attr_reader :response
|
10
|
+
|
11
|
+
def initialize request, response
|
12
|
+
@request, @response = request, response
|
13
|
+
end
|
14
|
+
|
15
|
+
def code
|
16
|
+
response.code.to_i if response
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
if description
|
21
|
+
return CGI.unescapeHTML [description, details].compact.join(' ')
|
22
|
+
end
|
23
|
+
|
24
|
+
return super unless code
|
25
|
+
"%d %s (%s %s)" % [
|
26
|
+
code, http_error, request.method, API.base_uri + request.path
|
27
|
+
]
|
28
|
+
end
|
29
|
+
|
30
|
+
def message
|
31
|
+
json and json['error'] and json['error']['errorMessage']
|
32
|
+
end
|
33
|
+
|
34
|
+
def code
|
35
|
+
json and json['error'] and json['error']['errorCode']
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def http_error
|
41
|
+
Helper.demodulize self.class.name.gsub(/([a-z])([A-Z])/, '\1 \2')
|
42
|
+
end
|
43
|
+
|
44
|
+
def json
|
45
|
+
return @json if defined? @json
|
46
|
+
@json = (JSON.parse(response.body) if response && !response.body.empty?)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# === 3xx Redirection
|
51
|
+
#
|
52
|
+
# Not an error, per se, but should result in one in the normal course of
|
53
|
+
# API interaction.
|
54
|
+
class Redirection < ResponseError
|
55
|
+
end
|
56
|
+
|
57
|
+
# === 4xx Client Error
|
58
|
+
#
|
59
|
+
# The superclass to all client errors (responses with status code 4xx).
|
60
|
+
class ClientError < ResponseError
|
61
|
+
end
|
62
|
+
|
63
|
+
# === 400 Bad Request
|
64
|
+
#
|
65
|
+
# Something was wrong with the request submitted, and it must be corrected
|
66
|
+
# before it can succeed. The response body will include details about the error.
|
67
|
+
class BadRequest < ClientError
|
68
|
+
end
|
69
|
+
|
70
|
+
# === 403 Forbidden
|
71
|
+
#
|
72
|
+
# Authorization failure; valid credentials were not provided.
|
73
|
+
class Forbidden < ClientError
|
74
|
+
end
|
75
|
+
|
76
|
+
# === 404 Not Found
|
77
|
+
#
|
78
|
+
# No resource was found at the specified URI.
|
79
|
+
class NotFound < ClientError
|
80
|
+
end
|
81
|
+
|
82
|
+
# === 405 Method Not Allowed
|
83
|
+
#
|
84
|
+
# The HTTP request specified a method (e.g. PUT, DELETE, OPTIONS, etc.)
|
85
|
+
# that is not supported by this resource.
|
86
|
+
class MethodNotAllowed < ClientError
|
87
|
+
end
|
88
|
+
|
89
|
+
# === 5xx Server Error
|
90
|
+
#
|
91
|
+
# The superclass to all server errors (responses with status code 5xx).
|
92
|
+
class ServerError < ResponseError
|
93
|
+
end
|
94
|
+
|
95
|
+
# === 500 Internal Server Error
|
96
|
+
#
|
97
|
+
# Unexpected server-side failure. Should this occur, please contact us
|
98
|
+
# for assistance. The response body will include a unique identifier
|
99
|
+
# that we can use to help locate the problem.
|
100
|
+
class InternalServerError < ServerError
|
101
|
+
end
|
102
|
+
|
103
|
+
# Error mapping by status code.
|
104
|
+
ERRORS = Hash.new { |hash, code|
|
105
|
+
unless hash.key? code
|
106
|
+
case code
|
107
|
+
when 400...500 then ClientError
|
108
|
+
when 500...600 then ServerError
|
109
|
+
else ResponseError
|
110
|
+
end
|
111
|
+
end
|
112
|
+
}.update(
|
113
|
+
400 => BadRequest,
|
114
|
+
403 => Forbidden,
|
115
|
+
404 => NotFound,
|
116
|
+
405 => MethodNotAllowed,
|
117
|
+
500 => InternalServerError
|
118
|
+
).freeze
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
4
|
+
|
5
|
+
module FlightStats
|
6
|
+
class API
|
7
|
+
module Net
|
8
|
+
module HTTPAdapter
|
9
|
+
# A hash of Net::HTTP settings configured before the request.
|
10
|
+
#
|
11
|
+
# @return [Hash]
|
12
|
+
def net_http
|
13
|
+
@net_http ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Used to store any Net::HTTP settings.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# FlightStats::API.net_http = {
|
20
|
+
# :verify_mode => OpenSSL::SSL::VERIFY_PEER,
|
21
|
+
# :ca_path => "/etc/ssl/certs",
|
22
|
+
# :ca_file => "/opt/local/share/curl/curl-ca-bundle.crt"
|
23
|
+
# }
|
24
|
+
attr_writer :net_http
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
METHODS = {
|
29
|
+
:head => ::Net::HTTP::Head,
|
30
|
+
:get => ::Net::HTTP::Get,
|
31
|
+
:post => ::Net::HTTP::Post,
|
32
|
+
:put => ::Net::HTTP::Put,
|
33
|
+
:delete => ::Net::HTTP::Delete
|
34
|
+
}
|
35
|
+
|
36
|
+
def request method, uri, options = {}
|
37
|
+
head = headers.dup
|
38
|
+
head.update options[:head] if options[:head]
|
39
|
+
head.delete_if { |_, value| value.nil? }
|
40
|
+
uri = base_uri + uri
|
41
|
+
|
42
|
+
query_params = "?"
|
43
|
+
query_params += "#{CGI.escape 'appId'}=#{CGI.escape FlightStats.app_id.to_s}"
|
44
|
+
query_params += "&#{CGI.escape 'appKey'}=#{CGI.escape FlightStats.app_key.to_s}"
|
45
|
+
if options[:params] && !options[:params].empty?
|
46
|
+
pairs = options[:params].map { |key, value|
|
47
|
+
"#{CGI.escape key.to_s}=#{CGI.escape value.to_s}"
|
48
|
+
}
|
49
|
+
query_params += "&#{pairs.join '&'}"
|
50
|
+
end
|
51
|
+
uri += query_params
|
52
|
+
|
53
|
+
request = METHODS[method].new uri.request_uri, head
|
54
|
+
if options[:body]
|
55
|
+
request['Content-Type'] = content_type
|
56
|
+
request.body = options[:body]
|
57
|
+
end
|
58
|
+
if options[:etag]
|
59
|
+
request['If-None-Match'] = options[:etag]
|
60
|
+
end
|
61
|
+
if options[:format]
|
62
|
+
request['Accept'] = FORMATS[options[:format]]
|
63
|
+
end
|
64
|
+
if options[:locale]
|
65
|
+
request['Accept-Language'] = options[:locale]
|
66
|
+
end
|
67
|
+
http = ::Net::HTTP.new uri.host, uri.port
|
68
|
+
http.use_ssl = uri.scheme == 'https'
|
69
|
+
net_http.each_pair { |key, value| http.send "#{key}=", value }
|
70
|
+
|
71
|
+
if FlightStats.logger
|
72
|
+
FlightStats.log :info, "===> %s %s" % [request.method, uri]
|
73
|
+
headers = request.to_hash
|
74
|
+
headers['authorization'] &&= ['Basic [FILTERED]']
|
75
|
+
FlightStats.log :debug, headers.inspect
|
76
|
+
if request.body && !request.body.empty?
|
77
|
+
FlightStats.log :debug, request.body
|
78
|
+
end
|
79
|
+
start_time = Time.now
|
80
|
+
end
|
81
|
+
|
82
|
+
response = http.start { http.request request }
|
83
|
+
code = response.code.to_i
|
84
|
+
|
85
|
+
if FlightStats.logger
|
86
|
+
latency = (Time.now - start_time) * 1_000
|
87
|
+
level = case code
|
88
|
+
when 200...300 then :info
|
89
|
+
when 300...400 then :warn
|
90
|
+
when 400...500 then :error
|
91
|
+
else :fatal
|
92
|
+
end
|
93
|
+
FlightStats.log level, "<=== %d %s (%.1fms)" % [
|
94
|
+
code,
|
95
|
+
response.class.name[9, response.class.name.length].gsub(
|
96
|
+
/([a-z])([A-Z])/, '\1 \2'
|
97
|
+
),
|
98
|
+
latency
|
99
|
+
]
|
100
|
+
FlightStats.log :debug, response.to_hash.inspect
|
101
|
+
FlightStats.log :debug, response.body if response.body
|
102
|
+
end
|
103
|
+
|
104
|
+
case code
|
105
|
+
when 200...300 then response
|
106
|
+
else raise ERRORS[code].new request, response
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
extend Net::HTTPAdapter
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module FlightStats
|
2
|
+
class Flight < Resource
|
3
|
+
attr_accessor :departure_airport_fs_code,
|
4
|
+
:arrival_airport_fs_code,
|
5
|
+
:departure_date_from,
|
6
|
+
:departure_date_to,
|
7
|
+
:departure_days_of_week,
|
8
|
+
:arrival_date_adjustment,
|
9
|
+
:departure_time,
|
10
|
+
:arrival_time,
|
11
|
+
:distance_miles,
|
12
|
+
:flight_duration_minutes,
|
13
|
+
:layover_duration_minutes,
|
14
|
+
:flight_type,
|
15
|
+
:service_type,
|
16
|
+
:online,
|
17
|
+
:flight_legs
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def direct_arriving_at(arrival_code, year, month, day, options = {})
|
21
|
+
from_response API.get("/flex/connections/rest/v1/json/direct/to/#{arrival_code}/arriving/#{year}/#{month}/#{day}", {}, options), 'flights'
|
22
|
+
end
|
23
|
+
|
24
|
+
def direct_departing_from(departure_code, year, month, day, options = {})
|
25
|
+
from_response API.get("/flex/connections/rest/v1/json/direct/from/#{departure_code}/departing/#{year}/#{month}/#{day}", {}, options), 'flights'
|
26
|
+
end
|
27
|
+
|
28
|
+
def direct_arriving_by_flight_number(carrier, flight_number, year, month, day, options = {})
|
29
|
+
from_response API.get("/flex/connections/rest/v1/json/direct/flight/#{carrier}/#{flight_number}/arriving/#{year}/#{month}/#{day}", {}, options), 'flights'
|
30
|
+
end
|
31
|
+
|
32
|
+
def direct_arriving_by_flight_number_and_location(carrier, flight_number, arrival_code, year, month, day, options = {})
|
33
|
+
from_response API.get("/flex/connections/rest/v1/json/direct/flight/#{carrier}/#{flight_number}/to/#{arrival_code}/arriving/#{year}/#{month}/#{day}", {}, options), 'flights'
|
34
|
+
end
|
35
|
+
|
36
|
+
def direct_departing_by_flight_number(carrier, flight_number, year, month, day, options = {})
|
37
|
+
from_response API.get("/flex/connections/rest/v1/json/direct/flight/#{carrier}/#{flight_number}/departing/#{year}/#{month}/#{day}", {}, options), 'flights'
|
38
|
+
end
|
39
|
+
|
40
|
+
def direct_departing_by_flight_number_and_location(carrier, flight_number, arrival_code, year, month, day, options = {})
|
41
|
+
from_response API.get("/flex/connections/rest/v1/json/direct/flight/#{carrier}/#{flight_number}/to/#{arrival_code}/departing/#{year}/#{month}/#{day}", {}, options), 'flights'
|
42
|
+
end
|
43
|
+
|
44
|
+
def direct_and_connecting_arriving(departure_code, arrival_code, year, month, day, options = {})
|
45
|
+
from_response API.get("/flex/connections/rest/v1/json/connecting/from/#{departure_code}/to/#{arrival_code}/arriving/#{year}/#{month}/#{day}", {}, options), 'flights'
|
46
|
+
end
|
47
|
+
|
48
|
+
def direct_and_connecting_departing(departure_code, arrival_code, year, month, day, options = {})
|
49
|
+
from_response API.get("/flex/connections/rest/v1/json/connecting/from/#{departure_code}/to/#{arrival_code}/departing/#{year}/#{month}/#{day}", {}, options), 'flights'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
str = "#{departure_airport_fs_code} - #{arrival_airport_fs_code} (#{distance_miles} miles): from #{departure_date_from} to #{departure_date_to}"
|
55
|
+
flight_legs.each do |leg|
|
56
|
+
str << "\n #{leg.to_s}"
|
57
|
+
end
|
58
|
+
str
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|