duffel_api 0.0.1.pre.dev → 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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +82 -0
- data/.github/dependabot.yml +6 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +9 -9
- data/CHANGELOG.md +1 -3
- data/CODE_OF_CONDUCT.md +1 -1
- data/Gemfile +10 -2
- data/LICENSE.txt +1 -1
- data/README.md +190 -33
- data/Rakefile +1 -3
- data/bin/console +7 -0
- data/bin/setup +2 -0
- data/duffel_api.gemspec +40 -0
- data/examples/book_and_change.rb +106 -0
- data/examples/book_with_seat.rb +91 -0
- data/examples/exploring_data.rb +43 -0
- data/examples/hold_and_pay_later.rb +82 -0
- data/examples/search_and_book.rb +91 -0
- data/lib/duffel_api/api_response.rb +17 -0
- data/lib/duffel_api/api_service.rb +37 -0
- data/lib/duffel_api/client.rb +87 -0
- data/lib/duffel_api/errors/airline_error.rb +8 -0
- data/lib/duffel_api/errors/api_error.rb +8 -0
- data/lib/duffel_api/errors/authentication_error.rb +8 -0
- data/lib/duffel_api/errors/error.rb +54 -0
- data/lib/duffel_api/errors/invalid_request_error.rb +8 -0
- data/lib/duffel_api/errors/invalid_state_error.rb +8 -0
- data/lib/duffel_api/errors/rate_limit_error.rb +8 -0
- data/lib/duffel_api/errors/validation_error.rb +8 -0
- data/lib/duffel_api/list_response.rb +27 -0
- data/lib/duffel_api/middlewares/raise_duffel_errors.rb +67 -0
- data/lib/duffel_api/paginator.rb +27 -0
- data/lib/duffel_api/request.rb +64 -0
- data/lib/duffel_api/resources/aircraft.rb +26 -0
- data/lib/duffel_api/resources/airline.rb +26 -0
- data/lib/duffel_api/resources/airport.rb +40 -0
- data/lib/duffel_api/resources/base_resource.rb +16 -0
- data/lib/duffel_api/resources/offer.rb +60 -0
- data/lib/duffel_api/resources/offer_passenger.rb +28 -0
- data/lib/duffel_api/resources/offer_request.rb +34 -0
- data/lib/duffel_api/resources/order.rb +58 -0
- data/lib/duffel_api/resources/order_cancellation.rb +34 -0
- data/lib/duffel_api/resources/order_change.rb +46 -0
- data/lib/duffel_api/resources/order_change_offer.rb +46 -0
- data/lib/duffel_api/resources/order_change_request.rb +30 -0
- data/lib/duffel_api/resources/payment.rb +26 -0
- data/lib/duffel_api/resources/payment_intent.rb +52 -0
- data/lib/duffel_api/resources/refund.rb +42 -0
- data/lib/duffel_api/resources/seat_map.rb +24 -0
- data/lib/duffel_api/resources/webhook.rb +32 -0
- data/lib/duffel_api/response.rb +45 -0
- data/lib/duffel_api/services/aircraft_service.rb +36 -0
- data/lib/duffel_api/services/airlines_service.rb +36 -0
- data/lib/duffel_api/services/airports_service.rb +36 -0
- data/lib/duffel_api/services/base_service.rb +29 -0
- data/lib/duffel_api/services/offer_passengers_service.rb +30 -0
- data/lib/duffel_api/services/offer_requests_service.rb +67 -0
- data/lib/duffel_api/services/offers_service.rb +36 -0
- data/lib/duffel_api/services/order_cancellations_service.rb +75 -0
- data/lib/duffel_api/services/order_change_offers_service.rb +36 -0
- data/lib/duffel_api/services/order_change_requests_service.rb +37 -0
- data/lib/duffel_api/services/order_changes_service.rb +56 -0
- data/lib/duffel_api/services/orders_service.rb +74 -0
- data/lib/duffel_api/services/payment_intents_service.rb +56 -0
- data/lib/duffel_api/services/payments_service.rb +26 -0
- data/lib/duffel_api/services/refunds_service.rb +36 -0
- data/lib/duffel_api/services/seat_maps_service.rb +19 -0
- data/lib/duffel_api/services/webhooks_service.rb +83 -0
- data/lib/duffel_api/version.rb +1 -1
- data/lib/duffel_api.rb +51 -4
- metadata +90 -12
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "duffel_api"
|
4
|
+
|
5
|
+
client = DuffelAPI::Client.new(
|
6
|
+
access_token: ENV["DUFFEL_ACCESS_TOKEN"],
|
7
|
+
)
|
8
|
+
|
9
|
+
# 365 days from now
|
10
|
+
departure_date = (Time.now + (60 * 60 * 24 * 365)).strftime("%Y-%m-%d")
|
11
|
+
|
12
|
+
offer_request = client.offer_requests.create(params: {
|
13
|
+
cabin_class: "economy",
|
14
|
+
passengers: [{
|
15
|
+
age: 28,
|
16
|
+
}],
|
17
|
+
slices: [{
|
18
|
+
# We use a non-sensical route to make sure we get speedy, reliable Duffel Airways
|
19
|
+
# results.
|
20
|
+
origin: "LGA",
|
21
|
+
destination: "JFK",
|
22
|
+
departure_date: departure_date,
|
23
|
+
}],
|
24
|
+
# This attribute is sent as a query parameter rather than in the body like the others.
|
25
|
+
# Worry not! The library handles this complexity for you.
|
26
|
+
return_offers: false,
|
27
|
+
})
|
28
|
+
|
29
|
+
puts "Created offer request: #{offer_request.id}"
|
30
|
+
|
31
|
+
offers = client.offers.all(params: { offer_request_id: offer_request.id })
|
32
|
+
|
33
|
+
puts "Got #{offers.count} offers"
|
34
|
+
|
35
|
+
selected_offer = offers.first
|
36
|
+
|
37
|
+
puts "Selected offer #{selected_offer.id} to book"
|
38
|
+
|
39
|
+
priced_offer = client.offers.get(selected_offer.id,
|
40
|
+
params: { return_available_services: true })
|
41
|
+
|
42
|
+
puts "The final price for offer #{priced_offer.id} is #{priced_offer.total_amount} " \
|
43
|
+
"#{priced_offer.total_currency}"
|
44
|
+
|
45
|
+
seat_maps = client.seat_maps.list(params: { offer_id: priced_offer.id })
|
46
|
+
|
47
|
+
available_seat = seat_maps.records.first.cabins.
|
48
|
+
first["rows"].
|
49
|
+
flat_map { |row| row["sections"] }.
|
50
|
+
flat_map { |section| section["elements"] }.
|
51
|
+
find do |element|
|
52
|
+
element["type"] == "seat" && element["available_services"].any?
|
53
|
+
end
|
54
|
+
|
55
|
+
available_seat_service = available_seat["available_services"].first
|
56
|
+
|
57
|
+
puts "Adding seat #{available_seat['designator']} costing " \
|
58
|
+
"#{available_seat_service['total_amount']} " \
|
59
|
+
"#{available_seat_service['total_currency']}"
|
60
|
+
|
61
|
+
total_amount = priced_offer.total_amount.to_f +
|
62
|
+
available_seat_service["total_amount"].to_f
|
63
|
+
|
64
|
+
order = client.orders.create(params: {
|
65
|
+
selected_offers: [priced_offer.id],
|
66
|
+
services: [{
|
67
|
+
id: available_seat_service["id"],
|
68
|
+
quantity: 1,
|
69
|
+
}],
|
70
|
+
payments: [
|
71
|
+
{
|
72
|
+
type: "balance",
|
73
|
+
amount: total_amount,
|
74
|
+
currency: priced_offer.total_currency,
|
75
|
+
},
|
76
|
+
],
|
77
|
+
passengers: [
|
78
|
+
{
|
79
|
+
id: priced_offer.passengers.first["id"],
|
80
|
+
title: "mr",
|
81
|
+
gender: "m",
|
82
|
+
given_name: "Tim",
|
83
|
+
family_name: "Rogers",
|
84
|
+
born_on: "1993-04-01",
|
85
|
+
phone_number: "+441290211999",
|
86
|
+
email: "tim@duffel.com",
|
87
|
+
},
|
88
|
+
],
|
89
|
+
})
|
90
|
+
|
91
|
+
puts "Created order #{order.id} with booking reference #{order.booking_reference}"
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "duffel_api"
|
4
|
+
|
5
|
+
client = DuffelAPI::Client.new(
|
6
|
+
access_token: ENV["DUFFEL_ACCESS_TOKEN"],
|
7
|
+
)
|
8
|
+
|
9
|
+
puts "Loading airports..."
|
10
|
+
|
11
|
+
airports = client.airports.all
|
12
|
+
|
13
|
+
puts "Got #{airports.count} airports"
|
14
|
+
|
15
|
+
puts "Found airport #{airports.first.name} (#{airports.first.iata_code})"
|
16
|
+
|
17
|
+
airport = client.airports.get(airports.first.id)
|
18
|
+
|
19
|
+
puts "Airport is located at #{airport.latitude}, #{airport.longitude}"
|
20
|
+
|
21
|
+
puts "Loading aircraft..."
|
22
|
+
|
23
|
+
aircraft = client.aircraft.all
|
24
|
+
|
25
|
+
puts "Got #{aircraft.count} aircraft"
|
26
|
+
|
27
|
+
puts "Found aircraft #{aircraft.first.name}"
|
28
|
+
|
29
|
+
single_aircraft = client.aircraft.get(aircraft.first.id)
|
30
|
+
|
31
|
+
puts "Aircraft's IATA code is #{single_aircraft.iata_code}"
|
32
|
+
|
33
|
+
puts "Loading airlines..."
|
34
|
+
|
35
|
+
airlines = client.airlines.all
|
36
|
+
|
37
|
+
puts "Got #{airlines.count} airlines"
|
38
|
+
|
39
|
+
puts "Found airline #{airlines.first.name}"
|
40
|
+
|
41
|
+
airline = client.airlines.get(airlines.first.id)
|
42
|
+
|
43
|
+
puts "Airline's IATA code is #{airline.iata_code}"
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "duffel_api"
|
4
|
+
|
5
|
+
client = DuffelAPI::Client.new(
|
6
|
+
access_token: ENV["DUFFEL_ACCESS_TOKEN"],
|
7
|
+
)
|
8
|
+
|
9
|
+
# 365 days from now
|
10
|
+
departure_date = (Time.now + (60 * 60 * 24 * 365)).strftime("%Y-%m-%d")
|
11
|
+
|
12
|
+
offer_request = client.offer_requests.create(params: {
|
13
|
+
cabin_class: "economy",
|
14
|
+
passengers: [{
|
15
|
+
age: 28,
|
16
|
+
}],
|
17
|
+
slices: [{
|
18
|
+
# We use a non-sensical route to make sure we get speedy, reliable Duffel Airways
|
19
|
+
# results.
|
20
|
+
origin: "LHR",
|
21
|
+
destination: "STN",
|
22
|
+
departure_date: departure_date,
|
23
|
+
}],
|
24
|
+
# This attribute is sent as a query parameter rather than in the body like the others.
|
25
|
+
# Worry not! The library handles this complexity for you.
|
26
|
+
return_offers: false,
|
27
|
+
})
|
28
|
+
|
29
|
+
puts "Created offer request: #{offer_request.id}"
|
30
|
+
|
31
|
+
offers = client.offers.all(params: { offer_request_id: offer_request.id })
|
32
|
+
|
33
|
+
puts "Got #{offers.count} offers"
|
34
|
+
|
35
|
+
selected_offer = offers.first
|
36
|
+
|
37
|
+
puts "Selected offer #{selected_offer.id} to book"
|
38
|
+
|
39
|
+
priced_offer = client.offers.get(selected_offer.id,
|
40
|
+
params: { return_available_services: true })
|
41
|
+
|
42
|
+
puts "The final price for offer #{priced_offer.id} is #{priced_offer.total_amount} " \
|
43
|
+
"#{priced_offer.total_currency}"
|
44
|
+
|
45
|
+
order = client.orders.create(params: {
|
46
|
+
type: "hold",
|
47
|
+
selected_offers: [priced_offer.id],
|
48
|
+
passengers: [
|
49
|
+
{
|
50
|
+
id: priced_offer.passengers.first["id"],
|
51
|
+
title: "mr",
|
52
|
+
gender: "m",
|
53
|
+
given_name: "Tim",
|
54
|
+
family_name: "Rogers",
|
55
|
+
born_on: "1993-04-01",
|
56
|
+
phone_number: "+441290211999",
|
57
|
+
email: "tim@duffel.com",
|
58
|
+
},
|
59
|
+
],
|
60
|
+
})
|
61
|
+
|
62
|
+
puts "Created hold order #{order.id} with booking reference #{order.booking_reference}"
|
63
|
+
|
64
|
+
updated_order = client.orders.get(order.id)
|
65
|
+
|
66
|
+
puts "Retrieved order and up-to-date price is #{updated_order.total_amount} " \
|
67
|
+
"#{updated_order.total_currency}"
|
68
|
+
|
69
|
+
payment = client.payments.create(params: {
|
70
|
+
order_id: order.id,
|
71
|
+
payment: {
|
72
|
+
type: "balance",
|
73
|
+
amount: updated_order.total_amount,
|
74
|
+
currency: updated_order.total_currency,
|
75
|
+
},
|
76
|
+
})
|
77
|
+
|
78
|
+
puts "Paid for order #{order.id} with payment #{payment.id}"
|
79
|
+
|
80
|
+
paid_order = client.orders.get(order.id)
|
81
|
+
|
82
|
+
puts "After payment, order has #{paid_order.documents.length} documents"
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "duffel_api"
|
4
|
+
|
5
|
+
client = DuffelAPI::Client.new(
|
6
|
+
access_token: ENV["DUFFEL_ACCESS_TOKEN"],
|
7
|
+
)
|
8
|
+
|
9
|
+
# 365 days from now
|
10
|
+
departure_date = (Time.now + (60 * 60 * 24 * 365)).strftime("%Y-%m-%d")
|
11
|
+
|
12
|
+
offer_request = client.offer_requests.create(params: {
|
13
|
+
cabin_class: "economy",
|
14
|
+
passengers: [{
|
15
|
+
age: 28,
|
16
|
+
}],
|
17
|
+
slices: [{
|
18
|
+
# We use a non-sensical route to make sure we get speedy, reliable Duffel Airways
|
19
|
+
# resullts.
|
20
|
+
origin: "LHR",
|
21
|
+
destination: "STN",
|
22
|
+
departure_date: departure_date,
|
23
|
+
}],
|
24
|
+
# This attribute is sent as a query parameter rather than in the body like the others.
|
25
|
+
# Worry not! The library handles this complexity for you.
|
26
|
+
return_offers: false,
|
27
|
+
})
|
28
|
+
|
29
|
+
puts "Created offer request: #{offer_request.id}"
|
30
|
+
|
31
|
+
offers = client.offers.all(params: { offer_request_id: offer_request.id })
|
32
|
+
|
33
|
+
puts "Got #{offers.count} offers"
|
34
|
+
|
35
|
+
selected_offer = offers.first
|
36
|
+
|
37
|
+
puts "Selected offer #{selected_offer.id} to book"
|
38
|
+
|
39
|
+
priced_offer = client.offers.get(selected_offer.id,
|
40
|
+
params: { return_available_services: true })
|
41
|
+
|
42
|
+
puts "The final price for offer #{priced_offer.id} is #{priced_offer.total_amount} " \
|
43
|
+
"#{priced_offer.total_currency}"
|
44
|
+
|
45
|
+
available_service = priced_offer.available_services.first
|
46
|
+
|
47
|
+
puts "Adding an extra bag with service #{available_service['id']}, " \
|
48
|
+
"costing #{available_service['total_amount']} #{available_service['total_currency']}"
|
49
|
+
|
50
|
+
total_amount = priced_offer.total_amount.to_f + available_service["total_amount"].to_f
|
51
|
+
|
52
|
+
order = client.orders.create(params: {
|
53
|
+
selected_offers: [priced_offer.id],
|
54
|
+
services: [{
|
55
|
+
id: available_service["id"],
|
56
|
+
quantity: 1,
|
57
|
+
}],
|
58
|
+
payments: [
|
59
|
+
{
|
60
|
+
type: "balance",
|
61
|
+
amount: total_amount,
|
62
|
+
currency: priced_offer.total_currency,
|
63
|
+
},
|
64
|
+
],
|
65
|
+
passengers: [
|
66
|
+
{
|
67
|
+
id: priced_offer.passengers.first["id"],
|
68
|
+
title: "mr",
|
69
|
+
gender: "m",
|
70
|
+
given_name: "Tim",
|
71
|
+
family_name: "Rogers",
|
72
|
+
born_on: "1993-04-01",
|
73
|
+
phone_number: "+441290211999",
|
74
|
+
email: "tim@duffel.com",
|
75
|
+
},
|
76
|
+
],
|
77
|
+
})
|
78
|
+
|
79
|
+
puts "Created order #{order.id} with booking reference #{order.booking_reference}"
|
80
|
+
|
81
|
+
order_cancellation = client.order_cancellations.create(params: {
|
82
|
+
order_id: order.id,
|
83
|
+
})
|
84
|
+
|
85
|
+
puts "Requested refund quote for order #{order.id} - " \
|
86
|
+
"#{order_cancellation.refund_amount} #{order_cancellation.refund_currency} is " \
|
87
|
+
"available"
|
88
|
+
|
89
|
+
client.order_cancellations.confirm(order_cancellation.id)
|
90
|
+
|
91
|
+
puts "Confirmed refund quote for order #{order.id}"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DuffelAPI
|
4
|
+
# wraps a faraday response object with some accessors
|
5
|
+
class APIResponse
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def initialize(response)
|
9
|
+
@response = response
|
10
|
+
end
|
11
|
+
|
12
|
+
def_delegator :@response, :headers
|
13
|
+
def_delegator :@response, :raw_body, :body
|
14
|
+
def_delegator :@response, :status_code
|
15
|
+
def_delegator :@response, :request_id
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "faraday"
|
4
|
+
require "uri"
|
5
|
+
|
6
|
+
module DuffelAPI
|
7
|
+
class APIService
|
8
|
+
def initialize(base_url, access_token, options = {})
|
9
|
+
@base_url = base_url
|
10
|
+
root_url, @path_prefix = unpack_url(base_url)
|
11
|
+
|
12
|
+
@connection = Faraday.new(root_url) do |faraday|
|
13
|
+
faraday.response :raise_duffel_errors
|
14
|
+
|
15
|
+
faraday.adapter(:net_http)
|
16
|
+
end
|
17
|
+
|
18
|
+
@headers = options[:default_headers] || {}
|
19
|
+
@headers["Authorization"] = "Bearer #{access_token}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def make_request(method, path, options = {})
|
23
|
+
raise ArgumentError, "options must be a hash" unless options.is_a?(Hash)
|
24
|
+
|
25
|
+
options[:headers] ||= {}
|
26
|
+
options[:headers] = @headers.merge(options[:headers])
|
27
|
+
Request.new(@connection, method, @path_prefix + path, options).request
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def unpack_url(url)
|
33
|
+
path = URI.parse(url).path
|
34
|
+
[URI.join(url).to_s, path == "/" ? "" : path]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DuffelAPI
|
4
|
+
class Client
|
5
|
+
API_VERSION = "beta"
|
6
|
+
|
7
|
+
def initialize(access_token:, base_url: "https://api.duffel.com")
|
8
|
+
@api_service = APIService.new(base_url, access_token, default_options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def aircraft
|
12
|
+
@aircraft ||= Services::AircraftService.new(@api_service)
|
13
|
+
end
|
14
|
+
|
15
|
+
def airlines
|
16
|
+
@airlines ||= Services::AirlinesService.new(@api_service)
|
17
|
+
end
|
18
|
+
|
19
|
+
def airports
|
20
|
+
@airports ||= Services::AirportsService.new(@api_service)
|
21
|
+
end
|
22
|
+
|
23
|
+
def offer_passengers
|
24
|
+
@offer_passengers ||= Services::OfferPassengersService.new(@api_service)
|
25
|
+
end
|
26
|
+
|
27
|
+
def offer_requests
|
28
|
+
@offer_requests ||= Services::OfferRequestsService.new(@api_service)
|
29
|
+
end
|
30
|
+
|
31
|
+
def offers
|
32
|
+
@offers ||= Services::OffersService.new(@api_service)
|
33
|
+
end
|
34
|
+
|
35
|
+
def order_cancellations
|
36
|
+
@order_cancellations ||= Services::OrderCancellationsService.new(@api_service)
|
37
|
+
end
|
38
|
+
|
39
|
+
def order_change_offers
|
40
|
+
@order_change_offers ||= Services::OrderChangeOffersService.new(@api_service)
|
41
|
+
end
|
42
|
+
|
43
|
+
def order_change_requests
|
44
|
+
@order_change_requests ||= Services::OrderChangeRequestsService.new(@api_service)
|
45
|
+
end
|
46
|
+
|
47
|
+
def order_changes
|
48
|
+
@order_changes ||= Services::OrderChangesService.new(@api_service)
|
49
|
+
end
|
50
|
+
|
51
|
+
def orders
|
52
|
+
@orders ||= Services::OrdersService.new(@api_service)
|
53
|
+
end
|
54
|
+
|
55
|
+
def payment_intents
|
56
|
+
@payment_intents ||= Services::PaymentIntentsService.new(@api_service)
|
57
|
+
end
|
58
|
+
|
59
|
+
def payments
|
60
|
+
@payments ||= Services::PaymentsService.new(@api_service)
|
61
|
+
end
|
62
|
+
|
63
|
+
def refunds
|
64
|
+
@refunds ||= Services::RefundsService.new(@api_service)
|
65
|
+
end
|
66
|
+
|
67
|
+
def seat_maps
|
68
|
+
@seat_maps ||= Services::SeatMapsService.new(@api_service)
|
69
|
+
end
|
70
|
+
|
71
|
+
def webhooks
|
72
|
+
@webhooks ||= Services::WebhooksService.new(@api_service)
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def default_options
|
78
|
+
{
|
79
|
+
default_headers: {
|
80
|
+
"Duffel-Version" => API_VERSION,
|
81
|
+
"User-Agent" => "Duffel/#{API_VERSION} duffel_api_ruby/#{DuffelAPI::VERSION}",
|
82
|
+
"Content-Type" => "application/json",
|
83
|
+
},
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DuffelAPI
|
4
|
+
module Errors
|
5
|
+
class Error < StandardError
|
6
|
+
attr_reader :error
|
7
|
+
|
8
|
+
def initialize(error, response = nil)
|
9
|
+
raise ArgumentError, "Duffel errors expect a hash" unless error.is_a?(Hash)
|
10
|
+
|
11
|
+
@error = error
|
12
|
+
@response = response
|
13
|
+
|
14
|
+
super(error)
|
15
|
+
end
|
16
|
+
|
17
|
+
def documentation_url
|
18
|
+
@error["documentation_url"]
|
19
|
+
end
|
20
|
+
|
21
|
+
def title
|
22
|
+
@error["title"]
|
23
|
+
end
|
24
|
+
|
25
|
+
def message
|
26
|
+
@error["message"]
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
@error["message"]
|
31
|
+
end
|
32
|
+
|
33
|
+
def type
|
34
|
+
@error["type"]
|
35
|
+
end
|
36
|
+
|
37
|
+
def code
|
38
|
+
@error["code"]
|
39
|
+
end
|
40
|
+
|
41
|
+
def request_id
|
42
|
+
@error["request_id"]
|
43
|
+
end
|
44
|
+
|
45
|
+
def source
|
46
|
+
@error["source"]
|
47
|
+
end
|
48
|
+
|
49
|
+
def api_response
|
50
|
+
APIResponse.new(@response)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DuffelAPI
|
4
|
+
class ListResponse
|
5
|
+
attr_reader :records
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@response = options.fetch(:response)
|
9
|
+
@resource_class = options.fetch(:resource_class)
|
10
|
+
@unenveloped_body = options.fetch(:unenveloped_body)
|
11
|
+
|
12
|
+
@records = @unenveloped_body.map { |item| @resource_class.new(item, @response) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def api_response
|
16
|
+
@api_response ||= APIResponse.new(@response)
|
17
|
+
end
|
18
|
+
|
19
|
+
def before
|
20
|
+
@response.parsed_body["meta"]["before"]
|
21
|
+
end
|
22
|
+
|
23
|
+
def after
|
24
|
+
@response.parsed_body["meta"]["after"]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "faraday"
|
4
|
+
|
5
|
+
module DuffelAPI
|
6
|
+
module Middlewares
|
7
|
+
class RaiseDuffelErrors < Faraday::Response::Middleware
|
8
|
+
UNEXPECTED_ERROR_STATUSES = (501..599).freeze
|
9
|
+
EXPECTED_ERROR_STATUSES = (400..500).freeze
|
10
|
+
|
11
|
+
# rubocop:disable Metrics/AbcSize
|
12
|
+
def on_complete(env)
|
13
|
+
if !json?(env) || UNEXPECTED_ERROR_STATUSES.include?(env.status)
|
14
|
+
response = Response.new(env.response)
|
15
|
+
raise DuffelAPI::Errors::Error.new(generate_error_data(env), response)
|
16
|
+
end
|
17
|
+
|
18
|
+
if EXPECTED_ERROR_STATUSES.include?(env.status)
|
19
|
+
json_body ||= JSON.parse(env.body) unless env.body.empty?
|
20
|
+
error = json_body["errors"].first
|
21
|
+
error_type = error["type"]
|
22
|
+
|
23
|
+
error_class = error_class_for_type(error_type)
|
24
|
+
|
25
|
+
response = Response.new(env.response)
|
26
|
+
|
27
|
+
raise error_class.new(error, response)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
# rubocop:enable Metrics/AbcSize
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def error_class_for_type(type)
|
35
|
+
{
|
36
|
+
airline_error: DuffelAPI::Errors::AirlineError,
|
37
|
+
api_error: DuffelAPI::Errors::APIError,
|
38
|
+
authentication_error: DuffelAPI::Errors::AuthenticationError,
|
39
|
+
invalid_request_error: DuffelAPI::Errors::InvalidRequestError,
|
40
|
+
invalid_state_error: DuffelAPI::Errors::InvalidStateError,
|
41
|
+
rate_limit_error: DuffelAPI::Errors::RateLimitError,
|
42
|
+
validation_error: DuffelAPI::Errors::ValidationError,
|
43
|
+
}.fetch(type.to_sym) || DuffelAPI::Errors::Error
|
44
|
+
end
|
45
|
+
|
46
|
+
def generate_error_data(env)
|
47
|
+
{
|
48
|
+
"message" => "Something went wrong with this request\n" \
|
49
|
+
"Code: #{env.status}\n" \
|
50
|
+
"Headers: #{env.response_headers}\n" \
|
51
|
+
"Body: #{env.body}",
|
52
|
+
"code" => env.status,
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def json?(env)
|
57
|
+
content_type = env.response_headers["Content-Type"] ||
|
58
|
+
env.response_headers["content-type"] || ""
|
59
|
+
|
60
|
+
content_type.include?("application/json")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
Faraday::Response.register_middleware raise_duffel_errors: DuffelAPI::
|
67
|
+
Middlewares::RaiseDuffelErrors
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DuffelAPI
|
4
|
+
class Paginator
|
5
|
+
def initialize(options = {})
|
6
|
+
@service = options.fetch(:service)
|
7
|
+
@options = options.fetch(:options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def enumerator
|
11
|
+
response = @service.list(@options)
|
12
|
+
|
13
|
+
Enumerator.new do |yielder|
|
14
|
+
loop do
|
15
|
+
response.records.each { |item| yielder << item }
|
16
|
+
|
17
|
+
after_cursor = response.after
|
18
|
+
break if after_cursor.nil?
|
19
|
+
|
20
|
+
@options[:params] ||= {}
|
21
|
+
@options[:params] = @options[:params].merge(after: after_cursor)
|
22
|
+
response = @service.list(@options)
|
23
|
+
end
|
24
|
+
end.lazy
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|