Skip to content

Commit b45604f

Browse files
Merge pull request #3029 from threefoldtech/development_2.6_implement_class_validation
implement a way to validate class prop/methods when set/call
2 parents db2daf4 + 40e824d commit b45604f

File tree

3 files changed

+173
-19
lines changed

3 files changed

+173
-19
lines changed

packages/grid_client/src/helpers/validator.ts

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,151 @@ function validateHexSeed(seed: string, length: number): boolean {
3131
return true;
3232
}
3333

34-
export { validateObject, validateInput, validateHexSeed };
34+
interface ValidationOptions {
35+
props?: boolean | string | string[];
36+
methods?: boolean | string | string[];
37+
}
38+
39+
/*
40+
* <script lang="ts">
41+
* import { isLength, isInt } from "class-validator";
42+
* // Example 1
43+
* @ValidateMembers()
44+
* class User {
45+
* @isLength(2) name: string
46+
* @isInt() age: number
47+
*
48+
* greeting() {
49+
* // Some logic
50+
* }
51+
* }
52+
* </script> */
53+
54+
/**
55+
* @description
56+
* This `ValidateMembers` is a config method which returns back a *classDecrator*
57+
* Allows to configure which setter/methods should trigger validation for that specific class
58+
*
59+
* Example As follow
60+
* @example
61+
* ```typescript
62+
* import { isLength, isInt } from "class-validator";
63+
*
64+
* // ⁣@ValidateMembers({ props: false, methods: true }) // - disable validation on set props
65+
* // ⁣@ValidateMembers({ props: true, methods: false }) // - disable validation on call methods
66+
* // ⁣@ValidateMembers({ props: 'name', methods: false }) // - validate only on setting 'name' prop
67+
* // And so on...
68+
* ⁣@ValidateMembers() // = ⁣@ValidateMembers({ props: true, methods: true })
69+
* class User {
70+
* ⁣@isLength(2) name: string
71+
* ⁣⁣@isInt() age: number
72+
*
73+
* greeting() {
74+
* // Some logic
75+
* }
76+
* }
77+
* ```
78+
*
79+
*
80+
*
81+
* @param options { ValidationOptions | undefined }
82+
* @returns { ClassDecorator }
83+
*/
84+
function ValidateMembers(options?: ValidationOptions): ClassDecorator {
85+
const _options = _normalizeValidationOptions(options);
86+
return (target: any): any => {
87+
const methods = _getMethods(target, _options);
88+
for (const method of methods) {
89+
const fn = target.prototype[method];
90+
target.prototype[method] = function (...args: any[]): any {
91+
const errors = validateSync(this);
92+
if (errors.length) {
93+
throw errors;
94+
}
95+
return fn.apply(this, args);
96+
};
97+
}
98+
99+
return class extends target {
100+
constructor(...args: any[]) {
101+
super(...args);
102+
103+
const props = _getProps(this, _options);
104+
for (const prop of props) {
105+
let _value = this[prop];
106+
107+
Object.defineProperty(this, prop, {
108+
configurable: false,
109+
enumerable: true,
110+
get: () => _value,
111+
set(value) {
112+
_value = value;
113+
const errors = validateSync(this);
114+
for (const error of errors) {
115+
if (error.property === prop) {
116+
throw error;
117+
}
118+
}
119+
},
120+
});
121+
}
122+
}
123+
};
124+
};
125+
}
126+
127+
function _normalizeValidationOptions(options?: ValidationOptions): Required<ValidationOptions> {
128+
return {
129+
props: options?.props ?? true,
130+
methods: options?.methods ?? true,
131+
};
132+
}
133+
134+
function _getProps(ctor: any, options: Required<ValidationOptions>): string[] {
135+
/* This env variable should be used while testing to prevent throw error while setting values */
136+
if (process.env.SKIP_PROPS_VALIDATION) {
137+
return [];
138+
}
139+
140+
if (options.props === true) {
141+
return Object.getOwnPropertyNames(ctor);
142+
}
143+
144+
if (typeof options.props === "string") {
145+
return [options.props];
146+
}
147+
148+
if (Array.isArray(options.props)) {
149+
return options.props;
150+
}
151+
152+
return [];
153+
}
154+
155+
function _getMethods(ctor: any, options: Required<ValidationOptions>): string[] {
156+
/* This env variable should be used to prevent throw error while calling methods if needed */
157+
if (process.env.SKIP_METHODS_VALIDATION) {
158+
return [];
159+
}
160+
161+
if (options.methods === true) {
162+
const methods = Object.getOwnPropertyNames(ctor.prototype);
163+
const constructorIndex = methods.indexOf("constructor");
164+
if (constructorIndex !== -1) {
165+
methods.splice(constructorIndex, 1);
166+
}
167+
return methods;
168+
}
169+
170+
if (typeof options.methods === "string") {
171+
return [options.methods];
172+
}
173+
174+
if (Array.isArray(options.methods)) {
175+
return options.methods;
176+
}
177+
178+
return [];
179+
}
180+
181+
export { validateObject, validateInput, validateHexSeed, type ValidationOptions, ValidateMembers };

