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

implement a way to validate class prop/methods when set/call #3029

Merged
Merged
Show file tree
Hide file tree
Changes from 7 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
1 change: 1 addition & 0 deletions .github/workflows/grid_client_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
RMB_PROXY: true
STORE_SECRET: secret
MNEMONIC: ${{ secrets.MNEMONIC }}
SKIP_PROPS_VALIDATION: true
MohamedElmdary marked this conversation as resolved.
Show resolved Hide resolved
steps:
- name: set network for Schedule
if: ${{env.NETWORK}} == ""
Expand Down
104 changes: 103 additions & 1 deletion packages/grid_client/src/helpers/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,106 @@ function validateHexSeed(seed: string, length: number): boolean {
return true;
}

export { validateObject, validateInput, validateHexSeed };
interface ValidationOptions {
props?: boolean | string | string[];
methods?: boolean | string | string[];
}

function Validate(options?: ValidationOptions): ClassDecorator {
MohamedElmdary marked this conversation as resolved.
Show resolved Hide resolved
const _options = _normalizeValidationOptions(options);
return (target: any): any => {
const methods = _getMethods(target, _options);
for (const method of methods) {
const fn = target.prototype[method];
target.prototype[method] = function (...args: any[]): any {
const errors = validateSync(this);
if (errors.length) {
throw errors;
}
return fn.apply(this, args);
};
}

return class extends target {
constructor(...args: any[]) {
super(...args);

const props = _getProps(this, _options);
for (const prop of props) {
let _value = this[prop];

Object.defineProperty(this, prop, {
configurable: false,
enumerable: true,
get: () => _value,
set(value) {
const errors = validateSync(this);
for (const error of errors) {
if (error.property === prop) {
throw error;
}
}
_value = value;
},
});
}
}
};
};
}

function _normalizeValidationOptions(options?: ValidationOptions): Required<ValidationOptions> {
return {
props: options?.props ?? true,
methods: options?.methods ?? true,
};
}

function _getProps(ctor: any, options: Required<ValidationOptions>): string[] {
/* This env variable should be used while testing to prevent throw error while setting values */
if (process.env.SKIP_PROPS_VALIDATION) {
return [];
}

if (options.props === true) {
return Object.getOwnPropertyNames(ctor);
}

if (typeof options.props === "string") {
return [options.props];
}

if (Array.isArray(options.props)) {
return options.props;
}

return [];
}

function _getMethods(ctor: any, options: Required<ValidationOptions>): string[] {
/* This env variable should be used to prevent throw error while calling methods if needed */
if (process.env.SKIP_METHODS_VALIDATION) {
return [];
}

if (options.methods === true) {
const methods = Object.getOwnPropertyNames(ctor.prototype);
const constructorIndex = methods.indexOf("constructor");
if (constructorIndex !== -1) {
methods.splice(constructorIndex, 1);
}
return methods;
}

if (typeof options.methods === "string") {
return [options.methods];
}

if (Array.isArray(options.methods)) {
return options.methods;
}

return [];
}

export { validateObject, validateInput, validateHexSeed, type ValidationOptions, Validate };
3 changes: 3 additions & 0 deletions packages/grid_client/src/zos/computecapacity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Expose } from "class-transformer";
import { IsInt, Max, Min } from "class-validator";

import { Validate } from "../helpers/validator";

@Validate()
class ComputeCapacity {
@Expose() @IsInt() @Min(1) @Max(32) cpu: number;
@Expose() @IsInt() @Min(256 * 1024 ** 2) @Max(256 * 1024 ** 3) memory: number; // in bytes
Expand Down
12 changes: 6 additions & 6 deletions packages/grid_client/tests/modules/compute_capacity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ beforeEach(() => {
computeCapacity = new ComputeCapacity();
});
describe("Compute Capacity module", () => {
test.skip("Compute Capacity instance is of type ComputeCapacity.", () => {
test("Compute Capacity instance is of type ComputeCapacity.", () => {
expect(computeCapacity).toBeInstanceOf(ComputeCapacity);
});

// 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
test.skip("Min values for cpu & memory.", () => {
test("Min values for cpu & memory.", () => {
const cpu = 0;
const mem = 255 * 1024 ** 2;

Expand All @@ -23,7 +23,7 @@ describe("Compute Capacity module", () => {
expect(result).toThrow();
});

test.skip("Max values for cpu & memory.", () => {
test("Max values for cpu & memory.", () => {
const cpu = 33;
const mem = 255 * 1024 ** 4;

Expand All @@ -35,7 +35,7 @@ describe("Compute Capacity module", () => {
expect(result).toThrow();
});

test.skip("cpu & memory doesn't accept decimal values.", () => {
test("cpu & memory doesn't accept decimal values.", () => {
const cpu = 1.5;
const mem = 1.2;

Expand All @@ -47,13 +47,13 @@ describe("Compute Capacity module", () => {
expect(result).toThrow();
});

test.skip("cpu & memory empty values.", () => {
test("cpu & memory empty values.", () => {
const result = () => computeCapacity.challenge();

expect(result).toThrow();
});

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

Expand Down
Loading