nestjs-backend-common 0.0.7 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/.github/README.md +6 -0
  2. package/.husky/pre-commit +5 -0
  3. package/.husky/pre-push +4 -0
  4. package/.husky/pre-version +4 -0
  5. package/.vscode/settings.json +7 -0
  6. package/dist/src/decorators/any-of/any-of.decorator.d.ts +6 -0
  7. package/dist/src/decorators/any-of/any-of.decorator.d.ts.map +1 -0
  8. package/dist/src/decorators/any-of/any-of.decorator.js +34 -0
  9. package/dist/src/decorators/any-of/any-of.decorator.js.map +1 -0
  10. package/dist/src/decorators/get-ip/get-ip.decorator.d.ts +4 -0
  11. package/dist/src/decorators/get-ip/get-ip.decorator.d.ts.map +1 -0
  12. package/dist/src/decorators/get-ip/get-ip.decorator.js +17 -0
  13. package/dist/src/decorators/get-ip/get-ip.decorator.js.map +1 -0
  14. package/dist/src/decorators/index.d.ts +3 -0
  15. package/dist/src/decorators/index.d.ts.map +1 -0
  16. package/dist/src/decorators/index.js +19 -0
  17. package/dist/src/decorators/index.js.map +1 -0
  18. package/dist/src/decorators/one-of/one-of.decorator.d.ts +6 -0
  19. package/dist/src/decorators/one-of/one-of.decorator.d.ts.map +1 -0
  20. package/dist/src/decorators/one-of/one-of.decorator.js +75 -0
  21. package/dist/src/decorators/one-of/one-of.decorator.js.map +1 -0
  22. package/dist/src/index.d.ts +1 -0
  23. package/dist/src/index.d.ts.map +1 -1
  24. package/dist/src/index.js +1 -0
  25. package/dist/src/index.js.map +1 -1
  26. package/package.json +6 -2
  27. package/src/decorators/any-of/any-of.decorator.spec.ts +144 -0
  28. package/src/decorators/any-of/any-of.decorator.ts +45 -0
  29. package/src/decorators/get-ip/get-ip.decorator.spec.ts +79 -0
  30. package/src/decorators/get-ip/get-ip.decorator.ts +22 -0
  31. package/src/decorators/index.ts +2 -0
  32. package/src/decorators/one-of/one-of.decorator.spec.ts +87 -0
  33. package/src/decorators/one-of/one-of.decorator.ts +99 -0
  34. package/src/index.ts +1 -0
package/.github/README.md CHANGED
@@ -11,3 +11,9 @@ All the utility functions and common modules I usually use in my NestJS applicat
11
11
  ```bash
12
12
  npm version patch --no-git-tag-version
13
13
  ```
