Skip to content

Commit f399a95

Browse files
authored
Merge pull request #3070 from SeedCompany/validation
2 parents eb741e6 + ccd9458 commit f399a95

File tree

10 files changed

+92
-46
lines changed

10 files changed

+92
-46
lines changed

src/common/validators/short-id.validator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ArgumentMetadata, PipeTransform } from '@nestjs/common';
22
import { ValidationOptions } from 'class-validator';
3-
import { ValidationException } from '../../core/validation.pipe';
3+
import { ValidationException } from '~/core/validation';
44
import { isValidId } from '../generate-id';
55
import { ValidateBy } from './validateBy';
66

src/common/validators/validateBy.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
1-
import { FnLike } from '@seedcompany/common';
1+
import { Type } from '@nestjs/common';
22
import {
33
registerDecorator,
44
ValidationOptions,
55
ValidatorConstraintInterface,
66
} from 'class-validator';
7+
import { MergeExclusive } from 'type-fest';
78

8-
export interface ValidateByOptions {
9-
name: string;
10-
constraints?: any[];
11-
validator: ValidatorConstraintInterface | FnLike;
12-
async?: boolean;
13-
}
9+
export type ValidateByOptions =
10+
| MergeExclusive<
11+
{
12+
validator: ValidatorConstraintInterface;
13+
name: string;
14+
async?: boolean;
15+
constraints?: any[];
16+
},
17+
{
18+
validator: Type<ValidatorConstraintInterface>;
19+
constraints?: any[];
20+
}
21+
>
22+
| Type<ValidatorConstraintInterface>;
1423

1524
export const ValidateBy =
1625
(
@@ -19,13 +28,11 @@ export const ValidateBy =
1928
): PropertyDecorator =>
2029
(object: Record<string, any>, propertyName: string | symbol) => {
2130
registerDecorator({
22-
name: options.name,
2331
target: object.constructor,
2432
propertyName: propertyName as string,
2533
options: validationOptions,
26-
constraints: options.constraints,
27-
validator: options.validator,
28-
async: options.async,
34+
...options,
35+
validator: typeof options === 'function' ? options : options.validator,
2936
});
3037
};
3138

src/components/changeset/changeset.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import { ChangesetAwareResolver } from './changeset-aware.resolver';
44
import { ValidateChangesetEditablePipe } from './changeset.arg';
55
import { ChangesetRepository } from './changeset.repository';
66
import { ChangesetResolver } from './changeset.resolver';
7+
import { ObjectViewPipe } from './dto';
78
import { EnforceChangesetEditablePipe } from './enforce-changeset-editable.pipe';
89

