Skip to content

Commit ef9e23d

Browse files
committed
fix(logs): enhance error handling in log processing and export functions
- Replaced generic error messages with structured ORPCError instances for better clarity and debugging. - Updated error handling in `getOneLogWithReconstruction` and `exportResponseByIdAndFormat` to include detailed messages and status codes. - Improved test coverage for error mapping in the Deepcrawl SDK, ensuring accurate error type handling.
1 parent 7b00e4d commit ef9e23d

6 files changed

Lines changed: 91 additions & 50 deletions

File tree

.changeset/bright-taxis-remain.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'deepcrawl': patch
3+
---
4+
5+
fix: align worker error codes with typed SDK errors
6+

apps/workers/v0/src/routers/logs/logs.processor.ts

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,11 @@ export async function getOneLogWithReconstruction(
366366
.limit(1);
367367

368368
if (result.length === 0) {
369-
throw new Error('Activity log not found');
369+
throw new ORPCError('NOT_FOUND', {
370+
status: 404,
371+
message: 'Activity log not found',
372+
data: { id },
373+
});
370374
}
371375

372376
const log = result[0];
@@ -444,26 +448,29 @@ export async function exportResponseByIdAndFormat(
444448
if (readResponse.markdown) {
445449
return readResponse.markdown;
446450
}
451+
const message =
452+
'No markdown content available for this request. The original request did not include markdown extraction.';
447453
throw new ORPCError('INVALID_EXPORT_FORMAT', {
448454
status: 400,
449-
message:
450-
'No markdown content available for this request. The original request did not include markdown extraction.',
451-
data: { id, path: activity.path },
455+
message,
456+
data: { id, path: activity.path, message },
452457
});
453458
}
454459
// Error response
460+
const message = 'Cannot export markdown from error response';
455461
throw new ORPCError('INVALID_EXPORT_FORMAT', {
456462
status: 400,
457-
message: 'Cannot export markdown from error response',
458-
data: { id, path: activity.path },
463+
message,
464+
data: { id, path: activity.path, message },
459465
});
460466
}
461467

462468
// Links endpoints don't have markdown
469+
const message = `Markdown export is not supported for ${activity.path} endpoint`;
463470
throw new ORPCError('INVALID_EXPORT_FORMAT', {
464471
status: 400,
465-
message: `Markdown export is not supported for ${activity.path} endpoint`,
466-
data: { id, path: activity.path },
472+
message,
473+
data: { id, path: activity.path, message },
467474
});
468475
}
469476

@@ -479,34 +486,39 @@ export async function exportResponseByIdAndFormat(
479486
if ('tree' in linksResponse && linksResponse.tree) {
480487
return linksResponse.tree;
481488
}
489+
const message =
490+
'No links tree available for this request. The original request did not include tree generation.';
482491
throw new ORPCError('INVALID_EXPORT_FORMAT', {
483492
status: 400,
484-
message:
485-
'No links tree available for this request. The original request did not include tree generation.',
486-
data: { id, path: activity.path },
493+
message,
494+
data: { id, path: activity.path, message },
487495
});
488496
}
489497
// Error response
498+
const message = 'Cannot export links from error response';
490499
throw new ORPCError('INVALID_EXPORT_FORMAT', {
491500
status: 400,
492-
message: 'Cannot export links from error response',
493-
data: { id, path: activity.path },
501+
message,
502+
data: { id, path: activity.path, message },
494503
});
495504
}
496505

497506
// Read endpoints don't have links tree
507+
const message = `Links export is not supported for ${activity.path} endpoint`;
498508
throw new ORPCError('INVALID_EXPORT_FORMAT', {
499509
status: 400,
500-
message: `Links export is not supported for ${activity.path} endpoint`,
501-
data: { id, path: activity.path },
510+
message,
511+
data: { id, path: activity.path, message },
502512
});
503513
}
504514