14
+
15
+ ## Why `nestjs-cls` Is Peer Dependency?
16
+
17
+ This is because we wanna share the same storage between our App and this library. Thus we have to define it as peer dependency;
18
+
19
+ > **Peer Dependencies**: These are dependencies that your project hooks into or modifies in the parent project, usually a plugin for some other library or tool. Peer dependencies are not automatically installed by npm. Instead, they are only checked for, ensuring that the parent project (the project that will depend on your project) has a dependency on the project you hook into. For example, if you create a plugin for a library like Chai, you would specify Chai as a peer dependency. This ensures that the user of your plugin has the correct version of Chai installed.
@@ -0,0 +1,5 @@
1
+ #!/bin/sh
2
+ . "$(dirname "$0")/_/husky.sh"
3
+
4
+ npm run format
5
+ npm run lint:fix
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ . "$(dirname "$0")/_/husky.sh"
3
+
4
+ npm run build
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ . "$(dirname "$0")/_/husky.sh"
3
+
4
+ npm run build
@@ -0,0 +1,7 @@
1
+ {
2
+ "editor.formatOnSave": true,
3
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
4
+ "editor.codeActionsOnSave": {
5
+ "source.fixAll": "always"
6
+ }
7
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @description
3
+ * Do not annotate the fields with `@IsOptional` since it will not validate them at all.
4
+ */
5
+ export declare function AnyOf(properties: string[]): (target: any) => void;
6
+ //# sourceMappingURL=any-of.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"any-of.decorator.d.ts","sourceRoot":"","sources":["../../../../src/decorators/any-of/any-of.decorator.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IACvB,QAAQ,GAAG,UAoC7B"}
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AnyOf = AnyOf;
4
+ const common_1 = require("@nestjs/common");
5
+ const class_validator_1 = require("class-validator");
6
+ /**
7
+ * @description
8
+ * Do not annotate the fields with `@IsOptional` since it will not validate them at all.
9
+ */
10
+ function AnyOf(properties) {
11
+ return function (target) {
12
+ for (const property of properties) {
13
+ const otherProps = properties.filter((prop) => prop !== property);
14
+ const decorators = [
15
+ (0, class_validator_1.ValidateIf)((obj) => {
16
+ const isCurrentPropDefined = obj[property] !== undefined;
17
+ const areOtherPropsUndefined = otherProps.reduce((acc, prop) => acc && obj[prop] === undefined, true);
18
+ return isCurrentPropDefined || areOtherPropsUndefined;
19
+ }),
20
+ ];
21
+ // For properties that have validators that don't fail on undefined (like @ValidateNested), we need to add IsDefined when all properties are undefined.
22
+ const areAllPropsUndefinedValidator = (0, class_validator_1.ValidateIf)((obj) => {
23
+ const areAllPropsUndefined = properties.every((prop) => obj[prop] === undefined);
24
+ return areAllPropsUndefined;
25
+ });
26
+ decorators.push(areAllPropsUndefinedValidator);
27
+ decorators.push((0, class_validator_1.IsDefined)());
28
+ for (const decorator of decorators) {
29
+ (0, common_1.applyDecorators)(decorator)(target.prototype, property);
30
+ }
31
+ }
32
+ };
33
+ }
34
+ //# sourceMappingURL=any-of.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"any-of.decorator.js","sourceRoot":"","sources":["../../../../src/decorators/any-of/any-of.decorator.ts"],"names":[],"mappings":";;AAOA,sBAqCC;AA5CD,2CAAiD;AACjD,qDAAwD;AAExD;;;GAGG;AACH,SAAgB,KAAK,CAAC,UAAoB;IACxC,OAAO,UAAU,MAAW;QAC1B,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;YAClC,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAClC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,QAAQ,CAC5B,CAAC;YACF,MAAM,UAAU,GAAG;gBACjB,IAAA,4BAAU,EAAC,CAAC,GAA4B,EAAE,EAAE;oBAC1C,MAAM,oBAAoB,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,SAAS,CAAC;oBACzD,MAAM,sBAAsB,GAAG,UAAU,CAAC,MAAM,CAC9C,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,SAAS,EAC7C,IAAI,CACL,CAAC;oBAEF,OAAO,oBAAoB,IAAI,sBAAsB,CAAC;gBACxD,CAAC,CAAC;aACH,CAAC;YAEF,uJAAuJ;YACvJ,MAAM,6BAA6B,GAAG,IAAA,4BAAU,EAC9C,CAAC,GAA4B,EAAE,EAAE;gBAC/B,MAAM,oBAAoB,GAAG,UAAU,CAAC,KAAK,CAC3C,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,SAAS,CAClC,CAAC;gBAEF,OAAO,oBAAoB,CAAC;YAC9B,CAAC,CACF,CAAC;YAEF,UAAU,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;YAC/C,UAAU,CAAC,IAAI,CAAC,IAAA,2BAAS,GAAE,CAAC,CAAC;YAE7B,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;gBACnC,IAAA,wBAAe,EAAC,SAAS,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YACzD,CAAC;QACH,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { ExecutionContext } from '@nestjs/common';
2
+ export declare function getIpFactory(data: unknown, ctx: ExecutionContext): string | undefined;
3
+ export declare const GetIp: (...dataOrPipes: unknown[]) => ParameterDecorator;
4
+ //# sourceMappingURL=get-ip.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-ip.decorator.d.ts","sourceRoot":"","sources":["../../../../src/decorators/get-ip/get-ip.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAGxB,wBAAgB,YAAY,CAC1B,IAAI,EAAE,OAAO,EACb,GAAG,EAAE,gBAAgB,GACpB,MAAM,GAAG,SAAS,CAUpB;AAED,eAAO,MAAM,KAAK,mDAAqC,CAAC"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GetIp = void 0;
4
+ exports.getIpFactory = getIpFactory;
5
+ const common_1 = require("@nestjs/common");
6
+ const class_validator_1 = require("class-validator");
7
+ function getIpFactory(data, ctx) {
8
+ const request = ctx.switchToHttp().getRequest();
9
+ const ip = request.ip ??
10
+ request.headers['x-forwarded-for'] ??
11
+ request.headers['X-Real-IP'];
12
+ if ((0, class_validator_1.isIP)(ip)) {
13
+ return ip;
14
+ }
15
+ }
16
+ exports.GetIp = (0, common_1.createParamDecorator)(getIpFactory);
17
+ //# sourceMappingURL=get-ip.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-ip.decorator.js","sourceRoot":"","sources":["../../../../src/decorators/get-ip/get-ip.decorator.ts"],"names":[],"mappings":";;;AAMA,oCAaC;AAnBD,2CAGwB;AACxB,qDAAuC;AAEvC,SAAgB,YAAY,CAC1B,IAAa,EACb,GAAqB;IAErB,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;IAChD,MAAM,EAAE,GACN,OAAO,CAAC,EAAE;QACV,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC;QAClC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAE/B,IAAI,IAAA,sBAAI,EAAC,EAAE,CAAC,EAAE,CAAC;QACb,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAEY,QAAA,KAAK,GAAG,IAAA,6BAAoB,EAAC,YAAY,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './any-of/any-of.decorator';
2
+ export * from './one-of/one-of.decorator';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/decorators/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC"}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./any-of/any-of.decorator"), exports);
18
+ __exportStar(require("./one-of/one-of.decorator"), exports);
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/decorators/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,4DAA0C;AAC1C,4DAA0C"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @description
3
+ * Throws an error if more than one of the properties are present in the request body/querystring.
4
+ */
5
+ export declare function OneOf(properties: string[]): (target: any) => void;
6
+ //# sourceMappingURL=one-of.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"one-of.decorator.d.ts","sourceRoot":"","sources":["../../../../src/decorators/one-of/one-of.decorator.ts"],"names":[],"mappings":"AASA;;;GAGG;AACH,wBAAgB,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IACvB,QAAQ,GAAG,UAoB7B"}
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.OneOf = OneOf;
10
+ const common_1 = require("@nestjs/common");
11
+ const class_validator_1 = require("class-validator");
12
+ /**
13
+ * @description
14
+ * Throws an error if more than one of the properties are present in the request body/querystring.
15
+ */
16
+ function OneOf(properties) {
17
+ return function (target) {
18
+ for (const property of properties) {
19
+ const otherProps = properties.filter((prop) => prop !== property);
20
+ (0, common_1.applyDecorators)((0, class_validator_1.ValidateIf)((obj) => {
21
+ const areOtherPropsUndefined = otherProps.reduce((acc, prop) => acc && obj[prop] === undefined, true);
22
+ const isCurrentPropDefined = obj[property] !== undefined;
23
+ return isCurrentPropDefined || areOtherPropsUndefined;
24
+ }), OneOfChecker(properties))(target.prototype, property);
25
+ }
26
+ };
27
+ }
28
+ function OneOfChecker(properties) {
29
+ return function (object, propertyName) {
30
+ (0, class_validator_1.registerDecorator)({
31
+ name: 'OneOfChecker',
32
+ constraints: [properties],
33
+ target: object.constructor,
34
+ propertyName: String(propertyName),
35
+ validator: OneOfCheckerConstraint,
36
+ });
37
+ };
38
+ }
39
+ let OneOfCheckerConstraint = class OneOfCheckerConstraint {
40
+ validate(value, validationArguments) {
41
+ let [properties] = validationArguments.constraints;
42
+ properties = properties.filter((property) => property !== validationArguments.property);
43
+ const data = validationArguments.object;
44
+ for (const property of properties) {
45
+ const propertyValue = data[property];
46
+ if (!isNil(value) && !isNil(propertyValue)) {
47
+ return false;
48
+ }
49
+ }
50
+ return true;
51
+ }
52
+ defaultMessage(validationArguments) {
53
+ const [properties] = validationArguments.constraints;
54
+ const props = properties.reduce((accumulator, current, index) => {
55
+ if (index === properties.length - 2) {
56
+ accumulator += current + ', and ';
57
+ }
58
+ else if (index === properties.length - 1) {
59
+ accumulator += current;
60
+ }
61
+ else {
62
+ accumulator += current + ', ';
63
+ }
64
+ return accumulator;
65
+ }, '');
66
+ return `Do not send ${props} at the same time!`;
67
+ }
68
+ };
69
+ OneOfCheckerConstraint = __decorate([
70
+ (0, class_validator_1.ValidatorConstraint)({ name: 'OneOfChecker' })
71
+ ], OneOfCheckerConstraint);
72
+ function isNil(value) {
73
+ return value === null || value === undefined;
74
+ }
75
+ //# sourceMappingURL=one-of.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"one-of.decorator.js","sourceRoot":"","sources":["../../../../src/decorators/one-of/one-of.decorator.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
1
  export * from './correlation-id';
