Skip to content

Commit 9c952ff

Browse files
authored
Merge pull request #58 from DouglasNeuroInformatics/dev
feat: allow using Zod schema directly to parse request body
2 parents 0eccb40 + 7c8e022 commit 9c952ff

File tree

9 files changed

+32
-29
lines changed

9 files changed

+32
-29
lines changed

example/app.test.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, vi } from 'vitest';
66
import { e2e } from '../src/testing/index.js';
77
import app from './app.js';
88

9-
import type { CreateCatDto } from './cats/dto/create-cat.dto.js';
10-
import type { Cat } from './cats/schemas/cat.schema.js';
9+
import type { $Cat, $CreateCatData } from './cats/schemas/cat.schema.js';
1110

1211
vi.unmock('@prisma/client');
1312

@@ -102,7 +101,7 @@ e2e(app, ({ api }) => {
102101

103102
describe('/cats', (it) => {
104103
let accessToken: string;
105-
let createdCat: Cat;
104+
let createdCat: $Cat;
106105

107106
beforeEach(async () => {
108107
const response = await api.post('/auth/login').send({ password: 'password', username: 'admin' });
@@ -126,13 +125,13 @@ e2e(app, ({ api }) => {
126125
.send({
127126
age: 1,
128127
name: 'Winston'
129-
} satisfies CreateCatDto);
128+
} satisfies $CreateCatData);
130129
expect(response.status).toBe(201);
131130
expect(response.body).toMatchObject({
132131
_id: expect.any(String),
133132
age: expect.any(Number),
134133
name: expect.any(String)
135-
} satisfies Cat);
134+
} satisfies $Cat);
136135
createdCat = response.body;
137136
});
138137

example/cats/__tests__/cats.service.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
55
import { PRISMA_CLIENT_TOKEN } from '../../../src/modules/prisma/prisma.config.js';
66
import { CatsService } from '../cats.service.js';
77

8-
import type { Cat } from '../schemas/cat.schema.js';
8+
import type { $Cat } from '../schemas/cat.schema.js';
99

1010
describe('CatsService', () => {
1111
let catsService: CatsService;
@@ -29,7 +29,7 @@ describe('CatsService', () => {
2929

3030
describe('create', () => {
3131
it('should create a new cat successfully', async () => {
32-
const mockCat: Omit<Cat, '_id'> = {
32+
const mockCat: Omit<$Cat, '_id'> = {
3333
age: 3,
3434
name: 'Whiskers'
3535
};
@@ -46,7 +46,7 @@ describe('CatsService', () => {
4646
});
4747

4848
it('should throw InternalServerErrorException when creation fails', async () => {
49-
const mockCat: Omit<Cat, '_id'> = {
49+
const mockCat: Omit<$Cat, '_id'> = {
5050
age: 3,
5151
name: 'Whiskers'
5252
};
@@ -57,7 +57,7 @@ describe('CatsService', () => {
5757

5858
describe('findAll', () => {
5959
it('should return all cats successfully', async () => {
60-
const mockCats: Cat[] = [
60+
const mockCats: $Cat[] = [
6161
{ _id: '1', age: 3, name: 'Whiskers' },
6262
{ _id: '2', age: 2, name: 'Mittens' }
6363
];

example/cats/cats.controller.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ import { ApiOperation } from '@nestjs/swagger';
33

44
import { RouteAccess } from '../../src/index.js';
55
import { CatsService } from './cats.service.js';
6-
import { CreateCatDto } from './dto/create-cat.dto.js';
7-
8-
import type { Cat } from './schemas/cat.schema.js';
6+
import { $Cat, $CreateCatData } from './schemas/cat.schema.js';
97

108
@Controller('cats')
119
export class CatsController {
@@ -14,14 +12,14 @@ export class CatsController {
1412
@ApiOperation({ summary: 'Create Cat' })
1513
@Post()
1614
@RouteAccess({ action: 'create', subject: 'Cat' })
17-
async create(@Body() createCatDto: CreateCatDto): Promise<Cat> {
18-
return this.catsService.create(createCatDto);
15+
async create(@Body() data: $CreateCatData): Promise<$Cat> {
16+
return this.catsService.create(data);
1917
}
2018

2119
@ApiOperation({ summary: 'Get All Cats' })
2220
@Get()
2321
@RouteAccess({ action: 'read', subject: 'Cat' })
24-
async findAll(): Promise<Cat[]> {
22+
async findAll(): Promise<$Cat[]> {
2523
return this.catsService.findAll();
2624
}
2725
}

example/cats/cats.service.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import { Injectable, InternalServerErrorException } from '@nestjs/common';
55
import { InjectPrismaClient } from '../../src/index.js';
66

77
import type { PrismaClientLike } from '../../src/index.js';
8-
import type { Cat } from './schemas/cat.schema.js';
8+
import type { $Cat } from './schemas/cat.schema.js';
99

1010
@Injectable()
1111
export class CatsService {
1212
constructor(@InjectPrismaClient() private readonly prismaClient: PrismaClientLike) {}
1313

14-
async create(cat: Omit<Cat, '_id'>): Promise<Cat> {
14+
async create(cat: Omit<$Cat, '_id'>): Promise<$Cat> {
1515
const id = crypto.randomUUID();
1616
const result = await this.prismaClient.$runCommandRaw({
1717
insert: 'Cat',
@@ -23,11 +23,11 @@ export class CatsService {
2323
return { ...cat, _id: id };
2424
}
2525

26-
async findAll(): Promise<Cat[]> {
26+
async findAll(): Promise<$Cat[]> {
2727
const result = (await this.prismaClient.$runCommandRaw({
2828
find: 'Cat',
2929
batchSize: 1000
30-
})) as { cursor: { firstBatch: Cat[]; id: string }; ok: number };
30+
})) as { cursor: { firstBatch: $Cat[]; id: string }; ok: number };
3131
if (result.ok !== 1) {
3232
throw new InternalServerErrorException('Failed to find cats');
3333
}

example/cats/dto/create-cat.dto.ts

Lines changed: 0 additions & 4 deletions
This file was deleted.

example/cats/schemas/cat.schema.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { z } from 'zod/v4';
22

3-
export type Cat = z.infer<typeof $Cat>;
3+
export type $Cat = z.infer<typeof $Cat>;
44
export const $Cat = z.object({
5-
_id: z.string().uuid(),
5+
_id: z.uuid(),
66
age: z.number(),
77
name: z.string()
88
});
9+
10+
export type $CreateCatData = z.infer<typeof $CreateCatData>;
11+
export const $CreateCatData = $Cat.omit({ _id: true });

example/pages/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { useState } from 'react';
22
import type { JSX } from 'react';
33

4-
import type { Cat } from '../cats/schemas/cat.schema.js';
4+
import type { $Cat } from '../cats/schemas/cat.schema.js';
55

66
type CatsProps = {
7-
cats: Cat[];
7+
cats: $Cat[];
88
};
99

1010
const Cats: React.FC<CatsProps> = ({ cats }): JSX.Element => {

src/modules/mail/mail.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { Module } from '@nestjs/common';
1+
import { Global, Module } from '@nestjs/common';
22

33
import { ConfigurableMailModule } from './mail.config.js';
44
import { MailService } from './mail.service.js';
55

6+
@Global()
67
@Module({
78
exports: [MailService],
89
providers: [MailService]

src/pipes/validation.pipe.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Injectable } from '@nestjs/common';
22
import type { ArgumentMetadata, PipeTransform } from '@nestjs/common';
3+
import { z } from 'zod/v4';
34

45
import { getValidationSchema, parseRequestBody } from '../utils/validation.utils.js';
56

@@ -12,7 +13,12 @@ export class ValidationPipe implements PipeTransform {
1213
throw new Error('Metatype must be defined!');
1314
}
1415

15-
const schema = getValidationSchema(metatype);
16+
let schema: z.ZodType;
17+
if (metatype instanceof z.ZodType) {
18+
schema = metatype;
19+
} else {
20+
schema = getValidationSchema(metatype);
21+
}
1622

1723
return parseRequestBody(value, schema);
1824
}

0 commit comments

Comments
 (0)