910
@Module({
1011
providers: [
1112
ChangesetAwareResolver,
1213
ChangesetResolver,
1314
ChangesetRepository,
15+
ObjectViewPipe,
1416
{ provide: APP_PIPE, useClass: EnforceChangesetEditablePipe },
1517
ValidateChangesetEditablePipe,
1618
],

src/components/changeset/dto/changeset.args.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { PipeTransform } from '@nestjs/common';
1+
import { Injectable, PipeTransform } from '@nestjs/common';
22
import { Args, ArgsType } from '@nestjs/graphql';
3-
import { ID, IdField, ObjectView } from '../../../common';
4-
import { ValidationPipe } from '../../../core/validation.pipe';
3+
import { ID, IdField, ObjectView } from '~/common';
4+
import { ValidationPipe } from '~/core/validation';
55

66
/**
77
* A helper for id & changeset arguments.
@@ -20,15 +20,15 @@ export type IdsAndView = ChangesetIds & { view: ObjectView };
2020
export const IdsAndViewArg = () =>
2121
Args({ type: () => ChangesetIds }, ObjectViewPipe);
2222

23-
class ObjectViewPipe implements PipeTransform {
24-
async transform({ id, changeset }: ChangesetIds) {
25-
await new ValidationPipe().transform(
26-
{ id, changeset },
27-
{
28-
metatype: ChangesetIds,
29-
type: 'body',
30-
},
31-
);
23+
@Injectable()
24+
export class ObjectViewPipe implements PipeTransform {
25+
constructor(private readonly validator: ValidationPipe) {}
26+
27+
async transform(input: ChangesetIds) {
28+
const { id, changeset } = await this.validator.transform(input, {
29+
metatype: ChangesetIds,
30+
type: 'body',
31+
});
3232
const view: ObjectView = changeset ? { changeset } : { active: true };
3333
return { id, changeset, view };
3434
}

src/core/core.module.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Global, Module } from '@nestjs/common';
2-
import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
2+
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
33
import { DataLoaderModule } from '@seedcompany/data-loader';
44
import { EmailModule } from '@seedcompany/nestjs-email';
55
import { ConsoleModule } from 'nestjs-console';
@@ -19,7 +19,7 @@ import { ResourceModule } from './resources/resource.module';
1919
import { ScalarProviders } from './scalars.resolver';
2020
import { TimeoutInterceptor } from './timeout.interceptor';
2121
import { TracingModule } from './tracing';
22-
import { ValidationPipe } from './validation.pipe';
22+
import { ValidationModule } from './validation/validation.module';
2323
import { WaitResolver } from './wait.resolver';
2424

2525
@Global()
@@ -36,13 +36,13 @@ import { WaitResolver } from './wait.resolver';
3636
EventsModule,
3737
TracingModule,
3838
ResourceModule,
39+
ValidationModule,
3940
],
4041
providers: [
4142
AwsS3Factory,
4243
ExceptionNormalizer,
4344
ExceptionFilter,
4445
{ provide: APP_FILTER, useExisting: ExceptionFilter },
45-
{ provide: APP_PIPE, useClass: ValidationPipe },
4646
{ provide: APP_INTERCEPTOR, useClass: TimeoutInterceptor },
4747
WaitResolver,
4848
...ScalarProviders,
@@ -60,6 +60,7 @@ import { WaitResolver } from './wait.resolver';
6060
EventsModule,
6161
ResourceModule,
6262
TracingModule,
63+
ValidationModule,
6364
],
6465
})
6566
export class CoreModule {}

src/core/exception/exception.filter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { GqlContextType, GqlExceptionFilter } from '@nestjs/graphql';
44
import { mapValues } from '@seedcompany/common';
55
import { ConfigService } from '../config/config.service';
66
import { ILogger, Logger, LogLevel } from '../logger';
7-
import { ValidationException } from '../validation.pipe';
7+
import { ValidationException } from '../validation';
88
import { ExceptionJson, ExceptionNormalizer } from './exception.normalizer';
99
import { isFromHackAttempt } from './is-from-hack-attempt';
1010

src/core/validation/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './validation.exception';
2+
export * from './validation.pipe';

src/core/validation.pipe.ts renamed to src/core/validation/validation.exception.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,9 @@
1-
import {
2-
ValidationPipe as BaseValidationPipe,
3-
Injectable,
4-
ValidationError,
5-
} from '@nestjs/common';
1+
import { ValidationError } from '@nestjs/common';
62
import { entries } from '@seedcompany/common';
73
import { SetRequired } from 'type-fest';
84
import { fileURLToPath } from 'url';
9-
import { ClientException } from '../common/exceptions';
10-
import { jestSkipFileInExceptionSource } from './exception';
11-
12-
@Injectable()
13-
export class ValidationPipe extends BaseValidationPipe {
14-
constructor() {
15-
super({
16-
transform: true,
17-
skipMissingProperties: true,
18-
exceptionFactory: (es) => new ValidationException(es),
19-
});
20-
}
21-
}
5+
import { ClientException } from '~/common/exceptions';
6+
import { jestSkipFileInExceptionSource } from '../exception';
227

238
export class ValidationException extends ClientException {
249
readonly errors: Record<string, Record<string, string>>;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Module } from '@nestjs/common';
2+
import { APP_PIPE } from '@nestjs/core';
3+
import { Validator } from 'class-validator';
4+
import { ValidationPipe } from './validation.pipe';
5+
6+
@Module({
7+
providers: [
8+
Validator,
9+
ValidationPipe,
10+
{ provide: APP_PIPE, useExisting: ValidationPipe },
11+
],
12+
exports: [ValidationPipe],
13+
})
14+
export class ValidationModule {}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import {
2+
ValidationPipe as BaseValidationPipe,
3+
Injectable,
4+
} from '@nestjs/common';
5+
import { ModuleRef } from '@nestjs/core';
6+
import { useContainer, ValidatorOptions } from 'class-validator';
7+
import { ValidationException } from './validation.exception';
8+
9+
@Injectable()
10+
export class ValidationPipe extends BaseValidationPipe {
11+
constructor(private readonly moduleRef: ModuleRef) {
12+
super({
13+
transform: true,
14+
skipMissingProperties: true,
15+
exceptionFactory: (es) => new ValidationException(es),
16+
});
17+
}
18+
private readonly containerForLib = {
19+
get: (type: any) => {
20+
if (type.name === 'CustomConstraint') {
21+
// Prototype-less constraints. Null to fall back to default, which just calls constructor once.
22+
return null;
23+
}
24+
return this.moduleRef.get(type);
25+
},
26+
};
27+
28+
protected async validate(
29+
object: object,
30+
validatorOptions?: ValidatorOptions,
31+
) {
32+
useContainer(this.containerForLib, { fallback: true });
33+
return await super.validate(object, validatorOptions);
34+
}
35+
}

0 commit comments

Comments
 (0)