smartcar 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.
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smartcar
4
+ # AuthClient class to take care of the Oauth 2.0 with Smartcar APIs
5
+ #
6
+ class AuthClient
7
+ include Smartcar::Utils
8
+
9
+ attr_reader :redirect_uri, :client_id, :client_secret, :scope, :mode, :flags, :origin
10
+
11
+ # Constructor for a client object
12
+ #
13
+ # @param [Hash] options
14
+ # @option options[:client_id] [String] - Client ID, if not passed fallsback to ENV['SMARTCAR_CLIENT_ID']
15
+ # @option options[:client_secret] [String] - Client Secret, if not passed fallsback to ENV['SMARTCAR_CLIENT_SECRET']
16
+ # @option options[:redirect_uri] [String] - Redirect URI, if not passed fallsback to ENV['SMARTCAR_REDIRECT_URI']
17
+ # @option options[:test_mode] [Boolean] - Setting this to 'true' runs it in test mode.
18
+ #
19
+ # @return [Smartcar::AuthClient] Returns a Smartcar::AuthClient Object that has other methods
20
+ def initialize(options)
21
+ options[:redirect_uri] ||= get_config('SMARTCAR_REDIRECT_URI')
22
+ options[:client_id] ||= get_config('SMARTCAR_CLIENT_ID')
23
+ options[:client_secret] ||= get_config('SMARTCAR_CLIENT_SECRET')
24
+ options[:mode] = options[:test_mode].is_a?(TrueClass) ? TEST : LIVE
25
+ options[:origin] = ENV['SMARTCAR_AUTH_ORIGIN'] || AUTH_ORIGIN
26
+ super
27
+ end
28
+
29
+ # Generate the OAuth authorization URL.
30
+ # @param scope [Array<String>] Array of permissions that specify what the user can access
31
+ # EXAMPLE : ['read_odometer', 'read_vehicle_info', 'required:read_location']
32
+ # For further details refer to https://smartcar.com/docs/guides/scope/
33
+ # @param [Hash] options
34
+ # @option options[:force_prompt] [Boolean] - Setting `force_prompt` to
35
+ # `true` will show the permissions approval screen on every authentication
36
+ # attempt, even if the user has previously consented to the exact scope of
37
+ # permissions.
38
+ # @option options[:single_select] [Hash] - An optional object that sets the
39
+ # behavior of the grant dialog displayed to the user. Object can contain two keys :
40
+ # - enabled - Boolean value, if set to `true`,
41
+ # `single_select` limits the user to selecting only one vehicle.
42
+ # - vin - String vin, if set, Smartcar will only authorize the vehicle
43
+ # with the specified VIN.
44
+ # See the [Single Select guide](https://smartcar.com/docs/guides/single-select/) for more information.
45
+ # @option options[:state] [String] - OAuth state parameter passed to the
46
+ # redirect uri. This parameter may be used for identifying the user who
47
+ # initiated the request.
48
+ # @option options[:make_bypass] [String] - `make_bypass' is an optional parameter that allows
49
+ # users to bypass the car brand selection screen.
50
+ # For a complete list of supported makes, please see our
51
+ # [API Reference](https://smartcar.com/docs/api#authorization) documentation.
52
+ # @option options[:flags] [Hash] - A hash of flag name string as key and a string or boolean value.
53
+ #
54
+ # @return [String] Authorization URL string
55
+ def get_auth_url(scope, options = {})
56
+ initialize_auth_parameters(scope, options)
57
+ add_single_select_options(options[:single_select])
58
+ client.auth_code.authorize_url(@auth_parameters)
59
+ end
60
+
61
+ # Generates the tokens hash using the code returned in oauth process.
62
+ # @param code [String] This is the code that is returned after user
63
+ # visits and authorizes on the authorization URL.
64
+ # @param [Hash] options
65
+ # @option options[:flags] [Hash] - A hash of flag name string as key and a string or boolean value.
66
+ #
67
+ # @return [Hash] Hash of token, refresh token, expiry info and token type
68
+ def exchange_code(code, options = {})
69
+ set_token_url(options[:flags])
70
+
71
+ token_hash = client.auth_code
72
+ .get_token(code, redirect_uri: redirect_uri)
73
+ .to_hash
74
+
75
+ JSON.parse(token_hash.to_json, object_class: OpenStruct)
76
+ rescue OAuth2::Error => e
77
+ raise build_error(e.response.status, e.response.body, e.response.headers)
78
+ end
79
+
80
+ # Refreshing the access token
81
+ # @param token [String] refresh_token received during token exchange
82
+ # @param [Hash] options
83
+ # @option options[:flags] [Hash] - A hash of flag name string as key and a string or boolean value.
84
+ #
85
+ # @return [Hash] Hash of token, refresh token, expiry info and token type
86
+ def exchange_refresh_token(token, options = {})
87
+ set_token_url(options[:flags])
88
+
89
+ token_object = OAuth2::AccessToken.from_hash(client, { refresh_token: token })
90
+ token_object = token_object.refresh!
91
+
92
+ JSON.parse(token_object.to_hash.to_json, object_class: OpenStruct)
93
+ rescue OAuth2::Error => e
94
+ raise build_error(e.response.status, e.response.body, e.response.headers)
95
+ end
96
+
97
+ # Checks if token is expired using Oauth2 classes
98
+ # @param expires_at [Number] expires_at as time since epoch
99
+ #
100
+ # @return [Boolean]
101
+ def expired?(expires_at)
102
+ OAuth2::AccessToken.from_hash(client, { expires_at: expires_at }).expired?
103
+ end
104
+
105
+ private
106
+
107
+ def build_flags(flags)
108
+ return unless flags
109
+
110
+ flags.map { |key, value| "#{key}:#{value}" }.join(' ')
111
+ end
112
+
113
+ def set_token_url(flags)
114
+ params = {}
115
+ params[:flags] = build_flags(flags) if flags
116
+ # Note - The inbuild interface to get the token does not allow any way to pass additional
117
+ # URL params. Hence building the token URL with the flags and setting it in client.
118
+ client.options[:token_url] = client.connection.build_url('/oauth/token', params).request_uri
119
+ end
120
+
121
+ def initialize_auth_parameters(scope, options)
122
+ @auth_parameters = {
123
+ response_type: CODE,
124
+ redirect_uri: redirect_uri,
125
+ mode: mode,
126
+ scope: scope.join(' ')
127
+ }
128
+ @auth_parameters[:approval_prompt] = options[:force_prompt] ? FORCE : AUTO unless options[:force_prompt].nil?
129
+ @auth_parameters[:state] = options[:state] if options[:state]
130
+ @auth_parameters[:make] = options[:make_bypass] if options[:make_bypass]
131
+ @auth_parameters[:flags] = build_flags(options[:flags]) if options[:flags]
132
+ end
133
+
134
+ def add_single_select_options(single_select)
135
+ return unless single_select
136
+
137
+ if single_select[:vin]
138
+ @auth_parameters[:single_select_vin] = single_select[:vin]
139
+ @auth_parameters[:single_select] = true
140
+ elsif !single_select[:enabled].nil?
141
+ @auth_parameters[:single_select] = single_select[:enabled]
142
+ end
143
+ end
144
+
145
+ # gets the Oauth Client object
146
+ #
147
+ # @return [OAuth2::Client] A Oauth Client object.
148
+ def client
149
+ @client ||= OAuth2::Client.new(client_id,
150
+ client_secret,
151
+ site: origin)
152
+ end
153
+ end
154
+ end
data/lib/smartcar/base.rb CHANGED
@@ -10,14 +10,12 @@ module Smartcar
10
10
 
