Skip to content

fix: internal error when Prisma client runtime error occurs #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zenstack-monorepo",
"version": "0.2.4",
"version": "0.2.8",
"description": "",
"scripts": {
"build": "pnpm -r build",
Expand Down
3 changes: 1 addition & 2 deletions packages/internal/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/internal",
"version": "0.2.4",
"version": "0.2.8",
"displayName": "ZenStack Internal Library",
"description": "ZenStack internal runtime library. This package is for supporting runtime functionality of ZenStack and not supposed to be used directly.",
"repository": {
Expand All @@ -12,7 +12,6 @@
"scripts": {
"build": "tsc",
"watch": "tsc --watch",
"test": "jest",
"lint": "eslint src --ext ts",
"prepublishOnly": "pnpm build"
},
Expand Down
38 changes: 20 additions & 18 deletions packages/internal/src/handler/data/handler.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { PrismaClientKnownRequestError } from '@prisma/client/runtime';
import cuid from 'cuid';
import { NextApiRequest, NextApiResponse } from 'next';
import { TRANSACTION_FIELD_NAME } from '../../constants';
import { RequestHandlerOptions } from '../../request-handler';
import {
DbClientContract,
DbOperations,
getServerErrorMessage,
QueryContext,
ServerErrorCode,
Service,
Expand Down Expand Up @@ -97,12 +97,14 @@ export default class DataHandler<DbClient extends DbClientContract>
message: err.message,
});
}
} else if (err instanceof PrismaClientKnownRequestError) {
} else if (this.isPrismaClientKnownRequestError(err)) {
// errors thrown by Prisma, try mapping to a known error
if (PRISMA_ERROR_MAPPING[err.code]) {
res.status(400).send({
code: PRISMA_ERROR_MAPPING[err.code],
message: 'database access error',
message: getServerErrorMessage(
PRISMA_ERROR_MAPPING[err.code]
),
});
} else {
res.status(400).send({
Expand All @@ -118,7 +120,10 @@ export default class DataHandler<DbClient extends DbClientContract>
if (err instanceof Error) {
console.error(err.stack);
}
res.status(500).send({ error: ServerErrorCode.UNKNOWN });
res.status(500).send({
error: ServerErrorCode.UNKNOWN,
message: getServerErrorMessage(ServerErrorCode.UNKNOWN),
});
}
}
}
Expand Down Expand Up @@ -146,10 +151,7 @@ export default class DataHandler<DbClient extends DbClientContract>
);

if (result.length === 0) {
throw new RequestHandlerError(
ServerErrorCode.ENTITY_NOT_FOUND,
'not found'
);
throw new RequestHandlerError(ServerErrorCode.ENTITY_NOT_FOUND);
}
res.status(200).send(result[0]);
} else {
Expand Down Expand Up @@ -261,8 +263,7 @@ export default class DataHandler<DbClient extends DbClientContract>
);
if (result.length === 0) {
throw new RequestHandlerError(
ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED,
`create result could not be read back due to policy check`
ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED
);
}
res.status(201).send(result[0]);
Expand All @@ -272,8 +273,7 @@ export default class DataHandler<DbClient extends DbClientContract>
err.code === ServerErrorCode.DENIED_BY_POLICY
) {
throw new RequestHandlerError(
ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED,
`create result could not be read back due to policy check`
ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED
);
} else {
throw err;
Expand Down Expand Up @@ -375,8 +375,7 @@ export default class DataHandler<DbClient extends DbClientContract>
);
if (result.length === 0) {
throw new RequestHandlerError(
ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED,
`update result could not be read back due to policy check`
ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED
);
}
res.status(200).send(result[0]);
Expand All @@ -386,8 +385,7 @@ export default class DataHandler<DbClient extends DbClientContract>
err.code === ServerErrorCode.DENIED_BY_POLICY
) {
throw new RequestHandlerError(
ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED,
`update result could not be read back due to policy check`
ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED
);
} else {
throw err;
Expand Down Expand Up @@ -460,9 +458,13 @@ export default class DataHandler<DbClient extends DbClientContract>
res.status(200).send(r);
} else {
throw new RequestHandlerError(
ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED,
`delete result could not be read back due to policy check`
ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED
);
}
}

private isPrismaClientKnownRequestError(err: any): err is { code: string } {
// we can't reference Prisma generated types so need to weakly check error type
return !!err.clientVersion && typeof err.code === 'string';
}
}
7 changes: 5 additions & 2 deletions packages/internal/src/handler/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { ServerErrorCode } from '../types';
import { getServerErrorMessage, ServerErrorCode } from '../types';

/**
* Defines contract for a Next.js API endpoint handler.
Expand All @@ -23,7 +23,10 @@ export interface RequestHandler {
* Error thrown during request handling
*/
export class RequestHandlerError extends Error {
constructor(public readonly code: ServerErrorCode, message: string) {
constructor(public readonly code: ServerErrorCode, message?: string) {
message = message
? `${getServerErrorMessage(code)}: ${message}`
: getServerErrorMessage(code);
super(message);
}

Expand Down
28 changes: 28 additions & 0 deletions packages/internal/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,31 @@ export enum ServerErrorCode {
*/
UNKNOWN = 'UNKNOWN',
}

export function getServerErrorMessage(code: ServerErrorCode): string {
switch (code) {
case ServerErrorCode.ENTITY_NOT_FOUND:
return 'the requested entity is not found';

case ServerErrorCode.INVALID_REQUEST_PARAMS:
return 'request parameters are invalid';

case ServerErrorCode.DENIED_BY_POLICY:
return 'the request was denied due to access policy violation';

case ServerErrorCode.UNIQUE_CONSTRAINT_VIOLATION:
return 'the request failed because of database unique constraint violation';

case ServerErrorCode.REFERENCE_CONSTRAINT_VIOLATION:
return 'the request failed because of database foreign key constraint violation';

case ServerErrorCode.READ_BACK_AFTER_WRITE_DENIED:
return 'the write operation succeeded, but the data cannot be read back due to access policy violation';

case ServerErrorCode.UNKNOWN:
return 'an unknown error occurred';

default:
return `generic error: ${code}`;
}
}
2 changes: 1 addition & 1 deletion packages/runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/runtime",
"displayName": "ZenStack Runtime Library",
"version": "0.2.4",
"version": "0.2.8",
"description": "This package contains runtime library for consuming client and server side code generated by ZenStack.",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"publisher": "zenstack",
"displayName": "ZenStack Language Tools",
"description": "ZenStack is a toolkit that simplifies full-stack development",
"version": "0.2.4",
"version": "0.2.8",
"author": {
"name": "ZenStack Team"
},
Expand Down
2 changes: 1 addition & 1 deletion samples/todo/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "todo",
"version": "0.2.4",
"version": "0.2.8",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down