my_john_deere_api 2.5.0 → 3.0.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/lib/my_john_deere_api.rb +1 -1
- data/lib/my_john_deere_api/authorize.rb +36 -27
- data/lib/my_john_deere_api/client.rb +38 -29
- data/lib/my_john_deere_api/consumer.rb +46 -35
- data/lib/my_john_deere_api/helpers/case_conversion.rb +5 -2
- data/lib/my_john_deere_api/helpers/uri_helpers.rb +1 -1
- data/lib/my_john_deere_api/model/contribution_definition.rb +1 -1
- data/lib/my_john_deere_api/net_http_retry/decorator.rb +10 -11
- data/lib/my_john_deere_api/net_http_retry/invalid_response_error.rb +2 -2
- data/lib/my_john_deere_api/request/collection/asset_locations.rb +1 -1
- data/lib/my_john_deere_api/request/collection/assets.rb +1 -1
- data/lib/my_john_deere_api/request/collection/base.rb +2 -14
- data/lib/my_john_deere_api/request/collection/contribution_definitions.rb +1 -1
- data/lib/my_john_deere_api/request/collection/contribution_products.rb +1 -1
- data/lib/my_john_deere_api/request/collection/fields.rb +1 -1
- data/lib/my_john_deere_api/request/collection/flags.rb +1 -1
- data/lib/my_john_deere_api/request/collection/organizations.rb +1 -1
- data/lib/my_john_deere_api/request/create/asset.rb +8 -11
- data/lib/my_john_deere_api/request/create/asset_location.rb +24 -25
- data/lib/my_john_deere_api/request/create/base.rb +2 -20
- data/lib/my_john_deere_api/request/individual/asset.rb +2 -2
- data/lib/my_john_deere_api/request/individual/base.rb +6 -24
- data/lib/my_john_deere_api/request/individual/contribution_definition.rb +1 -1
- data/lib/my_john_deere_api/request/individual/contribution_product.rb +1 -1
- data/lib/my_john_deere_api/request/individual/field.rb +1 -1
- data/lib/my_john_deere_api/request/individual/organization.rb +1 -1
- data/lib/my_john_deere_api/request/update/asset.rb +2 -2
- data/lib/my_john_deere_api/request/update/base.rb +1 -19
- data/lib/my_john_deere_api/version.rb +1 -1
- data/test/lib/my_john_deere_api/authorize_test.rb +37 -25
- data/test/lib/my_john_deere_api/client_test.rb +22 -56
- data/test/lib/my_john_deere_api/consumer_test.rb +16 -28
- data/test/lib/my_john_deere_api/helpers/uri_helpers_test.rb +0 -10
- data/test/lib/my_john_deere_api/model/asset_location_test.rb +0 -4
- data/test/lib/my_john_deere_api/model/asset_test.rb +9 -8
- data/test/lib/my_john_deere_api/model/base_test.rb +4 -8
- data/test/lib/my_john_deere_api/model/contribution_definition_test.rb +3 -7
- data/test/lib/my_john_deere_api/model/contribution_product_test.rb +4 -7
- data/test/lib/my_john_deere_api/model/field_test.rb +4 -6
- data/test/lib/my_john_deere_api/model/flag_test.rb +6 -7
- data/test/lib/my_john_deere_api/model/organization_test.rb +3 -5
- data/test/lib/my_john_deere_api/net_http_retry/decorator_test.rb +14 -14
- data/test/lib/my_john_deere_api/net_http_retry/invalid_response_error_test.rb +22 -2
- data/test/lib/my_john_deere_api/request/collection/asset_locations_test.rb +2 -2
- data/test/lib/my_john_deere_api/request/collection/assets_test.rb +2 -2
- data/test/lib/my_john_deere_api/request/collection/contribution_definitions_test.rb +2 -2
- data/test/lib/my_john_deere_api/request/collection/contribution_products_test.rb +2 -2
- data/test/lib/my_john_deere_api/request/collection/fields_test.rb +2 -2
- data/test/lib/my_john_deere_api/request/collection/flags_test.rb +2 -2
- data/test/lib/my_john_deere_api/request/collection/organizations_test.rb +2 -2
- data/test/lib/my_john_deere_api/request/create/asset_location_test.rb +3 -2
- data/test/lib/my_john_deere_api/request/create/asset_test.rb +5 -4
- data/test/lib/my_john_deere_api/request/create/base_test.rb +0 -14
- data/test/lib/my_john_deere_api/request/individual/asset_test.rb +2 -3
- data/test/lib/my_john_deere_api/request/individual/base_test.rb +0 -1
- data/test/lib/my_john_deere_api/request/individual/contribution_definition_test.rb +2 -3
- data/test/lib/my_john_deere_api/request/individual/contribution_product_test.rb +2 -3
- data/test/lib/my_john_deere_api/request/individual/field_test.rb +2 -3
- data/test/lib/my_john_deere_api/request/individual/organization_test.rb +2 -3
- data/test/lib/my_john_deere_api/request/update/asset_test.rb +5 -17
- data/test/lib/my_john_deere_api/request/update/base_test.rb +0 -14
- data/test/support/helper.rb +14 -5
- data/test/support/link_helpers.rb +14 -0
- data/test/support/response_helpers.rb +18 -0
- data/test/support/vcr/catalog.yml +44 -37
- data/test/support/vcr/get_access_token.yml +90 -17
- data/test/support/vcr/get_refresh_token.yml +159 -0
- data/test/support/vcr/get_request_url.yml +51 -0
- data/test/support/vcr_setup.rb +80 -19
- metadata +11 -8
- data/test/support/vcr/get_request_token.yml +0 -83
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b36f05ba98236dbdddfcab7086fa13c6af8d673dc8c2d2c1f185abcbf0f1ceeb
|
4
|
+
data.tar.gz: d5bda4fae253a293fd58b44045eacf2b7eba8dd142560aa3813da3deb334bc7c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 454ac16787a9448ce1fb0d5db1bc4f2b39b9c0c729552fe9c0730093d681dc442fc33b21cdbb8aea79678a1a2e1fd1bdab2ff042b931251818435cbcbd7a54e9
|
7
|
+
data.tar.gz: b90f4aa488daa2e0c99c31ede5045769f6d7cd04fce875d92375b369318845737fc5dbf9d9e4824b048980261e6ccc1880e8b4803a01cd9f0be4299d826cd491
|
data/lib/my_john_deere_api.rb
CHANGED
@@ -2,10 +2,7 @@ module MyJohnDeereApi
|
|
2
2
|
class Authorize
|
3
3
|
include Helpers::EnvironmentHelper
|
4
4
|
|
5
|
-
attr_reader :api_key, :api_secret,
|
6
|
-
:request_token, :request_secret,
|
7
|
-
:access_token, :access_secret,
|
8
|
-
:environment, :options
|
5
|
+
attr_reader :api_key, :api_secret, :environment, :options, :token_hash
|
9
6
|
|
10
7
|
DEFAULTS = {
|
11
8
|
environment: :live
|
@@ -23,6 +20,9 @@ module MyJohnDeereApi
|
|
23
20
|
@api_key = api_key
|
24
21
|
@api_secret = api_secret
|
25
22
|
self.environment = @options[:environment]
|
23
|
+
|
24
|
+
# This is only set upon verification
|
25
|
+
@token_hash = nil
|
26
26
|
end
|
27
27
|
|
28
28
|
##
|
@@ -32,38 +32,47 @@ module MyJohnDeereApi
|
|
32
32
|
def authorize_url
|
33
33
|
return @authorize_url if defined?(@authorize_url)
|
34
34
|
|
35
|
-
request_options = options.slice(:
|
35
|
+
request_options = options.slice(:redirect_uri, :state, :scope)
|
36
|
+
|
37
|
+
if options.key?(:scopes)
|
38
|
+
options[:scopes] << 'offline_access' unless options[:scopes].include?('offline_access')
|
39
|
+
request_options[:scope] = options[:scopes].join(' ')
|
40
|
+
end
|
36
41
|
|
37
|
-
|
38
|
-
|
39
|
-
|
42
|
+
# generate a default unique-ish "state" key if not provided
|
43
|
+
unless request_options.key?(:state)
|
44
|
+
request_options[:state] = (rand(8000) + 1000).to_s
|
45
|
+
end
|
40
46
|
|
41
|
-
@authorize_url =
|
47
|
+
@authorize_url = oauth_client.auth_code.authorize_url(request_options)
|
42
48
|
end
|
43
49
|
|
44
50
|
##
|
45
|
-
# API
|
51
|
+
# API client that makes authentication requests
|
46
52
|
|
47
|
-
def
|
48
|
-
return @
|
49
|
-
@
|
53
|
+
def oauth_client
|
54
|
+
return @oauth_client if defined?(@oauth_client)
|
55
|
+
@oauth_client = MyJohnDeereApi::Consumer.new(@api_key, @api_secret, environment: environment).auth_client
|
50
56
|
end
|
51
57
|
|
52
58
|
##
|
53
|
-
# Turn a verification code into access
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
59
|
+
# Turn a verification code into access token.
|
60
|
+
|
61
|
+
def verify(code)
|
62
|
+
token = oauth_client.auth_code.get_token(code, redirect_uri: options[:redirect_uri])
|
63
|
+
|
64
|
+
# normalize hash
|
65
|
+
@token_hash = JSON.parse(token.to_hash.to_json)
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Use an old token hash to generate a new token hash.
|
70
|
+
|
71
|
+
def refresh_from_hash(old_token_hash)
|
72
|
+
old_token = OAuth2::AccessToken.from_hash(oauth_client, old_token_hash)
|
73
|
+
new_token = old_token.refresh!
|
74
|
+
|
75
|
+
new_token.to_hash
|
67
76
|
end
|
68
77
|
end
|
69
78
|
end
|
@@ -4,7 +4,7 @@ module MyJohnDeereApi
|
|
4
4
|
include Helpers::CaseConversion
|
5
5
|
|
6
6
|
attr_accessor :contribution_definition_id
|
7
|
-
attr_reader :api_key, :api_secret, :
|
7
|
+
attr_reader :api_key, :api_secret, :token_hash, :http_retry_options
|
8
8
|
|
9
9
|
DEFAULTS = {
|
10
10
|
environment: :live,
|
@@ -13,18 +13,17 @@ module MyJohnDeereApi
|
|
13
13
|
|
14
14
|
##
|
15
15
|
# Creates the client with everything it needs to perform API requests.
|
16
|
-
# User-specific
|
17
|
-
# requests are only possible if
|
16
|
+
# User-specific token_hash is optional, but user-specific API
|
17
|
+
# requests are only possible if it is supplied.
|
18
18
|
#
|
19
19
|
# options:
|
20
20
|
#
|
21
21
|
# [:environment] :sandbox or :live
|
22
22
|
#
|
23
23
|
# [:contribution_definition_id] optional, but needed for some requests
|
24
|
-
# like asset create/update
|
24
|
+
# like asset create/update
|
25
25
|
#
|
26
|
-
# [:
|
27
|
-
# and the access_secret of the given user
|
26
|
+
# [:token_hash] a hash used to re-create the access token
|
28
27
|
|
29
28
|
def initialize(api_key, api_secret, options = {})
|
30
29
|
options = DEFAULTS.merge(options)
|
@@ -32,8 +31,8 @@ module MyJohnDeereApi
|
|
32
31
|
@api_key = api_key
|
33
32
|
@api_secret = api_secret
|
34
33
|
|
35
|
-
if options.has_key?(:
|
36
|
-
@
|
34
|
+
if options.has_key?(:token_hash) && options[:token_hash].is_a?(Hash)
|
35
|
+
@token_hash = options[:token_hash]
|
37
36
|
end
|
38
37
|
|
39
38
|
self.environment = options[:environment]
|
@@ -49,22 +48,38 @@ module MyJohnDeereApi
|
|
49
48
|
return @accessor if defined?(@accessor)
|
50
49
|
|
51
50
|
@accessor = NetHttpRetry::Decorator.new(
|
52
|
-
|
53
|
-
consumer.user_get,
|
54
|
-
access_token,
|
55
|
-
access_secret
|
56
|
-
),
|
51
|
+
OAuth2::AccessToken.from_hash(oauth_client, token_hash),
|
57
52
|
http_retry_options
|
58
53
|
)
|
59
54
|
end
|
60
55
|
|
56
|
+
##
|
57
|
+
# Returns the URI for the Contribution Definiton ID, if provided
|
58
|
+
|
59
|
+
def contribution_definition_uri
|
60
|
+
return @contribution_definition_uri if defined?(@contribution_definition_uri)
|
61
|
+
|
62
|
+
@contribution_definition_uri =
|
63
|
+
if contribution_definition_id
|
64
|
+
"#{site}/contributionDefinitions/#{contribution_definition_id}"
|
65
|
+
else
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Returns the base url for requests
|
72
|
+
|
73
|
+
def site
|
74
|
+
return @site if defined?(@site)
|
75
|
+
@site = accessor.client.site
|
76
|
+
end
|
77
|
+
|
61
78
|
##
|
62
79
|
# generic user-specific GET request method that returns JSON
|
63
80
|
|
64
81
|
def get resource
|
65
|
-
|
66
|
-
resource = "/#{resource}" unless resource =~ /^\//
|
67
|
-
response = accessor.get(resource, headers)
|
82
|
+
response = accessor.get(resource, headers: headers)
|
68
83
|
|
69
84
|
JSON.parse(response.body)
|
70
85
|
end
|
@@ -73,9 +88,7 @@ module MyJohnDeereApi
|
|
73
88
|
# generic user-specific POST request method that returns JSON or response
|
74
89
|
|
75
90
|
def post resource, body
|
76
|
-
|
77
|
-
resource = "/#{resource}" unless resource =~ /^\//
|
78
|
-
response = accessor.post(resource, camelize(body).to_json, post_headers)
|
91
|
+
response = accessor.post(resource, body: camelize(body).to_json, headers: post_headers)
|
79
92
|
|
80
93
|
if response.body && response.body.size > 0
|
81
94
|
JSON.parse(response.body)
|
@@ -88,9 +101,7 @@ module MyJohnDeereApi
|
|
88
101
|
# generic user-specific PUT request method that returns JSON or response
|
89
102
|
|
90
103
|
def put resource, body
|
91
|
-
|
92
|
-
resource = "/#{resource}" unless resource =~ /^\//
|
93
|
-
response = accessor.put(resource, camelize(body).to_json, post_headers)
|
104
|
+
response = accessor.put(resource, body: camelize(body).to_json, headers: post_headers)
|
94
105
|
|
95
106
|
if response.body && response.body.size > 0
|
96
107
|
JSON.parse(response.body)
|
@@ -103,9 +114,7 @@ module MyJohnDeereApi
|
|
103
114
|
# generic user-specific DELETE request method that returns JSON or response
|
104
115
|
|
105
116
|
def delete resource
|
106
|
-
|
107
|
-
resource = "/#{resource}" unless resource =~ /^\//
|
108
|
-
response = accessor.delete(resource, headers)
|
117
|
+
response = accessor.delete(resource, headers: headers)
|
109
118
|
|
110
119
|
if response.body && response.body.size > 0
|
111
120
|
JSON.parse(response.body)
|
@@ -133,11 +142,11 @@ module MyJohnDeereApi
|
|
133
142
|
private
|
134
143
|
|
135
144
|
##
|
136
|
-
# Returns an oAuth
|
145
|
+
# Returns an oAuth client which can be used to build requests
|
137
146
|
|
138
|
-
def
|
139
|
-
return @
|
140
|
-
@
|
147
|
+
def oauth_client
|
148
|
+
return @oauth_client if defined?(@oauth_client)
|
149
|
+
@oauth_client = MyJohnDeereApi::Consumer.new(@api_key, @api_secret, environment: environment).platform_client
|
141
150
|
end
|
142
151
|
|
143
152
|
def headers
|
@@ -3,12 +3,12 @@ module MyJohnDeereApi
|
|
3
3
|
include Helpers::CaseConversion
|
4
4
|
include Helpers::EnvironmentHelper
|
5
5
|
|
6
|
-
attr_reader :api_key, :api_secret, :environment, :
|
6
|
+
attr_reader :api_key, :api_secret, :environment, :site
|
7
7
|
|
8
8
|
# valid API urls
|
9
9
|
URLS = {
|
10
10
|
sandbox: 'https://sandboxapi.deere.com',
|
11
|
-
live: 'https://
|
11
|
+
live: 'https://partnerapi.deere.com',
|
12
12
|
}
|
13
13
|
|
14
14
|
DEFAULTS = {
|
@@ -22,65 +22,76 @@ module MyJohnDeereApi
|
|
22
22
|
@api_secret = api_secret
|
23
23
|
|
24
24
|
self.environment = options[:environment]
|
25
|
-
@
|
25
|
+
@site = options[:site] || URLS[@environment]
|
26
26
|
end
|
27
27
|
|
28
28
|
##
|
29
|
-
# oAuth
|
30
|
-
# app-wide, non user-specific GET requests.
|
29
|
+
# oAuth client for platform requests
|
31
30
|
|
32
|
-
def
|
33
|
-
@
|
31
|
+
def platform_client
|
32
|
+
return @platform_client if defined?(@platform_client)
|
33
|
+
|
34
|
+
@platform_client = OAuth2::Client.new(
|
35
|
+
api_key,
|
36
|
+
api_secret,
|
37
|
+
site: site,
|
38
|
+
headers: headers,
|
39
|
+
raise_errors: false,
|
40
|
+
)
|
34
41
|
end
|
35
42
|
|
36
43
|
##
|
37
|
-
# oAuth
|
44
|
+
# oAuth client for user authentication
|
38
45
|
|
39
|
-
def
|
40
|
-
@
|
41
|
-
end
|
46
|
+
def auth_client
|
47
|
+
return @auth_client if defined?(@auth_client)
|
42
48
|
|
43
|
-
|
49
|
+
# We build this without the `client` method because the authorization links
|
50
|
+
# require an extra API call to JD that is only needed for authorization.
|
44
51
|
|
45
|
-
|
46
|
-
OAuth::Consumer.new(
|
52
|
+
@auth_client = OAuth2::Client.new(
|
47
53
|
api_key,
|
48
54
|
api_secret,
|
49
55
|
site: site,
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
access_token_url: links[:access_token],
|
54
|
-
authorize_url: links[:authorize_request_token]
|
56
|
+
authorize_url: authorization_links[:authorization],
|
57
|
+
token_url: authorization_links[:token],
|
58
|
+
raise_errors: false,
|
55
59
|
)
|
56
60
|
end
|
57
61
|
|
58
|
-
|
59
|
-
|
62
|
+
private
|
63
|
+
|
64
|
+
def authorization
|
65
|
+
return @authorization if defined?(@authorization)
|
60
66
|
|
61
|
-
|
67
|
+
json = OAuth2::Client.new(api_key, api_secret)
|
62
68
|
.request(
|
63
69
|
:get,
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
header
|
70
|
+
'https://signin.johndeere.com/oauth2/aus78tnlaysMraFhC1t7/.well-known/oauth-authorization-server',
|
71
|
+
headers: headers,
|
72
|
+
raise_errors: false,
|
68
73
|
).body
|
69
74
|
|
70
|
-
|
75
|
+
@authorization = JSON.parse(json)
|
76
|
+
end
|
71
77
|
|
72
|
-
|
73
|
-
|
74
|
-
uri.query = nil
|
78
|
+
def authorization_links
|
79
|
+
return @authorization_links if defined?(@authorization_links)
|
75
80
|
|
76
|
-
|
77
|
-
|
81
|
+
@authorization_links = {
|
82
|
+
authorization: authorization['authorization_endpoint'],
|
83
|
+
token: authorization['token_endpoint'],
|
84
|
+
organizations: "https://connections.deere.com/connections/#{api_key}/select-organizations",
|
85
|
+
}
|
86
|
+
end
|
78
87
|
|
79
|
-
|
88
|
+
def scopes
|
89
|
+
return @scopes if defined?(@scopes)
|
90
|
+
@scopes = authorization['scopes_supported']
|
80
91
|
end
|
81
92
|
|
82
|
-
def
|
83
|
-
@
|
93
|
+
def headers
|
94
|
+
@headers ||= {accept: 'application/vnd.deere.axiom.v3+json'}
|
84
95
|
end
|
85
96
|
|
86
97
|
def keyify key_name
|
@@ -22,7 +22,8 @@ module MyJohnDeereApi::Helpers::CaseConversion
|
|
22
22
|
def camelize(something)
|
23
23
|
something = something.to_s if something.is_a?(Symbol)
|
24
24
|
|
25
|
-
|
25
|
+
case something
|
26
|
+
when String
|
26
27
|
list = something.strip.split(/[_\s]+/)
|
27
28
|
|
28
29
|
# preserve case of the first element
|
@@ -30,8 +31,10 @@ module MyJohnDeereApi::Helpers::CaseConversion
|
|
30
31
|
new_list += list.map(&:capitalize)
|
31
32
|
|
32
33
|
new_list.join('')
|
33
|
-
|
34
|
+
when Hash
|
34
35
|
something.transform_keys{ |key| camelize(key) }
|
36
|
+
when Array
|
37
|
+
something.map{|element| camelize(element)}
|
35
38
|
end
|
36
39
|
end
|
37
40
|
end
|
@@ -7,41 +7,40 @@ module MyJohnDeereApi
|
|
7
7
|
request_methods: [:get, :post, :put, :delete],
|
8
8
|
retry_delay_exponent: 2,
|
9
9
|
max_retries: 12,
|
10
|
-
retry_codes: [
|
11
|
-
valid_codes: [
|
10
|
+
retry_codes: [429, 503],
|
11
|
+
valid_codes: [200, 201, 204],
|
12
12
|
}
|
13
13
|
|
14
14
|
def initialize(object, options={})
|
15
15
|
@object = object
|
16
16
|
|
17
|
-
#
|
18
|
-
[:request_methods, :retry_delay_exponent, :max_retries].each do |option|
|
17
|
+
# options that can be used as-is
|
18
|
+
[:request_methods, :retry_delay_exponent, :max_retries, :retry_codes, :valid_codes].each do |option|
|
19
19
|
instance_variable_set(:"@#{option}", options[option] || DEFAULTS[option])
|
20
20
|
end
|
21
21
|
|
22
|
-
#
|
22
|
+
# options that require casting as integer arrays
|
23
23
|
[:retry_codes, :valid_codes].each do |option|
|
24
|
-
instance_variable_set(:"@#{option}", (options[option] || DEFAULTS[option]).map(&:
|
24
|
+
instance_variable_set(:"@#{option}", (options[option] || DEFAULTS[option]).map(&:to_i))
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
28
|
def request(method_name, *args)
|
29
29
|
retries = 0
|
30
30
|
result = object.send(method_name, *args)
|
31
|
-
|
32
|
-
while retry_codes.include?(result.code)
|
31
|
+
while retry_codes.include?(result.status)
|
33
32
|
if retries >= max_retries
|
34
|
-
raise MaxRetriesExceededError.new(method_name, "#{result.
|
33
|
+
raise MaxRetriesExceededError.new(method_name, "#{result.status} #{result.response.reason_phrase}")
|
35
34
|
end
|
36
35
|
|
37
|
-
delay = [result['retry-after'].to_i, retry_delay_exponent ** retries].max
|
36
|
+
delay = [result.headers['retry-after'].to_i, retry_delay_exponent ** retries].max
|
38
37
|
sleep(delay)
|
39
38
|
|
40
39
|
result = object.send(method_name, *args)
|
41
40
|
retries += 1
|
42
41
|
end
|
43
42
|
|
44
|
-
unless valid_codes.include?(result.
|
43
|
+
unless valid_codes.include?(result.status)
|
45
44
|
raise InvalidResponseError.new(result)
|
46
45
|
end
|
47
46
|
|