2
+ export * from './decorators';
2
3
  export * from './types';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC"}
package/dist/src/index.js CHANGED
@@ -15,5 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./correlation-id"), exports);
18
+ __exportStar(require("./decorators"), exports);
18
19
  __exportStar(require("./types"), exports);
19
20
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,mDAAiC;AACjC,0CAAwB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,mDAAiC;AACjC,+CAA6B;AAC7B,0CAAwB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestjs-backend-common",
3
- "version": "0.0.7",
3
+ "version": "0.1.0",
4
4
  "main": "dist/src/index",
5
5
  "types": "dist/src/index",
6
6
  "repository": {
@@ -23,13 +23,15 @@
23
23
  "homepage": "https://github.com/kasir-barati/nestjs-backend-common#readme",
24
24
  "description": "All the utility functions and common modules I usually use in my NestJS applications will be published and maintained here.",
25
25
  "peerDependencies": {
26
- "@nestjs/core": "^11.0.20",
27
26
  "@nestjs/common": "^11.0.20",
27
+ "@nestjs/core": "^11.0.20",
28
28
  "nestjs-cls": "^5.4.3",
29
29
  "reflect-metadata": "^0.2.2"
30
30
  },
31
31
  "dependencies": {
32
32
  "@nestjs/graphql": "^13.1.0",
33
+ "class-transformer": "^0.5.1",
34
+ "class-validator": "^0.14.2",
33
35
  "rxjs": "^7.8.2"
34
36
  },
35
37
  "devDependencies": {
@@ -42,6 +44,7 @@
42
44
  "eslint-plugin-perfectionist": "^4.11.0",
43
45
  "eslint-plugin-prettier": "^5.2.6",
44
46
  "globals": "^16.0.0",
47
+ "husky": "^9.1.7",
45
48
  "jest": "^29.7.0",
46
49
  "jest-extended": "^4.0.2",
47
50
  "prettier": "^3.5.3",
@@ -55,6 +58,7 @@
55
58
  "build": "rm -rf dist && tsc --project tsconfig.build.json",
56
59
  "lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
57
60
  "lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
61
+ "format": "prettier -w -u .",
58
62
  "prepublish": "npm run build"
59
63
  }
60
64
  }
