smartcar 0.1.1 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,12 +1,12 @@
1
1
  module Smartcar
2
2
  # class to represent Fuel info
3
- #
4
- # @author [ashwin]
5
- #
3
+ #@attr [Number] amountRemaining Amount of fuel remaining.
4
+ #@attr [Number] percentageRemaining Decimal value representing the remaining fuel percent.
5
+ #@attr [Number] range Remaining range of the vehicle.
6
6
  class Fuel < Base
7
- include Utils
7
+ # Path Proc for hitting fuel end point
8
8
  PATH = Proc.new{|id| "/vehicles/#{id}/fuel"}
9
- attr_accessor :amountRemaining, :percentRemaining, :range
9
+ attr_reader :amountRemaining, :percentRemaining, :range
10
10
 
11
11
  # just to have Ruby-esque method names
12
12
  alias_method :amount_remaining, :amountRemaining
@@ -1,11 +1,10 @@
1
1
  module Smartcar
2
2
  # class to represent Location info
3
- #
4
- # @author [ashwin]
5
- #
3
+ #@attr [Number] latitude Latitude of last recorded location.
4
+ #@attr [Number] longitude Longitude of last recorded location.
6
5
  class Location < Base
7
- include Utils
6
+ # Path Proc for hitting location end point
8
7
  PATH = Proc.new{|id| "/vehicles/#{id}/location"}
9
- attr_accessor :latitude, :longitude
8
+ attr_reader :latitude, :longitude
10
9
  end
11
10
  end
@@ -1,93 +1,91 @@
1
1
  module Smartcar
2
- # Oauth class to take care of the Oauth 2.0 with genomelink APIs
3
- #
4
- # @author [ashwin]
2
+ # Oauth class to take care of the Oauth 2.0 with Smartcar APIs
5
3
  #
6
4
  class Oauth < Base
7
- class << self
8
- # Generate the OAuth authorization URL.
9
- #
10
- # By default users are not shown the permission dialog if they have already
11
- # approved the set of scopes for this application. The application can elect
12
- # to always display the permissions dialog to the user by setting
13
- # approval_prompt to `force`.
14
- #
15
- # @param options [Hash]
16
- # @param options[:state] [String] - OAuth state parameter passed to the
17
- # redirect uri. This parameter may be used for identifying the user who
18
- # initiated the request.
19
- # @param options[:test_mode] [Boolean] - Setting this to 'true' runs it in test mode.
20
- # @param options[:force_prompt] [Boolean] - Setting `force_prompt` to
21
- # `true` will show the permissions approval screen on every authentication
22
- # attempt, even if the user has previously consented to the exact scope of
23
- # permissions.
24
- # @param options[:make] [String] - `make' is an optional parameter that allows
25
- # users to bypass the car brand selection screen.
26
- # For a complete list of supported makes, please see our
27
- # [API Reference](https://smartcar.com/docs/api#authorization) documentation.
28
- # @param options[:scope] [Array of Strings] - array of scopes that specify what the user can access
29
- # EXAMPLE : ['read_odometer', 'read_vehicle_info', 'required:read_location']
30
- # For further details refer to https://smartcar.com/docs/guides/scope/
31
- #
32
- # @return [String] URL where user needs to be redirected for authorization
33
- def authorization_url(options)
34
- parameters = {
35
- redirect_uri: get_config('REDIRECT_URI'),
36
- approval_prompt: options[:force_prompt] ? FORCE : AUTO,
37
- mode: options[:test_mode] ? TEST : LIVE,
38
- response_type: CODE
39
- }
40
- parameters[:scope] = options[:scope].join(' ') if options[:scope]
41
- %I(state make).each do |parameter|
42
- parameters[:parameter] = options[:parameter] unless options[:parameter].nil?
43
- end
44
-
45
- client.auth_code.authorize_url(parameters)
46
- end
5
+ extend Utils
6
+ # By default users are not shown the permission dialog if they have already
7
+ # approved the set of scopes for this application. The application can elect
8
+ # to always display the permissions dialog to the user by setting
9
+ # approval_prompt to `force`.
10
+ #
11
+ # @param options [Hash]
12
+ # @option options[:client_id] [String] - Client ID, if not passed fallsback to ENV['CLIENT_ID']
13
+ # @option options[:client_secret] [String] - Client Secret, if not passed fallsback to ENV['CLIENT_SECRET']
14
+ # @option options[:redirect_uri] [String] - Redirect URI, if not passed fallsback to ENV['REDIRECT_URI']
15
+ # @option options[:state] [String] - OAuth state parameter passed to the
16
+ # redirect uri. This parameter may be used for identifying the user who
17
+ # initiated the request.
18
+ # @option options[:test_mode] [Boolean] - Setting this to 'true' runs it in test mode.
19
+ # @option options[:force_prompt] [Boolean] - Setting `force_prompt` to
20
+ # `true` will show the permissions approval screen on every authentication
21
+ # attempt, even if the user has previously consented to the exact scope of
22
+ # permissions.
23
+ # @option options[:make] [String] - `make' is an optional parameter that allows
24
+ # users to bypass the car brand selection screen.
25
+ # For a complete list of supported makes, please see our
26
+ # [API Reference](https://smartcar.com/docs/api#authorization) documentation.
27
+ # @option options[:scope] [Array of Strings] - array of scopes that specify what the user can access
28
+ # EXAMPLE : ['read_odometer', 'read_vehicle_info', 'required:read_location']
29
+ # For further details refer to https://smartcar.com/docs/guides/scope/
30
+ #
31
+ # @return [Smartcar::Oauth] Returns a Smartcar::Oauth Object that has other methods
32
+ def initialize(options)
33
+ @redirect_uri = options[:redirect_uri] || get_config('REDIRECT_URI')
34
+ @client_id = options[:client_id] || get_config('CLIENT_ID')
35
+ @client_secret = options[:client_secret] || get_config('CLIENT_SECRET')
47
36
 
