Skip to content
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

[WIP] Feature: Add (Nested) Validation to Create / Update / Delete Args #23

Closed
Closed
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: 2 additions & 0 deletions examples/nest-graphql-typeorm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"@nestjs/platform-express": "^6.7.2",
"@nestjs/typeorm": "^6.2.0",
"apollo-server-express": "^2.9.7",
"class-transformer": "^0.2.3",
"class-validator": "^0.11.0",
"graphql": "^14.5.8",
"graphql-tools": "^4.0.6",
"pg": "^7.12.1",
Expand Down
12 changes: 11 additions & 1 deletion examples/nest-graphql-typeorm/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,17 @@ import { AppModule } from './app.module';

async function bootstrap(): Promise<void> {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ transform: true }));

app.useGlobalPipes(
new ValidationPipe({
transform: true,
whitelist: true,
forbidNonWhitelisted: true,
skipMissingProperties: false,
forbidUnknownValues: true,
}),
);

await app.listen(3000);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { CreateOneArgsType } from '@nestjs-query/query-graphql';
import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';
import { ArgsType, Field } from 'type-graphql';
import { TodoItemInputDTO } from '../dto/todo-item-input.dto';

@ArgsType()
export class CreateOneTodoItemArgs extends CreateOneArgsType(TodoItemInputDTO) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this required for the example?

@Type(() => TodoItemInputDTO)
@ValidateNested()
@Field({
description: 'The ToDo Item to be created',
})
input!: TodoItemInputDTO;
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { IsString, MaxLength, IsBoolean } from 'class-validator';
import { Field, InputType } from 'type-graphql';

