@unito/integration-sdk 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1236,22 +1236,22 @@ class Provider {
1236
1236
  try {
1237
1237
  const body = JSON.parse(responseBody);
1238
1238
  if (body.error) {
1239
- reject(this.handleError(400, body.error.message));
1239
+ reject(this.handleError(400, body.error.message, options));
1240
1240
  }
1241
1241
  resolve({ status: 201, headers: response.headers, body: body });
1242
1242
  }
1243
1243
  catch (error) {
1244
- reject(this.handleError(500, `Failed to parse response body: "${error}"`));
1244
+ reject(this.handleError(500, `Failed to parse response body: "${error}"`, options));
1245
1245
  }
1246
1246
  });
1247
1247
  });
1248
1248
  request.on('error', error => {
1249
- reject(this.handleError(400, `Error while calling the provider: "${error}"`));
1249
+ reject(this.handleError(400, `Error while calling the provider: "${error}"`, options));
1250
1250
  });
1251
1251
  form.pipe(request);
1252
1252
  }
1253
1253
  catch (error) {
1254
- reject(this.handleError(500, `Unexpected error while calling the provider: "${error}"`));
1254
+ reject(this.handleError(500, `Unexpected error while calling the provider: "${error}"`, options));
1255
1255
  }
1256
1256
  });
1257
1257
  };
@@ -1361,17 +1361,17 @@ class Provider {
1361
1361
  if (error instanceof Error) {
1362
1362
  switch (error.name) {
1363
1363
  case 'AbortError':
1364
- throw this.handleError(408, 'Request aborted');
1364
+ throw this.handleError(408, 'Request aborted', options);
1365
1365
  case 'TimeoutError':
1366
- throw this.handleError(408, 'Request timeout');
1366
+ throw this.handleError(408, 'Request timeout', options);
1367
1367
  }
1368
- throw this.handleError(500, `Unexpected error while calling the provider: name: "${error.name}" \n message: "${error.message}" \n stack: ${error.stack}`);
1368
+ throw this.handleError(500, `Unexpected error while calling the provider: name: "${error.name}" \n message: "${error.message}" \n stack: ${error.stack}`, options);
1369
1369
  }
1370
- throw this.handleError(500, 'Unexpected error while calling the provider - this is not normal, investigate');
1370
+ throw this.handleError(500, 'Unexpected error while calling the provider - this is not normal, investigate', options);
1371
1371
  }
1372
1372
  if (response.status >= 400) {
1373
1373
  const textResult = await response.text();
1374
- throw this.handleError(response.status, textResult);
1374
+ throw this.handleError(response.status, textResult, options);
1375
1375
  }
1376
1376
  const responseContentType = response.headers.get('content-type');
1377
1377
  let body;