48
- # [get_token description]
49
- # @param auth_code [String] This is the code that is returned after use
50
- # visits and authorizes on the authorization URL.
51
- #
52
- # @return [Hash] Hash of token, refresh token, expiry info and token type
53
- def get_token(auth_code)
54
- client.auth_code
55
- .get_token(
56
- auth_code,
57
- redirect_uri: get_config('REDIRECT_URI')
58
- ).to_hash
37
+ @auth_parameters = {
38
+ redirect_uri: @redirect_uri,
39
+ approval_prompt: options[:force_prompt] ? FORCE : AUTO,
40
+ mode: options[:test_mode] ? TEST : LIVE,
41
+ response_type: CODE
42
+ }
43
+ @auth_parameters[:scope] = options[:scope].join(' ') if options[:scope]
44
+ %I(state make).each do |parameter|
45
+ parameters[:parameter] = options[:parameter] unless options[:parameter].nil?
59
46
  end
47
+ end
60
48
 
61
- # [refresh_token description]
62
- # @param token_hash [Hash] This is the hash that is returned with the
63
- # get_token method
64
- #
65
- # @return [Hash] Hash of token, refresh token, expiry info and token type
66
- def refresh_token(token_hash)
67
- token_object = OAuth2::AccessToken.from_hash(client, token_hash)
68
- token_object = token_object.refresh!
69
- token_object.to_hash
70
- end
49
+ # Generate the OAuth authorization URL.
50
+ #
51
+ # @return [String] Authorization URL string
52
+ def authorization_url
53
+ client.auth_code.authorize_url(@auth_parameters)
54
+ end
71
55
 
72
- private
73
- # gets the Oauth Client object
74
- #
75
- # @return [OAuth2::Client] A Oauth Client object.
76
- def client
77
- @client ||= OAuth2::Client.new( get_config('CLIENT_ID'),
78
- get_config('CLIENT_SECRET'),
79
- :site => OAUTH_PATH
80
- )
81
- end
56
+ # Generates the tokens hash using the code returned in oauth process.
57
+ # @param auth_code [String] This is the code that is returned after user
58
+ # visits and authorizes on the authorization URL.
59
+ #
60
+ # @return [Hash] Hash of token, refresh token, expiry info and token type
61
+ def get_token(auth_code)
62
+ client.auth_code
63
+ .get_token(
64
+ auth_code,
65
+ redirect_uri: @redirect_uri
66
+ ).to_hash
67
+ end
82
68
 
