diff --git a/index.d.ts b/index.d.ts index df4f434..03efee0 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2,10 +2,9 @@ declare module 'replicate' { type Status = 'starting' | 'processing' | 'succeeded' | 'failed' | 'canceled'; type WebhookEventType = 'start' | 'output' | 'logs' | 'completed'; - interface Page { - previous?: string; - next?: string; - results: T[]; + export interface ApiError extends Error { + request: Request; + response: Response; } export interface Collection { @@ -58,6 +57,12 @@ declare module 'replicate' { export type Training = Prediction; + interface Page { + previous?: string; + next?: string; + results: T[]; + } + export default class Replicate { constructor(options: { auth: string; diff --git a/index.js b/index.js index ce97fcf..de75431 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,10 @@ +const ApiError = require('./lib/error'); + const collections = require('./lib/collections'); const models = require('./lib/models'); const predictions = require('./lib/predictions'); const trainings = require('./lib/trainings'); + const packageJSON = require('./package.json'); /** @@ -127,7 +130,7 @@ class Replicate { * @param {object} [parameters.params] - Query parameters * @param {object} [parameters.data] - Body parameters * @returns {Promise} - Resolves with the API response data - * @throws {Error} If the request failed + * @throws {ApiError} If the request failed */ async request(route, parameters) { const { auth, baseUrl, userAgent } = this; @@ -149,14 +152,22 @@ class Replicate { 'User-Agent': userAgent, }; - const response = await this.fetch(url, { + const options = { method, headers, body: data ? JSON.stringify(data) : undefined, - }); + }; + + const response = await this.fetch(url, options); if (!response.ok) { - throw new Error(`API request failed: ${response.statusText}`); + const request = new Request(url, options); + const responseText = await response.text(); + throw new ApiError( + `Request to ${url} failed with status ${response.status} ${response.statusText}: ${responseText}.`, + request, + response, + ); } return response.json(); @@ -173,7 +184,7 @@ class Replicate { * @param {Function} endpoint - Function that returns a promise for the next page of results * @yields {object[]} Each page of results */ - async *paginate(endpoint) { + async * paginate(endpoint) { const response = await endpoint(); yield response.results; if (response.next) { diff --git a/index.test.ts b/index.test.ts index d948a53..0538afd 100644 --- a/index.test.ts +++ b/index.test.ts @@ -163,6 +163,29 @@ describe('Replicate client', () => { }); }).rejects.toThrow('Invalid webhook URL'); }); + + test('Throws an error with details failing response is JSON', async () => { + nock(BASE_URL) + .post('/predictions') + .reply(400, { + status: 400, + detail: "Invalid input", + }, { "Content-Type": "application/json" }) + + try { + expect.assertions(2); + + await client.predictions.create({ + version: '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa', + input: { + text: null, + }, + }); + } catch (error) { + expect(error.response.status).toBe(400); + expect(error.message).toContain("Invalid input") + } + }) // Add more tests for error handling, edge cases, etc. }); diff --git a/lib/error.js b/lib/error.js new file mode 100644 index 0000000..50c0e2f --- /dev/null +++ b/lib/error.js @@ -0,0 +1,17 @@ +class ApiError extends Error { + /** + * Creates a representation of an API error. + * @param {string} message - Error message + * @param {Request} request - HTTP request + * @param {Response} response - HTTP response + * @returns {ApiError} + */ + constructor(message, request, response) { + super(message); + this.name = 'ApiError'; + this.request = request; + this.response = response; + } +} + +module.exports = ApiError;