duffel_api 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![RubyDoc.info documentation](http://img.shields.io/badge/yard-docs-blue.svg)](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
|
|