@@ -0,0 +1,144 @@
1
+ import { plainToInstance, Type } from 'class-transformer';
2
+ import {
3
+ ArrayNotEmpty,
4
+ IsArray,
5
+ IsEmail,
6
+ IsString,
7
+ validate,
8
+ ValidateNested,
9
+ } from 'class-validator';
10
+
11
+ import { AnyOf } from './any-of.decorator';
12
+
13
+ @AnyOf(['email', 'phone'])
14
+ class CreateUserDto {
15
+ @IsString()
16
+ email: string;
17
+
18
+ @IsString()
19
+ phone: string;
20
+ }
21
+
22
+ class NestedExampleDto {
23
+ @ValidateNested()
24
+ @Type(() => CreateUserDto)
25
+ user: CreateUserDto;
26
+ }
27
+
28
+ export class AdminUserInput {
29
+ @IsEmail()
30
+ email: string;
31
+ }
32
+
33
+ export class ModeratorUserInput {
34
+ @IsEmail()
35
+ email: string;
36
+
37
+ @IsArray()
38
+ @ArrayNotEmpty()
39
+ @IsString({ each: true })
40
+ accessRights: string[];
41
+ }
42
+
43
+ @AnyOf(['admin', 'moderator'])
44
+ export class DefineUserInput {
45
+ @ValidateNested()
46
+ @Type(() => AdminUserInput)
47
+ admin?: AdminUserInput;
48
+
49
+ @ValidateNested()
50
+ @Type(() => ModeratorUserInput)
51
+ moderator?: ModeratorUserInput;
52
+ }
53
+
54
+ describe('AnyOf', () => {
55
+ it.each([{ email: 'email@em.cc' }, { phone: '0123456789' }])(
56
+ 'should pass when email or phone is provided',
57
+ async (data) => {
58
+ const user = plainToInstance(CreateUserDto, data);
59
+
60
+ const errors = await validate(user);
61
+
62
+ expect(errors).toHaveLength(0);
63
+ },
64
+ );
65
+
66
+ it('should throw an error when nor email or phone is provided', async () => {
67
+ const user = plainToInstance(CreateUserDto, {});
68
+
69
+ const errors = await validate(user);
70
+
71
+ expect(errors).toHaveLength(2);
72
+ expect(errors[0].constraints).toStrictEqual({
73
+ isDefined: 'email should not be null or undefined',
74
+ isString: 'email must be a string',
75
+ });
76
+ expect(errors[1].constraints).toStrictEqual({
77
+ isDefined: 'phone should not be null or undefined',
78
+ isString: 'phone must be a string',
79
+ });
80
+ });
81
+
82
+ it.each([
83
+ { user: { email: 'email@em.cc' } },
84
+ { user: { phone: '0123456789' } },
85
+ ])(
86
+ 'should pass when email or phone is provided in the nested object',
87
+ async (data) => {
88
+ const user = plainToInstance(NestedExampleDto, data);
89
+
90
+ const errors = await validate(user);
91
+
92
+ expect(errors).toHaveLength(0);
93
+ },
94
+ );
95
+
96
+ it('should throw an error when nor email or phone is provided in the nested object', async () => {
97
+ const user = plainToInstance(NestedExampleDto, { user: {} });
98
+
99
+ const errors = await validate(user);
100
+
101
+ expect(errors).toHaveLength(1);
102
+ expect(errors[0].children![0].constraints).toEqual({
103
+ isDefined: 'email should not be null or undefined',
104
+ isString: 'email must be a string',
105
+ });
106
+ expect(errors[0].children![1].constraints).toEqual({
107
+ isDefined: 'phone should not be null or undefined',
108
+ isString: 'phone must be a string',
109
+ });
110
+ });
111
+
112
+ it.each([
113
+ { admin: { email: 'admin@em.cc' } },
114
+ {
115
+ moderator: {
116
+ email: 'moderator@em.cc',
117
+ accessRights: ['read', 'write'],
118
+ },
119
+ },
120
+ ])(
121
+ 'should pass when either "admin" or "moderator" is provided',
122
+ async (data) => {
123
+ const res = plainToInstance(DefineUserInput, data);
124
+
125
+ const errors = await validate(res);
126
+
127
+ expect(errors).toHaveLength(0);
128
+ },
129
+ );
130
+
131
+ it('should throw an error when neither "admin" nor "moderator" is provided', async () => {
132
+ const res = plainToInstance(DefineUserInput, {});
133
+
134
+ const errors = await validate(res);
135
+
136
+ expect(errors).toHaveLength(2);
137
+ expect(errors[0].constraints).toEqual({
138
+ isDefined: 'admin should not be null or undefined',
139
+ });
140
+ expect(errors[1].constraints).toEqual({
141
+ isDefined: 'moderator should not be null or undefined',
142
+ });
143
+ });
144
+ });
@@ -0,0 +1,45 @@
1
+ import { applyDecorators } from '@nestjs/common';
2
+ import { IsDefined, ValidateIf } from 'class-validator';
3
+
4
+ /**
5
+ * @description
6
+ * Do not annotate the fields with `@IsOptional` since it will not validate them at all.
7
+ */
8
+ export function AnyOf(properties: string[]) {
9
+ return function (target: any) {
10
+ for (const property of properties) {
11
+ const otherProps = properties.filter(
12
+ (prop) => prop !== property,
13
+ );
14
+ const decorators = [
15
+ ValidateIf((obj: Record<string, unknown>) => {
16
+ const isCurrentPropDefined = obj[property] !== undefined;
17
+ const areOtherPropsUndefined = otherProps.reduce(
18
+ (acc, prop) => acc && obj[prop] === undefined,
19
+ true,
20
+ );
21
+
22
+ return isCurrentPropDefined || areOtherPropsUndefined;
23
+ }),
24
+ ];
25
+
26
+ // For properties that have validators that don't fail on undefined (like @ValidateNested), we need to add IsDefined when all properties are undefined.
27
+ const areAllPropsUndefinedValidator = ValidateIf(
28
+ (obj: Record<string, unknown>) => {
29
+ const areAllPropsUndefined = properties.every(
30
+ (prop) => obj[prop] === undefined,
31
+ );
32
+
33
+ return areAllPropsUndefined;
34
+ },
35
+ );
36
+
37
+ decorators.push(areAllPropsUndefinedValidator);
38
+ decorators.push(IsDefined());
39
+
40
+ for (const decorator of decorators) {
41
+ applyDecorators(decorator)(target.prototype, property);
42
+ }
43
+ }
44
+ };
45
+ }
@@ -0,0 +1,79 @@
1
+ import { getIpFactory } from './get-ip.decorator';
2
+
3
+ describe('getIpFactory', () => {
4
+ it('should return request.ip if valid', () => {
5
+ const expectedIp = '127.0.0.1';
6
+ const mockCtx = {
7
+ switchToHttp: jest.fn().mockReturnValue({
8
+ getRequest: jest.fn().mockReturnValue({ ip: expectedIp }),
9
+ }),
10
+ } as any;
11
+
12
+ const result = getIpFactory(undefined, mockCtx);
13
+
14
+ expect(result).toBe(expectedIp);
15
+ });
16
+
17
+ it('should return x-forwarded-for if request.ip is invalid but x-forwarded-for is valid', () => {
18
+ const mockCtx = {
19
+ switchToHttp: jest.fn().mockReturnValue({
20
+ getRequest: jest.fn().mockReturnValue({
21
+ ip: undefined,
22
+ headers: {
23
+ 'x-forwarded-for': '8.8.8.8',
24
+ },
25
+ }),
26
+ }),
27
+ } as any;
28
+
29
+ const result = getIpFactory(undefined, mockCtx);
30
+
31
+ expect(result).toBe('8.8.8.8');
32
+ });
33
+
34
+ it('should return X-Real-IP if others are missing', () => {
35
+ const mockCtx = {
36
+ switchToHttp: jest.fn().mockReturnValue({
37
+ getRequest: jest.fn().mockReturnValue({
38
+ ip: undefined,
39
+ headers: {
40
+ 'X-Real-IP': '10.0.0.1',
41
+ },
42
+ }),
43
+ }),
44
+ } as any;
45
+
46
+ const result = getIpFactory(undefined, mockCtx);
47
+
48
+ expect(result).toBe('10.0.0.1');
49
+ });
50
+
51
+ it('should return undefined if no IP available', () => {
52
+ const mockCtx = {
53
+ switchToHttp: jest.fn().mockReturnValue({
54
+ getRequest: jest
55
+ .fn()
56
+ .mockReturnValue({ ip: undefined, headers: {} }),
57
+ }),
58
+ } as any;
59
+
60
+ const result = getIpFactory(undefined, mockCtx);
61
+
62
+ expect(result).toBeUndefined();
63
+ });
64
+
65
+ it('should return undefined if found IP is not valid', () => {
66
+ const mockCtx = {
67
+ switchToHttp: jest.fn().mockReturnValue({
68
+ getRequest: jest.fn().mockReturnValue({
69
+ ip: 'invalid-ip',
70
+ headers: {},
71
+ }),
72
+ }),
73
+ } as any;
74
+
75
+ const result = getIpFactory(undefined, mockCtx);
76
+
77
+ expect(result).toBeUndefined();
78
+ });
79
+ });
@@ -0,0 +1,22 @@
1
+ import {
2
+ createParamDecorator,
3
+ ExecutionContext,
4
+ } from '@nestjs/common';
5
+ import { isIP } from 'class-validator';
6
+
7
+ export function getIpFactory(
8
+ data: unknown,
9
+ ctx: ExecutionContext,
10
+ ): string | undefined {
11
+ const request = ctx.switchToHttp().getRequest();
12
+ const ip: string =
13
+ request.ip ??
14
+ request.headers['x-forwarded-for'] ??
15
+ request.headers['X-Real-IP'];
16
+
17
+ if (isIP(ip)) {
18
+ return ip;
19
+ }
20
+ }
21
+
22
+ export const GetIp = createParamDecorator(getIpFactory);
@@ -0,0 +1,2 @@
1
+ export * from './any-of/any-of.decorator';
2
+ export * from './one-of/one-of.decorator';
@@ -0,0 +1,87 @@
1
+ import { plainToInstance } from 'class-transformer';
2
+ import {
3
+ IsEmail,
4
+ IsNumber,
5
+ IsOptional,
6
+ IsString,
7
+ validate,
8
+ } from 'class-validator';
9
+
10
+ import { OneOf } from './one-of.decorator';
11
+
12
+ @OneOf(['email', 'phone'])
13
+ class CreateUserDto {
14
+ @IsEmail()
15
+ email: string;
16
+
17
+ @IsNumber()
18
+ phone: number;
19
+
20
+ @IsOptional()
21
+ @IsString()
22
+ name?: string;
23
+ }
24
+
25
+ describe('OneOf', () => {
26
+ it.each<Record<string, string | number>>([
27
+ { phone: 123456789 },
28
+ { email: 'some@mail.jp' },
29
+ ])(
30
+ 'should not throw an error when only email/phone is present',
31
+ async (plain) => {
32
+ const user = plainToInstance(CreateUserDto, plain);
33
+
34
+ const errors = await validate(user);
35
+
36
+ expect(errors).toHaveLength(0);
37
+ },
38
+ );
39
+
40
+ it.each<Record<string, unknown>>([
41
+ { email: 123 },
42
+ { email: 'some junk mail' },
43
+ ])('should validate email', async (plain) => {
44
+ const user = plainToInstance(CreateUserDto, plain);
45
+
46
+ const errors = await validate(user);
47
+
48
+ expect(errors).toHaveLength(1);
49
+ expect(errors[0].constraints).toStrictEqual({
50
+ isEmail: 'email must be an email',
51
+ });
52
+ });
53
+
54
+ it.each<Record<string, unknown>>([
55
+ { phone: false },
56
+ { phone: 'junk phone number' },
57
+ ])('should validate phone', async (plain) => {
58
+ const user = plainToInstance(CreateUserDto, plain);
59
+
60
+ const errors = await validate(user);
61
+
62
+ expect(errors).toHaveLength(1);
63
+ expect(errors[0].constraints).toStrictEqual({
64
+ isNumber:
65
+ 'phone must be a number conforming to the specified constraints',
66
+ });
67
+ });
68
+
69
+ it('should not throw an error when both phone and email is present', async () => {
70
+ const plain = { email: 'gg@pp.cc', phone: 1122334455 };
71
+ const user = plainToInstance(CreateUserDto, plain);
72
+
73
+ const errors = await validate(user);
74
+
75
+ expect(errors).toHaveLength(2);
76
+ expect(errors).toContainEqual({
77
+ children: [],
78
+ constraints: {
79
+ OneOfChecker:
80
+ 'Do not send email, and phone at the same time!',
81
+ },
82
+ property: 'email',
83
+ target: { email: 'gg@pp.cc', phone: 1122334455 },
84
+ value: 'gg@pp.cc',
85
+ });
86
+ });
87
+ });
@@ -0,0 +1,99 @@
1
+ import { applyDecorators } from '@nestjs/common';
2
+ import {
3
+ registerDecorator,
4
+ ValidateIf,
5
+ ValidationArguments,
6
+ ValidatorConstraint,
7
+ ValidatorConstraintInterface,
8
+ } from 'class-validator';
9
+
10
+ /**
11
+ * @description
12
+ * Throws an error if more than one of the properties are present in the request body/querystring.
13
+ */
14
+ export function OneOf(properties: string[]) {
15
+ return function (target: any) {
16
+ for (const property of properties) {
17
+ const otherProps = properties.filter(
18
+ (prop) => prop !== property,
19
+ );
20
+
21
+ applyDecorators(
22
+ ValidateIf((obj: Record<string, unknown>) => {
23
+ const areOtherPropsUndefined = otherProps.reduce(
24
+ (acc, prop) => acc && obj[prop] === undefined,
25
+ true,
26
+ );
27
+ const isCurrentPropDefined = obj[property] !== undefined;
28
+
29
+ return isCurrentPropDefined || areOtherPropsUndefined;
30
+ }),
31
+ OneOfChecker(properties),
32
+ )(target.prototype, property);
33
+ }
34
+ };
35
+ }
36
+
37
+ function OneOfChecker(properties: string[]): PropertyDecorator {
38
+ return function (
39
+ object: object,
40
+ propertyName: string | symbol,
41
+ ): void {
42
+ registerDecorator({
43
+ name: 'OneOfChecker',
44
+ constraints: [properties],
45
+ target: object.constructor,
46
+ propertyName: String(propertyName),
47
+ validator: OneOfCheckerConstraint,
48
+ });
49
+ };
50
+ }
51
+
52
+ @ValidatorConstraint({ name: 'OneOfChecker' })
53
+ class OneOfCheckerConstraint implements ValidatorConstraintInterface {
54
+ validate(
55
+ value: unknown,
56
+ validationArguments: ValidationArguments,
57
+ ): boolean | Promise<boolean> {
58
+ let [properties] = validationArguments.constraints as [string[]];
59
+ properties = properties.filter(
60
+ (property) => property !== validationArguments.property,
61
+ );
62
+ const data = validationArguments.object as Record<
63
+ string,
64
+ unknown
65
+ >;
66
+
67
+ for (const property of properties) {
68
+ const propertyValue = data[property];
69
+
70
+ if (!isNil(value) && !isNil(propertyValue)) {
71
+ return false;
72
+ }
73
+ }
74
+ return true;
75
+ }
76
+
77
+ defaultMessage(validationArguments: ValidationArguments): string {
78
+ const [properties] = validationArguments.constraints as [
79
+ string[],
80
+ ];
81
+ const props = properties.reduce((accumulator, current, index) => {
82
+ if (index === properties.length - 2) {
83
+ accumulator += current + ', and ';
84
+ } else if (index === properties.length - 1) {
85
+ accumulator += current;
86
+ } else {
87
+ accumulator += current + ', ';
88
+ }
89
+
90
+ return accumulator;
91
+ }, '');
92
+
93
+ return `Do not send ${props} at the same time!`;
94
+ }
95
+ }
96
+
97
+ function isNil(value: unknown) {
98
+ return value === null || value === undefined;
99
+ }
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './correlation-id';
2
+ export * from './decorators';
2
3
  export * from './types';