Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
186 lines
5.1 KiB
Text
186 lines
5.1 KiB
Text
import { defineIntegration } from '../integration.js';
|
|
import { isError } from '../utils/is.js';
|
|
import { truncate } from '../utils/string.js';
|
|
|
|
const DEFAULT_LIMIT = 10;
|
|
const INTEGRATION_NAME = 'ZodErrors';
|
|
|
|
/**
|
|
* Simplified ZodIssue type definition
|
|
*/
|
|
|
|
function originalExceptionIsZodError(originalException) {
|
|
return (
|
|
isError(originalException) &&
|
|
originalException.name === 'ZodError' &&
|
|
Array.isArray((originalException ).issues)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Formats child objects or arrays to a string
|
|
* that is preserved when sent to Sentry.
|
|
*
|
|
* Without this, we end up with something like this in Sentry:
|
|
*
|
|
* [
|
|
* [Object],
|
|
* [Object],
|
|
* [Object],
|
|
* [Object]
|
|
* ]
|
|
*/
|
|
function flattenIssue(issue) {
|
|
return {
|
|
...issue,
|
|
path: 'path' in issue && Array.isArray(issue.path) ? issue.path.join('.') : undefined,
|
|
keys: 'keys' in issue ? JSON.stringify(issue.keys) : undefined,
|
|
unionErrors: 'unionErrors' in issue ? JSON.stringify(issue.unionErrors) : undefined,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Takes ZodError issue path array and returns a flattened version as a string.
|
|
* This makes it easier to display paths within a Sentry error message.
|
|
*
|
|
* Array indexes are normalized to reduce duplicate entries
|
|
*
|
|
* @param path ZodError issue path
|
|
* @returns flattened path
|
|
*
|
|
* @example
|
|
* flattenIssuePath([0, 'foo', 1, 'bar']) // -> '<array>.foo.<array>.bar'
|
|
*/
|
|
function flattenIssuePath(path) {
|
|
return path
|
|
.map(p => {
|
|
if (typeof p === 'number') {
|
|
return '<array>';
|
|
} else {
|
|
return p;
|
|
}
|
|
})
|
|
.join('.');
|
|
}
|
|
|
|
/**
|
|
* Zod error message is a stringified version of ZodError.issues
|
|
* This doesn't display well in the Sentry UI. Replace it with something shorter.
|
|
*/
|
|
function formatIssueMessage(zodError) {
|
|
const errorKeyMap = new Set();
|
|
for (const iss of zodError.issues) {
|
|
const issuePath = flattenIssuePath(iss.path);
|
|
if (issuePath.length > 0) {
|
|
errorKeyMap.add(issuePath);
|
|
}
|
|
}
|
|
|
|
const errorKeys = Array.from(errorKeyMap);
|
|
if (errorKeys.length === 0) {
|
|
// If there are no keys, then we're likely validating the root
|
|
// variable rather than a key within an object. This attempts
|
|
// to extract what type it was that failed to validate.
|
|
// For example, z.string().parse(123) would return "string" here.
|
|
let rootExpectedType = 'variable';
|
|
if (zodError.issues.length > 0) {
|
|
const iss = zodError.issues[0];
|
|
if (iss !== undefined && 'expected' in iss && typeof iss.expected === 'string') {
|
|
rootExpectedType = iss.expected;
|
|
}
|
|
}
|
|
return `Failed to validate ${rootExpectedType}`;
|
|
}
|
|
return `Failed to validate keys: ${truncate(errorKeys.join(', '), 100)}`;
|
|
}
|
|
|
|
/**
|
|
* Applies ZodError issues to an event extra and replaces the error message
|
|
*/
|
|
function applyZodErrorsToEvent(
|
|
limit,
|
|
saveZodIssuesAsAttachment = false,
|
|
event,
|
|
hint,
|
|
) {
|
|
if (
|
|
!event.exception?.values ||
|
|
!hint.originalException ||
|
|
!originalExceptionIsZodError(hint.originalException) ||
|
|
hint.originalException.issues.length === 0
|
|
) {
|
|
return event;
|
|
}
|
|
|
|
try {
|
|
const issuesToFlatten = saveZodIssuesAsAttachment
|
|
? hint.originalException.issues
|
|
: hint.originalException.issues.slice(0, limit);
|
|
const flattenedIssues = issuesToFlatten.map(flattenIssue);
|
|
|
|
if (saveZodIssuesAsAttachment) {
|
|
// 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({
|
|
issues: flattenedIssues,
|
|
}),
|
|
});
|
|
}
|
|
|
|
return {
|
|
...event,
|
|
exception: {
|
|
...event.exception,
|
|
values: [
|
|
{
|
|
...event.exception.values[0],
|
|
value: formatIssueMessage(hint.originalException),
|
|
},
|
|
...event.exception.values.slice(1),
|
|
],
|
|
},
|
|
extra: {
|
|
...event.extra,
|
|
'zoderror.issues': flattenedIssues.slice(0, limit),
|
|
},
|
|
};
|
|
} catch (e) {
|
|
// Hopefully we never throw errors here, but record it
|
|
// with the event just in case.
|
|
return {
|
|
...event,
|
|
extra: {
|
|
...event.extra,
|
|
'zoderrors sentry integration parse error': {
|
|
message: 'an exception was thrown while processing ZodError within applyZodErrorsToEvent()',
|
|
error: e instanceof Error ? `${e.name}: ${e.message}\n${e.stack}` : 'unknown',
|
|
},
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
const _zodErrorsIntegration = ((options = {}) => {
|
|
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
|
|
return {
|
|
name: INTEGRATION_NAME,
|
|
processEvent(originalEvent, hint) {
|
|
const processedEvent = applyZodErrorsToEvent(limit, options.saveZodIssuesAsAttachment, originalEvent, hint);
|
|
return processedEvent;
|
|
},
|
|
};
|
|
}) ;
|
|
|
|
/**
|
|
* Sentry integration to process Zod errors, making them easier to work with in Sentry.
|
|
*/
|
|
const zodErrorsIntegration = defineIntegration(_zodErrorsIntegration);
|
|
|
|
export { applyZodErrorsToEvent, flattenIssue, flattenIssuePath, formatIssueMessage, zodErrorsIntegration };
|
|
//# sourceMappingURL=zoderrors.js.map
|