Skip to content

Commit

Permalink
feat: add setApiVersion method and SmartcarErrorV2 class (#122)
Browse files Browse the repository at this point in the history
This release adds support for v2.0 of Smartcar's API by introducing the `smartcar.setApiVersion` method.

We have also introduced a `SmartcarErrorV2` class whose fields match the error fields returned by v2.0 of the API as documented on the [API Reference](https://smartcar.com/docs/api/?version=v2.0#errors). This class extends the `SmartcarError` class to ease the migration process.

For a detailed breakdown of the changes and how to migrate see our [API Changelog for v2.0](https://smartcar.com/docs/changelog/v2.0/) and our [v2.0 Error Guides](https://smartcar.com/docs/errors/v2.0/billing).

Co-authored-by: Gurpreet Atwal <[email protected]>
  • Loading branch information
rsimari and gurpreetatwal authored Apr 24, 2021
1 parent 3db7edf commit 25790fc
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 6 deletions.
105 changes: 105 additions & 0 deletions doc/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Smartcar Node SDK documentation.
* [.errors](#module_smartcar.errors)
* [.Vehicle](#module_smartcar.Vehicle)
* [.AuthClient](#module_smartcar.AuthClient)
* [.setApiVersion(version)](#module_smartcar.setApiVersion)
* [.isExpired(expiration)](#module_smartcar.isExpired) ⇒ <code>Boolean</code>
* [.getVehicleIds(token, [paging])](#module_smartcar.getVehicleIds)[<code>Promise.&lt;VehicleIds&gt;</code>](#module_smartcar..VehicleIds)
* [.getUserId(token)](#module_smartcar.getUserId) ⇒ <code>Promise.&lt;String&gt;</code>
Expand All @@ -89,6 +90,17 @@ Smartcar Node SDK documentation.
### smartcar.AuthClient
**Kind**: static property of [<code>smartcar</code>](#module_smartcar)
**See**: [AuthClient](#AuthClient)
<a name="module_smartcar.setApiVersion"></a>

### smartcar.setApiVersion(version)
Sets the version of Smartcar API you are using

**Kind**: static method of [<code>smartcar</code>](#module_smartcar)

| Param | Type |
| --- | --- |
| version | <code>String</code> |

<a name="module_smartcar.isExpired"></a>

### smartcar.isExpired(expiration) ⇒ <code>Boolean</code>
Expand Down Expand Up @@ -171,6 +183,16 @@ Return the user's id.
## errors

* [errors](#module_errors)
* [.SmartcarErrorV2](#module_errors.SmartcarErrorV2)
* [new errors.SmartcarErrorV2(error)](#new_module_errors.SmartcarErrorV2_new)
* [.type](#module_errors.SmartcarErrorV2+type) : <code>string</code>
* [.code](#module_errors.SmartcarErrorV2+code) : <code>string</code>
* [.description](#module_errors.SmartcarErrorV2+description) : <code>string</code>
* [.statusCode](#module_errors.SmartcarErrorV2+statusCode) : <code>number</code>
* [.requestId](#module_errors.SmartcarErrorV2+requestId) : <code>string</code>
* [.resolution](#module_errors.SmartcarErrorV2+resolution) : <code>string</code>
* [.docURL](#module_errors.SmartcarErrorV2+docURL) : <code>string</code>
* [.detail](#module_errors.SmartcarErrorV2+detail) : <code>Array.&lt;object&gt;</code>
* [.SmartcarError(message)](#module_errors.SmartcarError) ⇐ <code>Error</code>
* [.ValidationError(message)](#module_errors.ValidationError) ⇐ <code>SmartcarError</code>
* [.AuthenticationError(message)](#module_errors.AuthenticationError) ⇐ <code>SmartcarError</code>
Expand All @@ -184,6 +206,89 @@ Return the user's id.
* [.SmartcarNotCapableError(message)](#module_errors.SmartcarNotCapableError) ⇐ <code>SmartcarError</code>
* [.GatewayTimeoutError(message)](#module_errors.GatewayTimeoutError)

<a name="module_errors.SmartcarErrorV2"></a>

### errors.SmartcarErrorV2
Enhanced errors from API v2.0
Please see our [v2.0 error guides](https://smartcar.com/docs/errors/v2.0/billing) to see a list of all the possible error types and codes

**Kind**: static class of [<code>errors</code>](#module_errors)

* [.SmartcarErrorV2](#module_errors.SmartcarErrorV2)
* [new errors.SmartcarErrorV2(error)](#new_module_errors.SmartcarErrorV2_new)
* [.type](#module_errors.SmartcarErrorV2+type) : <code>string</code>
* [.code](#module_errors.SmartcarErrorV2+code) : <code>string</code>
* [.description](#module_errors.SmartcarErrorV2+description) : <code>string</code>
* [.statusCode](#module_errors.SmartcarErrorV2+statusCode) : <code>number</code>
* [.requestId](#module_errors.SmartcarErrorV2+requestId) : <code>string</code>
* [.resolution](#module_errors.SmartcarErrorV2+resolution) : <code>string</code>
* [.docURL](#module_errors.SmartcarErrorV2+docURL) : <code>string</code>
* [.detail](#module_errors.SmartcarErrorV2+detail) : <code>Array.&lt;object&gt;</code>

<a name="new_module_errors.SmartcarErrorV2_new"></a>

#### new errors.SmartcarErrorV2(error)

| Param | Type | Description |
| --- | --- | --- |
| error | <code>Object</code> \| <code>String</code> | response body from a v2.0 request |

<a name="module_errors.SmartcarErrorV2+type"></a>

#### smartcarErrorV2.type : <code>string</code>
Type of error

**Kind**: instance property of [<code>SmartcarErrorV2</code>](#module_errors.SmartcarErrorV2)
**Access**: public
<a name="module_errors.SmartcarErrorV2+code"></a>

#### smartcarErrorV2.code : <code>string</code>
Error code

**Kind**: instance property of [<code>SmartcarErrorV2</code>](#module_errors.SmartcarErrorV2)
**Access**: public
<a name="module_errors.SmartcarErrorV2+description"></a>

#### smartcarErrorV2.description : <code>string</code>
Description of meaning of the error

**Kind**: instance property of [<code>SmartcarErrorV2</code>](#module_errors.SmartcarErrorV2)
**Access**: public
<a name="module_errors.SmartcarErrorV2+statusCode"></a>

#### smartcarErrorV2.statusCode : <code>number</code>
HTTP status code

**Kind**: instance property of [<code>SmartcarErrorV2</code>](#module_errors.SmartcarErrorV2)
**Access**: public
<a name="module_errors.SmartcarErrorV2+requestId"></a>

#### smartcarErrorV2.requestId : <code>string</code>
Unique identifier for request

**Kind**: instance property of [<code>SmartcarErrorV2</code>](#module_errors.SmartcarErrorV2)
**Access**: public
<a name="module_errors.SmartcarErrorV2+resolution"></a>

#### smartcarErrorV2.resolution : <code>string</code>
Possible resolution for fixing the error

**Kind**: instance property of [<code>SmartcarErrorV2</code>](#module_errors.SmartcarErrorV2)
**Access**: public
<a name="module_errors.SmartcarErrorV2+docURL"></a>

#### smartcarErrorV2.docURL : <code>string</code>
Reference to Smartcar documentation

**Kind**: instance property of [<code>SmartcarErrorV2</code>](#module_errors.SmartcarErrorV2)
**Access**: public
<a name="module_errors.SmartcarErrorV2+detail"></a>

#### smartcarErrorV2.detail : <code>Array.&lt;object&gt;</code>
Further detail about the error

**Kind**: instance property of [<code>SmartcarErrorV2</code>](#module_errors.SmartcarErrorV2)
**Access**: public
<a name="module_errors.SmartcarError"></a>

### errors.SmartcarError(message) ⇐ <code>Error</code>
Expand Down
11 changes: 10 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ const smartcar = {
};
/* eslint-enable global-require */


/**
* Sets the version of Smartcar API you are using
* @method
* @param {String} version
*/
smartcar.setApiVersion = function(version) {
config.version = version;
};

/**
* Check if a token has expired.
*
Expand Down Expand Up @@ -85,7 +95,6 @@ smartcar.getVehicleIds = Promise.method(function(token, paging) {
if (!_.isString(token)) {
throw new TypeError('"token" argument must be a string');
}

return util.request.get(util.getUrl(), {
auth: {
bearer: token,
Expand Down
4 changes: 2 additions & 2 deletions lib/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"auth": "https://auth.smartcar.com",
"api": "https://api.smartcar.com",
"connect": "https://connect.smartcar.com",
"version": "1.0",
"timeout": 310000
"timeout": 310000,
"version": "1.0"
}
71 changes: 71 additions & 0 deletions lib/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ util.inherits(errors.SmartcarError, Error);
errors.ValidationError = function(message) {
this.name = 'validation_error';
this.statusCode = 400;
/* istanbul ignore next */
this.message = message || 'Invalid or missing request parameters';
Error.captureStackTrace(this, this.constructor);
};
Expand Down Expand Up @@ -180,9 +181,79 @@ util.inherits(errors.SmartcarNotCapableError, errors.SmartcarError);
*/
errors.GatewayTimeoutError = function(message) {
this.name = 'smartcar_gateway_timeout_error';
/* istanbul ignore next */
this.message = message || 'ELB threw a 504.';
Error.captureStackTrace(this, this.constructor);
};
util.inherits(errors.GatewayTimeoutError, errors.SmartcarError);

/**
* Enhanced errors from API v2.0
* Please see our [v2.0 error guides]{@link https://smartcar.com/docs/errors/v2.0/billing} to see a list of all the possible error types and codes
*
* @param {Object|String} error - response body from a v2.0 request
*/
errors.SmartcarErrorV2 = class extends errors.SmartcarError {
constructor(error) {
if (typeof error === 'string') {
super(error);
this.description = error;
this.name = 'SmartcarErrorV2';
return;
} else {
super(`${error.type}:${error.code} - ${error.description}`);
}
this.name = 'SmartcarErrorV2';

/**
* Type of error
* @type {string}
* @public
*/
this.type = error.type;
/**
* Error code
* @type {string}
* @public
*/
this.code = error.code;
/**
* Description of meaning of the error
* @type {string}
* @public
*/
this.description = error.description;
/**
* HTTP status code
* @type {number}
* @public
*/
this.statusCode = error.statusCode;
/**
* Unique identifier for request
* @type {string}
* @public
*/
this.requestId = error.requestId;
/**
* Possible resolution for fixing the error
* @type {string}
* @public
*/
this.resolution = error.resolution;
/**
* Reference to Smartcar documentation
* @type {string}
* @public
*/
this.docURL = error.docURL;
/**
* Further detail about the error
* @type {object[]}
* @public
*/
this.detail = error.detail;
}
};

module.exports = errors;
5 changes: 4 additions & 1 deletion lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,13 @@ util.wrap = function(promise) {
};

util.catch = function(caught) {

const options = caught.options;
const body = _.get(caught, 'response.body', {});

if (options.uri.includes('/v2.0/')) {
throw new errors.SmartcarErrorV2(body);
}

switch (caught.statusCode) {
case 400:
throw new errors.ValidationError(body.error_description || body.message);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"cover": "nyc npm run test:unit -s",
"cover:integration": "nyc npm test:integration -s",
"jsdoc": "jsdoc -c .jsdoc.json .",
"docs": "mkdir -p doc && jsdoc2md --example-lang js --template doc/.template.hbs --files .docs.js index.js lib/* | sed 's/[ \t]*$//' > doc/readme.md"
"docs": "mkdir -p doc && jsdoc2md --example-lang js --template doc/.template.hbs --files .docs.js index.js lib/* | sed -e 's/[ \t]*$//' -e 's/\\[\\ &#x27;//g' -e 's/&#x27;\\ \\]//g' > doc/readme.md"
},
"dependencies": {
"bluebird": "^3.5.5",
Expand Down
1 change: 1 addition & 0 deletions test/end-to-end/vehicle.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ test.before(async(t) => {
getVehicle('VOLKSWAGEN', ['required:control_charge']),
]);

smartcar.setApiVersion('1.0');
t.context = {volt, egolf};
});

Expand Down
11 changes: 10 additions & 1 deletion test/unit/lib/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ test('inheritance check', function(t) {
);
break;
default:
t.true(new errors[error]() instanceof errors.SmartcarError);
t.true(new errors[error]('Message') instanceof errors.SmartcarError);
}
});

Expand All @@ -30,6 +30,15 @@ test('message check', function(t) {
case 'VehicleStateError':
t.regex(new errors[error]('R2D2', {code: 'VS_000'}).message, /R2D2/);
break;
case 'SmartcarErrorV2':
t.regex(new errors[error]('R2D2').description, /R2D2/);
const sampleError = {
type: '<type>',
code: '<code>',
description: '<description>',
};
t.is(new errors[error](sampleError).message, '<type>:<code> - <description>');
break;
default:
t.regex(new errors[error]('R2D2').message, /R2D2/);
}
Expand Down
51 changes: 51 additions & 0 deletions test/unit/lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@ const Promise = require('bluebird');
const {StatusCodeError} = require('request-promise/errors');

const util = require('../../../lib/util');
const smartcar = require('../../../');
const config = require('../../../lib/config');
const errors = require('../../../lib/errors');

const API_URL = config.api + '/v' + config.version;

test.afterEach((t) => {
smartcar.setApiVersion('1.0');
t.true(config.version === '1.0');
});

test('formatAccess', function(t) {

/* eslint-disable camelcase */
Expand Down Expand Up @@ -63,6 +69,12 @@ test('getUrl - id & endpoint', function(t) {
t.is(url, API_URL + '/vehicles/VID/odometer');
});

test('getUrl - version 2.0', function(t) {
smartcar.setApiVersion('2.0');
const url = util.getUrl('VID', 'odometer');
t.is(url, 'https://api.smartcar.com/v2.0/vehicles/VID/odometer');
});

test('request - default opts', async function(t) {
const n = nock('https://mock.com')
.get('/test')
Expand Down Expand Up @@ -291,3 +303,42 @@ test('catch - SmartcarError', async function(t) {
t.true(n.isDone());

});

test.serial('catch - SmartcarErrorV2', async function(t) {

const n = nock('https://api.smartcar.com/v2.0')
.get('/something')
.reply(500, {
type: 'type',
code: 'code',
description: 'description',
resolution: null,
detail: null,
requestId: '123',
docURL: null,
statusCode: 500,
});

const err = await t.throwsAsync(util.request('https://api.smartcar.com/v2.0/something'));
const boxed = t.throws(() => util.catch(err));

t.true(boxed instanceof errors.SmartcarErrorV2);
t.is(boxed.description, 'description');
t.true(n.isDone());

});

test.serial('catch - SmartcarErrorV2 - string response', async function(t) {

const n = nock('https://api.smartcar.com/v2.0')
.get('/something')
.reply(500, 'just a string response');

const err = await t.throwsAsync(util.request('https://api.smartcar.com/v2.0/something'));
const boxed = t.throws(() => util.catch(err));

t.true(boxed instanceof errors.SmartcarErrorV2);
t.is(boxed.description, 'just a string response');
t.true(n.isDone());

});

0 comments on commit 25790fc

Please sign in to comment.