83
- # gets a given env variable, checks for existence and throws exception if not present
84
- # @param config_name [String] key of the env variable
85
- #
86
- # @return [String] value of the env variable
87
- def get_config(config_name)
88
- raise ConfigNotFound, "Environment variable #{config_name} not found !" unless ENV[config_name]
89
- ENV[config_name]
90
- end
69
+ # Refreshing the access token
70
+ # @param token_hash [Hash] This is the hash that is returned with the
71
+ # get_token method
72
+ #
73
+ # @return [Hash] Hash of token, refresh token, expiry info and token type
74
+ def refresh_token(token_hash)
75
+ token_object = OAuth2::AccessToken.from_hash(client, token_hash)
76
+ token_object = token_object.refresh!
77
+ token_object.to_hash
78
+ end
79
+
80
+ private
81
+ # gets the Oauth Client object
82
+ #
83
+ # @return [OAuth2::Client] A Oauth Client object.
84
+ def client
85
+ @client ||= OAuth2::Client.new( @client_id,
86
+ @client_secret,
87
+ :site => OAUTH_PATH
88
+ )
91
89
  end
92
90
  end
93
91
  end
@@ -1,11 +1,9 @@
1
1
  module Smartcar
2
- # class to get Charge info
3
- #
4
- # @author [ashwin]
5
- #
2
+ # class to represent Odometer
3
+ #@attr [Number] distanceLast recorded odometer reading.
6
4
  class Odometer < Base
7
- include Utils
5
+ # Path Proc for hitting odometer end point
8
6
  PATH = Proc.new{|id| "/vehicles/#{id}/odometer"}
9
- attr_accessor :distance
7
+ attr_reader :distance
10
8
  end
11
9
  end
@@ -1,11 +1,9 @@
1
1
  module Smartcar
2
- # class to get Charge info
3
- #
4
- # @author [ashwin]
5
- #
6
- class Permissions
7
- include Utils
2
+ # class to represent permissions response
3
+ #@attr [Array] permissions Array of permissions granted on the vehicle.
4
+ class Permissions < Base
5
+ # Path Proc for hitting permissions end point
8
6
  PATH = Proc.new{|id| "/vehicles/#{id}/permissions"}
9
- attr_accessor :permissions
7
+ attr_reader :permissions
10
8
  end
11
9
  end
@@ -1,12 +1,14 @@
1
1
  module Smartcar
2
- # class to represent Engine oil life
3
- #
4
- # @author [ashwin]
5
- #
2
+ # class to represent Tire Pressure response
3
+ #@attr [Number] back_left Last recorded tire pressure of the back left tire.
4
+ #@attr [Number] back_right Last recorded tire pressure of the back right tire.
5
+ #@attr [Number] front_left Last recorded tire pressure of the front left tire.
6
+ #@attr [Number] front_right Last recorded tire pressure of the front right tire.
7
+
6
8
  class TirePressure < Base
7
- include Utils
9
+ # Path Proc for hitting tire pressure end point
8
10
  PATH = Proc.new{|id| "/vehicles/#{id}/tires/pressure"}
9
- attr_accessor :backLeft, :backRight, :frontLeft, :frontRight
11
+ attr_reader :backLeft, :backRight, :frontLeft, :frontRight
10
12
 
11
13
  # just to have Ruby-esque method names
12
14
  alias_method :back_left, :backLeft
@@ -1,11 +1,11 @@
1
1
  module Smartcar
2
2
  # Class to get to user API.
3
- #
4
- # @author [ashwin]
5
- #
3
+ #@attr [String] id Smartcar user id.
4
+ #@attr [String] token Access token used to connect to Smartcar API.
6
5
  class User < Base
6
+ # Path for hitting user end point
7
7
  USER_PATH = '/user'.freeze
8
- attr_accessor :id, :token
8
+ attr_reader :id, :token
9
9
 
10
10
  def initialize(token:)
11
11
  raise InvalidParameterValue.new, "Access Token(token) is a required field" if token.nil?
@@ -21,6 +21,6 @@ module Smartcar
21
21
  def self.user_id(token:)
22
22
  new(token: token).get(USER_PATH)['id']
23
23
  end
24
-
24
+
25
25
  end
26
26
  end
@@ -1,11 +1,35 @@
1
- module Utils
2
- # A constructor to take a hash and assign it to the instance variables
3
- # @param options = {} [Hash] Could by any class's hash, but the first level keys should be defined in the class
4
- #
5
- # @return [Subclass os Base] Returns object of any subclass like Report
6
- def initialize(options = {})
7
- options.each do |attribute, value|
8
- instance_variable_set("@#{attribute}", value)
9
- end
1
+ # Utils module , provides utility methods to underlying classes
2
+ module Utils
3
+ # A constructor to take a hash and assign it to the instance variables
4
+ # @param options = {} [Hash] Could by any class's hash, but the first level keys should be defined in the class
5
+ #
6
+ # @return [Subclass os Base] Returns object of any subclass like Report
7
+ def initialize(options = {})
8
+ options.each do |attribute, value|
9
+ instance_variable_set("@#{attribute}", value)
10
10
  end
11
- end
11
+ end
12
+
13
+ # gets a given env variable, checks for existence and throws exception if not present
14
+ # @param config_name [String] key of the env variable
15
+ #
16
+ # @return [String] value of the env variable
17
+ def get_config(config_name)
18
+ config_name = "INTEGRATION_#{config_name}" if ENV['MODE'] == 'test'
19
+ raise Smartcar::ConfigNotFound, "Environment variable #{config_name} not found !" unless ENV[config_name]
20
+ ENV[config_name]
21
+ end
22
+
23
+ # Given the response from smartcar API, returns an error object if needed
24
+ # @param response [Object] response Object with status and body
25
+ #
26
+ # @return [Object] nil OR Error object
27
+ def get_error(response)
28
+ status = response.status
29
+ return nil if [200,204].include?(status)
30
+ return Smartcar::ServiceUnavailableError.new("Service Unavailable - #{response.body}") if status == 404
31
+ return Smartcar::BadRequestError.new("Bad Request - #{response.body}") if status == 400
32
+ return Smartcar::AuthenticationError.new("Authentication error") if status == 401
33
+ return Smartcar::ExternalServiceError.new("API error - #{response.body}")
34
+ end
35
+ end
@@ -4,14 +4,21 @@ module Smartcar
4
4
  # For Ex. Vehicle object will be treate as an entity and doing vehicle_object.
5
5
  # Battery should return Battery object.
6
6
  #
7
- # @author [ashwin]
8
- #
7
+ #@attr [String] token Access token used to connect to Smartcar API.
8
+ #@attr [String] id Smartcar vehicle ID.
9
+ #@attr [String] unit_system unit system to represent the data in.
9
10
  class Vehicle < Base
11
+ include Utils
12
+
10
13
 
14
+ # Path for hitting compatibility end point
11
15
  COMPATIBLITY_PATH = '/compatibility'.freeze
16
+
17
+ # Path for hitting vehicle ids end point
12
18
  PATH = Proc.new{|id| "/vehicles/#{id}"}
13
- attr_accessor :token, :id, :unit_system
14
19
 
20
+ attr_reader :id
21
+ attr_accessor :token, :unit_system
15
22
 
16
23
  def initialize(token:, id:, unit_system: IMPERIAL)
17
24
  raise InvalidParameterValue.new, "Invalid Units provided : #{unit_system}" unless UNITS.include?(unit_system)
@@ -22,13 +29,6 @@ module Smartcar
22
29
  @unit_system = unit_system
23
30
  end
24
31
 
25
- # Accessor method for vehicle attributes.
26
- %I(make model year).each do |method_name|
27
- define_method method_name do
28
- vehicle_attributes.send(method_name)
29
- end
30
- end
31
-
32
32
  # Class method Used to get all the vehicles in the app. This only returns
33
33
  # API - https://smartcar.com/docs/api#get-all-vehicles
34
34
  # @param token [String] - Access token
@@ -36,10 +36,11 @@ module Smartcar
36
36
  #
37
37
  # @return [Array] of vehicle IDs(Strings)
38
38
  def self.all_vehicle_ids(token:, options: {})
