smartcar 2.5.0 → 3.0.0

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