11
11
  # Error raised when an invalid parameter is passed.
12
12
  class InvalidParameterValue < StandardError; end
13
- # Constant for Bearer auth type
14
- BEARER = 'BEARER'
15
13
  # Constant for Basic auth type
16
- BASIC = 'BASIC'
14
+ BASIC = 'Basic'
17
15
  # Number of seconds to wait for response
18
16
  REQUEST_TIMEOUT = 310
19
17
 
20
- attr_accessor :token, :error, :meta, :unit_system, :version
18
+ attr_accessor :token, :error, :unit_system, :version, :auth_type
21
19
 
22
20
  %i[get post patch put delete].each do |verb|
23
21
  # meta programming and define all Restful methods.
@@ -27,8 +25,7 @@ module Smartcar
27
25
  # @return [Hash] The response Json parsed as a hash.
28
26
  define_method verb do |path, data = nil|
29
27
  response = service.send(verb) do |request|
30
- request.headers['Authorization'] = "BEARER #{token}"
31
- request.headers['Authorization'] = "BASIC #{generate_basic_auth}" if data && data[:auth] == BASIC
28
+ request.headers['Authorization'] = auth_type == BASIC ? "Basic #{token}" : "Bearer #{token}"
32
29
  request.headers['sc-unit-system'] = unit_system if unit_system
