Skip to content

Instantly share code, notes, and snippets.

@armueller
Last active March 10, 2021 16:30
Show Gist options
  • Save armueller/ac31bfe290f256eda830ea495d3426dd to your computer and use it in GitHub Desktop.
Save armueller/ac31bfe290f256eda830ea495d3426dd to your computer and use it in GitHub Desktop.
Typeguard Transparency Helper Example
import { isType } from './typeguardHelper';
import { Car, carRuleSet, Engine, engineRuleSet, Wheel, wheelRuleSet } from './Car';
const mockEngine: Engine = {
cylinders: 8,
maxSpeed: 7400,
fuelType: 'diesel'
};
const mockWheel: Wheel = {
size: 20,
tredDepth: 2,
reccomendedPsi: 33,
};
const mockCar: Car = {
make: 'RAM',
model: '2500',
year: 1999,
lastService: new Date(),
engine: mockEngine,
wheels: [mockWheel, mockWheel, mockWheel, mockWheel],
};
describe('Car', () => {
describe('is Car', () => {
test.each([
[undefined, false],
[null, false],
[{}, false],
[{
...mockCar,
make: '' // missing make
}, false],
[{
...mockCar,
model: '' // missing model
}, false],
[{
...mockCar,
year: '1999' // year should be a number
}, false],
[{
...mockCar,
lastService: '2021-02-28T21:01:34.817Z' // lastService should be a date
}, false],
[{
...mockCar,
engine: {} // engine should be of type Engine
}, false],
[{
...mockCar,
wheels: [] // a car needs wheels
}, false],
[mockCar, true],
])('Obj is car', (obj, isValid) => {
expect(isType<Car>(obj, carRuleSet)).toBe(isValid);
});
});
describe('is Engine', () => {
test.each([
[undefined, false],
[null, false],
[{}, false],
[{
...mockEngine,
cylinders: '8'
}, false],
[{
...mockEngine,
maxSpeed: '7400'
}, false],
[{
...mockEngine,
fuelType: 'deisel'
}, false],
[mockEngine, true],
])('Obj is engine', (obj, isValid) => {
expect(isType<Engine>(obj, engineRuleSet)).toBe(isValid);
});
});
describe('is Wheel', () => {
test.each([
[undefined, false],
[null, false],
[{}, false],
[{
...mockWheel,
size: '20'
}, false],
[{
...mockWheel,
tredDepth: '2'
}, false],
[{
...mockWheel,
reccomendedPsi: '33'
}, false],
[mockWheel, true],
])('Obj is wheel', (obj, isValid) => {
expect(isType<Wheel>(obj, wheelRuleSet)).toBe(isValid);
});
});
});
import { isType, RuleSet } from './typeguardHelper';
export interface Car {
make: string;
model: string;
year: number;
lastService: Date;
engine: Engine;
wheels: Wheel[];
}
export const carRuleSet: RuleSet = {
make: (value) => !!value && typeof value === 'string',
model: (value) => !!value && typeof value === 'string',
year: (value) => !!value && typeof value === 'number' && value > 1900 && value < new Date().getFullYear(),
lastService: (value) => !!value && value instanceof Date,
engine: (value) => isType(value, engineRuleSet),
wheels: (value) => Array.isArray(value) && value.length === 4 && value.every((v: any) => isType(v, wheelRuleSet)),
};
export interface Engine {
cylinders: number;
maxSpeed: number;
fuelType: string;
}
export const engineRuleSet: RuleSet = {
cylinders: (value) => !!value && typeof value === 'number',
maxSpeed: (value) => !!value && typeof value === 'number',
fuelType: (value) => !!value && typeof value === 'string' && (value === 'gas' || value === 'diesel'),
};
export interface Wheel {
size: number;
tredDepth: number;
reccomendedPsi: number;
}
export const wheelRuleSet: RuleSet = {
size: (value) => !!value && typeof value === 'number',
tredDepth: (value) => !!value && typeof value === 'number',
reccomendedPsi: (value) => !!value && typeof value === 'number',
};
export interface RuleSet {
[key: string]: (value: any) => boolean;
}
export function isType<T>(obj: any, ruleSet: RuleSet): obj is T {
if (!obj) {
console.debug('obj == null');
return false;
}
const failedKeys = Object.keys(ruleSet).filter(key => !ruleSet[key](obj[key]));
if (failedKeys.length === 0) {
return true;
}
failedKeys.forEach(key => console.debug(`Value for key did not match rule: obj.${key}: ${obj[key]}`));
return false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment