diff --git a/README.md b/README.md index fa4def4..023b624 100644 --- a/README.md +++ b/README.md @@ -87,8 +87,8 @@ try { } } catch (err) { console.error('❌ Unable to process Assembly.', err) - if (err instanceof ApiError && err.response.assembly_id) { - console.error(`💡 More info: https://transloadit.com/assemblies/${err.response.assembly_id}`) + if (err instanceof ApiError && err.assemblyId) { + console.error(`💡 More info: https://transloadit.com/assemblies/${err.assemblyId}`) } } ``` @@ -416,12 +416,10 @@ const url = client.getSignedSmartCDNUrl({ ### Errors Any errors originating from Node.js will be passed on and we use [GOT](https://github.com/sindresorhus/got) v11 for HTTP requests. [Errors from `got`](https://github.com/sindresorhus/got/tree/v11.8.6?tab=readme-ov-file#errors) will also be passed on, _except_ the `got.HTTPError` which will be replaced with a `transloadit.ApiError`, which will have its `cause` property set to the instance of the original `got.HTTPError`. `transloadit.ApiError` has these properties: - -- `HTTPError.response` the JSON object returned by the server. It has these properties - - `error` (`string`) - [The Transloadit API error code](https://transloadit.com/docs/api/response-codes/#error-codes). - - `message` (`string`) - A textual representation of the Transloadit API error. - - `assembly_id`: (`string`) - If the request is related to an assembly, this will be the ID of the assembly. - - `assembly_ssl_url` (`string`) - If the request is related to an assembly, this will be the SSL URL to the assembly . + - `code` (`string`) - [The Transloadit API error code](https://transloadit.com/docs/api/response-codes/#error-codes). + - `rawMessage` (`string`) - A textual representation of the Transloadit API error. + - `assemblyId`: (`string`) - If the request is related to an assembly, this will be the ID of the assembly. + - `assemblySslUrl` (`string`) - If the request is related to an assembly, this will be the SSL URL to the assembly . To identify errors you can either check its props or use `instanceof`, e.g.: @@ -435,15 +433,15 @@ try { if (err.code === 'ENOENT') { return console.error('Cannot open file', err) } - if (err instanceof transloadit.ApiError && err.response.error === 'ASSEMBLY_INVALID_STEPS') { + if (err instanceof transloadit.ApiError && err.code === 'ASSEMBLY_INVALID_STEPS') { return console.error('Invalid Assembly Steps', err) } } ``` -**Note:** Assemblies that have an error status (`assembly.error`) will only result in an error being thrown from `createAssembly` and `replayAssembly`. For other Assembly methods, no errors will be thrown, but any error can be found in the response's `error` property +**Note:** Assemblies that have an error status (`assembly.error`) will only result in an error being thrown from `createAssembly` and `replayAssembly`. For other Assembly methods, no errors will be thrown, but any error can be found in the response's `error` property (also `ApiError.code`). -- [More information on Transloadit errors (`ApiError.response.error`)](https://transloadit.com/docs/api/response-codes/#error-codes) +- [More information on Transloadit errors (`ApiError.code`)](https://transloadit.com/docs/api/response-codes/#error-codes) - [More information on request errors](https://github.com/sindresorhus/got#errors) ### Rate limiting & auto retry diff --git a/examples/retry.js b/examples/retry.js index e4295ac..963ec88 100644 --- a/examples/retry.js +++ b/examples/retry.js @@ -22,7 +22,7 @@ async function run() { const { items } = await transloadit.listTemplates({ sort: 'created', order: 'asc' }) return items } catch (err) { - if (err instanceof ApiError && err.response.error === 'INVALID_SIGNATURE') { + if (err instanceof ApiError && err.code === 'INVALID_SIGNATURE') { // This is an unrecoverable error, abort retry throw new pRetry.AbortError('INVALID_SIGNATURE') } diff --git a/src/ApiError.ts b/src/ApiError.ts index a2c3f08..1896448 100644 --- a/src/ApiError.ts +++ b/src/ApiError.ts @@ -3,7 +3,6 @@ import { HTTPError } from 'got' export interface TransloaditErrorResponseBody { error?: string message?: string - http_code?: string assembly_ssl_url?: string assembly_id?: string } @@ -11,7 +10,12 @@ export interface TransloaditErrorResponseBody { export class ApiError extends Error { override name = 'ApiError' - response: TransloaditErrorResponseBody + // there might not be an error code (or message) if the server didn't respond with any JSON response at all + // e.g. if there was a 500 in the HTTP reverse proxy + code?: string + rawMessage?: string + assemblySslUrl?: string + assemblyId?: string override cause?: HTTPError | undefined @@ -20,13 +24,13 @@ export class ApiError extends Error { appendStack?: string body: TransloaditErrorResponseBody | undefined }) { - const { cause, body, appendStack } = params + const { cause, body = {}, appendStack } = params const parts = ['API error'] if (cause?.response.statusCode) parts.push(`(HTTP ${cause.response.statusCode})`) - if (body?.error) parts.push(`${body.error}:`) - if (body?.message) parts.push(body.message) - if (body?.assembly_ssl_url) parts.push(body.assembly_ssl_url) + if (body.error) parts.push(`${body.error}:`) + if (body.message) parts.push(body.message) + if (body.assembly_ssl_url) parts.push(body.assembly_ssl_url) const message = parts.join(' ') @@ -44,7 +48,10 @@ export class ApiError extends Error { this.stack += `\n${appendStack.replace(/^([^\n]+\n)/, '')}` } - this.response = body ?? {} + this.rawMessage = body.message + this.assemblyId = body.assembly_id + this.assemblySslUrl = body.assembly_ssl_url + this.code = body.error this.cause = cause } } diff --git a/test/integration/live-api.test.ts b/test/integration/live-api.test.ts index dd3a5d4..40ff690 100644 --- a/test/integration/live-api.test.ts +++ b/test/integration/live-api.test.ts @@ -398,10 +398,8 @@ describe('API integration', { timeout: 60000 }, () => { const promise = createAssembly(client, opts) await promise.catch((err) => { expect(err).toMatchObject({ - response: expect.objectContaining({ - error: 'INVALID_INPUT_ERROR', - assembly_id: expect.any(String), - }), + code: 'INVALID_INPUT_ERROR', + assemblyId: expect.any(String), }) }) await expect(promise).rejects.toThrow(Error) @@ -729,9 +727,7 @@ describe('API integration', { timeout: 60000 }, () => { expect(ok).toBe('TEMPLATE_DELETED') await expect(client.getTemplate(templId!)).rejects.toThrow( expect.objectContaining({ - response: expect.objectContaining({ - error: 'TEMPLATE_NOT_FOUND', - }), + code: 'TEMPLATE_NOT_FOUND', }) ) }) @@ -802,9 +798,7 @@ describe('API integration', { timeout: 60000 }, () => { expect(ok).toBe('TEMPLATE_CREDENTIALS_DELETED') await expect(client.getTemplateCredential(credId!)).rejects.toThrow( expect.objectContaining({ - response: expect.objectContaining({ - error: 'TEMPLATE_CREDENTIALS_NOT_READ', - }), + code: 'TEMPLATE_CREDENTIALS_NOT_READ', }) ) }) diff --git a/test/unit/mock-http.test.ts b/test/unit/mock-http.test.ts index 9e05203..e462646 100644 --- a/test/unit/mock-http.test.ts +++ b/test/unit/mock-http.test.ts @@ -98,10 +98,8 @@ describe('Mocked API tests', () => { await expect(client.createAssembly()).rejects.toThrow( expect.objectContaining({ - response: { - error: 'INVALID_FILE_META_DATA', - message: 'Invalid file metadata', - }, + code: 'INVALID_FILE_META_DATA', + rawMessage: 'Invalid file metadata', message: 'API error (HTTP 400) INVALID_FILE_META_DATA: Invalid file metadata', }) ) @@ -122,7 +120,7 @@ describe('Mocked API tests', () => { expect.objectContaining({ message: 'API error (HTTP 400) INVALID_FILE_META_DATA: Invalid file metadata https://api2-oltu.transloadit.com/assemblies/foo', - response: expect.objectContaining({ assembly_id: '123' }), + assemblyId: '123', }) ) @@ -150,14 +148,12 @@ describe('Mocked API tests', () => { expect.stringMatching(` at .+`), expect.stringMatching(` at .+`), expect.stringMatching(` name: 'ApiError',`), - expect.stringMatching(` response: \\{`), - expect.stringMatching(` error: 'INVALID_FILE_META_DATA',`), - expect.stringMatching(` message: 'Invalid file metadata',`), - expect.stringMatching(` assembly_id: '123',`), + expect.stringMatching(` rawMessage: 'Invalid file metadata',`), + expect.stringMatching(` assemblyId: '123',`), expect.stringMatching( - ` assembly_ssl_url: 'https:\\/\\/api2-oltu\\.transloadit\\.com\\/assemblies\\/foo'` + ` assemblySslUrl: 'https:\\/\\/api2-oltu\\.transloadit\\.com\\/assemblies\\/foo'` ), - expect.stringMatching(` \\},`), + expect.stringMatching(` code: 'INVALID_FILE_META_DATA',`), expect.stringMatching(` cause: HTTPError: Response code 400 \\(Bad Request\\)`), expect.stringMatching(` at .+`), expect.stringMatching(` at .+`), @@ -211,9 +207,7 @@ describe('Mocked API tests', () => { await expect(client.createAssembly()).rejects.toThrow( expect.objectContaining({ message: 'API error (HTTP 413) RATE_LIMIT_REACHED: Request limit reached', - response: expect.objectContaining({ - error: 'RATE_LIMIT_REACHED', - }), + code: 'RATE_LIMIT_REACHED', }) ) scope.done() @@ -292,10 +286,8 @@ describe('Mocked API tests', () => { await expect(client.createAssembly()).rejects.toThrow( expect.objectContaining({ - response: expect.objectContaining({ - error: 'IMPORT_FILE_ERROR', - assembly_id: '1', - }), + code: 'IMPORT_FILE_ERROR', + assemblyId: '1', }) ) scope.done() @@ -310,9 +302,7 @@ describe('Mocked API tests', () => { await expect(client.replayAssembly('1')).rejects.toThrow( expect.objectContaining({ - response: expect.objectContaining({ - error: 'IMPORT_FILE_ERROR', - }), + code: 'IMPORT_FILE_ERROR', }) ) scope.done()