33
30
  request.headers['Content-Type'] = 'application/json'
34
31
  complete_path = "/v#{version}#{path}"
@@ -39,38 +36,31 @@ module Smartcar
39
36
  request.body = data.to_json if data
40
37
  end
41
38
  end
42
- error = get_error(response)
43
- raise error if error
44
-
45
- [JSON.parse(response.body), response.headers]
39
+ handle_error(response)
40
+ # required to handle unsubscribe response
41
+ body = response.body.empty? ? '{}' : response.body
42
+ [JSON.parse(body), response.headers]
46
43
  end
47
44
  end
48
45
 
49
46
  # This requires a proc 'PATH' to be defined in the class
50
47
  # @param path [String] resource path
51
- # @param options [Hash] query params
48
+ # @param query_params [Hash] query params
52
49
  # @param auth [String] type of auth
53
50
  #
54
51
  # @return [Object]
55
- def fetch(path:, options: {}, auth: 'BEARER')
56
- path += "?#{URI.encode_www_form(options)}" unless options.empty?
57
- get(path, { auth: auth })
52
+ def fetch(path:, query_params: {})
53
+ path += "?#{URI.encode_www_form(query_params)}" unless query_params.empty?
54
+ get(path)
58
55
  end
59
56
 
60
57
  private
61
58
 
62
- # returns auth token for BASIC auth
63
- #
64
- # @return [String] Base64 encoding of CLIENT:SECRET
65
- def generate_basic_auth
66
- Base64.strict_encode64("#{get_config('CLIENT_ID')}:#{get_config('CLIENT_SECRET')}")
67
- end
68
-
69
59
  # gets a smartcar API service/client
70
60
  #
71
61
  # @return [OAuth2::AccessToken] An initialized AccessToken instance that acts as service client
72
62
  def service
73
- @service ||= Faraday.new(url: SITE, request: { timeout: REQUEST_TIMEOUT })
63
+ @service ||= Faraday.new(url: ENV['SMARTCAR_API_ORIGIN'] || API_ORIGIN, request: { timeout: REQUEST_TIMEOUT })
74
64
  end
75
65
  end
76
66
  end
@@ -13,43 +13,104 @@ module Smartcar
13
13
  end
14
14
  end
15
15
 
16
- # Utility method to return a hash of the isntance variables
17
- #
18
- # @return [Hash] hash of all instance variables
19
- def to_hash
20
- instance_variables.each_with_object({}) do |attribute, hash|
21
- hash[attribute.to_s.delete('@').to_sym] = instance_variable_get(attribute)
22
- end
23
- end
24
-
25
16
  # gets a given env variable, checks for existence and throws exception if not present
26
17
  # @param config_name [String] key of the env variable
27
18
  #
28
19
  # @return [String] value of the env variable
29
20
  def get_config(config_name)
30
- config_name = "INTEGRATION_#{config_name}" if ENV['MODE'] == 'test'
21
+ # ENV.MODE is set to test by e2e tests.
22
+ config_name = "E2E_#{config_name}" if ENV['MODE'] == 'test'
31
23
  raise Smartcar::ConfigNotFound, "Environment variable #{config_name} not found !" unless ENV[config_name]
32
24
 
33
25
  ENV[config_name]
34
26
  end
35
27
 
