duffel_api 0.1.0 → 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.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/.gitignore +1 -0
- data/Appraisals +11 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +3 -2
- data/README.md +34 -1
- data/duffel_api.gemspec +6 -4
- data/examples/book_with_extra_baggage.rb +83 -0
- data/examples/book_with_seat.rb +1 -2
- data/lib/duffel_api/api_response.rb +35 -5
- data/lib/duffel_api/api_service.rb +18 -4
- data/lib/duffel_api/client.rb +24 -1
- data/lib/duffel_api/errors/error.rb +44 -1
- data/lib/duffel_api/list_response.rb +17 -0
- data/lib/duffel_api/middlewares/raise_duffel_errors.rb +18 -2
- data/lib/duffel_api/paginator.rb +11 -3
- data/lib/duffel_api/request.rb +32 -16
- data/lib/duffel_api/resources/aircraft.rb +7 -6
- data/lib/duffel_api/resources/airline.rb +7 -6
- data/lib/duffel_api/resources/airport.rb +21 -6
- data/lib/duffel_api/resources/base_resource.rb +3 -0
- data/lib/duffel_api/resources/offer_passenger.rb +11 -0
- data/lib/duffel_api/resources/offer_request.rb +15 -6
- data/lib/duffel_api/response.rb +24 -5
- data/lib/duffel_api/services/aircraft_service.rb +18 -0
- data/lib/duffel_api/services/airlines_service.rb +17 -0
- data/lib/duffel_api/services/airports_service.rb +18 -0
- data/lib/duffel_api/services/base_service.rb +17 -4
- data/lib/duffel_api/services/offer_passengers_service.rb +7 -0
- data/lib/duffel_api/services/offer_requests_service.rb +24 -0
- data/lib/duffel_api/services/offers_service.rb +18 -0
- data/lib/duffel_api/services/order_cancellations_service.rb +28 -0
- data/lib/duffel_api/services/order_change_offers_service.rb +18 -0
- data/lib/duffel_api/services/order_change_requests_service.rb +11 -0
- data/lib/duffel_api/services/order_changes_service.rb +17 -0
- data/lib/duffel_api/services/orders_service.rb +28 -0
- data/lib/duffel_api/services/payment_intents_service.rb +15 -0
- data/lib/duffel_api/services/payments_service.rb +5 -0
- data/lib/duffel_api/services/refunds_service.rb +10 -0
- data/lib/duffel_api/services/seat_maps_service.rb +6 -0
- data/lib/duffel_api/services/webhooks_service.rb +23 -2
- data/lib/duffel_api/version.rb +1 -1
- data/lib/duffel_api/webhook_event.rb +119 -0
- data/lib/duffel_api.rb +1 -0
- metadata +36 -6
- data/.circleci/config.yml +0 -82
- data/.github/dependabot.yml +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f34f8f28b32ebbcce50624f302a8c571322804417841653645dd6ea659f8a22
|
4
|
+
data.tar.gz: fab5945c12ed6054f01330dc2bd62cb814cd90f486b7dc36ed4caa0c8197617c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89708a5b441623dfecaee21f06bfba8da70e93b251d2587895a1b7ea3a9df1375251ae5a85de0a4550bf96ef46249a3c8d341f85c187d268cdf4fc6c6029c2da
|
7
|
+
data.tar.gz: c0e3165cf5a164dab4d2da40e9171fc2c680800be4a2f43c96e18556c75c9890555bec6cd3455c90de3c8141347bd32fc13d50eb78e33336ce8954a38387633e
|
data/.DS_Store
ADDED
Binary file
|
data/.gitignore
CHANGED
data/Appraisals
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
## [0.2.0] - 2022-01-11
|
2
|
+
|
3
|
+
- Add `WebhookEvent.genuine?` for checking whether a webhook event was genuinely sent by Duffel
|
4
|
+
- Add support for `faraday` `v2.x`
|
5
|
+
- Add YARD in-code documentation, [published to RubyDoc.info](https://rubydoc.info/github/duffelhq/duffel-api-ruby/main)
|
6
|
+
|
1
7
|
## [0.1.0] - 2021-12-31
|
2
8
|
|
3
9
|
- Initial release
|
data/Gemfile
CHANGED
@@ -6,14 +6,15 @@ source "https://rubygems.org"
|
|
6
6
|
gemspec
|
7
7
|
|
8
8
|
group :development, :test do
|
9
|
-
gem "gc_ruboconfig", "~> 2.
|
9
|
+
gem "gc_ruboconfig", "~> 2.30.0"
|
10
10
|
gem "pry", "~> 0.14.1"
|
11
11
|
gem "rake", "~> 13.0"
|
12
12
|
gem "rspec", "~> 3.10.0"
|
13
13
|
gem "rspec-its", "~> 1.3.0"
|
14
|
-
gem "rspec_junit_formatter", "~> 0.
|
14
|
+
gem "rspec_junit_formatter", "~> 0.5.0"
|
15
15
|
gem "rubocop", "~> 1.24.0"
|
16
16
|
gem "rubocop-rake", "~> 0.6.0"
|
17
17
|
gem "simplecov", "~> 0.21.2"
|
18
18
|
gem "webmock", "~> 3.14.0"
|
19
|
+
gem "yard", "~> 0.9.27"
|
19
20
|
end
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Duffel API Ruby client library
|
2
2
|
|
3
|
+
[](https://rubydoc.info/github/duffelhq/duffel-api-ruby)
|
4
|
+
|
3
5
|
A Ruby client library for the [Duffel API](https://duffel.com/docs/api).
|
4
6
|
|
5
7
|
## Contents
|
@@ -18,7 +20,7 @@ A Ruby client library for the [Duffel API](https://duffel.com/docs/api).
|
|
18
20
|
In most cases, you'll want to add `duffel_api` to your project as a dependency by listing it in your `Gemfile`, and then running `bundle`:
|
19
21
|
|
20
22
|
```ruby
|
21
|
-
gem "duffel_api", "~> 0.
|
23
|
+
gem "duffel_api", "~> 0.2.0"
|
22
24
|
```
|
23
25
|
|
24
26
|
You can install `duffel_api` outside of the context of a project by running `gem install duffel_api` - for example if you want to play with the client library in `irb`.
|
@@ -209,4 +211,35 @@ If an error has been raised, you can call `#api_response` on the exception, whic
|
|
209
211
|
|
210
212
|
From the `APIResponse`, you can call `#headers`, `#status_code`, `#raw_body`, `#parsed_body`, `#meta` or `#request_id` to get key information from the response.
|
211
213
|
|
214
|
+
### Verifying webhooks
|
215
|
+
|
216
|
+
You can set up [webhooks](https://duffel.com/docs/guides/receiving-webhooks) with Duffel to receive notifications about events that happen in your Duffel account - for example, when an airline has a schedule change affecting one of your orders.
|
217
|
+
|
218
|
+
These webhook events are signed with a shared secret. This allows you to be sure that any webhook events are genuinely sent from Duffel when you receive them.
|
219
|
+
|
220
|
+
When you create a webhook, you'll set a secret. With that secret in mind, you can verify that a webhook is genuine like this:
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
# In Rails, you'd get this with `request.raw_post`.
|
224
|
+
request_body = '{"created_at":"2022-01-08T18:44:56.129339Z","data":{"changes":{},"object":{}},"id":"eve_0000AFEsrBKZAcKgGtZCnQ","live_mode":false,"object":"order","type":"order.updated"}'
|
225
|
+
# In Rails, you'd get this with `request.headers['X-Duffel-Signature']`.
|
226
|
+
request_signature = "t=1641667496,v1=691f25ffb1f206c0fda5bb7b1a9d60fafe42c5f42819d44a06a7cfe09486f102"
|
227
|
+
|
228
|
+
# Note that this code doesn't require your access token - `DuffelAPI::WebhookEvent`
|
229
|
+
# doesn't expect you to have a `Client` initialised
|
230
|
+
if DuffelAPI::WebhookEvent.genuine?(
|
231
|
+
request_body: request_body,
|
232
|
+
request_signature: request_signature,
|
233
|
+
webhook_secret: "a_secret"
|
234
|
+
)
|
235
|
+
puts "This is a real webhook from Duffel 🌟"
|
236
|
+
else
|
237
|
+
puts "This is a fake webhook! ☠️"
|
238
|
+
end
|
239
|
+
```
|
240
|
+
|
241
|
+
## Learn more
|
242
|
+
|
243
|
+
You can find complete documentation on this library's classes and methods in the in-code
|
244
|
+
documentation on [RubyDoc.info](https://rubydoc.info/github/duffelhq/duffel-api-ruby).
|
212
245
|
|
data/duffel_api.gemspec
CHANGED
@@ -20,17 +20,19 @@ Gem::Specification.new do |spec|
|
|
20
20
|
# Specify which files should be added to the gem when it is released.
|
21
21
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
22
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
|
-
`git ls-files -z`.split("\x0").reject
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
24
|
+
f.match(%r{\A(?:test|spec|features|gemfiles|.circleci|.github)/})
|
25
|
+
end
|
24
26
|
end
|
25
27
|
|
26
28
|
spec.bindir = "exe"
|
27
29
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
28
30
|
spec.require_paths = ["lib"]
|
29
31
|
|
30
|
-
spec.add_dependency "
|
32
|
+
spec.add_dependency "base16", "~> 0.0.2"
|
33
|
+
spec.add_dependency "faraday", ">= 0.9.2", "< 3"
|
31
34
|
|
32
|
-
|
33
|
-
# spec.add_dependency "example-gem", "~> 1.0"
|
35
|
+
spec.add_development_dependency "appraisal", "~> 2.4"
|
34
36
|
|
35
37
|
# For more information and examples about making a new gem, checkout our
|
36
38
|
# guide at: https://bundler.io/guides/creating_gem.html
|
@@ -0,0 +1,83 @@
|
|
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
|
+
# Send the query param return_available_services to request ancillaries (e.g. baggage)
|
40
|
+
priced_offer = client.offers.get(selected_offer.id,
|
41
|
+
params: { return_available_services: true })
|
42
|
+
|
43
|
+
puts "The final price for offer #{priced_offer.id} is #{priced_offer.total_amount} " \
|
44
|
+
"#{priced_offer.total_currency}"
|
45
|
+
|
46
|
+
available_baggage = priced_offer.available_services.
|
47
|
+
find { |service| service["type"] == "baggage" }
|
48
|
+
|
49
|
+
puts "Adding #{available_baggage['metadata']['maximum_weight_kg']}kg extra baggage for " \
|
50
|
+
"#{available_baggage['total_amount']} " \
|
51
|
+
"#{available_baggage['total_currency']}"
|
52
|
+
|
53
|
+
total_amount = priced_offer.total_amount.to_f +
|
54
|
+
available_baggage["total_amount"].to_f
|
55
|
+
|
56
|
+
order = client.orders.create(params: {
|
57
|
+
selected_offers: [priced_offer.id],
|
58
|
+
services: [{
|
59
|
+
id: available_baggage["id"],
|
60
|
+
quantity: 1,
|
61
|
+
}],
|
62
|
+
payments: [
|
63
|
+
{
|
64
|
+
type: "balance",
|
65
|
+
amount: total_amount,
|
66
|
+
currency: priced_offer.total_currency,
|
67
|
+
},
|
68
|
+
],
|
69
|
+
passengers: [
|
70
|
+
{
|
71
|
+
id: priced_offer.passengers.first["id"],
|
72
|
+
title: "mr",
|
73
|
+
gender: "m",
|
74
|
+
given_name: "Tim",
|
75
|
+
family_name: "Rogers",
|
76
|
+
born_on: "1993-04-01",
|
77
|
+
phone_number: "+441290211999",
|
78
|
+
email: "tim@duffel.com",
|
79
|
+
},
|
80
|
+
],
|
81
|
+
})
|
82
|
+
|
83
|
+
puts "Created order #{order.id} with booking reference #{order.booking_reference}"
|
data/examples/book_with_seat.rb
CHANGED
@@ -36,8 +36,7 @@ selected_offer = offers.first
|
|
36
36
|
|
37
37
|
puts "Selected offer #{selected_offer.id} to book"
|
38
38
|
|
39
|
-
priced_offer = client.offers.get(selected_offer.id
|
40
|
-
params: { return_available_services: true })
|
39
|
+
priced_offer = client.offers.get(selected_offer.id)
|
41
40
|
|
42
41
|
puts "The final price for offer #{priced_offer.id} is #{priced_offer.total_amount} " \
|
43
42
|
"#{priced_offer.total_currency}"
|
@@ -1,17 +1,47 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DuffelAPI
|
4
|
-
#
|
4
|
+
# An HTTP response returned from the API
|
5
5
|
class APIResponse
|
6
6
|
extend Forwardable
|
7
7
|
|
8
|
+
# Builds an `APIResponse` from a `Response`, the library's internal representation
|
9
|
+
# of an HTTP response
|
10
|
+
#
|
11
|
+
# @param response [Response]
|
12
|
+
# @return [APIResponse]
|
8
13
|
def initialize(response)
|
9
14
|
@response = response
|
10
15
|
end
|
11
16
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
17
|
+
# Returns the HTTP response headers
|
18
|
+
#
|
19
|
+
# @return [Hash]
|
20
|
+
def headers
|
21
|
+
@response.headers
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the raw body of the HTTP response
|
25
|
+
#
|
26
|
+
# @return [String]
|
27
|
+
def body
|
28
|
+
@response.raw_body
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the HTTP status code of the HTTP response
|
32
|
+
#
|
33
|
+
# @return [Integer]
|
34
|
+
def status_code
|
35
|
+
@response.status_code
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the request ID from the Duffel API, included in the response headers.
|
39
|
+
# This could be `nil` if the response didn't make it to the Duffel API itself and,
|
40
|
+
# for example, only reached a load balancer.
|
41
|
+
#
|
42
|
+
# @return [String, nil]
|
43
|
+
def request_id
|
44
|
+
@response.request_id
|
45
|
+
end
|
16
46
|
end
|
17
47
|
end
|
@@ -4,8 +4,17 @@ require "faraday"
|
|
4
4
|
require "uri"
|
5
5
|
|
6
6
|
module DuffelAPI
|
7
|
+
# An internal class used within the library that is able to make requests to
|
8
|
+
# the Duffel API and handle errors
|
7
9
|
class APIService
|
8
|
-
|
10
|
+
# Sets up an API service based on a base URL, access token and set of default
|
11
|
+
# headers
|
12
|
+
#
|
13
|
+
# @param base_url [String] A test or live mode access token
|
14
|
+
# @param access_token [String] The URL of the Duffel API
|
15
|
+
# @param default_headers [Hash] The headers to include by default in HTTP requests
|
16
|
+
# @return [APIService]
|
17
|
+
def initialize(base_url, access_token, default_headers:)
|
9
18
|
@base_url = base_url
|
10
19
|
root_url, @path_prefix = unpack_url(base_url)
|
11
20
|
|
@@ -15,16 +24,21 @@ module DuffelAPI
|
|
15
24
|
faraday.adapter(:net_http)
|
16
25
|
end
|
17
26
|
|
18
|
-
@headers =
|
19
|
-
@headers["Authorization"] = "Bearer #{access_token}"
|
27
|
+
@headers = default_headers.merge("Authorization" => "Bearer #{access_token}")
|
20
28
|
end
|
21
29
|
|
30
|
+
# Makes a request to the API, including any defauot headers
|
31
|
+
#
|
32
|
+
# @param method [Symbol] the HTTP method to make the request with
|
33
|
+
# @param path [String] the path to make the request to
|
34
|
+
# @param options [Hash] options to be passed with `Request#new`
|
35
|
+
# @return [Request]
|
22
36
|
def make_request(method, path, options = {})
|
23
37
|
raise ArgumentError, "options must be a hash" unless options.is_a?(Hash)
|
24
38
|
|
25
39
|
options[:headers] ||= {}
|
26
40
|
options[:headers] = @headers.merge(options[:headers])
|
27
|
-
Request.new(@connection, method, @path_prefix + path, options).
|
41
|
+
Request.new(@connection, method, @path_prefix + path, **options).call
|
28
42
|
end
|
29
43
|
|
30
44
|
private
|
data/lib/duffel_api/client.rb
CHANGED
@@ -1,73 +1,96 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DuffelAPI
|
4
|
+
# A client for accessing the Duffel API, configured with a provided access token and
|
5
|
+
# base URL, which provides access to API services
|
4
6
|
class Client
|
5
7
|
API_VERSION = "beta"
|
6
8
|
|
9
|
+
# Sets up the client with your access token
|
10
|
+
#
|
11
|
+
# @param access_token [String] A test or live mode access token
|
12
|
+
# @param base_url [String] The URL of the Duffel API
|
13
|
+
# @return [Client]
|
7
14
|
def initialize(access_token:, base_url: "https://api.duffel.com")
|
8
|
-
@api_service = APIService.new(base_url, access_token, default_options)
|
15
|
+
@api_service = APIService.new(base_url, access_token, **default_options)
|
9
16
|
end
|
10
17
|
|
18
|
+
# @return [Services::AircraftService]
|
11
19
|
def aircraft
|
12
20
|
@aircraft ||= Services::AircraftService.new(@api_service)
|
13
21
|
end
|
14
22
|
|
23
|
+
# @return [Services::AirlinesService]
|
15
24
|
def airlines
|
16
25
|
@airlines ||= Services::AirlinesService.new(@api_service)
|
17
26
|
end
|
18
27
|
|
28
|
+
# @return [Services::AirportsService]
|
19
29
|
def airports
|
20
30
|
@airports ||= Services::AirportsService.new(@api_service)
|
21
31
|
end
|
22
32
|
|
33
|
+
# @return [Services::OfferPassengersService]
|
23
34
|
def offer_passengers
|
24
35
|
@offer_passengers ||= Services::OfferPassengersService.new(@api_service)
|
25
36
|
end
|
26
37
|
|
38
|
+
# @return [Services::OfferRequestsService]
|
27
39
|
def offer_requests
|
28
40
|
@offer_requests ||= Services::OfferRequestsService.new(@api_service)
|
29
41
|
end
|
30
42
|
|
43
|
+
# @return [Services::OffersService]
|
31
44
|
def offers
|
32
45
|
@offers ||= Services::OffersService.new(@api_service)
|
33
46
|
end
|
34
47
|
|
48
|
+
# @return [Services::OrderCancellationsService]
|
35
49
|
def order_cancellations
|
36
50
|
@order_cancellations ||= Services::OrderCancellationsService.new(@api_service)
|
37
51
|
end
|
38
52
|
|
53
|
+
# @return [Services::OrderChangeOffersService]
|
39
54
|
def order_change_offers
|
40
55
|
@order_change_offers ||= Services::OrderChangeOffersService.new(@api_service)
|
41
56
|
end
|
42
57
|
|
58
|
+
# @return [Services::OrderChangeRequestsService]
|
43
59
|
def order_change_requests
|
44
60
|
@order_change_requests ||= Services::OrderChangeRequestsService.new(@api_service)
|
45
61
|
end
|
46
62
|
|
63
|
+
# @return [Services::OrderChangesService]
|
47
64
|
def order_changes
|
48
65
|
@order_changes ||= Services::OrderChangesService.new(@api_service)
|
49
66
|
end
|
50
67
|
|
68
|
+
# @return [Services::OrdersService]
|
51
69
|
def orders
|
52
70
|
@orders ||= Services::OrdersService.new(@api_service)
|
53
71
|
end
|
54
72
|
|
73
|
+
# @return [Services::PaymentIntentsService]
|
55
74
|
def payment_intents
|
56
75
|
@payment_intents ||= Services::PaymentIntentsService.new(@api_service)
|
57
76
|
end
|
58
77
|
|
78
|
+
# @return [Services::PaymentsService]
|
59
79
|
def payments
|
60
80
|
@payments ||= Services::PaymentsService.new(@api_service)
|
61
81
|
end
|
62
82
|
|
83
|
+
# @return [Services::RefundsService]
|
63
84
|
def refunds
|
64
85
|
@refunds ||= Services::RefundsService.new(@api_service)
|
65
86
|
end
|
66
87
|
|
88
|
+
# @return [Services::SeatMapsService]
|
67
89
|
def seat_maps
|
68
90
|
@seat_maps ||= Services::SeatMapsService.new(@api_service)
|
69
91
|
end
|
70
92
|
|
93
|
+
# @return [Services::WebhooksService]
|
71
94
|
def webhooks
|
72
95
|
@webhooks ||= Services::WebhooksService.new(@api_service)
|
73
96
|
end
|
@@ -5,6 +5,14 @@ module DuffelAPI
|
|
5
5
|
class Error < StandardError
|
6
6
|
attr_reader :error
|
7
7
|
|
8
|
+
# Builds an error, which provides access to the raw response. In general,
|
9
|
+
# subclasses of this error (e.g. `APIError`) will be raised, apart from
|
10
|
+
# for unrecognised errors returned by the Duffel API or errors that don't
|
11
|
+
# look like standardised Duffel API errors.
|
12
|
+
#
|
13
|
+
# @param error [Hash] the parsed error data from the API
|
14
|
+
# @param response [APIResponse, nil]
|
15
|
+
# @return [Error]
|
8
16
|
def initialize(error, response = nil)
|
9
17
|
raise ArgumentError, "Duffel errors expect a hash" unless error.is_a?(Hash)
|
10
18
|
|
@@ -14,38 +22,73 @@ module DuffelAPI
|
|
14
22
|
super(error)
|
15
23
|
end
|
16
24
|
|
25
|
+
# Returns a URL where documentation about the error can be found. This can be
|
26
|
+
# `nil` for errors that don't look like standardised Duffel errors (e.g. errors
|
27
|
+
# returned by the load balancer rather than the API itself).
|
28
|
+
#
|
29
|
+
# @return [String, nil]
|
17
30
|
def documentation_url
|
18
31
|
@error["documentation_url"]
|
19
32
|
end
|
20
33
|
|
34
|
+
# Returns the title associated with the error. This can be `nil` for errors that
|
35
|
+
# don't look like standardised Duffel errors (e.g. errors returned by the load
|
36
|
+
# balancer rather than the API itself).
|
37
|
+
#
|
38
|
+
# @return [String, nil]
|
21
39
|
def title
|
22
40
|
@error["title"]
|
23
41
|
end
|
24
42
|
|
43
|
+
# Return the message associated with the error
|
44
|
+
#
|
45
|
+
# @return [String]
|
25
46
|
def message
|
26
47
|
@error["message"]
|
27
48
|
end
|
28
49
|
|
50
|
+
# Returns a string representation of the error, taken from its `#message`
|
51
|
+
#
|
52
|
+
# @return [String]
|
29
53
|
def to_s
|
30
54
|
@error["message"]
|
31
55
|
end
|
32
56
|
|
57
|
+
# Returns the type of the error. See the Duffel API reference for possible values.
|
58
|
+
# This can be `nil` for errors that don't look like standardised Duffel errors
|
59
|
+
# (e.g. errors returned by the load balancer rather than the API itself).
|
60
|
+
#
|
61
|
+
# @return [String, nil]
|
33
62
|
def type
|
34
63
|
@error["type"]
|
35
64
|
end
|
36
65
|
|
66
|
+
# Returns the code of the error. See the Duffel API reference for possible values.
|
67
|
+
# This can be `nil` for errors that don't look like standardised Duffel errors
|
68
|
+
# (e.g. errors returned by the load balancer rather than the API itself).
|
69
|
+
#
|
70
|
+
# @return [String, nil]
|
37
71
|
def code
|
38
72
|
@error["code"]
|
39
73
|
end
|
40
74
|
|
75
|
+
# Returns the request ID of the request that generated the error.
|
76
|
+
#
|
77
|
+
# @return [String]
|
41
78
|
def request_id
|
42
|
-
|
79
|
+
api_response.request_id
|
43
80
|
end
|
44
81
|
|
82
|
+
# Return s the source of the error.
|
83
|
+
#
|
84
|
+
# @return [Hash, nil]
|
45
85
|
def source
|
46
86
|
@error["source"]
|
47
87
|
end
|
48
88
|
|
89
|
+
# Returns the raw API response where this error originated from
|
90
|
+
#
|
91
|
+
# @return [APIResponse]
|
49
92
|
def api_response
|
50
93
|
APIResponse.new(@response)
|
51
94
|
end
|
@@ -1,7 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DuffelAPI
|
4
|
+
# A page of results returned by a "list" action in the API, provides access to the
|
5
|
+
# records on the page, and metadata about the page itself.
|
4
6
|
class ListResponse
|
7
|
+
# Returns the records contained within the page
|
8
|
+
#
|
9
|
+
# @return [Array<Resources::BaseResource>] an array of records - for example, for the
|
10
|
+
# list action for offers, this would be a list of `Resources::Offer`s
|
5
11
|
attr_reader :records
|
6
12
|
|
7
13
|
def initialize(options = {})
|
@@ -12,14 +18,25 @@ module DuffelAPI
|
|
12
18
|
@records = @unenveloped_body.map { |item| @resource_class.new(item, @response) }
|
13
19
|
end
|
14
20
|
|
21
|
+
# Returns the raw API response received for this listing request
|
22
|
+
#
|
23
|
+
# @return [APIResponse]
|
15
24
|
def api_response
|
16
25
|
@api_response ||= APIResponse.new(@response)
|
17
26
|
end
|
18
27
|
|
28
|
+
# Returns the cursor representing the previous page of paginated results, if there is
|
29
|
+
# a previous page
|
30
|
+
#
|
31
|
+
# @return [String, nil]
|
19
32
|
def before
|
20
33
|
@response.parsed_body["meta"]["before"]
|
21
34
|
end
|
22
35
|
|
36
|
+
# Returns the cursor representing the next page of paginated results, if there is
|
37
|
+
# a next page
|
38
|
+
#
|
39
|
+
# @return [String, nil]
|
23
40
|
def after
|
24
41
|
@response.parsed_body["meta"]["after"]
|
25
42
|
end
|
@@ -4,11 +4,14 @@ require "faraday"
|
|
4
4
|
|
5
5
|
module DuffelAPI
|
6
6
|
module Middlewares
|
7
|
-
class RaiseDuffelErrors < Faraday::
|
7
|
+
class RaiseDuffelErrors < Faraday::Middleware
|
8
8
|
UNEXPECTED_ERROR_STATUSES = (501..599).freeze
|
9
9
|
EXPECTED_ERROR_STATUSES = (400..500).freeze
|
10
10
|
|
11
11
|
# rubocop:disable Metrics/AbcSize
|
12
|
+
# Handles a completed (Faraday) request and raises an error, if appropriate
|
13
|
+
#
|
14
|
+
# @param [Faraday::Env] env
|
12
15
|
def on_complete(env)
|
13
16
|
if !json?(env) || UNEXPECTED_ERROR_STATUSES.include?(env.status)
|
14
17
|
response = Response.new(env.response)
|
@@ -31,6 +34,11 @@ module DuffelAPI
|
|
31
34
|
|
32
35
|
private
|
33
36
|
|
37
|
+
# Picks the correct error class to use for an error returned by the Duffel API
|
38
|
+
# based on its type
|
39
|
+
#
|
40
|
+
# @param type [Atom] the type returned by the API
|
41
|
+
# @return [Errors::Error]
|
34
42
|
def error_class_for_type(type)
|
35
43
|
{
|
36
44
|
airline_error: DuffelAPI::Errors::AirlineError,
|
@@ -43,16 +51,24 @@ module DuffelAPI
|
|
43
51
|
}.fetch(type.to_sym) || DuffelAPI::Errors::Error
|
44
52
|
end
|
45
53
|
|
54
|
+
# Generates error data - specifically a message - based on the `Faraday::Env` for
|
55
|
+
# non-standard Duffel errors
|
56
|
+
#
|
57
|
+
# @param env [Faraday::Env]
|
58
|
+
# @return [Hash]
|
46
59
|
def generate_error_data(env)
|
47
60
|
{
|
48
61
|
"message" => "Something went wrong with this request\n" \
|
49
62
|
"Code: #{env.status}\n" \
|
50
63
|
"Headers: #{env.response_headers}\n" \
|
51
64
|
"Body: #{env.body}",
|
52
|
-
"code" => env.status,
|
53
65
|
}
|
54
66
|
end
|
55
67
|
|
68
|
+
# Works out if the response is a JSON response based on the `Faraday::Env`
|
69
|
+
#
|
70
|
+
# @param env [Faraday::Env]
|
71
|
+
# @return [Boolean]
|
56
72
|
def json?(env)
|
57
73
|
content_type = env.response_headers["Content-Type"] ||
|
58
74
|
env.response_headers["content-type"] || ""
|
data/lib/duffel_api/paginator.rb
CHANGED
@@ -1,12 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DuffelAPI
|
4
|
+
# An internal class used within the library to paginated automatically thruogh results
|
5
|
+
# from list actions that can be spread over multiple pages
|
4
6
|
class Paginator
|
5
|
-
|
6
|
-
|
7
|
-
|
7
|
+
# @param service [Services::BaseService] a service which implements `#list`
|
8
|
+
# @param options [Hash] the options originally passed to `#all`
|
9
|
+
def initialize(service:, options:)
|
10
|
+
@service = service
|
11
|
+
@options = options
|
8
12
|
end
|
9
13
|
|
14
|
+
# Returns an enumerator that is able to automatically cycle through paginated data
|
15
|
+
# returned by the API
|
16
|
+
#
|
17
|
+
# @return [Enumerator]
|
10
18
|
def enumerator
|
11
19
|
response = @service.list(@options)
|
12
20
|
|