Skip to content

Commit

Permalink
fix: improve stacktrace of errors generated by proxied Prisma methods (
Browse files Browse the repository at this point in the history
  • Loading branch information
ymc9 authored Jun 13, 2023
1 parent a078b23 commit 1b67eba
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 1 deletion.
67 changes: 66 additions & 1 deletion packages/runtime/src/enhancements/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ export class DefaultPrismaProxyHandler implements PrismaProxyHandler {
}
}

// a marker for filtering error stack trace
const ERROR_MARKER = '__error_marker__';

/**
* Makes a Prisma client proxy.
*/
Expand Down Expand Up @@ -216,9 +219,71 @@ export function makeProxy<T extends PrismaProxyHandler>(
return undefined;
}

return makeHandler(target, prop);
return createHandlerProxy(makeHandler(target, prop));
},
});

return proxy;
}

// A proxy for capturing errors and processing stack trace
function createHandlerProxy<T extends PrismaProxyHandler>(handler: T): T {
return new Proxy(handler, {
get(target, propKey) {
const prop = target[propKey as keyof T];
if (typeof prop !== 'function') {
return prop;
}

// eslint-disable-next-line @typescript-eslint/ban-types
const origMethod = prop as Function;
return async function (...args: any[]) {
const _err = new Error(ERROR_MARKER);
try {
return await origMethod.apply(handler, args);
} catch (err) {
if (_err.stack && err instanceof Error) {
(err as any).internalStack = err.stack;
err.stack = cleanCallStack(_err.stack, propKey.toString(), err.message);
}
throw err;
}
};
},
});
}

// Filter out @zenstackhq/runtime stack (generated by proxy) from stack trace
function cleanCallStack(stack: string, method: string, message: string) {
// message line
let resultStack = `Error calling enhanced Prisma method \`${method}\`: ${message}`;

const lines = stack.split('\n');
let foundMarker = false;

for (let i = 0; i < lines.length; i++) {
const line = lines[i];

if (!foundMarker) {
// find marker, then stack trace lines follow
if (line.includes(ERROR_MARKER)) {
foundMarker = true;
}
continue;
}

// skip leading zenstack and anonymous lines
if (line.includes('@zenstackhq/runtime') || line.includes('<anonymous>')) {
continue;
}

// capture remaining lines
resultStack += lines
.slice(i)
.map((l) => '\n' + l)
.join();
break;
}

return resultStack;
}
39 changes: 39 additions & 0 deletions tests/integration/tests/misc/stacktrace.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { loadSchema } from '@zenstackhq/testtools';
import path from 'path';

describe('Stack trace tests', () => {
let origDir: string;

beforeAll(async () => {
origDir = path.resolve('.');
});

afterEach(() => {
process.chdir(origDir);
});

it('stack trace', async () => {
const { withPolicy } = await loadSchema(
`
model Model {
id String @id @default(uuid())
}
`
);

const db = withPolicy();
let error: Error | undefined = undefined;

try {
await db.model.create({ data: {} });
} catch (err) {
error = err as Error;
}

expect(error?.stack).toContain(
"Error calling enhanced Prisma method `create`: denied by policy: model entities failed 'create' check"
);
expect(error?.stack).toContain(`misc/stacktrace.test.ts`);
expect((error as any).internalStack).toBeTruthy();
});
});

0 comments on commit 1b67eba

Please sign in to comment.