diff --git a/apps/server/src/shared/common/guards/type.guard.spec.ts b/apps/server/src/shared/common/guards/type.guard.spec.ts index 8436c8bc6d2..93fc82e67a5 100644 --- a/apps/server/src/shared/common/guards/type.guard.spec.ts +++ b/apps/server/src/shared/common/guards/type.guard.spec.ts @@ -1,27 +1,33 @@ import { TypeGuard } from './type.guard'; +type ExampleObjectType = { + id?: number; + name?: string; + email?: string; +}; + describe('TypeGuard', () => { describe('isError', () => { describe('when passing type of value is an Error', () => { - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isError(new Error())).toBe(true); }); }); describe('when passing type of value is NOT an Error', () => { - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isError(undefined)).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isError(null)).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isError({})).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isError('string')).toBe(false); }); }); @@ -29,29 +35,29 @@ describe('TypeGuard', () => { describe('isNull', () => { describe('when passing type of value is null', () => { - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isNull(null)).toBe(true); }); }); describe('when passing type of value is NOT null', () => { - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isNull(undefined)).toBe(false); }); - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isNull('string')).toBe(false); }); - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isNull('')).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isNull({})).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isNull(1)).toBe(false); }); }); @@ -59,29 +65,29 @@ describe('TypeGuard', () => { describe('isUndefined', () => { describe('when passing type of value is undefined', () => { - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isUndefined(undefined)).toBe(true); }); }); describe('when passing type of value is NOT undefined', () => { - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isUndefined(null)).toBe(false); }); - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isUndefined('string')).toBe(false); }); - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isUndefined('')).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isUndefined({})).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isUndefined(1)).toBe(false); }); }); @@ -89,33 +95,33 @@ describe('TypeGuard', () => { describe('isNumber', () => { describe('when passing type of value is a number', () => { - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isNumber(123)).toBe(true); }); - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isNumber(-1)).toBe(true); }); - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isNumber(NaN)).toBe(true); }); }); describe('when passing type of value is NOT a number', () => { - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isNumber(undefined)).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isNumber(null)).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isNumber({})).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isNumber('string')).toBe(false); }); }); @@ -137,19 +143,19 @@ describe('TypeGuard', () => { }); describe('when passing type of value is NOT a number', () => { - it('should be return false', () => { + it('should return false', () => { expect(() => TypeGuard.checkNumber(undefined)).toThrowError('Type is not a number'); }); - it('should be return false', () => { + it('should return false', () => { expect(() => TypeGuard.checkNumber(null)).toThrowError('Type is not a number'); }); - it('should be return false', () => { + it('should return false', () => { expect(() => TypeGuard.checkNumber({})).toThrowError('Type is not a number'); }); - it('should be return false', () => { + it('should return false', () => { expect(() => TypeGuard.checkNumber('string')).toThrowError('Type is not a number'); }); }); @@ -157,29 +163,29 @@ describe('TypeGuard', () => { describe('isString', () => { describe('when passing type of value is a string', () => { - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isString('string')).toBe(true); }); - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isString('')).toBe(true); }); }); describe('when passing type of value is NOT a string', () => { - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isString(undefined)).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isString(null)).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isString({})).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isString(1)).toBe(false); }); }); @@ -197,19 +203,19 @@ describe('TypeGuard', () => { }); describe('when passing type of value is NOT a string', () => { - it('should be return false', () => { + it('should return false', () => { expect(() => TypeGuard.checkString(undefined)).toThrowError('Type is not a string'); }); - it('should be return false', () => { + it('should return false', () => { expect(() => TypeGuard.checkString(null)).toThrowError('Type is not a string'); }); - it('should be return false', () => { + it('should return false', () => { expect(() => TypeGuard.checkString({})).toThrowError('Type is not a string'); }); - it('should be return false', () => { + it('should return false', () => { expect(() => TypeGuard.checkString(1)).toThrowError('Type is not a string'); }); }); @@ -229,23 +235,23 @@ describe('TypeGuard', () => { }); describe('when value is NOT in values', () => { - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isStringOfStrings(undefined, ['string'])).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isStringOfStrings(null, ['string'])).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isStringOfStrings({}, ['string'])).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isStringOfStrings(1, ['string'])).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isStringOfStrings('string', [''])).toBe(false); }); }); @@ -306,29 +312,29 @@ describe('TypeGuard', () => { describe('isArray', () => { describe('when passing type of value is an array', () => { - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isArray([])).toBe(true); }); - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isArray(['', '', ''])).toBe(true); }); }); describe('when passing type of value is NOT an array', () => { - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isArray(undefined)).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isArray(null)).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isArray({})).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isArray(1)).toBe(false); }); }); @@ -366,37 +372,37 @@ describe('TypeGuard', () => { describe('isArrayWithElements', () => { describe('when passing type of value is an array with elements', () => { - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isArrayWithElements([1, 2, 3])).toBe(true); }); - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isArrayWithElements(['a', 'b', 'c'])).toBe(true); }); - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isArrayWithElements([{ a: 1 }, { b: 2 }])).toBe(true); }); }); describe('when passing type of value is NOT an array with elements', () => { - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isArrayWithElements([])).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isArrayWithElements(undefined)).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isArrayWithElements(null)).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isArrayWithElements({})).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isArrayWithElements(1)).toBe(false); }); }); @@ -434,33 +440,33 @@ describe('TypeGuard', () => { describe('isDefinedObject', () => { describe('when passing type of value is an object', () => { - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isDefinedObject({})).toBe(true); }); - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isDefinedObject({ a: 1 })).toBe(true); }); - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isDefinedObject({ a: { b: 1 } })).toBe(true); }); }); describe('when passing type of value is NOT an object', () => { - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isDefinedObject(undefined)).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isDefinedObject(null)).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isDefinedObject([])).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isDefinedObject('string')).toBe(false); }); }); @@ -482,19 +488,19 @@ describe('TypeGuard', () => { }); describe('when passing type of value is NOT an object', () => { - it('should be return false', () => { + it('should return false', () => { expect(() => TypeGuard.checkDefinedObject(undefined)).toThrowError('Type is not an object'); }); - it('should be return false', () => { + it('should return false', () => { expect(() => TypeGuard.checkDefinedObject(null)).toThrowError('Type is not an object'); }); - it('should be return false', () => { + it('should return false', () => { expect(() => TypeGuard.checkDefinedObject([])).toThrowError('Type is not an object'); }); - it('should be return false', () => { + it('should return false', () => { expect(() => TypeGuard.checkDefinedObject('string')).toThrowError('Type is not an object'); }); }); @@ -614,33 +620,33 @@ describe('TypeGuard', () => { describe('isEachKeyInObject', () => { describe('when passing value is an object that has all requested keys', () => { - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isEachKeyInObject({ x1: 'abc', x2: 'bcd' }, ['x1', 'x2'])).toBe(true); }); - it('should be return true', () => { + it('should return true', () => { expect(TypeGuard.isEachKeyInObject({ x1: 'abc', x2: 'bcd' }, ['x1'])).toBe(true); }); }); describe('when passing params do not match', () => { - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isEachKeyInObject({ x1: 'abc', x2: 'bcd' }, ['x1', 'x2', 'x3'])).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isEachKeyInObject({ x1: 'abc', x2: 'bcd' }, 'x1' as unknown as string[])).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isEachKeyInObject('string', ['x1', 'x2'])).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isEachKeyInObject(undefined, ['x1', 'x2'])).toBe(false); }); - it('should be return false', () => { + it('should return false', () => { expect(TypeGuard.isEachKeyInObject(null, ['x1', 'x2'])).toBe(false); }); }); @@ -649,7 +655,9 @@ describe('TypeGuard', () => { describe('checkKeyInObject', () => { describe('when passing value is an object that has the requested key', () => { it('should be return the key value', () => { - expect(TypeGuard.checkKeyInObject({ xyz: 'abc' }, 'xyz')).toEqual('abc'); + const result = TypeGuard.checkKeyInObject({ xyz: 'abc' }, 'xyz'); + + expect(result).toEqual('abc'); }); }); @@ -697,6 +705,44 @@ describe('TypeGuard', () => { }); }); + describe('checkKeysInInstance', () => { + describe('when passing value is an object that has the requested keys', () => { + it('should be return the object', () => { + const example: ExampleObjectType = { name: 'abc' }; + + const checkedObject = TypeGuard.checkKeysInInstance(example, ['name']); + + expect(checkedObject).toEqual(example); + }); + + it('should know the property is defined', () => { + const example: ExampleObjectType = { name: 'abc' }; + + const checkedObject = TypeGuard.checkKeysInInstance(example, ['name']); + + expect(checkedObject.name).toEqual('abc'); + }); + }); + + describe('when passing value and keys do not match', () => { + it('should throw an error', () => { + const example: ExampleObjectType = { id: 1, name: 'John Doe' }; + + expect(() => TypeGuard.checkKeysInInstance(example, ['email'])).toThrowError( + 'Object lacks this property: email. ' + ); + }); + + it('should throw an error', () => { + const example: ExampleObjectType = { id: 1, name: 'John Doe' }; + + expect(() => TypeGuard.checkKeysInInstance(example, ['email'])).toThrowError( + 'Object lacks this property: email. ' + ); + }); + }); + }); + describe('checkNotNullOrUndefined', () => { describe('when value is null', () => { it('should throw error if it is passed', () => { diff --git a/apps/server/src/shared/common/guards/type.guard.ts b/apps/server/src/shared/common/guards/type.guard.ts index d9f5b687308..342fc38c534 100644 --- a/apps/server/src/shared/common/guards/type.guard.ts +++ b/apps/server/src/shared/common/guards/type.guard.ts @@ -1,29 +1,31 @@ +type EnsureKeysAreSet = T & { [P in K]-?: T[P] }; + export class TypeGuard { - static isError(value: unknown): value is Error { + public static isError(value: unknown): value is Error { const isError = value instanceof Error; return isError; } - static isNull(value: unknown): value is null { + public static isNull(value: unknown): value is null { const isNull = value === null; return isNull; } - static isUndefined(value: unknown): value is undefined { + public static isUndefined(value: unknown): value is undefined { const isUndefined = value === undefined; return isUndefined; } - static isNumber(value: unknown): value is number { + public static isNumber(value: unknown): value is number { const isNumber = typeof value === 'number'; return isNumber; } - static checkNumber(value: unknown): number { + public static checkNumber(value: unknown): number { if (!TypeGuard.isNumber(value)) { throw new Error('Type is not a number'); } @@ -31,13 +33,13 @@ export class TypeGuard { return value; } - static isString(value: unknown): value is string { + public static isString(value: unknown): value is string { const isString = typeof value === 'string'; return isString; } - static checkString(value: unknown): string { + public static checkString(value: unknown): string { if (!TypeGuard.isString(value)) { throw new Error('Type is not a string'); } @@ -45,13 +47,13 @@ export class TypeGuard { return value; } - static isStringOfStrings(value: unknown, values: T[]): value is T { + public static isStringOfStrings(value: unknown, values: T[]): value is T { const isStringOfValue = TypeGuard.isString(value) && values.includes(value as T); return isStringOfValue; } - static checkStringOfStrings(value: unknown, values: T[]): T { + public static checkStringOfStrings(value: unknown, values: T[]): T { if (!TypeGuard.isStringOfStrings(value, values)) { throw new Error('Value is not in strings'); } @@ -59,13 +61,13 @@ export class TypeGuard { return value; } - static isArray(value: unknown): value is [] { + public static isArray(value: unknown): value is [] { const isArray = Array.isArray(value); return isArray; } - static checkArray(value: unknown): [] { + public static checkArray(value: unknown): [] { if (!TypeGuard.isArray(value)) { throw new Error('Type is not an array.'); } @@ -73,13 +75,13 @@ export class TypeGuard { return value; } - static isArrayWithElements(value: unknown): value is [] { + public static isArrayWithElements(value: unknown): value is [] { const isArrayWithElements = TypeGuard.isArray(value) && value.length > 0; return isArrayWithElements; } - static checkArrayWithElements(value: unknown): [] { + public static checkArrayWithElements(value: unknown): [] { if (!TypeGuard.isArrayWithElements(value)) { throw new Error('Type is not an array with elements.'); } @@ -87,13 +89,13 @@ export class TypeGuard { return value; } - static isDefinedObject(value: unknown): value is object { + public static isDefinedObject(value: unknown): value is object { const isObject = typeof value === 'object' && !TypeGuard.isArray(value) && !TypeGuard.isNull(value); return isObject; } - static checkDefinedObject(value: unknown): object { + public static checkDefinedObject(value: unknown): object { if (!TypeGuard.isDefinedObject(value)) { throw new Error('Type is not an object.'); } @@ -102,7 +104,7 @@ export class TypeGuard { } /** @return undefined if no object or key do not exists, otherwise the value of the key. */ - static getValueFromObjectKey(value: unknown, key: string): unknown { + public static getValueFromObjectKey(value: unknown, key: string): unknown { TypeGuard.checkString(key); const result: unknown = TypeGuard.isDefinedObject(value) ? value[key] : undefined; @@ -110,7 +112,7 @@ export class TypeGuard { return result; } - static getValueFromDeepObjectKey(value: unknown, keyPath: string[]): unknown { + public static getValueFromDeepObjectKey(value: unknown, keyPath: string[]): unknown { TypeGuard.checkArrayWithElements(keyPath); let result: unknown = value; @@ -122,7 +124,7 @@ export class TypeGuard { return result; } - static isEachKeyInObject>(value: unknown, keys: (keyof T)[]): value is T { + public static isEachKeyInObject>(value: unknown, keys: (keyof T)[]): value is T { if (!TypeGuard.isDefinedObject(value) || !TypeGuard.isArray(keys)) { return false; } @@ -139,7 +141,7 @@ export class TypeGuard { } /** @return value of requested key in object. */ - static checkKeyInObject(value: T, key: string, toThrow?: Error): unknown { + public static checkKeyInObject(value: T, key: string, toThrow?: Error): unknown { TypeGuard.checkString(key); const object = TypeGuard.checkDefinedObject(value); @@ -151,7 +153,11 @@ export class TypeGuard { return object[key]; } - static checkKeysInObject>(value: unknown, keys: (keyof T)[], toThrow?: Error): T { + public static checkKeysInObject>( + value: unknown, + keys: (keyof T)[], + toThrow?: Error + ): T { const object = TypeGuard.checkDefinedObject(value); if (!TypeGuard.isEachKeyInObject(object, keys)) { @@ -168,11 +174,20 @@ export class TypeGuard { return object; } - // add additional method checkKeysInObject with key array see use case for example in method mapEtherpadSessionToSession - // return an value that represent as type a interface that include all checked keys. - // Same interface can be usefull for checkKeyInObject + public static checkKeysInInstance( + obj: T, + keys: K[], + contextInfo = '' + ): EnsureKeysAreSet { + for (const key of keys) { + if (!(key in obj) || obj[key] === undefined) { + throw new Error(`Object lacks this property: ${String(key)}. ${contextInfo}`); + } + } + return obj as EnsureKeysAreSet; + } - static checkNotNullOrUndefined(value: T | null | undefined, toThrow?: Error): T { + public static checkNotNullOrUndefined(value: T | null | undefined, toThrow?: Error): T { if (TypeGuard.isNull(value)) { throw toThrow || new Error('Type is null.'); }