Skip to content

Commit

Permalink
feat: handle known client errors for salesforce (#303)
Browse files Browse the repository at this point in the history
  • Loading branch information
sodiray authored Aug 17, 2023
1 parent ed90916 commit d3a19b4
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 15 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vesselapi/integrations",
"version": "1.0.70",
"version": "1.0.71",
"description": "Vessel integrations",
"main": "dist/index.js",
"module": "dist/index.mjs",
Expand Down
77 changes: 74 additions & 3 deletions src/platforms/salesforce/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Auth, HttpsUrl } from '@/sdk';
import { Auth, HttpsUrl, IntegrationError } from '@/sdk';
import { formatUpsertInputWithNative, makeRequestFactory } from '@/sdk/client';
import { shake } from 'radash';
import { crush, guard, shake } from 'radash';
import { z } from 'zod';
import { formatQuery, salesforceQueryBuilder } from './actions/query-builder';
import { SALESFORCE_API_VERSION } from './constants';
Expand Down Expand Up @@ -85,7 +85,7 @@ const request = makeRequestFactory(async (auth, options) => {
} else {
throw new Error('Salesforce only supports OAuth2 authentication');
}
});
}, errorMapper);

const query = {
list: <T extends z.ZodType>({
Expand Down Expand Up @@ -775,3 +775,74 @@ export const client = {
passthrough: request.passthrough(),
fetch: request.fetch(),
};

const SalesforceErrorCodes = {
INVALID_TYPE: 'Insufficient Permissions',
INVALID_QUERY_FILTER_OPERATOR: 'Invalid value provided',
DUPLICATES_DETECTED: 'Duplicate CRM object(s)',
DUPLICATE_VALUE: 'Duplicate CRM object(s)',
REQUIRED_FIELD_MISSING: 'Required field(s) missing',
MALFORMED_ID:
'The supplied association id is either malformed or for the wrong object type',
INVALID_CROSS_REFERENCE_KEY: 'Invalid association id(s)',
INVALID_EMAIL_ADDRESS: 'Invalid email address',
OBJECT_NOT_FOUND:
'CRM object not found. Either it does not exist, or is not accessible',
NOT_FOUND:
'CRM object not found. Either it does not exist, or is not accessible',
INSUFFICIENT_ACCESS: 'Insufficient permissions or read-only',
INSUFFICIENT_ACCESS_OR_READONLY: 'Insufficient permissions or read-only',
INVALID_FIELD_FOR_INSERT_UPDATE: 'Field value(s) are invalid',
INVALID_FIELD: 'Field value(s) are invalid',
FIELD_INTEGRITY_EXCEPTION: 'Field value(s) are invalid',
JSON_PARSER_ERROR: 'Field value(s) are invalid',
INVALID_OR_NULL_FOR_RESTRICTED_PICKLIST: 'Field value(s) are invalid',
// Happens when someone tries to set an event attendee
// response status back to NEW for an existing invitee.
INVALID_STATUS: 'Field value(s) are invalid',
STORAGE_LIMIT_EXCEEDED:
'Storage limit exceeded for Salesforce instance. Delete some records before creating new ones',
CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY: 'CRM object update failed',
// Happens when you try to delete a record
// that was already deleted.
ENTITY_IS_DELETED: 'CRM object is deleted',
// Happens when you try to perform a CRUD
// operation on an entity that is associated with
// an object you don't have access to.
INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY:
'Insufficient permissions to access an object associated with this object',
STRING_TOO_LONG: 'String field value(s) are too long',
};

const serializeError = (error: any) => {
return (
guard(() =>
JSON.stringify({
name: error.name,
message: error.message,
code: error.code,
stack: error.stack,
cause: error.cause,
...crush(error),
}),
) ?? `${error}`
);
};

function errorMapper(error: any) {
const str = serializeError(error);
const errorKey =
(Object.keys(SalesforceErrorCodes).find((key) =>
str.includes(key),
) as keyof typeof SalesforceErrorCodes) ?? null;

if (!errorKey) return;

return new IntegrationError(SalesforceErrorCodes[errorKey], {
type: 'client',
cause: error,
// We don't want to alert on these because
// they are known issues/errors
alert: false,
});
}
34 changes: 23 additions & 11 deletions src/sdk/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ export const makeRequestFactory = (
query: qs.List;
}
>,
/**
* Used to catch errors thrown during a request. Can
* optionally return a different error to throw instead
*/
errorTransformer?: (error: any) => IntegrationError | undefined,
) => {
function createRequest<
TArgs extends {},
Expand Down Expand Up @@ -201,17 +206,24 @@ export const makeRequestFactory = (
Accept: 'application/json',
};

return (
options.method === 'GET' && options.json
? _requestWithAxios
: _requestWithFetch
)({
options: {
...options,
headers,
},
url,
});
try {
return (
options.method === 'GET' && options.json
? _requestWithAxios
: _requestWithFetch
)({
options: {
...options,
headers,
},
url,
});
} catch (err) {
// Run the error through the error transformer
// it may return a different error, if not we
// throw the original error
throw errorTransformer?.(err) ?? err;
}
});
const makeValidatedRequest = async (
auth: Auth,
Expand Down
6 changes: 6 additions & 0 deletions src/sdk/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ export type HttpErrorMeta = {
export type ClientErrorMeta = {
type: 'client';
cause?: unknown;
/**
* If set to true this error will be treated
* as an issue that needs attention, causing
* an alert.
*/
alert?: boolean;
};

export class IntegrationError extends Error {
Expand Down

0 comments on commit d3a19b4

Please sign in to comment.