diff --git a/package.json b/package.json index d7dcdc11f..a7e2d3088 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "0.2.4", + "version": "0.2.8", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/internal/package.json b/packages/internal/package.json index 01022a0e8..3a1755157 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -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": { @@ -12,7 +12,6 @@ "scripts": { "build": "tsc", "watch": "tsc --watch", - "test": "jest", "lint": "eslint src --ext ts", "prepublishOnly": "pnpm build" }, diff --git a/packages/internal/src/handler/data/handler.ts b/packages/internal/src/handler/data/handler.ts index cc9213fd1..916c4958c 100644 --- a/packages/internal/src/handler/data/handler.ts +++ b/packages/internal/src/handler/data/handler.ts @@ -1,5 +1,4 @@ /* 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'; @@ -7,6 +6,7 @@ import { RequestHandlerOptions } from '../../request-handler'; import { DbClientContract, DbOperations, + getServerErrorMessage, QueryContext, ServerErrorCode, Service, @@ -97,12 +97,14 @@ export default class DataHandler 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({ @@ -118,7 +120,10 @@ export default class DataHandler 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), + }); } } } @@ -146,10 +151,7 @@ export default class DataHandler ); 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 { @@ -261,8 +263,7 @@ export default class DataHandler ); 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]); @@ -272,8 +273,7 @@ export default class DataHandler 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; @@ -375,8 +375,7 @@ export default class DataHandler ); 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]); @@ -386,8 +385,7 @@ export default class DataHandler 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; @@ -460,9 +458,13 @@ export default class DataHandler 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'; + } } diff --git a/packages/internal/src/handler/types.ts b/packages/internal/src/handler/types.ts index 757aa4f2c..b481f6208 100644 --- a/packages/internal/src/handler/types.ts +++ b/packages/internal/src/handler/types.ts @@ -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. @@ -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); } diff --git a/packages/internal/src/types.ts b/packages/internal/src/types.ts index e841423d9..9f0a01b69 100644 --- a/packages/internal/src/types.ts +++ b/packages/internal/src/types.ts @@ -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}`; + } +} diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 1e3951913..c7da9dc11 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -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", diff --git a/packages/schema/package.json b/packages/schema/package.json index f117429fb..984c6fe8f 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -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" }, diff --git a/samples/todo/package.json b/samples/todo/package.json index 53daf9bf4..8a89ca097 100644 --- a/samples/todo/package.json +++ b/samples/todo/package.json @@ -1,6 +1,6 @@ { "name": "todo", - "version": "0.2.4", + "version": "0.2.8", "private": true, "scripts": { "dev": "next dev",