Skip to content

Commit

Permalink
feat: extended trpc error for better prisma error management
Browse files Browse the repository at this point in the history
  • Loading branch information
yoannfleurydev committed Sep 29, 2023
1 parent 2c4891e commit 72f48a6
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 53 deletions.
12 changes: 10 additions & 2 deletions src/features/users/PageUserCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,16 @@ export default function PageUserCreate() {
router.back();
},
onError: (error) => {
if (error.data?.code === 'CONFLICT') {
form.setErrors({ email: t('users:data.email.alreadyUsed') });
if (
error.data?.code === 'CONFLICT' &&
// TODO faire un guard et qui check les clefs etc.
Array.isArray(error.data.prismaError?.target) &&
error.data.prismaError?.target?.includes('email')
) {
// form.setErrors({ email: t('users:data.email.alreadyUsed') });
form.setErrors({
[error.data.prismaError?.target[0]]: 'Oops, already used',
});
return;
}
toastError({
Expand Down
7 changes: 3 additions & 4 deletions src/server/api/routers/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { TRPCError } from '@trpc/server';
import { z } from 'zod';

import { adminProcedure, createTRPCRouter } from '@/server/api/trpc';
import { prismaThrowFormatedTRPCError } from '@/server/db';
import { ExtendedTRPCError, prismaThrowFormatedTRPCError } from '@/server/db';

const zUserRole = () => z.enum(['USER', 'ADMIN']).catch('USER');

Expand Down Expand Up @@ -104,9 +104,8 @@ export const usersRouter = createTRPCRouter({
data: input,
});
} catch (e) {
prismaThrowFormatedTRPCError(e);
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
throw new ExtendedTRPCError({
cause: e,
});
}
}),
Expand Down
17 changes: 16 additions & 1 deletion src/server/api/trpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will
* need to use are documented accordingly near the end.
*/
import { Prisma } from '@prisma/client';
import { TRPCError, initTRPC } from '@trpc/server';
import type { FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch';
import { randomUUID } from 'node:crypto';
Expand All @@ -32,11 +33,20 @@ import { logger } from '@/server/logger';
*
* @see https://trpc.io/docs/context
*/
export const createTRPCContext = async ({}: FetchCreateContextFnOptions) => {
export const createTRPCContext = async ({
req,
}: FetchCreateContextFnOptions) => {
const user = await getServerAuthSession();

const apiType: 'REST' | 'TRPC' = new URL(req.url).pathname.startsWith(
'/api/rest'
)
? 'REST'
: 'TRPC';

return {
user,
apiType,
db,
};
};
Expand All @@ -61,6 +71,10 @@ const t = initTRPC
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
prismaError:
error.cause instanceof Prisma.PrismaClientKnownRequestError
? error.cause.meta
: null,
},
};
},
Expand Down Expand Up @@ -89,6 +103,7 @@ const loggerMiddleware = t.middleware(async (opts) => {
type: opts.type,
requestId: randomUUID(),
userId: opts.ctx.user?.id,
apiType: opts.ctx.apiType,
};

// We are doing the next operation in tRPC
Expand Down
29 changes: 29 additions & 0 deletions src/server/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,42 @@ export const db =

if (env.NODE_ENV !== 'production') globalForPrisma.prisma = db;

export class ExtendedTRPCError extends TRPCError {
public readonly meta: Prisma.PrismaClientKnownRequestError['meta'];

constructor(opts: {
message?: TRPCError['message'];
code?: TRPCError['code'];
cause?: unknown;
}) {
if (
opts.cause instanceof Prisma.PrismaClientKnownRequestError &&
opts.cause.code === 'P2002'
) {
super({ code: 'CONFLICT', message: opts.message, cause: opts.cause });
return;
}

super({
code: opts.code ?? 'INTERNAL_SERVER_ERROR',
message: opts.message,
cause: opts.cause,
});
}
}

/**
*
* @deprecated
*/
export const prismaThrowFormatedTRPCError = (error: unknown) => {
if (
error instanceof Prisma.PrismaClientKnownRequestError &&
error.code === 'P2002'
) {
throw new TRPCError({
code: 'CONFLICT',
cause: error,
});
}
};
106 changes: 60 additions & 46 deletions src/server/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,55 +11,69 @@ export const logger = env.LOGGER_PRETTY
? pino(
options,
pretty({
ignore: 'scope,type,path,pid,hostname,requestId,durationMs,userId',
ignore:
'scope,type,path,pid,hostname,requestId,durationMs,userId,apiType',
messageFormat: (log, messageKey) => {
const { requestId, scope, type, path, durationMs, message, userId } =
z
.object({
requestId: z
.string()
.optional()
.catch(undefined)
.transform((v) => (v ? `${gray(v)} - ` : '')),
userId: z
.string()
.optional()
.catch(undefined)
.transform((v) =>
v ? `👤 ${cyan(v)} - ` : magenta('🕶️ Anonymous ')
),
scope: z
.string()
.optional()
.catch(undefined)
.transform((v) => (v ? gray(`(${v})`) : '')),
type: z
.string()
.optional()
.catch(undefined)
.transform((v) =>
v ? `${green(v.toLocaleUpperCase())} on ` : ''
),
path: z
.string()
.optional()
.catch(undefined)
.transform((v) => (v ? `${blue(v)} ` : '')),
durationMs: z
.number()
.optional()
.catch(undefined)
.transform((v) => (v ? gray(`(took ${v}ms) `) : '')),
message: z
.string()
.optional()
.catch(undefined)
.transform((v) => (v ? black(`${v} `) : '')),
})
.parse({ ...log, message: log[messageKey] });
const {
requestId,
scope,
type,
path,
durationMs,
message,
userId,
apiType,
} = z
.object({
requestId: z
.string()
.optional()
.catch(undefined)
.transform((v) => (v ? `${gray(v)} - ` : '')),
userId: z
.string()
.optional()
.catch(undefined)
.transform((v) =>
v ? `👤 ${cyan(v)} - ` : magenta('🕶️ Anonymous ')
),
scope: z
.string()
.optional()
.catch(undefined)
.transform((v) => (v ? gray(`(${v})`) : '')),
apiType: z
.enum(['REST', 'TRPC'])
.optional()
.catch(undefined)
.transform((v) => (v ? gray(`[${v}] `) : '')),
type: z
.string()
.optional()
.catch(undefined)
.transform((v) =>
v ? `${green(v.toLocaleUpperCase())} on ` : ''
),
path: z
.string()
.optional()
.catch(undefined)
.transform((v) => (v ? `${blue(v)} ` : '')),
durationMs: z
.number()
.optional()
.catch(undefined)
.transform((v) => (v ? gray(`(took ${v}ms) `) : '')),
message: z
.string()
.optional()
.catch(undefined)
.transform((v) => (v ? black(`${v} `) : '')),
})
.parse({ ...log, message: log[messageKey] });

return black(
`${userId}${requestId}${type}${path}· ${message}${scope}${durationMs}`
`${apiType}${userId}${requestId}${type}${path}· ${message}${scope}${durationMs}`
);
},
})
Expand Down

0 comments on commit 72f48a6

Please sign in to comment.