36
- # Given the response from smartcar API, returns an error object if needed
28
+ def build_meta(headers)
29
+ meta_hash = {
30
+ 'sc-data-age' => :data_age,
31
+ 'sc-unit-system' => :unit_system,
32
+ 'sc-request-id' => :request_id
33
+ }.each_with_object({}) do |(header_name, key), meta|
34
+ meta[key] = headers[header_name] if headers[header_name]
35
+ end
36
+ meta = JSON.parse(meta_hash.to_json, object_class: OpenStruct)
37
+ meta.data_age &&= DateTime.parse(meta.data_age)
38
+
39
+ meta
40
+ end
41
+
42
+ def build_response(body, headers)
43
+ response = JSON.parse(body.to_json, object_class: OpenStruct)
44
+ response.meta = build_meta(headers)
45
+ response
46
+ end
47
+
48
+ def build_aliases(response, aliases)
49
+ (aliases || []).each do |original_name, alias_name|
50
+ response.send("#{alias_name}=".to_sym, response.send(original_name.to_sym))
51
+ end
52
+
53
+ response
54
+ end
55
+
56
+ def build_error(status, body_string, headers)
57
+ content_type = headers['content-type'] || ''
58
+ return SmartcarError.new(status, body_string, headers) unless content_type.include?('application/json')
59
+
60
+ begin
61
+ parsed_body = JSON.parse(body_string, { symbolize_names: true })
62
+ rescue StandardError => e
63
+ return SmartcarError.new(
64
+ status,
65
+ {
66
+ message: e.message,
67
+ type: 'SDK_ERROR'
68
+ },
69
+ headers
70
+ )
71
+ end
72
+
73
+ return SmartcarError.new(status, parsed_body, headers) if parsed_body[:error] || parsed_body[:type]
74
+
75
+ SmartcarError.new(status, parsed_body.merge({ type: 'SDK_ERROR' }), headers)
76
+ end
77
+
78
+ # Given the response from smartcar API, throws an error if needed
37
79
  # @param response [Object] response Object with status and body
38
- #
39
- # @return [Object] nil OR Error object
40
- def get_error(response)
80
+ def handle_error(response)
41
81
  status = response.status
42
82
  return nil if [200, 204].include?(status)
43
- return Smartcar::ServiceUnavailableError.new("Service Unavailable - #{response.body}") if status == 404
44
- return Smartcar::BadRequestError.new("Bad Request - #{response.body}") if status == 400
45
- return Smartcar::AuthenticationError.new('Authentication error') if status == 401
46
83
 
47
- Smartcar::ExternalServiceError.new("API error - #{response.body}")
84
+ raise build_error(response.status, response.body, response.headers)
85
+ end
86
+
87
+ def process_batch_response(response_body, response_headers)
88
+ response_object = OpenStruct.new
89
+ response_body['responses'].each do |item|
90
+ attribute_name = convert_path_to_attribute(item['path'])
91
+ aliases = Vehicle::METHODS[attribute_name.to_sym][:aliases]
92
+ # merging the top level request headers and separate headers for each item of batch
93
+ headers = response_headers.merge(item['headers'])
94
+ response = if [200, 204].include?(item['code'])
95
+ build_aliases(build_response(item['body'], headers), aliases)
96
+ else
97
+ build_error(item['code'], item['body'].to_json, headers)
98
+ end
99
+ response_object.define_singleton_method attribute_name do
100
+ raise response if response.is_a?(SmartcarError)
101
+
102
+ response
103
+ end
104
+ end
105
+ response_object
48
106
  end
49
107
 
50
- def handle_errors(response)
51
- error = get_error(response)
52
- raise error if error
108
+ # takes a path and converts it to the keys we use.
109
+ # EX - '/charge' -> :charge, '/battery/capacity' -> :battery_capacity
110
+ def convert_path_to_attribute(path)
111
+ return :attributes if path == '/'
112
+
113
+ path.split('/').reject(&:empty?).join('_').to_sym
53
114
  end
54
115
  end
55
116
  end
@@ -8,74 +8,231 @@ module Smartcar
8
8
  #
9
9
  # @attr [String] token Access token used to connect to Smartcar API.
10
10
  # @attr [String] id Smartcar vehicle ID.
11
- # @attr [String] unit_system unit system to represent the data in.
11
+ # @attr [Hash] options
12
+ # @attr unit_system [String] Unit system to represent the data in, defaults to Imperial
13
+ # @attr version [String] API version to be used.
12
14
  class Vehicle < Base