@@ -1382,13 +1382,13 @@ class Provider {
1382
1382
  if (responseContentType && !responseContentType.includes('application/json')) {
1383
1383
  const textResult = await response.text();
1384
1384
  throw this.handleError(500, `Unsupported content-type, expected 'application/json' but got '${responseContentType}'.
1385
- Original response (${response.status}): ${textResult}`);
1385
+ Original response (${response.status}): ${textResult}`, options);
1386
1386
  }
1387
1387
  try {
1388
1388
  body = response.body ? await response.json() : undefined;
1389
1389
  }
1390
1390
  catch (err) {
1391
- throw this.handleError(500, `Invalid JSON response`);
1391
+ throw this.handleError(500, `Invalid JSON response`, options);
1392
1392
  }
1393
1393
  }
1394
1394
  else if (headers.Accept == 'application/octet-stream') {
@@ -1400,14 +1400,14 @@ class Provider {
1400
1400
  body = (await response.text());
1401
1401
  }
1402
1402
  else {
1403
- throw this.handleError(500, 'Unsupported Content-Type');
1403
+ throw this.handleError(500, 'Unsupported Content-Type', options);
1404
1404
  }
1405
1405
  return { status: response.status, headers: response.headers, body };
1406
1406
  };
1407
1407
  return this.rateLimiter ? this.rateLimiter(options, callToProvider) : callToProvider();
1408
1408
  }
1409
- handleError(responseStatus, message) {
1410
- const customError = this.customErrorHandler?.(responseStatus, message);
1409
+ handleError(responseStatus, message, options) {
1410
+ const customError = this.customErrorHandler?.(responseStatus, message, options);
1411
1411
  return customError ?? buildHttpError(responseStatus, message);
1412
1412
  }
1413
1413
  }
@@ -95,7 +95,10 @@ export declare class Provider {
95
95
  *
96
96
  * @see buildHttpError for the list of standard errors the SDK can handle.
97
97
  */
98
- protected customErrorHandler: ((responseStatus: number, message: string) => HttpErrors.HttpError | undefined) | undefined;
98
+ protected customErrorHandler: ((responseStatus: number, message: string, options: {
99
+ credentials: Credentials;
100
+ logger: Logger;
101
+ }) => HttpErrors.HttpError | undefined) | undefined;
99
102
  /**
100
103
  * Initializes a Provider with the given options.
101
104
  *
@@ -137,22 +137,22 @@ export class Provider {
137
137
  try {
138
138
  const body = JSON.parse(responseBody);
139
139
  if (body.error) {
140
- reject(this.handleError(400, body.error.message));
140
+ reject(this.handleError(400, body.error.message, options));
141
141
  }
142
142
  resolve({ status: 201, headers: response.headers, body: body });
143
143
  }
144
144
  catch (error) {
145
- reject(this.handleError(500, `Failed to parse response body: "${error}"`));
145
+ reject(this.handleError(500, `Failed to parse response body: "${error}"`, options));
146
146
  }
147
147
  });
148
148
  });
149
149
  request.on('error', error => {
150
- reject(this.handleError(400, `Error while calling the provider: "${error}"`));
150
+ reject(this.handleError(400, `Error while calling the provider: "${error}"`, options));
151
151
  });
152
152
  form.pipe(request);
153
153
  }
154
154
  catch (error) {
155
- reject(this.handleError(500, `Unexpected error while calling the provider: "${error}"`));
155
+ reject(this.handleError(500, `Unexpected error while calling the provider: "${error}"`, options));
156
156
  }
157
157
  });
158
158
  };
@@ -262,17 +262,17 @@ export class Provider {
262
262
  if (error instanceof Error) {
263
263
  switch (error.name) {
264
264
  case 'AbortError':
265
- throw this.handleError(408, 'Request aborted');
265
+ throw this.handleError(408, 'Request aborted', options);
266
266
  case 'TimeoutError':
267
- throw this.handleError(408, 'Request timeout');
267
+ throw this.handleError(408, 'Request timeout', options);
268
268
  }
269
- throw this.handleError(500, `Unexpected error while calling the provider: name: "${error.name}" \n message: "${error.message}" \n stack: ${error.stack}`);
269
+ throw this.handleError(500, `Unexpected error while calling the provider: name: "${error.name}" \n message: "${error.message}" \n stack: ${error.stack}`, options);
270
270
  }
271
- throw this.handleError(500, 'Unexpected error while calling the provider - this is not normal, investigate');
271
+ throw this.handleError(500, 'Unexpected error while calling the provider - this is not normal, investigate', options);
272
272
  }
273
273
  if (response.status >= 400) {
274
274
  const textResult = await response.text();
275
- throw this.handleError(response.status, textResult);
275
+ throw this.handleError(response.status, textResult, options);
276
276
  }
277
277
  const responseContentType = response.headers.get('content-type');
278
278
  let body;
@@ -283,13 +283,13 @@ export class Provider {
283
283
  if (responseContentType && !responseContentType.includes('application/json')) {
284
284
  const textResult = await response.text();
285
285
  throw this.handleError(500, `Unsupported content-type, expected 'application/json' but got '${responseContentType}'.
286
- Original response (${response.status}): ${textResult}`);
286
+ Original response (${response.status}): ${textResult}`, options);
287
287
  }
288
288
  try {
289
289
  body = response.body ? await response.json() : undefined;
290
290
  }
291
291
  catch (err) {
292
- throw this.handleError(500, `Invalid JSON response`);
292
+ throw this.handleError(500, `Invalid JSON response`, options);
293
293
  }
294
294
  }
295
295
  else if (headers.Accept == 'application/octet-stream') {
@@ -301,14 +301,14 @@ export class Provider {
301
301
  body = (await response.text());
302
302
  }
303
303
  else {
304
- throw this.handleError(500, 'Unsupported Content-Type');
304
+ throw this.handleError(500, 'Unsupported Content-Type', options);
305
305
  }
306
306
  return { status: response.status, headers: response.headers, body };
307
307
  };
308
308
  return this.rateLimiter ? this.rateLimiter(options, callToProvider) : callToProvider();
309
309
  }
310
- handleError(responseStatus, message) {
311
- const customError = this.customErrorHandler?.(responseStatus, message);
310
+ handleError(responseStatus, message, options) {
311
+ const customError = this.customErrorHandler?.(responseStatus, message, options);
312
312
  return customError ?? buildHttpError(responseStatus, message);
313
313
  }
314
314
  }
