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