13
- include VehicleUtils::Data
14
- include VehicleUtils::Actions
15
- include VehicleUtils::Batch
16
- # Path for hitting compatibility end point
17
- COMPATIBLITY_PATH = '/compatibility'
18
-
19
- # Path for hitting vehicle ids end point
20
- PATH = proc { |id| "/vehicles/#{id}" }
21
-
22
15
  attr_reader :id
23
16
 
24
- def initialize(token:, id:, unit_system: IMPERIAL, version: Smartcar.get_api_version)
17
+ # @private
18
+ METHODS = {
19
+ permissions: { path: proc { |id| "/vehicles/#{id}/permissions" }, skip: true },
20
+ attributes: { path: proc { |id| "/vehicles/#{id}" } },
21
+ battery: {
22
+ path: proc { |id| "/vehicles/#{id}/battery" },
23
+ aliases: { 'percentRemaining' => 'percentage_remaining' }
24
+ },
25
+ battery_capacity: { path: proc { |id| "/vehicles/#{id}/battery/capacity" } },
26
+ charge: {
27
+ path: proc { |id| "/vehicles/#{id}/charge" },
28
+ aliases: { 'isPluggedIn' => 'is_plugged_in?' }
29
+ },
30
+ engine_oil: {
31
+ path: proc { |id| "/vehicles/#{id}/engine/oil" },
32
+ aliases: { 'lifeRemaining' => 'life_remaining' }
33
+ },
34
+ fuel: {
35
+ path: proc { |id| "/vehicles/#{id}/fuel" },
36
+ aliases: {
37
+ 'amountRemaining' => 'amount_remaining',
38
+ 'percentRemaining' => 'percent_remaining'
39
+ }
40
+ },
41
+ location: { path: proc { |id| "/vehicles/#{id}/location" } },
42
+ odometer: { path: proc { |id| "/vehicles/#{id}/odometer" } },
43
+ tire_pressure: {
44
+ path: proc { |id| "/vehicles/#{id}/tires/pressure" },
45
+ aliases: {
46
+ 'backLeft' => 'back_left',
47
+ 'backRight' => 'back_right',
48
+ 'frontLeft' => 'front_left',
49
+ 'frontRight' => 'front_right'
50
+ }
51
+ },
52
+ vin: { path: proc { |id| "/vehicles/#{id}/vin" } },
53
+ disconnect!: { type: :delete, path: proc { |id| "/vehicles/#{id}/application" } },
54
+ lock!: { type: :post, path: proc { |id| "/vehicles/#{id}/security" }, body: { action: 'LOCK' } },
55
+ unlock!: { type: :post, path: proc { |id| "/vehicles/#{id}/security" }, body: { action: 'UNLOCK' } },
56
+ start_charge!: { type: :post, path: proc { |id| "/vehicles/#{id}/charge" }, body: { action: 'START' } },
57
+ stop_charge!: { type: :post, path: proc { |id| "/vehicles/#{id}/charge" }, body: { action: 'STOP' } },
58
+ subscribe!: {
59
+ type: :post,
60
+ path: proc { |id, webhook_id| "/vehicles/#{id}/webhooks/#{webhook_id}" },
61
+ aliases: {
62
+ 'webhookId' => 'webhook_id',
63
+ 'vehicleId' => 'vehicle_id'
64
+ },
65
+ skip: true
66
+ },
67
+ unsubscribe!: { type: :post, path: proc { |id, webhook_id|
68
+ "/vehicles/#{id}/webhooks/#{webhook_id}"
69
+ }, skip: true }
70
+ }.freeze
71
+
72
+ def initialize(token:, id:, options: { unit_system: METRIC, version: Smartcar.get_api_version })
25
73
  super
26
- raise InvalidParameterValue.new, "Invalid Units provided : #{unit_system}" unless UNITS.include?(unit_system)
74
+ @token = token
75
+ @id = id
76
+ @unit_system = options[:unit_system]
77
+ @version = options[:version]
78
+
79
+ raise InvalidParameterValue.new, "Invalid Units provided : #{@unit_system}" unless UNITS.include?(@unit_system)
27
80
  raise InvalidParameterValue.new, 'Vehicle ID (id) is a required field' if id.nil?
28
81
  raise InvalidParameterValue.new, 'Access Token(token) is a required field' if token.nil?