@InputType('TodoItemInput')
export class TodoItemInputDTO {
@IsString()
@MaxLength(20)
@Field()
title!: string;

@IsBoolean()
@Field()
completed!: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ import { TodoItemInputDTO } from './dto/todo-item-input.dto';
import { TodoItemDTO } from './dto/todo-item.dto';
import { TodoItemService } from './todo-item.service';
import { TodoItemConnection, TodoItemQuery } from './types';
import { CreateOneTodoItemArgs } from './args/custom-args.types';

const guards = [AuthGuard];

@Resolver(() => TodoItemDTO)
export class TodoItemResolver extends CRUDResolver(TodoItemDTO, {
CreateDTOClass: TodoItemInputDTO,
UpdateDTOClass: TodoItemInputDTO,
create: { guards },
create: {
guards,
CreateOneArgs: CreateOneTodoItemArgs,
CreateDTOClass: TodoItemInputDTO,
},
update: { guards },
delete: { guards },
relations: {
Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,14 @@ describe('DeleteResolver', () => {
it('should call the service deleteOne with the provided input', async () => {
const mockService = mock<QueryService<TestResolverDTO>>();
const input: DeleteOneArgsType = {
input: 'id-1',
id: 'id-1',
};
const output: TestResolverDTO = {
id: 'id-1',
stringField: 'foo',
};
const resolver = new TestResolver(instance(mockService));
when(mockService.deleteOne(input.input)).thenResolve(output);
when(mockService.deleteOne(input.id)).thenResolve(output);
const result = await resolver.deleteOne(input);
return expect(result).toEqual(output);
});
Expand Down
4 changes: 2 additions & 2 deletions packages/query-graphql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"@nestjs/common": "^6.9.0",
"@nestjs/graphql": "^6.5.3",
"class-transformer": "^0.2.3",
"class-validator": "^0.10.2",
"class-validator": "^0.11.0",
"graphql": "^14.5.8",
"graphql-relay": "^0.6.0",
"reflect-metadata": "^0.1.13",
Expand All @@ -55,7 +55,7 @@
"@types/node-fetch": "^2.5.4",
"@types/pluralize": "0.0.29",
"class-transformer": "^0.2.3",
"class-validator": "^0.10.2",
"class-validator": "^0.11.0",
"graphql": "^14.5.8",
"graphql-relay": "^0.6.0",
"reflect-metadata": "^0.1.13",
Expand Down
2 changes: 1 addition & 1 deletion packages/query-graphql/src/resolvers/delete.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const Deletable = <DTO>(DTOClass: Class<DTO>, opts: DeleteResolverOpts<DT
@ResolverMutation(() => DeleteOneResponse, { name: `deleteOne${baseName}` }, commonResolverOpts, opts.one ?? {})
async deleteOne(@Args() input: DO): Promise<Partial<DTO>> {
const deleteOne = await transformAndValidate(DO, input);
return this.service.deleteOne(deleteOne.input);
return this.service.deleteOne(deleteOne.id);
}

@ResolverMutation(() => DMR, { name: `deleteMany${pluralBaseName}` }, commonResolverOpts, opts.many ?? {})
Expand Down
10 changes: 7 additions & 3 deletions packages/query-graphql/src/types/create-many-args.type.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { Class } from '@nestjs-query/core';
import { Field, ArgsType } from 'type-graphql';
import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';
import { ArgsType, Field } from 'type-graphql';

export interface CreateManyArgsType<C> {
input: C[];
}

export function CreateManyArgsType<C>(ITemClass: Class<C>): Class<CreateManyArgsType<C>> {
export function CreateManyArgsType<C>(ItemClass: Class<C>): Class<CreateManyArgsType<C>> {
@ArgsType()
class CreateManyArgs implements CreateManyArgsType<C> {
@Field(() => [ITemClass], { description: 'Array of records to create' })
@Type(() => ItemClass)
@ValidateNested({ each: true })
@Field(() => [ItemClass], { description: 'Array of records to create' })
input!: C[];
}
return CreateManyArgs;
Expand Down
6 changes: 5 additions & 1 deletion packages/query-graphql/src/types/create-one-args.type.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Class } from '@nestjs-query/core';
import { Field, ArgsType } from 'type-graphql';
import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';
import { ArgsType, Field } from 'type-graphql';

export interface CreateOneArgsType<C> {
input: C;
Expand All @@ -8,6 +10,8 @@ export interface CreateOneArgsType<C> {
export function CreateOneArgsType<C>(ItemClass: Class<C>): Class<CreateOneArgsType<C>> {
@ArgsType()
class CreateOneArgs implements CreateOneArgsType<C> {
@Type(() => ItemClass)
@ValidateNested()
@Field(() => ItemClass, { description: 'The record to create' })
input!: C;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/query-graphql/src/types/delete-many-args.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export interface DeleteManyArgsType<T> {
export function DeleteManyArgsType<T>(FilterType: Class<Filter<T>>): Class<DeleteManyArgsType<T>> {
@ArgsType()
class DeleteManyArgs implements DeleteManyArgsType<T> {
@Field(() => FilterType, { description: 'Filter to find records to delete' })
@IsNotEmptyObject()
@Field(() => FilterType, { description: 'Filter to find records to delete' })
input!: Filter<T>;
}
return DeleteManyArgs;
Expand Down
6 changes: 4 additions & 2 deletions packages/query-graphql/src/types/delete-one-args.type.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Class } from '@nestjs-query/core';
import { Field, ID, ArgsType } from 'type-graphql';
import { IsNotEmpty } from 'class-validator';

export interface DeleteOneArgsType {
input: string | number;
id: string | number;
}

/** @internal */
Expand All @@ -13,8 +14,9 @@ export function DeleteOneArgsType(): Class<DeleteOneArgsType> {
}
@ArgsType()
class DeleteOneArgs implements DeleteOneArgsType {
@IsNotEmpty()
@Field(() => ID, { description: 'The id of the record to delete.' })
input!: string | number;
id!: string | number;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should update the docs to reflect this change.

}
deleteOneArgsType = DeleteOneArgs;
return deleteOneArgsType;
Expand Down
7 changes: 5 additions & 2 deletions packages/query-graphql/src/types/update-many-args.type.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DeepPartial, Filter, Class } from '@nestjs-query/core';
import { Field, ArgsType } from 'type-graphql';
import { IsNotEmptyObject } from 'class-validator';
import { IsNotEmptyObject, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';

export interface UpdateManyArgsType<T, U extends DeepPartial<T>> {
filter: Filter<T>;
Expand All @@ -13,10 +14,12 @@ export function UpdateManyArgsType<T, U extends DeepPartial<T>>(
): Class<UpdateManyArgsType<T, U>> {
@ArgsType()
class UpdateManyArgs implements UpdateManyArgsType<T, U> {
@Field(() => FilterType, { description: 'Filter used to find fields to update' })
@IsNotEmptyObject()
@Field(() => FilterType, { description: 'Filter used to find fields to update' })
filter!: Filter<T>;

@Type(() => UpdateType)
@ValidateNested()
@Field(() => UpdateType, { description: 'The update to apply to all records found using the filter' })
input!: U;
}
Expand Down
5 changes: 5 additions & 0 deletions packages/query-graphql/src/types/update-one-args.type.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { DeepPartial, Class } from '@nestjs-query/core';
import { Field, ID, ArgsType } from 'type-graphql';
import { IsNotEmpty, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';

export interface UpdateOneArgsType<DTO, U extends DeepPartial<DTO>> {
id: string | number;
Expand All @@ -9,9 +11,12 @@ export interface UpdateOneArgsType<DTO, U extends DeepPartial<DTO>> {
export function UpdateOneArgsType<T, U extends DeepPartial<T>>(UpdateType: Class<U>): Class<UpdateOneArgsType<T, U>> {
@ArgsType()
class UpdateOneArgs implements UpdateOneArgsType<T, U> {
@IsNotEmpty()
@Field(() => ID, { description: 'The id of the record to update' })
id!: string | number;

@Type(() => UpdateType)
@ValidateNested()
@Field(() => UpdateType, { description: 'The update to apply.' })
input!: U;
}
Expand Down