Skip to content

Commit

Permalink
chore: Export Zod errors integration and add upstream improvements
Browse files Browse the repository at this point in the history
- 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)
  • Loading branch information
jahands committed Feb 11, 2025
1 parent 117a3bd commit fb69cd2
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 26 deletions.
8 changes: 8 additions & 0 deletions .changeset/itchy-rats-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'toucan-js': patch
---

chore: Export Zod errors integration and add upstream improvements

- Adds improvements based on feedback I got while PR'ing this to sentry-javascript: https://github.com/getsentry/sentry-javascript/pull/15111
- Exports zodErrorsIntegration in the root index.ts (missed this in the original PR)
1 change: 1 addition & 0 deletions packages/toucan-js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export {
extraErrorDataIntegration,
rewriteFramesIntegration,
sessionTimingIntegration,
zodErrorsIntegration
} from './integrations';
export type { LinkedErrorsOptions, RequestDataOptions } from './integrations';
export { Toucan } from './sdk';
Expand Down
61 changes: 35 additions & 26 deletions packages/toucan-js/src/integrations/zod/zoderrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { isError, truncate } from '@sentry/utils';
import { defineIntegration } from './integration';

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

const INTEGRATION_NAME = 'ZodErrors';
const DEFAULT_LIMIT = 10;
Expand All @@ -18,7 +17,9 @@ interface ZodErrorsOptions {
*/
limit?: number;
/**
* Optionally save full error info as an attachment in Sentry
* Save full list of Zod issues as an attachment in Sentry
*
* @default false
*/
saveAttachments?: boolean;
}
Expand All @@ -29,24 +30,28 @@ function originalExceptionIsZodError(
return (
isError(originalException) &&
originalException.name === 'ZodError' &&
Array.isArray((originalException as ZodError).errors)
Array.isArray((originalException as ZodError).issues)
);
}

/**
* Simplified ZodIssue type definition
*/
type SimpleZodIssue = {
path: Array<string | number>;
interface ZodIssue {
path: (string | number)[];
message?: string;
expected?: unknown;
received?: unknown;
unionErrors?: unknown[];
keys?: unknown[];
invalid_literal?: unknown;
};
}

type SingleLevelZodIssue<T extends SimpleZodIssue> = {
interface ZodError extends Error {
issues: ZodIssue[];
}

type SingleLevelZodIssue<T extends ZodIssue> = {
[P in keyof T]: T[P] extends string | number | undefined
? T[P]
: T[P] extends unknown[]
Expand All @@ -67,9 +72,7 @@ type SingleLevelZodIssue<T extends SimpleZodIssue> = {
* [Object]
* ]
*/
export function flattenIssue(
issue: ZodIssue,
): SingleLevelZodIssue<SimpleZodIssue> {
export function flattenIssue(issue: ZodIssue): SingleLevelZodIssue<ZodIssue> {
return {
...issue,
path:
Expand Down Expand Up @@ -128,7 +131,11 @@ export function formatIssueMessage(zodError: ZodError): string {
let rootExpectedType = 'variable';
if (zodError.issues.length > 0) {
const iss = zodError.issues[0];
if ('expected' in iss && typeof iss.expected === 'string') {
if (
iss !== undefined &&
'expected' in iss &&
typeof iss.expected === 'string'
) {
rootExpectedType = iss.expected;
}
}
Expand All @@ -138,38 +145,39 @@ export function formatIssueMessage(zodError: ZodError): string {
}

/**
* Applies ZodError issues to an event context and replaces the error message
* Applies ZodError issues to an event extra and replaces the error message
*/
function applyZodErrorsToEvent(
export function applyZodErrorsToEvent(
limit: number,
event: Event,
saveAttachments?: boolean,
hint?: EventHint,
saveAttachments: boolean = false,
hint: EventHint,
): Event {
if (
event.exception === undefined ||
event.exception.values === undefined ||
hint === undefined ||
hint.originalException === undefined ||
!event.exception?.values ||
!hint.originalException ||
!originalExceptionIsZodError(hint.originalException) ||
hint.originalException.issues.length === 0
) {
return event;
}

try {
const flattenedIssues = hint.originalException.errors.map(flattenIssue);

if (saveAttachments === true) {
// Add an attachment with all issues (no limits), as well as the default
// flatten format to see if it's preferred over our custom flatten format.
const issuesToFlatten = saveAttachments
? hint.originalException.issues
: hint.originalException.issues.slice(0, limit);
const flattenedIssues = issuesToFlatten.map(flattenIssue);

if (saveAttachments) {
// Sometimes having the full error details can be helpful.
// Attachments have much higher limits, so we can include the full list of issues.
if (!Array.isArray(hint.attachments)) {
hint.attachments = [];
}
hint.attachments.push({
filename: 'zod_issues.json',
data: JSON.stringify({
issueDetails: hint.originalException.flatten(flattenIssue),
issues: flattenedIssues,
}),
});
}
Expand Down Expand Up @@ -199,7 +207,8 @@ function applyZodErrorsToEvent(
extra: {
...event.extra,
'zoderrors sentry integration parse error': {
message: `an exception was thrown while processing ZodError within applyZodErrorsToEvent()`,
message:
'an exception was thrown while processing ZodError within applyZodErrorsToEvent()',
error:
e instanceof Error
? `${e.name}: ${e.message}\n${e.stack}`
Expand Down

0 comments on commit fb69cd2

Please sign in to comment.