82
+ end
29
83
 
30
- @token = token
31
- @id = id
32
- @unit_system = unit_system
33
- @version = version
84
+ # @!method attributes()
85
+ # Returns make model year and id of the vehicle
86
+ #
87
+ # API Documentation - https://smartcar.com/api#get-vehicle-attributes
88
+ #
89
+ # @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/api#get-vehicle-attributes
90
+ # and a meta attribute with the relevant items from response headers.
91
+
92
+ # @!method battery()
93
+ # Returns the state of charge (SOC) and remaining range of an electric or plug-in hybrid vehicle's battery.
94
+ #
95
+ # API Documentation https://smartcar.com/docs/api#get-ev-battery
96
+ #
97
+ # @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-ev-battery
98
+ # and a meta attribute with the relevant items from response headers.
99
+
100
+ # @!method battery_capacity()
101
+ # Returns the capacity of an electric or plug-in hybrid vehicle's battery.
102
+ #
103
+ # API Documentation https://smartcar.com/docs/api#get-ev-battery-capacity
104
+ #
105
+ # @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-ev-battery-capacity
106
+ # and a meta attribute with the relevant items from response headers.
107
+
108
+ # @!method charge()
109
+ # Returns the current charge status of the vehicle.
110
+ #
111
+ # API Documentation https://smartcar.com/docs/api#get-ev-battery
112
+ #
113
+ # @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-ev-battery
114
+ # and a meta attribute with the relevant items from response headers.
115
+
116
+ # @!method engine_oil()
117
+ # Returns the remaining life span of a vehicle's engine oil
118
+ #
119
+ # API Documentation https://smartcar.com/docs/api#get-engine-oil-life
120
+ #
121
+ # @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-engine-oil-life
122
+ # and a meta attribute with the relevant items from response headers.
123
+
124
+ # @!method fuel()
125
+ # Returns the status of the fuel remaining in the vehicle's gas tank.
126
+ #
127
+ # API Documentation https://smartcar.com/docs/api#get-fuel-tank
128
+ #
129
+ # @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-fuel-tank
130
+ # and a meta attribute with the relevant items from response headers.
131
+
132
+ # @!method location()
133
+ # Returns the last known location of the vehicle in geographic coordinates.
134
+ #
135
+ # API Documentation https://smartcar.com/docs/api#get-location
136
+ #
137
+ # @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-location
138
+ # and a meta attribute with the relevant items from response headers.
139
+
140
+ # @!method odometer()
141
+ # Returns the vehicle's last known odometer reading.
142
+ #
143
+ # API Documentation https://smartcar.com/docs/api#get-odometer
144
+ #
145
+ # @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-odometer
146
+ # and a meta attribute with the relevant items from response headers.
147
+
148
+ # @!method tire_pressure()
149
+ # Returns the air pressure of each of the vehicle's tires.
150
+ #
151
+ # API Documentation https://smartcar.com/docs/api#get-tire-pressure
152
+ #
153
+ # @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-tire-pressure
154
+ # and a meta attribute with the relevant items from response headers.
155
+
156
+ # @!method vin()
157
+ # Returns the vehicle's manufacturer identifier (VIN).
158
+ #
159
+ # API Documentation https://smartcar.com/docs/api#get-vin
160
+ #
161
+ # @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-vin
162
+ # and a meta attribute with the relevant items from response headers.
163
+
164
+ # NOTES :
165
+ # - We only generate the methods where there is no query string or additional options considering thats
166
+ # the majority, for all the ones that require parameters, write them separately.
167
+ # Ex. permissions, subscribe, unsubscribe
168
+ # - The following snippet generates methods dynamically , but if we are adding a new item,
169
+ # make sure we also add the doc for it.
170
+ METHODS.each do |method, item|
171
+ # We add these to the METHODS object to keep it in one place, but mark them to be skipped
172
+ # for dynamic generation
173
+ next if item[:skip]
174
+
175
+ define_method method do
176
+ body, headers = case item[:type]
177
+ when :post
178
+ post(item[:path].call(id), item[:body])
179
+ when :delete
180
+ delete(item[:path].call(id))
181
+ else
182
+ fetch(path: item[:path].call(id))
183
+ end
184
+ build_aliases(build_response(body, headers), item[:aliases])
185
+ end
34
186
  end