39
- new(token: token, id: 'none').fetch(
39
+ response, meta = new(token: token, id: 'none').fetch(
40
40
  path: PATH.call(''),
41
41
  options: options
42
- )['vehicles']
42
+ )
43
+ response['vehicles']
43
44
  end
44
45
 
45
46
  # Class method Used to check compatiblity for VIN and scope
@@ -52,13 +53,26 @@ module Smartcar
52
53
  raise InvalidParameterValue.new, "vin is a required field" if vin.nil?
53
54
  raise InvalidParameterValue.new, "scope is a required field" if scope.nil?
54
55
 
55
- new(token: 'none', id: 'none').fetch(path: COMPATIBLITY_PATH,
56
+ response, meta = new(token: 'none', id: 'none').fetch(path: COMPATIBLITY_PATH,
56
57
  options: {
57
58
  vin: vin,
58
59
  scope: scope.join(' ')
59
60
  },
60
61
  auth: BASIC
61
- )['compatible']
62
+ )
63
+ response['compatible']
64
+ end
65
+
66
+ # Method to get batch requests
67
+ # API - https://smartcar.com/docs/api#post-batch-request
68
+ # @param attributes [Array] Array of strings or symbols of attributes to be fetched together
69
+ #
70
+ # @return [Hash] Hash wth key as requested attribute(symbol) and value as Error OR Object of the requested attribute
71
+ def batch(attributes = [])
72
+ raise InvalidParameterValue.new, "vin is a required field" if attributes.nil?
73
+ request_body = get_batch_request_body(attributes)
74
+ response, _meta = post(PATH.call(id) + "/batch", request_body)
75
+ process_batch_response(response)
62
76
  end
63
77
 
64
78
  # Fetch the list of permissions that this application has been granted for
@@ -68,10 +82,7 @@ module Smartcar
68
82
  #
69
83
  # @return [Array] of permissions (Strings)
70
84
  def permissions(options: {})
71
- Permissions.new(fetch(
72
- path: Permissions::PATH.call(id),
73
- options: options
74
- )).permissions
85
+ get_attribute(Permissions)
75
86
  end
76
87
 
77
88
  # Method Used toRevoke access for the current requesting application
@@ -83,104 +94,201 @@ module Smartcar
83
94
  response['status'] == SUCCESS
84
95
  end
85
96
 
86
- # Methods Used lock or unlock car
97
+ # Methods Used to lock car
87
98
  # API - https://smartcar.com/docs/api#post-security
88
99
  #
89
100
  # @return [Boolean] true if success
90
- %w(lock unlock).each do |method_name|
91
- define_method "#{method_name}!" do
92
- lock_or_unlock!(action: Smartcar.const_get(method_name.upcase))
93
- end
101
+ def lock!
102
+ lock_or_unlock!(action: Smartcar::LOCK)
94
103
  end
95
104
 
96
- # Following section defined methods using meta programing to fetch various
97
- # details of a vehicle. The key is the method name, and value is Class that
98
- # wraps the data.
99
- {
100
- # Returns the state of charge (SOC) and remaining range of an electric or
101
- # plug-in hybrid vehicle's battery.
102
- # API - https://smartcar.com/docs/api#get-ev-battery
103
- #
104
- # @return [Battery] object
105
- battery: Battery,
106
- # Returns the current charge status of the vehicle.
107
- # API - https://smartcar.com/docs/api#get-ev-battery
108
- #
109
- # @return [Charge] object
110
- charge: Charge,
111
- # Returns the remaining life span of a vehicle's engine oil
112
- # API - https://smartcar.com/docs/api#get-engine-oil-life
113
- #
114
- # @return [EngineOil] object
115
- engine_oil: EngineOil,
116
- # Returns the status of the fuel remaining in the vehicle's gas tank.
117
- # API - https://smartcar.com/docs/api#get-fuel-tank
118
- #
119
- # @return [Fuel] object
120
- fuel: Fuel,
121
- # Returns the last known location of the vehicle in geographic coordinates.
122
- # API - https://smartcar.com/docs/api#get-location
123
- #
124
- # @return [Location] object
125
- location: Location,
126
- # Returns the vehicle's last known odometer reading.
127
- # API - https://smartcar.com/docs/api#get-odometer
128
- #
129
- # @return [Odometer] object
130
- odometer: Odometer,
131
- # Returns the air pressure of each of the vehicle's tires.
132
- # API - https://smartcar.com/docs/api#get-tire-pressure
133
- #
134
- # @return [TirePressure] object
135
- tire_pressure: TirePressure,
136
- }.each do |method_name, klass|
137
- define_method method_name do
138
- klass.new(
139
- fetch(
140
- path: klass::PATH.call(id)
141
- )
142
- )
143
- end
105
+ # Methods Used to unlock car
106
+ # API - https://smartcar.com/docs/api#post-security
107
+ #
108
+ # @return [Boolean] true if success
109
+ def unlock!
110
+ lock_or_unlock!(action: Smartcar::UNLOCK)
144
111
  end
