Skip to content

Commit 6343c52

Browse files
authored
Provide more information in API errors (#103)
1 parent 725755a commit 6343c52

File tree

4 files changed

+65
-9
lines changed

4 files changed

+65
-9
lines changed

index.d.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ declare module 'replicate' {
22
type Status = 'starting' | 'processing' | 'succeeded' | 'failed' | 'canceled';
33
type WebhookEventType = 'start' | 'output' | 'logs' | 'completed';
44

5-
interface Page<T> {
6-
previous?: string;
7-
next?: string;
8-
results: T[];
5+
export interface ApiError extends Error {
6+
request: Request;
7+
response: Response;
98
}
109

1110
export interface Collection {
@@ -58,6 +57,12 @@ declare module 'replicate' {
5857

5958
export type Training = Prediction;
6059

60+
interface Page<T> {
61+
previous?: string;
62+
next?: string;
63+
results: T[];
64+
}
65+
6166
export default class Replicate {
6267
constructor(options: {
6368
auth: string;

index.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
const ApiError = require('./lib/error');
2+
13
const collections = require('./lib/collections');
24
const models = require('./lib/models');
35
const predictions = require('./lib/predictions');
46
const trainings = require('./lib/trainings');
7+
58
const packageJSON = require('./package.json');
69

710
/**
@@ -127,7 +130,7 @@ class Replicate {
127130
* @param {object} [parameters.params] - Query parameters
128131
* @param {object} [parameters.data] - Body parameters
129132
* @returns {Promise<object>} - Resolves with the API response data
130-
* @throws {Error} If the request failed
133+
* @throws {ApiError} If the request failed
131134
*/
132135
async request(route, parameters) {
133136
const { auth, baseUrl, userAgent } = this;
@@ -149,14 +152,22 @@ class Replicate {
149152
'User-Agent': userAgent,
150153
};
151154

152-
const response = await this.fetch(url, {
155+
const options = {
153156
method,
154157
headers,
155158
body: data ? JSON.stringify(data) : undefined,
156-
});
159+
};
160+
161+
const response = await this.fetch(url, options);
157162

158163
if (!response.ok) {
159-
throw new Error(`API request failed: ${response.statusText}`);
164+
const request = new Request(url, options);
165+
const responseText = await response.text();
166+
throw new ApiError(
167+
`Request to ${url} failed with status ${response.status} ${response.statusText}: ${responseText}.`,
168+
request,
169+
response,
170+
);
160171
}
161172

162173
return response.json();
@@ -173,7 +184,7 @@ class Replicate {
173184
* @param {Function} endpoint - Function that returns a promise for the next page of results
174185
* @yields {object[]} Each page of results
175186
*/
176-
async *paginate(endpoint) {
187+
async * paginate(endpoint) {
177188
const response = await endpoint();
178189
yield response.results;
179190
if (response.next) {

index.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,29 @@ describe('Replicate client', () => {
163163
});
164164
}).rejects.toThrow('Invalid webhook URL');
165165
});
166+
167+
test('Throws an error with details failing response is JSON', async () => {
168+
nock(BASE_URL)
169+
.post('/predictions')
170+
.reply(400, {
171+
status: 400,
172+
detail: "Invalid input",
173+
}, { "Content-Type": "application/json" })
174+
175+
try {
176+
expect.assertions(2);
177+
178+
await client.predictions.create({
179+
version: '5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa',
180+
input: {
181+
text: null,
182+
},
183+
});
184+
} catch (error) {
185+
expect(error.response.status).toBe(400);
186+
expect(error.message).toContain("Invalid input")
187+
}
188+
})
166189
// Add more tests for error handling, edge cases, etc.
167190
});
168191

lib/error.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
class ApiError extends Error {
2+
/**
3+
* Creates a representation of an API error.
4+
* @param {string} message - Error message
5+
* @param {Request} request - HTTP request
6+
* @param {Response} response - HTTP response
7+
* @returns {ApiError}
8+
*/
9+
constructor(message, request, response) {
10+
super(message);
11+
this.name = 'ApiError';
12+
this.request = request;
13+
this.response = response;
14+
}
15+
}
16+
17+
module.exports = ApiError;

0 commit comments

Comments
 (0)