packages/grid_client/src/zos/computecapacity.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { Expose } from "class-transformer";
22
import { IsInt, Max, Min } from "class-validator";
33

4+
import { ValidateMembers } from "../helpers/validator";
5+
6+
@ValidateMembers()
47
class ComputeCapacity {
58
@Expose() @IsInt() @Min(1) @Max(32) cpu: number;
69
@Expose() @IsInt() @Min(256 * 1024 ** 2) @Max(256 * 1024 ** 3) memory: number; // in bytes

packages/grid_client/tests/modules/compute_capacity.test.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,62 +6,66 @@ beforeEach(() => {
66
computeCapacity = new ComputeCapacity();
77
});
88
describe("Compute Capacity module", () => {
9-
test.skip("Compute Capacity instance is of type ComputeCapacity.", () => {
9+
test("Compute Capacity instance is of type ComputeCapacity.", () => {
1010
expect(computeCapacity).toBeInstanceOf(ComputeCapacity);
1111
});
1212

1313
// The following tests are skipped as there's an issue w input validation. Should be returned once validation is fixed here: https://github.com/threefoldtech/tfgrid-sdk-ts/issues/2821
14-
test.skip("Min values for cpu & memory.", () => {
14+
test("Min values for cpu & memory.", () => {
1515
const cpu = 0;
1616
const mem = 255 * 1024 ** 2;
1717

18-
computeCapacity.cpu = cpu;
19-
computeCapacity.memory = mem;
20-
18+
const setCPU = () => (computeCapacity.cpu = cpu);
19+
const setMem = () => (computeCapacity.memory = mem);
2120
const result = () => computeCapacity.challenge();
2221

22+
expect(setCPU).toThrow();
23+
expect(setMem).toThrow();
2324
expect(result).toThrow();
2425
});
2526

26-
test.skip("Max values for cpu & memory.", () => {
27+
test("Max values for cpu & memory.", () => {
2728
const cpu = 33;
2829
const mem = 255 * 1024 ** 4;
2930

30-
computeCapacity.cpu = cpu;
31-
computeCapacity.memory = mem;
32-
31+
const setCPU = () => (computeCapacity.cpu = cpu);
32+
const setMem = () => (computeCapacity.memory = mem);
3333
const result = () => computeCapacity.challenge();
3434

35+
expect(setCPU).toThrow();
36+
expect(setMem).toThrow();
3537
expect(result).toThrow();
3638
});
3739

38-
test.skip("cpu & memory doesn't accept decimal values.", () => {
40+
test("cpu & memory doesn't accept decimal values.", () => {
3941
const cpu = 1.5;
4042
const mem = 1.2;
4143

42-
computeCapacity.cpu = cpu;
43-
computeCapacity.memory = mem;
44-
44+
const setCPU = () => (computeCapacity.cpu = cpu);
45+
const setMem = () => (computeCapacity.memory = mem);
4546
const result = () => computeCapacity.challenge();
4647

48+
expect(setCPU).toThrow();
49+
expect(setMem).toThrow();
4750
expect(result).toThrow();
4851
});
4952

50-
test.skip("cpu & memory empty values.", () => {
53+
test("cpu & memory empty values.", () => {
5154
const result = () => computeCapacity.challenge();
5255

5356
expect(result).toThrow();
5457
});
5558

56-
test.skip("An error should be thrown if cpu & memory negative values.", () => {
59+
test("An error should be thrown if cpu & memory negative values.", () => {
5760
const negative_cpu = -1;
5861
const negative_mem = -1;
5962

60-
computeCapacity.cpu = negative_cpu;
61-
computeCapacity.memory = negative_mem;
62-
63+
const setCPU = () => (computeCapacity.cpu = negative_cpu);
64+
const setMem = () => (computeCapacity.memory = negative_mem);
6365
const result = () => computeCapacity.challenge();
6466

67+
expect(setCPU).toThrow();
68+
expect(setMem).toThrow();
6569
expect(result).toThrow();
6670
});
6771
});

0 commit comments

Comments
 (0)