easypost 3.4.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +19 -5
- data/.gitignore +2 -0
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +149 -138
- data/Gemfile +2 -0
- data/README.md +51 -8
- data/Rakefile +2 -1
- data/UPGRADE_GUIDE.md +62 -0
- data/VERSION +1 -1
- data/bin/easypost-irb +5 -3
- data/easycop.yml +180 -0
- data/easypost.gemspec +21 -19
- data/lib/easypost/address.rb +26 -26
- data/lib/easypost/api_key.rb +3 -0
- data/lib/easypost/batch.rb +31 -30
- data/lib/easypost/brand.rb +13 -0
- data/lib/easypost/carrier_account.rb +4 -0
- data/lib/easypost/carrier_type.rb +3 -0
- data/lib/easypost/connection.rb +67 -0
- data/lib/easypost/customs_info.rb +5 -1
- data/lib/easypost/customs_item.rb +5 -1
- data/lib/easypost/error.rb +7 -7
- data/lib/easypost/event.rb +5 -1
- data/lib/easypost/insurance.rb +4 -0
- data/lib/easypost/object.rb +44 -28
- data/lib/easypost/order.rb +15 -11
- data/lib/easypost/parcel.rb +7 -0
- data/lib/easypost/pickup.rb +15 -9
- data/lib/easypost/pickup_rate.rb +3 -1
- data/lib/easypost/postage_label.rb +3 -0
- data/lib/easypost/rate.rb +7 -0
- data/lib/easypost/refund.rb +3 -0
- data/lib/easypost/report.rb +9 -16
- data/lib/easypost/resource.rb +55 -25
- data/lib/easypost/scan_form.rb +8 -3
- data/lib/easypost/shipment.rb +47 -51
- data/lib/easypost/tax_identifier.rb +6 -0
- data/lib/easypost/tracker.rb +9 -4
- data/lib/easypost/user.rb +31 -10
- data/lib/easypost/util.rb +22 -17
- data/lib/easypost/version.rb +3 -1
- data/lib/easypost/webhook.rb +18 -12
- data/lib/easypost.rb +99 -107
- metadata +80 -22
- data/lib/easypost/print_job.rb +0 -2
- data/lib/easypost/printer.rb +0 -24
data/lib/easypost/tracker.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A Tracker object contains all of the tracking information for a package.
|
1
4
|
class EasyPost::Tracker < EasyPost::Resource
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
5
|
+
# Create multiple Tracker objects in bulk.
|
6
|
+
def self.create_list(params = {}, api_key = nil)
|
7
|
+
url = "#{self.url}/create_list"
|
8
|
+
new_params = { 'trackers' => params }
|
9
|
+
EasyPost.make_request(:post, url, api_key, new_params)
|
10
|
+
true # This endpoint does not return a response so we return true here instead
|
6
11
|
end
|
7
12
|
end
|
data/lib/easypost/user.rb
CHANGED
@@ -1,38 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The User object can be used to manage your own account and to create child accounts.
|
1
4
|
class EasyPost::User < EasyPost::Resource
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
+
# Create a child User.
|
6
|
+
def self.create(params = {}, api_key = nil)
|
7
|
+
response = EasyPost.make_request(:post, url, api_key, { class_name.to_sym => params })
|
8
|
+
EasyPost::Util.convert_to_easypost_object(response, api_key)
|
5
9
|
end
|
6
10
|
|
11
|
+
# Save (update) a User.
|
7
12
|
def save
|
8
|
-
if @unsaved_values.length
|
13
|
+
if @unsaved_values.length.positive?
|
9
14
|
values = {}
|
10
15
|
@unsaved_values.each { |k| values[k] = @values[k] }
|
11
16
|
|
12
|
-
wrapped_params = {user: values}
|
17
|
+
wrapped_params = { user: values }
|
13
18
|
|
14
19
|
response = EasyPost.make_request(:put, url, @api_key, wrapped_params)
|
15
20
|
refresh_from(response, api_key)
|
16
21
|
end
|
17
|
-
|
22
|
+
self
|
18
23
|
end
|
19
24
|
|
25
|
+
# Retrieve the authenticated User.
|
20
26
|
def self.retrieve_me
|
21
|
-
|
27
|
+
all
|
22
28
|
end
|
23
29
|
|
30
|
+
# Retrieve a list of ApiKey objects.
|
24
31
|
def self.all_api_keys
|
25
32
|
EasyPost::ApiKey.all
|
26
33
|
end
|
27
34
|
|
35
|
+
# Retrieve a list of ApiKey objects of a child User.
|
28
36
|
def api_keys
|
29
37
|
api_keys = EasyPost::User.all_api_keys
|
30
38
|
|
31
|
-
if api_keys.id ==
|
39
|
+
if api_keys.id == id
|
32
40
|
my_api_keys = api_keys.keys
|
33
41
|
else
|
34
|
-
|
35
|
-
if child.id ==
|
42
|
+
api_keys.children.each do |child|
|
43
|
+
if child.id == id
|
36
44
|
my_api_keys = child.keys
|
37
45
|
break
|
38
46
|
end
|
@@ -41,4 +49,17 @@ class EasyPost::User < EasyPost::Resource
|
|
41
49
|
|
42
50
|
my_api_keys
|
43
51
|
end
|
52
|
+
|
53
|
+
# Update the Brand of a User.
|
54
|
+
def update_brand(**attrs)
|
55
|
+
brand = EasyPost::Brand.new
|
56
|
+
data = { object: 'Brand', user_id: id, **attrs }
|
57
|
+
# Add accessors manually because there's no API to retrieve a brand
|
58
|
+
brand.add_accessors(data.keys)
|
59
|
+
# Assigning values with accessors defined above
|
60
|
+
data.each do |key, val|
|
61
|
+
brand.send("#{key}=", val)
|
62
|
+
end
|
63
|
+
brand.save
|
64
|
+
end
|
44
65
|
end
|
data/lib/easypost/util.rb
CHANGED
@@ -1,28 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Internal utilities helpful for this libraries operation.
|
1
4
|
module EasyPost::Util
|
5
|
+
# Converts an object to an object ID.
|
2
6
|
def self.objects_to_ids(obj)
|
3
7
|
case obj
|
4
8
|
when EasyPost::Resource
|
5
|
-
|
9
|
+
{ id: obj.id }
|
6
10
|
when Hash
|
7
11
|
result = {}
|
8
12
|
obj.each { |k, v| result[k] = objects_to_ids(v) unless v.nil? }
|
9
|
-
|
13
|
+
result
|
10
14
|
when Array
|
11
|
-
|
15
|
+
obj.map { |v| objects_to_ids(v) }
|
12
16
|
else
|
13
|
-
|
17
|
+
obj
|
14
18
|
end
|
15
19
|
end
|
16
20
|
|
21
|
+
# Normalizes a list of strings.
|
17
22
|
def self.normalize_string_list(lst)
|
18
23
|
lst = lst.is_a?(String) ? lst.split(',') : Array(lst)
|
19
24
|
lst.map(&:to_s).map(&:downcase).map(&:strip)
|
20
25
|
end
|
21
26
|
|
22
|
-
|
27
|
+
# Convert data to an EasyPost Object.
|
28
|
+
def self.convert_to_easypost_object(response, api_key, parent = nil, name = nil)
|
23
29
|
types = {
|
24
30
|
'Address' => EasyPost::Address,
|
25
31
|
'Batch' => EasyPost::Batch,
|
32
|
+
'Brand' => EasyPost::Brand,
|
26
33
|
'CarrierAccount' => EasyPost::CarrierAccount,
|
27
34
|
'CustomsInfo' => EasyPost::CustomsInfo,
|
28
35
|
'CustomsItem' => EasyPost::CustomsItem,
|
@@ -34,25 +41,25 @@ module EasyPost::Util
|
|
34
41
|
'Pickup' => EasyPost::Pickup,
|
35
42
|
'PickupRate' => EasyPost::PickupRate,
|
36
43
|
'PostageLabel' => EasyPost::PostageLabel,
|
37
|
-
'Printer' => EasyPost::Printer,
|
38
|
-
'PrintJob' => EasyPost::PrintJob,
|
39
44
|
'Rate' => EasyPost::Rate,
|
40
45
|
'Refund' => EasyPost::Refund,
|
41
46
|
'RefundReport' => EasyPost::Report,
|
42
47
|
'Report' => EasyPost::Report,
|
43
48
|
'ScanForm' => EasyPost::ScanForm,
|
44
49
|
'Shipment' => EasyPost::Shipment,
|
50
|
+
'TaxIdentifier' => EasyPost::TaxIdentifier,
|
45
51
|
'ShipmentInvoiceReport' => EasyPost::Report,
|
46
52
|
'ShipmentReport' => EasyPost::Report,
|
47
53
|
'Tracker' => EasyPost::Tracker,
|
48
54
|
'TrackerReport' => EasyPost::Report,
|
49
55
|
'User' => EasyPost::User,
|
50
|
-
'Webhook' => EasyPost::Webhook
|
56
|
+
'Webhook' => EasyPost::Webhook,
|
51
57
|
}
|
52
58
|
|
53
59
|
prefixes = {
|
54
60
|
'adr' => EasyPost::Address,
|
55
61
|
'batch' => EasyPost::Batch,
|
62
|
+
'brd' => EasyPost::Brand,
|
56
63
|
'ca' => EasyPost::CarrierAccount,
|
57
64
|
'cstinfo' => EasyPost::CustomsInfo,
|
58
65
|
'cstitem' => EasyPost::CustomsItem,
|
@@ -65,8 +72,6 @@ module EasyPost::Util
|
|
65
72
|
'pl' => EasyPost::PostageLabel,
|
66
73
|
'plrep' => EasyPost::Report,
|
67
74
|
'prcl' => EasyPost::Parcel,
|
68
|
-
'printer' => EasyPost::Printer,
|
69
|
-
'printjob' => EasyPost::PrintJob,
|
70
75
|
'rate' => EasyPost::Rate,
|
71
76
|
'refrep' => EasyPost::Report,
|
72
77
|
'rfnd' => EasyPost::Refund,
|
@@ -76,33 +81,33 @@ module EasyPost::Util
|
|
76
81
|
'shprep' => EasyPost::Report,
|
77
82
|
'trk' => EasyPost::Tracker,
|
78
83
|
'trkrep' => EasyPost::Report,
|
79
|
-
'user' => EasyPost::User
|
84
|
+
'user' => EasyPost::User,
|
80
85
|
}
|
81
86
|
|
82
87
|
case response
|
83
88
|
when Array
|
84
|
-
|
89
|
+
response.map { |i| convert_to_easypost_object(i, api_key, parent) }
|
85
90
|
when Hash
|
86
|
-
if cls_name = response[:object]
|
91
|
+
if (cls_name = response[:object])
|
87
92
|
cls = types[cls_name]
|
88
93
|
elsif response[:id]
|
89
94
|
if response[:id].index('_').nil?
|
90
95
|
cls = EasyPost::EasyPostObject
|
91
|
-
elsif cls_prefix = response[:id][0..response[:id].index('_')]
|
96
|
+
elsif (cls_prefix = response[:id][0..response[:id].index('_')])
|
92
97
|
cls = prefixes[cls_prefix[0..-2]]
|
93
98
|
end
|
94
99
|
elsif response['id']
|
95
100
|
if response['id'].index('_').nil?
|
96
101
|
cls = EasyPost::EasyPostObject
|
97
|
-
elsif cls_prefix = response['id'][0..response['id'].index('_')]
|
102
|
+
elsif (cls_prefix = response['id'][0..response['id'].index('_')])
|
98
103
|
cls = prefixes[cls_prefix[0..-2]]
|
99
104
|
end
|
100
105
|
end
|
101
106
|
|
102
107
|
cls ||= EasyPost::EasyPostObject
|
103
|
-
|
108
|
+
cls.construct_from(response, api_key, parent, name)
|
104
109
|
else
|
105
|
-
|
110
|
+
response
|
106
111
|
end
|
107
112
|
end
|
108
113
|
end
|
data/lib/easypost/version.rb
CHANGED
data/lib/easypost/webhook.rb
CHANGED
@@ -1,29 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Each Webhook contains the url which EasyPost will notify whenever an object in our system updates. Several types of objects are processed
|
4
|
+
# asynchronously in the EasyPost system, so whenever an object updates, an Event is sent via HTTP POST to each configured webhook URL.
|
1
5
|
class EasyPost::Webhook < EasyPost::Resource
|
2
|
-
|
3
|
-
|
4
|
-
# with the objects field
|
5
|
-
unless
|
6
|
-
raise EasyPost::Error.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{
|
6
|
+
# Update a Webhook.
|
7
|
+
def update(params = {})
|
8
|
+
# NOTE: This method is redefined here since the "url" method conflicts with the objects field
|
9
|
+
unless id
|
10
|
+
raise EasyPost::Error.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}")
|
7
11
|
end
|
12
|
+
|
8
13
|
instance_url = "#{self.class.url}/#{CGI.escape(id)}"
|
9
14
|
|
10
15
|
response = EasyPost.make_request(:put, instance_url, @api_key, params)
|
11
|
-
|
16
|
+
refresh_from(response, api_key, true)
|
12
17
|
|
13
|
-
|
18
|
+
self
|
14
19
|
end
|
15
20
|
|
21
|
+
# Delete a Webhook.
|
16
22
|
def delete
|
17
|
-
# NOTE: This method is redefined here since the "url" method conflicts
|
18
|
-
|
19
|
-
|
20
|
-
raise EasyPost::Error.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{self.id.inspect}")
|
23
|
+
# NOTE: This method is redefined here since the "url" method conflicts with the objects field
|
24
|
+
unless id
|
25
|
+
raise EasyPost::Error.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}")
|
21
26
|
end
|
27
|
+
|
22
28
|
instance_url = "#{self.class.url}/#{CGI.escape(id)}"
|
23
29
|
|
24
30
|
response = EasyPost.make_request(:delete, instance_url, @api_key)
|
25
31
|
refresh_from(response, api_key)
|
26
32
|
|
27
|
-
|
33
|
+
self
|
28
34
|
end
|
29
35
|
end
|
data/lib/easypost.rb
CHANGED
@@ -1,148 +1,140 @@
|
|
1
|
-
|
2
|
-
require "cgi"
|
3
|
-
require "net/http"
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
|
9
|
-
require
|
3
|
+
require 'base64'
|
4
|
+
require 'cgi'
|
5
|
+
require 'net/http'
|
6
|
+
|
7
|
+
require 'easypost/version'
|
8
|
+
require 'easypost/util'
|
9
|
+
require 'easypost/object'
|
10
|
+
require 'easypost/resource'
|
11
|
+
require 'easypost/error'
|
12
|
+
require 'easypost/connection'
|
10
13
|
|
11
14
|
# Resources
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require
|
17
|
-
require
|
18
|
-
require
|
19
|
-
require
|
20
|
-
require
|
21
|
-
require
|
22
|
-
require
|
23
|
-
require
|
24
|
-
require
|
25
|
-
require
|
26
|
-
require
|
27
|
-
require
|
28
|
-
require
|
29
|
-
require
|
30
|
-
require
|
31
|
-
require
|
32
|
-
require
|
33
|
-
require
|
34
|
-
require
|
35
|
-
require
|
15
|
+
require 'easypost/address'
|
16
|
+
require 'easypost/api_key'
|
17
|
+
require 'easypost/batch'
|
18
|
+
require 'easypost/brand'
|
19
|
+
require 'easypost/carrier_account'
|
20
|
+
require 'easypost/carrier_type'
|
21
|
+
require 'easypost/customs_info'
|
22
|
+
require 'easypost/customs_item'
|
23
|
+
require 'easypost/event'
|
24
|
+
require 'easypost/insurance'
|
25
|
+
require 'easypost/order'
|
26
|
+
require 'easypost/parcel'
|
27
|
+
require 'easypost/pickup_rate'
|
28
|
+
require 'easypost/pickup'
|
29
|
+
require 'easypost/postage_label'
|
30
|
+
require 'easypost/rate'
|
31
|
+
require 'easypost/refund'
|
32
|
+
require 'easypost/report'
|
33
|
+
require 'easypost/scan_form'
|
34
|
+
require 'easypost/shipment'
|
35
|
+
require 'easypost/tax_identifier'
|
36
|
+
require 'easypost/tracker'
|
37
|
+
require 'easypost/user'
|
38
|
+
require 'easypost/webhook'
|
36
39
|
|
37
40
|
module EasyPost
|
38
|
-
|
39
|
-
|
41
|
+
DEFAULT_API_BASE = 'https://api.easypost.com'
|
42
|
+
DEFAULT_USER_AGENT = "EasyPost/v2 RubyClient/#{EasyPost::VERSION} Ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
40
43
|
|
41
|
-
|
42
|
-
|
44
|
+
class << self
|
45
|
+
attr_accessor :api_key, :api_base
|
46
|
+
attr_writer :default_connection
|
43
47
|
end
|
44
48
|
|
45
|
-
|
46
|
-
|
49
|
+
self.api_base = DEFAULT_API_BASE
|
50
|
+
|
51
|
+
def self.default_headers
|
52
|
+
@default_headers ||= {
|
53
|
+
'Content-Type' => 'application/json',
|
54
|
+
'User-Agent' => EasyPost::DEFAULT_USER_AGENT,
|
55
|
+
}
|
47
56
|
end
|
48
57
|
|
49
|
-
def self.
|
50
|
-
@
|
58
|
+
def self.default_connection
|
59
|
+
@default_connection ||= EasyPost::Connection.new(
|
60
|
+
uri: URI(api_base),
|
61
|
+
config: http_config,
|
62
|
+
)
|
51
63
|
end
|
52
64
|
|
53
|
-
def self.
|
54
|
-
|
65
|
+
def self.authorization(key)
|
66
|
+
"Basic #{Base64.strict_encode64("#{key}:")}"
|
55
67
|
end
|
56
68
|
|
69
|
+
# Reset the HTTP config.
|
57
70
|
def self.reset_http_config
|
58
|
-
|
71
|
+
http_config.clear
|
72
|
+
self.default_connection = nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.default_http_config
|
76
|
+
http_config = {
|
59
77
|
timeout: 60,
|
60
78
|
open_timeout: 30,
|
61
79
|
verify_ssl: OpenSSL::SSL::VERIFY_PEER,
|
62
80
|
}
|
63
81
|
|
64
82
|
ruby_version = Gem::Version.new(RUBY_VERSION)
|
65
|
-
if ruby_version >= Gem::Version.new(
|
66
|
-
|
83
|
+
if ruby_version >= Gem::Version.new('2.5.0')
|
84
|
+
http_config[:min_version] = OpenSSL::SSL::TLS1_2_VERSION
|
67
85
|
else
|
68
|
-
|
86
|
+
http_config[:ssl_version] = :TLSv1_2 # rubocop:disable Naming/VariableNumber
|
69
87
|
end
|
70
88
|
|
71
|
-
|
89
|
+
http_config
|
72
90
|
end
|
73
91
|
|
92
|
+
# Get the HTTP config.
|
74
93
|
def self.http_config
|
75
|
-
@http_config ||=
|
94
|
+
@http_config ||= default_http_config
|
76
95
|
end
|
77
96
|
|
97
|
+
# Set the HTTP config.
|
78
98
|
def self.http_config=(http_config_params)
|
79
99
|
http_config.merge!(http_config_params)
|
80
|
-
end
|
81
|
-
|
82
|
-
def self.make_client(uri)
|
83
|
-
client = if http_config[:proxy]
|
84
|
-
proxy_uri = URI(http_config[:proxy])
|
85
|
-
Net::HTTP.new(
|
86
|
-
uri.host,
|
87
|
-
uri.port,
|
88
|
-
proxy_uri.host,
|
89
|
-
proxy_uri.port,
|
90
|
-
proxy_uri.user,
|
91
|
-
proxy_uri.password
|
92
|
-
)
|
93
|
-
else
|
94
|
-
Net::HTTP.new(uri.host, uri.port)
|
95
|
-
end
|
96
|
-
client.use_ssl = true
|
97
|
-
|
98
|
-
http_config.each do |name, value|
|
99
|
-
# Discrepancies between RestClient and Net::HTTP.
|
100
|
-
if name == :verify_ssl
|
101
|
-
name = :verify_mode
|
102
|
-
elsif name == :timeout
|
103
|
-
name = :read_timeout
|
104
|
-
end
|
105
|
-
|
106
|
-
# Handled in the creation of the client.
|
107
|
-
if name == :proxy
|
108
|
-
next
|
109
|
-
end
|
110
|
-
|
111
|
-
client.send("#{name}=", value)
|
112
|
-
end
|
113
100
|
|
114
|
-
|
101
|
+
self.default_connection = nil
|
115
102
|
end
|
116
103
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
end
|
124
|
-
|
125
|
-
request["Content-Type"] = "application/json"
|
126
|
-
request["User-Agent"] = "EasyPost/v2 RubyClient/#{VERSION} Ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
127
|
-
if api_key = api_key || @api_key
|
128
|
-
request["Authorization"] = "Basic #{Base64.strict_encode64("#{api_key}:")}"
|
129
|
-
end
|
104
|
+
# Create an EasyPost Client.
|
105
|
+
#
|
106
|
+
# @deprecated
|
107
|
+
def self.make_client(url)
|
108
|
+
EasyPost::Connection.new(uri: URI(url), config: http_config).create
|
109
|
+
end
|
130
110
|
|
131
|
-
|
111
|
+
# Make an HTTP request against the {default_connection}
|
112
|
+
#
|
113
|
+
# @param method [Symbol] the HTTP Verb (get, method, put, post, etc.)
|
114
|
+
# @param path [String] URI path of the resource
|
115
|
+
# @param requested_api_key [String] ({EasyPost.api_key}) key set Authorization header.
|
116
|
+
# @param body [Object] (nil) object to be dumped to JSON
|
117
|
+
# @raise [EasyPost::Error] if the response has a non-2xx status code
|
118
|
+
# @return [Hash] JSON object parsed from the response body
|
119
|
+
def self.make_request(method, path, api_key = nil, body = nil)
|
120
|
+
default_connection.call(method, path, api_key || EasyPost.api_key, body)
|
121
|
+
end
|
132
122
|
|
133
|
-
|
134
|
-
|
135
|
-
|
123
|
+
def self.parse_response(status:, body:, json:)
|
124
|
+
if status >= 400
|
125
|
+
error = JSON.parse(body)['error']
|
126
|
+
|
127
|
+
raise EasyPost::Error.new(
|
128
|
+
error['message'],
|
129
|
+
status,
|
130
|
+
error['code'],
|
131
|
+
error['errors'],
|
132
|
+
body,
|
133
|
+
)
|
136
134
|
end
|
137
135
|
|
138
|
-
|
139
|
-
JSON.parse(response.body)
|
140
|
-
else
|
141
|
-
response.body
|
142
|
-
end
|
136
|
+
json ? JSON.parse(body) : body
|
143
137
|
rescue JSON::ParserError
|
144
|
-
raise
|
145
|
-
"Invalid response object from API, unable to decode.\n#{response.body}"
|
146
|
-
)
|
138
|
+
raise "Invalid response object from API, unable to decode.\n#{body}"
|
147
139
|
end
|
148
140
|
end
|