duffel_api 0.0.1.pre.dev → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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