Skip to content

Commit c324ab0

Browse files
committed
feat: new error formatter, provides the same information regardless of service
BREAKING CHANGE: Errors objects returned from service errors are now different To migrate your code, see the upgrade guide for the new error structure:
1 parent e3cd70a commit c324ab0

2 files changed

Lines changed: 2252 additions & 2256 deletions

File tree

lib/requestwrapper.ts

Lines changed: 74 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -44,80 +44,92 @@ function parsePath(path: string, params: Object): string {
4444
}
4545

4646
/**
47-
* Check if the service/request have error and try to format them.
47+
* Determine if the error is due to bad credentials
48+
* @private
49+
* @param {Object} error - error object returned from axios
50+
* @returns {boolean} true if error is due to authentication
51+
*/
52+
function isAuthenticationError(error: any): boolean {
53+
let isAuthErr = false;
54+
const code = error.status;
55+
const body = error.data;
56+
57+
// handle specific error from iam service, should be relevant across platforms
58+
const isIamServiceError = body.context &&
59+
body.context.url &&
60+
body.context.url.indexOf('iam') > -1;
61+
62+
if (code === 401 || code === 403 || isIamServiceError) {
63+
isAuthErr = true;
64+
}
65+
66+
return isAuthErr;
67+
}
68+
69+
/**
70+
* Format error returned by axios
4871
* @param {Function} cb the request callback
4972
* @private
5073
* @returns {request.RequestCallback}
5174
*/
52-
export function formatErrorIfExists(cb: Function) {
53-
return (error, response, body) => {
54-
// eslint-disable-line complexity
55-
56-
// If we have an error return it.
57-
if (error) {
58-
// first ensure that it's an instanceof Error
59-
if (!(error instanceof Error)) {
60-
body = error;
61-
error = new Error(error.message || error.error || error);
62-
error.body = body;
63-
}
64-
if (response && response.headers) {
65-
error[globalTransactionId] = response.headers[globalTransactionId];
66-
}
67-
cb(error, body, response);
68-
return;
69-
}
75+
export function formatError(axiosError: any) {
76+
// return an actual error object,
77+
// but make it flexible so we can add properties like 'body'
78+
const error: any = new Error();
79+
80+
// axios specific handling
81+
if (axiosError.response) {
82+
axiosError = axiosError.response;
83+
// The request was made and the server responded with a status code
84+
// that falls out of the range of 2xx
85+
delete axiosError.config;
86+
delete axiosError.request;
7087

88+
error.name = axiosError.statusText;
89+
error.code = axiosError.status;
90+
error.message = axiosError.data.error || axiosError.statusText;
91+
92+
// some services bury the useful error message within 'data'
93+
// adding it to the error under the key 'body' as a string or object
94+
let errorBody;
7195
try {
72-
// in most cases, request will have already parsed the body as JSON
73-
body = JSON.parse(body);
96+
// try/catch to handle objects with circular references
97+
errorBody = JSON.stringify(axiosError.data);
7498
} catch (e) {
75-
// if it fails, just return the body as-is
99+
// ignore the error, use the object, and tack on a warning
100+
errorBody = axiosError.data;
101+
errorBody.warning = 'body contains circular reference';
76102
}
77103

78-
// If we have a response and it contains an error
79-
if (body && (body.error || body.error_code)) {
80-
// visual recognition sets body.error to a json object with code/description/error_id instead of putting them top-left
81-
if (typeof body.error === 'object' && body.error.description) {
82-
const errObj = body.error; // just in case there's a body.error.error...
83-
Object.keys(body.error).forEach(key => {
84-
body[key] = body.error[key];
85-
});
86-
Object.keys(body.error).forEach(key => {
87-
body[key] = body.error[key];
88-
});
89-
body.error = errObj.description;
90-
} else if (typeof body.error === 'object' && typeof body.error.error === 'object') {
91-
// this can happen with, for example, the assistant createSynonym() API
92-
body.rawError = body.error;
93-
body.error = JSON.stringify(body.error.error);
94-
}
95-
// language translaton returns json with error_code and error_message
96-
error = new Error(body.error || body.error_message || 'Error Code: ' + body.error_code);
97-
error.code = body.error_code;
98-
Object.keys(body).forEach(key => {
99-
error[key] = body[key];
100-
});
101-
body = null;
102-
}
103-
// If we still don't have an error and there was an error...
104-
if (!error && (response.statusCode < 200 || response.statusCode >= 300)) {
105-
error = new Error(typeof body === 'object' ? JSON.stringify(body) : body);
106-
error.code = response.statusCode;
107-
body = null;
104+
error.body = errorBody;
105+
106+
// iam service uses transaction-id
107+
if (axiosError.headers['transaction-id']) {
108+
error['transaction-id'] = axiosError.headers['transaction-id'];
108109
}
109110

110-
// ensure a more descriptive error message
111-
if (error && (error.code === 401 || error.code === 403)) {
112-
error.body = error.message;
113-
error.message = 'Unauthorized: Access is denied due to invalid credentials.';
111+
// other services use x-global-transaction-id
112+
if (axiosError.headers['x-global-transaction-id']) {
113+
error['x-global-transaction-id'] = axiosError.headers['x-global-transaction-id'];
114114
}
115-
if (error && response && response.headers) {
116-
error[globalTransactionId] = response.headers[globalTransactionId];
115+
116+
// print a more descriptive error message for auth issues
117+
if (isAuthenticationError(axiosError)) {
118+
error.message = 'Access is denied due to invalid credentials.';
117119
}
118-
cb(error, body, response);
119-
return;
120-
};
120+
121+
} else if (axiosError.request) {
122+
// The request was made but no response was received
123+
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
124+
// http.ClientRequest in node.js
125+
error.message = axiosError.request;
126+
127+
} else {
128+
// Something happened in setting up the request that triggered an Error
129+
error.message = axiosError.message;
130+
}
131+
132+
return error;
121133
}
122134

123135
/**
@@ -239,20 +251,6 @@ export function sendRequest(parameters, _callback) {
239251
_callback(null, res.data, res);
240252
})
241253
.catch(error => {
242-
if (error.response) {
243-
// The request was made and the server responded with a status code
244-
// that falls out of the range of 2xx
245-
delete error.response.config;
246-
delete error.response.request;
247-
_callback(error.response);
248-
} else if (error.request) {
249-
// The request was made but no response was received
250-
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
251-
// http.ClientRequest in node.js
252-
_callback(error.request);
253-
} else {
254-
// Something happened in setting up the request that triggered an Error
255-
_callback(error.message);
256-
}
254+
_callback(formatError(error));
257255
});
258256
}

0 commit comments

Comments
 (0)