@@ -314,6 +314,47 @@ describe('Provider', () => {
314
314
  assert.ok(error instanceof HttpErrors.HttpError);
315
315
  assert.equal(error.message, 'Weird provider behavior');
316
316
  });
317
+ it('should contain the credential in the custom error handler', async (context) => {
318
+ const provider = new Provider({
319
+ prepareRequest: requestOptions => {
320
+ return {
321
+ url: `www.${requestOptions.credentials.domain ?? 'myApi.com'}`,
322
+ headers: {
323
+ 'X-Custom-Provider-Header': 'value',
324
+ 'X-Provider-Credential-Header': requestOptions.credentials.apiKey,
325
+ },
326
+ };
327
+ },
328
+ rateLimiter: undefined,
329
+ customErrorHandler: (responseStatus, _message, options) => {
330
+ if (responseStatus === 400) {
331
+ // What matter is that we have access to the context in the error handler
332
+ throw new HttpErrors.BadRequestError(`Error with API key ${options?.credentials.apiKey}`);
333
+ }
334
+ return undefined;
335
+ },
336
+ });
337
+ const response = new Response(undefined, {
338
+ status: 400,
339
+ headers: { 'Content-Type': 'application/json' },
340
+ });
341
+ context.mock.method(global, 'fetch', () => Promise.resolve(response));
342
+ const options = {
343
+ credentials: { apiKey: 'apikey#1111' },
344
+ logger: logger,
345
+ signal: new AbortController().signal,
346
+ additionnalheaders: { 'X-Additional-Header': 'value1' },
347
+ };
348
+ let error;
349
+ try {
350
+ await provider.delete('/endpoint/123', options);
351
+ }
352
+ catch (e) {
353
+ error = e;
354
+ }
355
+ assert.ok(error instanceof HttpErrors.HttpError);
356
+ assert.equal(error.message, 'Error with API key apikey#1111');
357
+ });
317
358
  it('uses default behavior if custom error handler returns undefined', async (context) => {
318
359
  const rateLimitedProvider = new Provider({
319
360
  prepareRequest: requestOptions => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unito/integration-sdk",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Integration SDK",
5
5
  "type": "module",
6
6
  "types": "dist/src/index.d.ts",
@@ -100,7 +100,14 @@ export class Provider {
100
100
  * @see buildHttpError for the list of standard errors the SDK can handle.
101
101
  */
102
102
  protected customErrorHandler:
103
- | ((responseStatus: number, message: string) => HttpErrors.HttpError | undefined)
103
+ | ((
104
+ responseStatus: number,
105
+ message: string,
106
+ options: {
107
+ credentials: Credentials;
108
+ logger: Logger;
109
+ },
110
+ ) => HttpErrors.HttpError | undefined)
104
111
  | undefined;
105
112
 
106
113
  /**
@@ -216,22 +223,22 @@ export class Provider {
216
223
  try {
217
224
  const body = JSON.parse(responseBody);
218
225
  if (body.error) {
219
- reject(this.handleError(400, body.error.message));
226
+ reject(this.handleError(400, body.error.message, options));
220
227
  }
221
228
  resolve({ status: 201, headers: response.headers as unknown as Headers, body: body as T });
222
229
  } catch (error) {
223
- reject(this.handleError(500, `Failed to parse response body: "${error}"`));
230
+ reject(this.handleError(500, `Failed to parse response body: "${error}"`, options));
224
231
  }
225
232
  });
226
233
  });
227
234
 
228
235
  request.on('error', error => {
229
- reject(this.handleError(400, `Error while calling the provider: "${error}"`));
236
+ reject(this.handleError(400, `Error while calling the provider: "${error}"`, options));
230
237
  });
231
238
 
232
239
  form.pipe(request);
233
240
  } catch (error) {
234
- reject(this.handleError(500, `Unexpected error while calling the provider: "${error}"`));
241
+ reject(this.handleError(500, `Unexpected error while calling the provider: "${error}"`, options));
235
242
  }
236
243
  });
237
244
  };
@@ -358,22 +365,27 @@ export class Provider {
358
365
  if (error instanceof Error) {
359
366
  switch (error.name) {
360
367
  case 'AbortError':
361
- throw this.handleError(408, 'Request aborted');
368
+ throw this.handleError(408, 'Request aborted', options);
362
369
  case 'TimeoutError':
363
- throw this.handleError(408, 'Request timeout');
370
+ throw this.handleError(408, 'Request timeout', options);
364
371
  }
365
372
  throw this.handleError(
366
373
  500,
367
374
  `Unexpected error while calling the provider: name: "${error.name}" \n message: "${error.message}" \n stack: ${error.stack}`,
375
+ options,
368
376
  );
369
377
  }
370
378
 
371
- throw this.handleError(500, 'Unexpected error while calling the provider - this is not normal, investigate');
379
+ throw this.handleError(
380
+ 500,
381
+ 'Unexpected error while calling the provider - this is not normal, investigate',
382
+ options,
383
+ );
372
384
  }
373
385
 
374
386
  if (response.status >= 400) {
375
387
  const textResult = await response.text();
376
- throw this.handleError(response.status, textResult);
388
+ throw this.handleError(response.status, textResult, options);
377
389
  }
378
390
 
379
391
  const responseContentType = response.headers.get('content-type');
@@ -389,13 +401,14 @@ export class Provider {
389
401
  500,
390
402
  `Unsupported content-type, expected 'application/json' but got '${responseContentType}'.
391
403
  Original response (${response.status}): ${textResult}`,
404
+ options,
392
405
  );
393
406
  }
394
407
 
395
408
  try {
396
409
  body = response.body ? await response.json() : undefined;
397
410
  } catch (err) {
398
- throw this.handleError(500, `Invalid JSON response`);
411
+ throw this.handleError(500, `Invalid JSON response`, options);
399
412
  }
400
413
  } else if (headers.Accept == 'application/octet-stream') {
401
414
  // When we expect octet-stream, we accept any Content-Type the provider sends us, we just want to stream it.
@@ -404,7 +417,7 @@ export class Provider {
404
417
  // Accept text based content types
405
418
  body = (await response.text()) as T;
406
419
  } else {
407
- throw this.handleError(500, 'Unsupported Content-Type');
420
+ throw this.handleError(500, 'Unsupported Content-Type', options);
408
421
  }
409
422
 
410
423
  return { status: response.status, headers: response.headers, body };
@@ -413,8 +426,8 @@ export class Provider {
413
426
  return this.rateLimiter ? this.rateLimiter(options, callToProvider) : callToProvider();
414
427
  }
415
428
 
416
- private handleError(responseStatus: number, message: string): HttpErrors.HttpError {
417
- const customError = this.customErrorHandler?.(responseStatus, message);
429
+ private handleError(responseStatus: number, message: string, options: RequestOptions): HttpErrors.HttpError {
430
+ const customError = this.customErrorHandler?.(responseStatus, message, options);
418
431
 
419
432
  return customError ?? buildHttpError(responseStatus, message);
420
433
  }
@@ -374,6 +374,53 @@ describe('Provider', () => {
374
374
  assert.equal(error.message, 'Weird provider behavior');
375
375
  });
376
376
 
377
+ it('should contain the credential in the custom error handler', async context => {
378
+ const provider = new Provider({
379
+ prepareRequest: requestOptions => {
380
+ return {
381
+ url: `www.${requestOptions.credentials.domain ?? 'myApi.com'}`,
382
+ headers: {
383
+ 'X-Custom-Provider-Header': 'value',
384
+ 'X-Provider-Credential-Header': requestOptions.credentials.apiKey as string,
385
+ },
386
+ };
387
+ },
388
+ rateLimiter: undefined,
389
+ customErrorHandler: (responseStatus: number, _message: string, options) => {
390
+ if (responseStatus === 400) {
391
+ // What matter is that we have access to the context in the error handler
392
+ throw new HttpErrors.BadRequestError(`Error with API key ${options?.credentials.apiKey}`);
393
+ }
394
+
395
+ return undefined;
396
+ },
397
+ });
398
+
399
+ const response = new Response(undefined, {
400
+ status: 400,
401
+ headers: { 'Content-Type': 'application/json' },
402
+ });
403
+
404
+ context.mock.method(global, 'fetch', () => Promise.resolve(response));
405
+
406
+ const options = {
407
+ credentials: { apiKey: 'apikey#1111' },
408
+ logger: logger,
409
+ signal: new AbortController().signal,
410
+ additionnalheaders: { 'X-Additional-Header': 'value1' },
411
+ };
412
+
413
+ let error;
414
+ try {
415
+ await provider.delete('/endpoint/123', options);
416
+ } catch (e) {
417
+ error = e;
418
+ }
419
+
420
+ assert.ok(error instanceof HttpErrors.HttpError);
421
+ assert.equal(error.message, 'Error with API key apikey#1111');
422
+ });
423
+
377
424
  it('uses default behavior if custom error handler returns undefined', async context => {
378
425
  const rateLimitedProvider = new Provider({
379
426
  prepareRequest: requestOptions => {