35
187
 
36
- # Class method Used to get all the vehicles in the app. This only returns
37
- # API - https://smartcar.com/docs/api#get-all-vehicles
38
- # @param token [String] - Access token
39
- # @param options [Hash] - Optional filter parameters (check documentation)
40
- #
41
- # @return [Array] of vehicle IDs(Strings)
42
- def self.all_vehicle_ids(token:, options: {}, version: Smartcar.get_api_version)
43
- response, = new(token: token, id: 'none', version: version).fetch(
44
- path: PATH.call(''),
45
- options: options
46
- )
47
- response['vehicles']
188
+ # Method to fetch the list of permissions that this application has been granted for this vehicle.
189
+ # API - https://smartcar.com/docs/api#get-application-permissions
190
+ #
191
+ # @param paging [Hash] Optional filter parameters (check documentation)
192
+ #
193
+ # @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-application-permissions
194
+ # and a meta attribute with the relevant items from response headers.
195
+ def permissions(paging = {})
196
+ response, headers = fetch(path: METHODS.dig(:permissions, :path).call(id), query_params: paging)
197
+ build_response(response, headers)
48
198
  end
49
199
 
50
- # Class method Used to check compatiblity for VIN and scope
51
- # API - https://smartcar.com/docs/api#connect-compatibility
52
- # @param vin [String] VIN of the vehicle to be checked
53
- # @param scope [Array of Strings] - array of scopes
54
- # @param country [String] An optional country code according to
55
- # [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2).
56
- # Defaults to US.
57
- #
58
- # @return [Boolean] true or false
59
- def self.compatible?(vin:, scope:, country: 'US', version: Smartcar.get_api_version)
60
- raise InvalidParameterValue.new, 'vin is a required field' if vin.nil?
61
- raise InvalidParameterValue.new, 'scope is a required field' if scope.nil?
62
-
63
- response, = new(token: 'none', id: 'none', version: version).fetch(
64
- path: COMPATIBLITY_PATH,
65
- options: {
66
- vin: vin,
67
- scope: scope.join(' '),
68
- country: country
69
- },
70
- auth: BASIC
71
- )
72
- response['compatible']
200
+ # Subscribe the vehicle to given webhook Id.
201
+ #
202
+ # @param webhook_id [String] Webhook id to subscribe to
203
+ #
204
+ # @return [OpenStruct] And object representing the JSON response and a meta attribute
205
+ # with the relevant items from response headers.
206
+ def subscribe!(webhook_id)
207
+ response, headers = post(METHODS.dig(:subscribe!, :path).call(id, webhook_id), {})
208
+ build_aliases(build_response(response, headers), METHODS.dig(:subscribe!, :aliases))
73
209
  end
74
210
 
75
- private
211
+ # Unubscribe the vehicle from given webhook Id.
212
+ #
213
+ # @param amt [String] Application management token
214
+ # @param webhook_id [String] Webhook id to subscribe to
215
+ #
216
+ # @return [OpenStruct] Meta attribute with the relevant items from response headers.
217
+ def unsubscribe!(amt, webhook_id)
218
+ # swapping off the token with amt for unsubscribe.
219
+ access_token = token
220
+ self.token = amt
221
+ response, headers = delete(METHODS.dig(:unsubscribe!, :path).call(id, webhook_id))
222
+ self.token = access_token
223
+ build_response(response, headers)
224
+ end
76
225
 
77
- def get_object(klass, data)
78
- klass.new(data)
226
+ # Method to get batch requests.
227
+ # API - https://smartcar.com/docs/api#post-batch-request
228
+ # @param paths [Array] Array of paths as strings. Ex ['/battery', '/odometer']
229
+ #
230
+ # @return [OpenStruct] Object with one attribute per requested path that returns
231
+ # an OpenStruct object of the requested attribute or taises if it is an error.
232
+ def batch(paths)
233
+ request_body = { requests: paths.map { |path| { path: path } } }
234
+ response, headers = post("/vehicles/#{id}/batch", request_body)
235
+ process_batch_response(response, headers)
79
236
  end
80
237
  end
81
238
  end