145
112
 
146
- #NOTE : The following two also could be defined by metaprogramming,
147
- # But these are items that dont change and hence can be cached in the
148
- # vehicle object.
113
+ # Method used to start charging a car
114
+ #
115
+ #
116
+ # @return [Boolean] true if success
117
+ def start_charge!
118
+ start_or_stop_charge!(action: Smartcar::START_CHARGE)
119
+ end
149
120
 
150
- # Returns the vehicle's manufacturer identifier.
151
- # API - https://smartcar.com/docs/api#get-vin
121
+ # Method used to stop charging a car
152
122
  #
153
- # @return [Vin] object
154
- def vin
155
- @vin ||= Vin.new(
156
- fetch(
157
- path: Vin::PATH.call(id)
158
- )
159
- ).vin
123
+ #
124
+ # @return [Boolean] true if success
125
+ def stop_charge!
126
+ start_or_stop_charge!(action: Smartcar::STOP_CHARGE)
160
127
  end
161
128
 
162
- # Returns the vehicle's model make and year.
163
- # API - https://smartcar.com/docs/api#get-vehicle-attributes
129
+ # Returns make model year and id of the vehicle
130
+ # API - https://smartcar.com/api#get-vehicle-attributes
164
131
  #
165
132
  # @return [VehicleAttributes] object
166
133
  def vehicle_attributes
167
- @vehicle_attributes ||= VehicleAttributes.new(
168
- fetch(
169
- path: PATH.call(id)
170
- )
171
- )
134
+ get_attribute(VehicleAttributes)
135
+ end
136
+
137
+ # Returns the state of charge (SOC) and remaining range of an electric or
138
+ # plug-in hybrid vehicle's battery.
139
+ # API - https://smartcar.com/docs/api#get-ev-battery
140
+ #
141
+ # @return [Battery] object
142
+ def battery
143
+ get_attribute(Battery)
144
+ end
145
+
146
+ # Returns the current charge status of the vehicle.
147
+ # API - https://smartcar.com/docs/api#get-ev-battery
148
+ #
149
+ # @return [Charge] object
150
+ def charge
151
+ get_attribute(Charge)
152
+ end
153
+
154
+ # Returns the remaining life span of a vehicle's engine oil
155
+ # API - https://smartcar.com/docs/api#get-engine-oil-life
156
+ #
157
+ # @return [EngineOil] object
158
+ def engine_oil
159
+ get_attribute(EngineOil)
160
+ end
161
+
162
+ # Returns the status of the fuel remaining in the vehicle's gas tank.
163
+ # API - https://smartcar.com/docs/api#get-fuel-tank
164
+ #
165
+ # @return [Fuel] object
166
+ def fuel
167
+ get_attribute(Fuel)
168
+ end
169
+
170
+ # Returns the last known location of the vehicle in geographic coordinates.
171
+ # API - https://smartcar.com/docs/api#get-location
172
+ #
173
+ # @return [Location] object
174
+ def location
175
+ get_attribute(Location)
176
+ end
177
+
178
+ # Returns the vehicle's last known odometer reading.
179
+ # API - https://smartcar.com/docs/api#get-odometer
180
+ #
181
+ # @return [Odometer] object
182
+ def odometer
183
+ get_attribute(Odometer)
184
+ end
185
+
186
+ # Returns the air pressure of each of the vehicle's tires.
187
+ # API - https://smartcar.com/docs/api#get-tire-pressure
188
+ #
189
+ # @return [TirePressure] object
190
+ def tire_pressure
191
+ get_attribute(TirePressure)
192
+ end
193
+
194
+ # Returns the vehicle's manufacturer identifier (VIN).
195
+ # API - https://smartcar.com/docs/api#get-vin
196
+ #
197
+ # @return [String] Vin of the vehicle.
198
+ def vin
199
+ _object = get_attribute(Vin)
200
+ @vin ||= _object.vin
172
201
  end
