Skip to content

Commit

Permalink
Allow users to provide customized Faraday::Connection objects (#51)
Browse files Browse the repository at this point in the history
* Allow users to provide customized Faraday::Connection objects

* chore: add assertions to test

* chore: version bump

* fix: version bump

Co-authored-by: Kyle Rippey <[email protected]>
  • Loading branch information
s-ashwinkumar and kylerippey authored Apr 27, 2022
1 parent a376950 commit de4acb5
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 44 deletions.
32 changes: 6 additions & 26 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
smartcar (3.1.1)
smartcar (3.2.0)
oauth2 (~> 1.4)
recursive-open-struct (~> 1.1.3)

Expand All @@ -20,36 +20,16 @@ GEM
rexml
diff-lcs (1.5.0)
docile (1.4.0)
faraday (1.9.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
faraday (2.2.0)
faraday-net_http (~> 2.0)
ruby2_keywords (>= 0.0.4)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.3)
multipart-post (>= 1.2, < 3)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday-net_http (2.0.2)
hashdiff (1.0.1)
jwt (2.3.0)
multi_json (1.15.0)
multi_xml (0.6.0)
multipart-post (2.1.1)
oauth2 (1.4.7)
faraday (>= 0.8, < 2.0)
oauth2 (1.4.9)
faraday (>= 0.17.3, < 3.0)
jwt (>= 1.0, < 3.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,32 @@ Example Usage for oAuth -
# get a new token or use .
```

## Advanced configuration

This SDK uses the [Faraday HTTP client library](https://lostisland.github.io/faraday/) which supports extensive customization through the use of middleware. If you need to customize the behavior of HTTP request/response processing, you can provide your own instance of Faraday::Connection to most methods in this library.

**Important:** If you provide your own Faraday connection, you are responsible for configuring all HTTP connection behavior, including timeouts! This SDK uses some custom timeouts internally to ensure best behavior by default, so unless you want to customize them you may want to replicate those timeouts.

Example of providing a custom Faraday connection to various methods:
```ruby
# Example Faraday connection that uses the Instrumentation middleware
service = Faraday::Connection.new(url: Smartcar::API_ORIGIN, request: { timeout: Smartcar::DEFAULT_REQUEST_TIMEOUT }) do |c|
c.request :instrumentation
end

# Passing the custom service to #get_vehicles
Smartcar.get_vehicles(token: token, options: { service: service })

# Passing the custom service to #get_user
Smartcar.get_user(token: token, options: { service: service })

# Passing the custom service to #get_compatibility
Smartcar.get_compatibility(vin: vin, scope: scope, options: { service: service })

# Passing the custom service into a Smartcar::Vehicle object
vehicle = Smartcar::Vehicle.new(token: token, id: id, options: { service: service })
```

## Development

To install this gem onto your local machine, run `bundle exec rake install`.
Expand Down
29 changes: 21 additions & 8 deletions lib/smartcar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class ConfigNotFound < StandardError; end
# Constant for units
UNITS = [IMPERIAL, METRIC].freeze

# Number of seconds to wait for responses
DEFAULT_REQUEST_TIMEOUT = 310

# Smartcar API version variable - defaulted to 2.0
@api_version = '2.0'

Expand Down Expand Up @@ -64,9 +67,10 @@ def get_api_version
# @option options [String] :client_secret Client Secret that overrides ENV
# @option options [String] :version API version to use, defaults to what is globally set
# @option options [Hash] :flags A hash of flag name string as key and a string or boolean value.
# @option options [Boolean] :test_mode Wether to use test mode or not.
# @option options [String] :test_mode_compatibility_level this is required argument while
# using test mode with a real vin. For more information refer to docs.
# @option options [Boolean] :test_mode Whether to use test mode or not.
# @option options [String] :test_mode_compatibility_level this is required argument while using
# test mode with a real vin. For more information refer to docs.
# @option options [Faraday::Connection] :service Optional connection object to be used for requests
#
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#compatibility-api
# and a meta attribute with the relevant items from response headers.
Expand All @@ -77,7 +81,8 @@ def get_compatibility(vin:, scope:, country: 'US', options: {})
base_object = Base.new(
{
version: options[:version] || Smartcar.get_api_version,
auth_type: Base::BASIC
auth_type: Base::BASIC,
service: options[:service]
}
)

Expand All @@ -93,14 +98,18 @@ def get_compatibility(vin:, scope:, country: 'US', options: {})
#
# API Documentation - https://smartcar.com/docs/api#get-user
# @param token [String] Access token
# @param version [String] Optional API version to use, defaults to what is globally set
# @param options [Hash] Other optional parameters including overrides
# @option options [Faraday::Connection] :service Optional connection object to be used for requests
#
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-user
# and a meta attribute with the relevant items from response headers.
def get_user(token:, version: Smartcar.get_api_version)
def get_user(token:, version: Smartcar.get_api_version, options: {})
base_object = Base.new(
{
token: token,
version: version
version: version,
service: options[:service]
}
)
base_object.build_response(*base_object.fetch(path: PATHS[:user]))
Expand All @@ -111,14 +120,18 @@ def get_user(token:, version: Smartcar.get_api_version)
# API Documentation - https://smartcar.com/docs/api#get-all-vehicles
# @param token [String] - Access token
# @param paging [Hash] - Optional filter parameters (check documentation)
# @param version [String] Optional API version to use, defaults to what is globally set
# @param options [Hash] Other optional parameters including overrides
# @option options [Faraday::Connection] :service Optional connection object to be used for requests
#
# @return [OpenStruct] And object representing the JSON response mentioned in https://smartcar.com/docs/api#get-all-vehicles
# and a meta attribute with the relevant items from response headers.
def get_vehicles(token:, paging: {}, version: Smartcar.get_api_version)
def get_vehicles(token:, paging: {}, version: Smartcar.get_api_version, options: {})
base_object = Base.new(
{
token: token,
version: version
version: version,
service: options[:service]
}
)
base_object.build_response(*base_object.fetch(
Expand Down
7 changes: 4 additions & 3 deletions lib/smartcar/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ class Base
class InvalidParameterValue < StandardError; end
# Constant for Basic auth type
BASIC = 'Basic'
# Number of seconds to wait for response
REQUEST_TIMEOUT = 310

attr_accessor :token, :error, :unit_system, :version, :auth_type

Expand Down Expand Up @@ -65,7 +63,10 @@ def fetch(path:, query_params: {})
#
# @return [OAuth2::AccessToken] An initialized AccessToken instance that acts as service client
def service
@service ||= Faraday.new(url: ENV['SMARTCAR_API_ORIGIN'] || API_ORIGIN, request: { timeout: REQUEST_TIMEOUT })
@service ||= Faraday.new(
url: ENV['SMARTCAR_API_ORIGIN'] || API_ORIGIN,
request: { timeout: DEFAULT_REQUEST_TIMEOUT }
)
end
end
end
8 changes: 5 additions & 3 deletions lib/smartcar/vehicle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module Smartcar
# @attr [Hash] options
# @attr unit_system [String] Unit system to represent the data in, defaults to Imperial
# @attr version [String] API version to be used.
# @attr service [Faraday::Connection] An optional connection object to be used for requests.
class Vehicle < Base
attr_reader :id

Expand Down Expand Up @@ -69,12 +70,13 @@ class Vehicle < Base
}, skip: true }
}.freeze

def initialize(token:, id:, options: { unit_system: METRIC, version: Smartcar.get_api_version })
def initialize(token:, id:, options: {})
super
@token = token
@id = id
@unit_system = options[:unit_system]
@version = options[:version]
@unit_system = options[:unit_system] || METRIC
@version = options[:version] || Smartcar.get_api_version
@service = options[:service]

raise InvalidParameterValue.new, "Invalid Units provided : #{@unit_system}" unless UNITS.include?(@unit_system)
raise InvalidParameterValue.new, 'Vehicle ID (id) is a required field' if id.nil?
Expand Down
2 changes: 1 addition & 1 deletion lib/smartcar/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

module Smartcar
# Gem current version number
VERSION = '3.1.1'
VERSION = '3.2.0'
end
103 changes: 100 additions & 3 deletions spec/smartcar/integration/smartcar_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,15 @@
}
)

subject.get_compatibility(
response = subject.get_compatibility(
vin: 'vin',
scope: scopes,
country: 'US',
options: {
test_mode: true
}
)
expect(response.compatible).to be true
end
end

Expand All @@ -92,14 +93,15 @@
}
)

subject.get_compatibility(
response = subject.get_compatibility(
vin: 'vin',
scope: scopes,
country: 'US',
options: {
test_mode_compatibility_level: 'pizza'
}
)
expect(response.compatible).to be true
end
end

Expand All @@ -121,14 +123,76 @@
}
)

subject.get_compatibility(
response = subject.get_compatibility(
vin: 'vin',
scope: scopes,
country: 'US',
options: {
flags: { flagA: 'a', flagB: 'b' }
}
)
expect(response.compatible).to be true
end
end

context 'when a service object is provided' do
let(:mock_service) { Faraday.new(url: 'https://custom-api.smartcar.com') }

it 'should use the provided service object' do
scopes = %w[read_odometer read_location]
stub_request(:get, 'https://custom-api.smartcar.com/v2.0/compatibility?country=US&scope=read_odometer%20read_location&vin=vin')
.with(
basic_auth: [ENV['E2E_SMARTCAR_CLIENT_ID'], ENV['E2E_SMARTCAR_CLIENT_SECRET']]
)
.to_return(
{
status: 200,
headers: { 'content-type' => 'application/json; charset=utf-8' },
body:
{
compatible: true
}.to_json
}
)

response = subject.get_compatibility(
vin: 'vin',
scope: scopes,
country: 'US',
options: {
service: mock_service
}
)
expect(response.compatible).to be true
end
end
end

describe '.get_user' do
context 'when a service object is provided' do
let(:mock_service) { Faraday.new(url: 'https://custom-api.smartcar.com') }

it 'should use the provided service object' do
stub_request(:get, 'https://custom-api.smartcar.com/v2.0/user')
.with(headers: { 'Authorization' => 'Bearer token' })
.to_return(
{
status: 200,
headers: { 'content-type' => 'application/json; charset=utf-8' },
body:
{
user: { id: 'abc12345-6789-1234-abcd-123abc123abc' }
}.to_json
}
)

response = subject.get_user(
token: 'token',
options: {
service: mock_service
}
)
expect(response.user.id).to eq('abc12345-6789-1234-abcd-123abc123abc')
end
end
end
Expand Down Expand Up @@ -156,5 +220,38 @@
expect(response.paging.offset).to be(0)
expect(response.paging.count).to be(1)
end

context 'when a service object is provided' do
let(:mock_service) { Faraday.new(url: 'https://custom-api.smartcar.com') }

it 'should use the provided service object' do
stub_request(:get, 'https://custom-api.smartcar.com/v2.0/vehicles?limit=1')
.with(headers: { 'Authorization' => 'Bearer token' })
.to_return(
{
status: 200,
headers: { 'content-type' => 'application/json; charset=utf-8' },
body:
{
vehicles: ['vehicle1'],
paging: { count: 1, offset: 0 }
}.to_json
}
)

response = subject.get_vehicles(
token: 'token',
paging: { limit: 1 },
options: {
service: mock_service
}
)
expect(response.vehicles.is_a?(Array)).to be_truthy
expect(response.vehicles[0] == 'vehicle1').to be_truthy
expect(response.paging.is_a?(OpenStruct)).to be_truthy
expect(response.paging.offset).to be(0)
expect(response.paging.count).to be(1)
end
end
end
end
26 changes: 26 additions & 0 deletions spec/smartcar/integration/vehicle_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,32 @@
expect(result.pizza).to eq('pasta')
end
end

context 'with a custom service' do
let(:mock_service) { Faraday.new(url: 'https://custom-api.smartcar.com') }

it 'uses the provided service object' do
subject = Smartcar::Vehicle.new(
token: 'token',
id: 'vehicle_id',
options: {
service: mock_service
}
)

stub_request(:get, 'https://custom-api.smartcar.com/v2.0/vehicles/vehicle_id/odometer')
.with(headers: { 'Authorization' => 'Bearer token', 'sc-unit-system' => 'metric' })
.to_return(
{
status: 200,
body: { pizza: 'pasta' }.to_json
}
)

result = subject.odometer
expect(result.pizza).to eq('pasta')
end
end
end

describe '#batch' do
Expand Down

0 comments on commit de4acb5

Please sign in to comment.