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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +82 -0
  3. data/.github/dependabot.yml +6 -0
  4. data/.gitignore +9 -0
  5. data/.rubocop.yml +9 -9
  6. data/CHANGELOG.md +1 -3
  7. data/CODE_OF_CONDUCT.md +1 -1
  8. data/Gemfile +10 -2
  9. data/LICENSE.txt +1 -1
  10. data/README.md +190 -33
  11. data/Rakefile +1 -3
  12. data/bin/console +7 -0
  13. data/bin/setup +2 -0
  14. data/duffel_api.gemspec +40 -0
  15. data/examples/book_and_change.rb +106 -0
  16. data/examples/book_with_seat.rb +91 -0
  17. data/examples/exploring_data.rb +43 -0
  18. data/examples/hold_and_pay_later.rb +82 -0
  19. data/examples/search_and_book.rb +91 -0
  20. data/lib/duffel_api/api_response.rb +17 -0
  21. data/lib/duffel_api/api_service.rb +37 -0
  22. data/lib/duffel_api/client.rb +87 -0
  23. data/lib/duffel_api/errors/airline_error.rb +8 -0
  24. data/lib/duffel_api/errors/api_error.rb +8 -0
  25. data/lib/duffel_api/errors/authentication_error.rb +8 -0
  26. data/lib/duffel_api/errors/error.rb +54 -0
  27. data/lib/duffel_api/errors/invalid_request_error.rb +8 -0
  28. data/lib/duffel_api/errors/invalid_state_error.rb +8 -0
  29. data/lib/duffel_api/errors/rate_limit_error.rb +8 -0
  30. data/lib/duffel_api/errors/validation_error.rb +8 -0
  31. data/lib/duffel_api/list_response.rb +27 -0
  32. data/lib/duffel_api/middlewares/raise_duffel_errors.rb +67 -0
  33. data/lib/duffel_api/paginator.rb +27 -0
  34. data/lib/duffel_api/request.rb +64 -0
  35. data/lib/duffel_api/resources/aircraft.rb +26 -0
  36. data/lib/duffel_api/resources/airline.rb +26 -0
  37. data/lib/duffel_api/resources/airport.rb +40 -0
  38. data/lib/duffel_api/resources/base_resource.rb +16 -0
  39. data/lib/duffel_api/resources/offer.rb +60 -0
  40. data/lib/duffel_api/resources/offer_passenger.rb +28 -0
  41. data/lib/duffel_api/resources/offer_request.rb +34 -0
  42. data/lib/duffel_api/resources/order.rb +58 -0
  43. data/lib/duffel_api/resources/order_cancellation.rb +34 -0
  44. data/lib/duffel_api/resources/order_change.rb +46 -0
  45. data/lib/duffel_api/resources/order_change_offer.rb +46 -0
  46. data/lib/duffel_api/resources/order_change_request.rb +30 -0
  47. data/lib/duffel_api/resources/payment.rb +26 -0
  48. data/lib/duffel_api/resources/payment_intent.rb +52 -0
  49. data/lib/duffel_api/resources/refund.rb +42 -0
  50. data/lib/duffel_api/resources/seat_map.rb +24 -0
  51. data/lib/duffel_api/resources/webhook.rb +32 -0
  52. data/lib/duffel_api/response.rb +45 -0
  53. data/lib/duffel_api/services/aircraft_service.rb +36 -0
  54. data/lib/duffel_api/services/airlines_service.rb +36 -0
  55. data/lib/duffel_api/services/airports_service.rb +36 -0
  56. data/lib/duffel_api/services/base_service.rb +29 -0
  57. data/lib/duffel_api/services/offer_passengers_service.rb +30 -0
  58. data/lib/duffel_api/services/offer_requests_service.rb +67 -0
  59. data/lib/duffel_api/services/offers_service.rb +36 -0
  60. data/lib/duffel_api/services/order_cancellations_service.rb +75 -0
  61. data/lib/duffel_api/services/order_change_offers_service.rb +36 -0
  62. data/lib/duffel_api/services/order_change_requests_service.rb +37 -0
  63. data/lib/duffel_api/services/order_changes_service.rb +56 -0
  64. data/lib/duffel_api/services/orders_service.rb +74 -0
  65. data/lib/duffel_api/services/payment_intents_service.rb +56 -0
  66. data/lib/duffel_api/services/payments_service.rb +26 -0
  67. data/lib/duffel_api/services/refunds_service.rb +36 -0
  68. data/lib/duffel_api/services/seat_maps_service.rb +19 -0
  69. data/lib/duffel_api/services/webhooks_service.rb +83 -0
  70. data/lib/duffel_api/version.rb +1 -1
  71. data/lib/duffel_api.rb +51 -4
  72. 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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DuffelAPI
4
+ module Errors
5
+ class AirlineError < Error
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DuffelAPI
4
+ module Errors
5
+ class APIError < Error
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DuffelAPI
4
+ module Errors
5
+ class AuthenticationError < Error
6
+ end
7
+ end
8
+ 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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DuffelAPI
4
+ module Errors
5
+ class InvalidRequestError < Error
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DuffelAPI
4
+ module Errors
5
+ class InvalidStateError < Error
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DuffelAPI
4
+ module Errors
5
+ class RateLimitError < Error
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DuffelAPI
4
+ module Errors
5
+ class ValidationError < Error
6
+ end
7
+ end
8
+ 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