505-
default:
515+
default: {
516+
const message = `Unsupported export format: ${format}`;
506517
throw new ORPCError('INVALID_EXPORT_FORMAT', {
507518
status: 400,
508-
message: `Unsupported export format: ${format}`,
509-
data: { id, format },
519+
message,
520+
data: { id, format, message },
510521
});
522+
}
511523
}
512524
}

packages/sdks/js-ts/src/__tests__/deepcrawl.test.ts

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import { ORPCError } from '@orpc/client';
12
import { describe, expect, it } from 'vitest';
2-
import { DeepcrawlAuthError } from '../_types';
3+
import {
4+
DeepcrawlAuthError,
5+
DeepcrawlInvalidExportFormatError,
6+
} from '../_types';
37
import { DeepcrawlApp } from '../deepcrawl';
48

59
describe('DeepcrawlApp', () => {
@@ -117,23 +121,30 @@ describe('DeepcrawlApp', () => {
117121
code: string;
118122
status: number;
119123
message: string;
124+
data?: unknown;
120125
};
121126

122127
type FakeSafeClient = {
123-
read: {
128+
read?: {
124129
getMarkdown: () => Promise<[FakeOrpcError, null]>;
125130
};
131+
logs?: {
132+
exportResponse: () => Promise<[FakeOrpcError, null]>;
133+
};
126134
};
127135

128-
type DeepcrawlAppWithSafeClient = DeepcrawlApp & {
129-
safeClient: FakeSafeClient;
130-
};
136+
function setSafeClient(app: DeepcrawlApp, safeClient: FakeSafeClient) {
137+
const appWithSafeClient = app as unknown as {
138+
safeClient: FakeSafeClient;
139+
};
140+
appWithSafeClient.safeClient = safeClient;
141+
}
131142

132143
it('should map UNAUTHORIZED ORPC errors to DeepcrawlAuthError', async () => {
133144
const app = new DeepcrawlApp({ apiKey: 'test-key' });
134145

135146
// Override the internal safe client to simulate oRPC errors without network calls.
136-
(app as unknown as DeepcrawlAppWithSafeClient).safeClient = {
147+
setSafeClient(app, {
137148
read: {
138149
getMarkdown: async () => [
139150
{
@@ -144,7 +155,7 @@ describe('DeepcrawlApp', () => {
144155
null,
145156
],
146157
},
147-
};
158+
});
148159

149160
try {
150161
await app.getMarkdown('https://example.com');
@@ -159,32 +170,45 @@ describe('DeepcrawlApp', () => {
159170
}
160171
});
161172

162-
it('should map legacy UNAUTHORIZED: ORPC errors to DeepcrawlAuthError', async () => {
173+
it('should map INVALID_EXPORT_FORMAT ORPC errors to DeepcrawlInvalidExportFormatError', async () => {
163174
const app = new DeepcrawlApp({ apiKey: 'test-key' });
164175

165-
// Backward-compat with a previously incorrect server error code.
166-
(app as unknown as DeepcrawlAppWithSafeClient).safeClient = {
167-
read: {
168-
getMarkdown: async () => [
169-
{
170-
code: 'UNAUTHORIZED:',
171-
status: 401,
172-
message: 'Authentication failed',
173-
},
174-
null,
175-
],
176+
const orpcError = new ORPCError('INVALID_EXPORT_FORMAT', {
177+
defined: true,
178+
status: 400,
179+
message: 'Invalid export format',
180+
data: {
181+
id: 'log-123',
182+
format: 'markdown',
183+
path: 'read-readUrl',
184+
message: 'Cannot export markdown from error response',
176185
},
177-
};
186+
});
187+
188+
setSafeClient(app, {
189+
logs: {
190+
exportResponse: async () => [orpcError, null],
191+
},
192+
});
178193

179194
try {
180-
await app.getMarkdown('https://example.com');
181-
throw new Error('Expected getMarkdown() to throw');
195+
await app.exportResponse({ id: 'log-123', format: 'markdown' });
196+
throw new Error('Expected exportResponse() to throw');
182197
} catch (error) {
183-
expect(error).toBeInstanceOf(DeepcrawlAuthError);
184-
expect((error as DeepcrawlAuthError).code).toBe('UNAUTHORIZED');
185-
expect((error as DeepcrawlAuthError).status).toBe(401);
186-
expect((error as DeepcrawlAuthError).message).toBe(
187-
'Authentication failed',
198+
expect(error).toBeInstanceOf(DeepcrawlInvalidExportFormatError);
199+
expect((error as DeepcrawlInvalidExportFormatError).code).toBe(
200+
'INVALID_EXPORT_FORMAT',
201+
);
202+
expect((error as DeepcrawlInvalidExportFormatError).status).toBe(400);
203+
expect((error as DeepcrawlInvalidExportFormatError).message).toBe(
204+
'Cannot export markdown from error response',
205+
);
206+
expect((error as DeepcrawlInvalidExportFormatError).id).toBe('log-123');
207+
expect((error as DeepcrawlInvalidExportFormatError).format).toBe(
208+
'markdown',
209+
);
210+
expect((error as DeepcrawlInvalidExportFormatError).path).toBe(
211+
'read-readUrl',
188212
);
189213
}
190214
});

packages/sdks/js-ts/src/__tests__/types.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('Error Classes', () => {
2828
expect(error.message).toBe('Failed to read page');
2929
expect(error.name).toBe('DeepcrawlReadError');
3030
expect(error.code).toBe('READ_ERROR_RESPONSE');
31-
expect(error.status).toBe(400);
31+
expect(error.status).toBe(500);
3232
expect(error.targetUrl).toBe('https://example.com');
3333
expect(error.success).toBe(false);
3434
expect(error).toBeInstanceOf(DeepcrawlError);
@@ -49,7 +49,7 @@ describe('Error Classes', () => {
4949
expect(error.message).toBe('Failed to extract links');
5050
expect(error.name).toBe('DeepcrawlLinksError');
5151
expect(error.code).toBe('LINKS_ERROR_RESPONSE');
52-
expect(error.status).toBe(400);
52+
expect(error.status).toBe(500);
5353
expect(error.targetUrl).toBe('https://example.com');
5454
expect(error.timestamp).toBe('2024-01-01T00:00:00Z');
5555
expect(error).toBeInstanceOf(DeepcrawlError);

packages/sdks/js-ts/src/_types/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ export abstract class DeepcrawlError<TData = unknown> extends Error {
213213
export class DeepcrawlReadError extends DeepcrawlError<ReadErrorResponse> {
214214
constructor(data: ReadErrorResponse) {
215215
// Pass data.error as the message for consistency with JavaScript Error patterns
216-
super('READ_ERROR_RESPONSE', data.error, data, 400, true);
216+
super('READ_ERROR_RESPONSE', data.error, data, 500, true);
217217
}
218218

219219
get userMessage(): string {
@@ -240,7 +240,7 @@ export class DeepcrawlReadError extends DeepcrawlError<ReadErrorResponse> {
240240
export class DeepcrawlLinksError extends DeepcrawlError<LinksErrorResponse> {
241241
constructor(data: LinksErrorResponse) {
242242
// Pass data.error as the message for consistency with JavaScript Error patterns
243-
super('LINKS_ERROR_RESPONSE', data.error, data, 400, true);
243+
super('LINKS_ERROR_RESPONSE', data.error, data, 500, true);
244244
}
245245

246246
get userMessage(): string {

packages/sdks/js-ts/src/deepcrawl.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,6 @@ function handleDeepcrawlError(
173173
// Handle infrastructure errors (rate limiting, auth, etc.)
174174
switch (error.code) {
175175
case 'UNAUTHORIZED':
176-
case 'UNAUTHORIZED:':
177176
throw new DeepcrawlAuthError(error.message || 'Unauthorized');
178177
case 'BAD_REQUEST':
179178
throw new DeepcrawlValidationError(error.message || 'Bad request');

0 commit comments

Comments
 (0)