Skip to content

Commit fb69cd2

Browse files
committed
chore: Export Zod errors integration and add upstream improvements
- Adds improvements based on feedback I got while PR'ing this to sentry-javascript: getsentry/sentry-javascript#15111 - Exports zodErrorsIntegration in the root index.ts (missed this in the original PR)
1 parent 117a3bd commit fb69cd2

File tree

3 files changed

+44
-26
lines changed

3 files changed

+44
-26
lines changed

.changeset/itchy-rats-brush.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'toucan-js': patch
3+
---
4+
5+
chore: Export Zod errors integration and add upstream improvements
6+
7+
- Adds improvements based on feedback I got while PR'ing this to sentry-javascript: https://github.com/getsentry/sentry-javascript/pull/15111
8+
- Exports zodErrorsIntegration in the root index.ts (missed this in the original PR)

packages/toucan-js/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export {
55
extraErrorDataIntegration,
66
rewriteFramesIntegration,
77
sessionTimingIntegration,
8+
zodErrorsIntegration
89
} from './integrations';
910
export type { LinkedErrorsOptions, RequestDataOptions } from './integrations';
1011
export { Toucan } from './sdk';

packages/toucan-js/src/integrations/zod/zoderrors.ts

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { isError, truncate } from '@sentry/utils';
55
import { defineIntegration } from './integration';
66

77
import type { Event, EventHint } from '@sentry/types';
8-
import type { ZodError, ZodIssue } from 'zod';
98

109
const INTEGRATION_NAME = 'ZodErrors';
1110
const DEFAULT_LIMIT = 10;
@@ -18,7 +17,9 @@ interface ZodErrorsOptions {
1817
*/
1918
limit?: number;
2019
/**
21-
* Optionally save full error info as an attachment in Sentry
20+
* Save full list of Zod issues as an attachment in Sentry
21+
*
22+
* @default false
2223
*/
2324
saveAttachments?: boolean;
2425
}
@@ -29,24 +30,28 @@ function originalExceptionIsZodError(
2930
return (
3031
isError(originalException) &&
3132
originalException.name === 'ZodError' &&
32-
Array.isArray((originalException as ZodError).errors)
33+
Array.isArray((originalException as ZodError).issues)
3334
);
3435
}
3536

3637
/**
3738
* Simplified ZodIssue type definition
3839
*/
39-
type SimpleZodIssue = {
40-
path: Array<string | number>;
40+
interface ZodIssue {
41+
path: (string | number)[];
4142
message?: string;
4243
expected?: unknown;
4344
received?: unknown;
4445
unionErrors?: unknown[];
4546
keys?: unknown[];
4647
invalid_literal?: unknown;
47-
};
48+
}
4849

49-
type SingleLevelZodIssue<T extends SimpleZodIssue> = {
50+
interface ZodError extends Error {
51+
issues: ZodIssue[];
52+
}
53+
54+
type SingleLevelZodIssue<T extends ZodIssue> = {
5055
[P in keyof T]: T[P] extends string | number | undefined
5156
? T[P]
5257
: T[P] extends unknown[]
@@ -67,9 +72,7 @@ type SingleLevelZodIssue<T extends SimpleZodIssue> = {
6772
* [Object]
6873
* ]
6974
*/
70-
export function flattenIssue(
71-
issue: ZodIssue,
72-
): SingleLevelZodIssue<SimpleZodIssue> {
75+
export function flattenIssue(issue: ZodIssue): SingleLevelZodIssue<ZodIssue> {
7376
return {
7477
...issue,
7578
path:
@@ -128,7 +131,11 @@ export function formatIssueMessage(zodError: ZodError): string {
128131
let rootExpectedType = 'variable';
129132
if (zodError.issues.length > 0) {
130133
const iss = zodError.issues[0];
131-
if ('expected' in iss && typeof iss.expected === 'string') {
134+
if (
135+
iss !== undefined &&
136+
'expected' in iss &&
137+
typeof iss.expected === 'string'
138+
) {
132139
rootExpectedType = iss.expected;
133140
}
134141
}
@@ -138,38 +145,39 @@ export function formatIssueMessage(zodError: ZodError): string {
138145
}
139146

140147
/**
141-
* Applies ZodError issues to an event context and replaces the error message
148+
* Applies ZodError issues to an event extra and replaces the error message
142149
*/
143-
function applyZodErrorsToEvent(
150+
export function applyZodErrorsToEvent(
144151
limit: number,
145152
event: Event,
146-
saveAttachments?: boolean,
147-
hint?: EventHint,
153+
saveAttachments: boolean = false,
154+
hint: EventHint,
148155
): Event {
149156
if (
150-
event.exception === undefined ||
151-
event.exception.values === undefined ||
152-
hint === undefined ||
153-
hint.originalException === undefined ||
157+
!event.exception?.values ||
158+
!hint.originalException ||
154159
!originalExceptionIsZodError(hint.originalException) ||
155160
hint.originalException.issues.length === 0
156161
) {
157162
return event;
158163
}
159164

160165
try {
161-
const flattenedIssues = hint.originalException.errors.map(flattenIssue);
162-
163-
if (saveAttachments === true) {
164-
// Add an attachment with all issues (no limits), as well as the default
165-
// flatten format to see if it's preferred over our custom flatten format.
166+
const issuesToFlatten = saveAttachments
167+
? hint.originalException.issues
168+
: hint.originalException.issues.slice(0, limit);
169+
const flattenedIssues = issuesToFlatten.map(flattenIssue);
170+
171+
if (saveAttachments) {
172+
// Sometimes having the full error details can be helpful.
173+
// Attachments have much higher limits, so we can include the full list of issues.
166174
if (!Array.isArray(hint.attachments)) {
167175
hint.attachments = [];
168176
}
169177
hint.attachments.push({
170178
filename: 'zod_issues.json',
171179
data: JSON.stringify({
172-
issueDetails: hint.originalException.flatten(flattenIssue),
180+
issues: flattenedIssues,
173181
}),
174182
});
175183
}
@@ -199,7 +207,8 @@ function applyZodErrorsToEvent(
199207
extra: {
200208
...event.extra,
201209
'zoderrors sentry integration parse error': {
202-
message: `an exception was thrown while processing ZodError within applyZodErrorsToEvent()`,
210+
message:
211+
'an exception was thrown while processing ZodError within applyZodErrorsToEvent()',
203212
error:
204213
e instanceof Error
205214
? `${e.name}: ${e.message}\n${e.stack}`

0 commit comments

Comments
 (0)