173
202
 
174
203
  private
175
204
 
205
+ def allowed_attributes
206
+ @allowed_attributes ||= {
207
+ battery: get_path(Battery),
208
+ charge: get_path(Charge),
209
+ engine_oil: get_path(EngineOil),
210
+ fuel: get_path(Fuel),
211
+ location: get_path(Location),
212
+ odometer: get_path(Odometer),
213
+ permissions: get_path(Permissions),
214
+ tire_pressure: get_path(TirePressure),
215
+ vin: get_path(Vin),
216
+ }
217
+ end
218
+
219
+ def path_to_class
220
+ @path_to_class ||= {
221
+ get_path(Battery) => Battery,
222
+ get_path(Charge) => Charge,
223
+ get_path(EngineOil) => EngineOil,
224
+ get_path(Fuel) => Fuel,
225
+ get_path(Location) => Location,
226
+ get_path(Odometer) => Odometer,
227
+ get_path(Permissions) => Permissions,
228
+ get_path(TirePressure) => TirePressure,
229
+ get_path(Vin) => Vin,
230
+ }
231
+ end
232
+
233
+ # @private
234
+ BatchItemResponse = Struct.new(:body, :status, :headers) do
235
+ def body_with_meta
236
+ body.merge(meta: headers)
237
+ end
238
+ end
239
+
240
+ def get_batch_request_body(attributes)
241
+ attributes = validated_attributes(attributes)
242
+ requests = attributes.each_with_object([]) do |item, requests|
243
+ requests << { path: allowed_attributes[item] }
244
+ end
245
+ { requests: requests }
246
+ end
247
+
248
+ def process_batch_response(responses)
249
+ inverted_map = allowed_attributes.invert
250
+ responses["responses"].each_with_object({}) do |response, result|
251
+ item_response = BatchItemResponse.new(response["body"], response["code"], response["headers"])
252
+ error = get_error(item_response)
253
+ path = response["path"]
254
+ result[inverted_map[path]] = error || get_object(path_to_class[path], item_response.body_with_meta)
255
+ end
256
+ end
257
+
258
+ def validated_attributes(attributes)
259
+ attributes.map!(&:to_sym)
260
+ unsupported_attributes = (attributes - allowed_attributes.keys) || []
261
+ unless unsupported_attributes.empty?
262
+ message = "Unsupported attribute(s) requested in batch - #{unsupported_attributes.join(',')}"
263
+ raise InvalidParameterValue.new, message
264
+ end
265
+ attributes
266
+ end
267
+
268
+ def get_attribute(klass)
269
+ body, meta = fetch(
270
+ path: klass::PATH.call(id)
271
+ )
272
+ get_object(klass, body.merge(meta: meta))
273
+ end
274
+
275
+ def get_object(klass, data)
276
+ klass.new(data)
277
+ end
278
+
279
+ def get_path(klass)
280
+ path = klass::PATH.call(id)
281
+ path.split("/vehicles/#{id}").last
282
+ end
283
+
176
284
  def lock_or_unlock!(action:)
177
- response = post(PATH.call(id) + "/security", {action: action}.to_json)
285
+ response, meta = post(PATH.call(id) + "/security", { action: action })
178
286
  response['status'] == SUCCESS
179
287
  end
180
288
 
181
- class VehicleAttributes
182
- include Utils
183
- attr_accessor :id, :make, :model, :year
289
+ def start_or_stop_charge!(action:)
290
+ response, meta = post(PATH.call(id) + "/charge", { action: action })
291
+ response['status'] == SUCCESS
184
292
  end
185
293
  end
186
294
  end