Created
December 12, 2017 13:10
-
-
Save SoEasy/5df7b9768463c44804d2773d043fd2fd to your computer and use it in GitHub Desktop.
TypeScript contract-based decorators
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type MethodDecorator = (target: any, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor) => | |
TypedPropertyDescriptor | void; | |
type AssertFn = (...args: Array<any>) => void; | |
class TSContract { | |
private static isCustomContractInterruptionCb: boolean = false; | |
private static contractInterruptionCb(message: string): void { | |
console.warn(message); | |
} | |
static setupContractInterruptionCb(cb: (message: string) => void): void { | |
if (TSContract.isCustomContractInterruptionCb) { | |
console.warn('Custom contract interruption callback already setted, break'); | |
return; | |
} | |
TSContract.contractInterruptionCb = cb; | |
TSContract.isCustomContractInterruptionCb = true; | |
} | |
static assert(expression: boolean, errorMessage: string): void { | |
if (!expression) { | |
TSContract.contractInterruptionCb(errorMessage); | |
} | |
} | |
static In(assertFn: AssertFn): MethodDecorator { | |
return function(target: any, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor { | |
const originalValue = descriptor.value; | |
function newValue(...args) { | |
assertFn(...args); | |
return originalValue.apply(this, args); | |
} | |
descriptor.value = newValue; | |
return descriptor; | |
}; | |
} | |
static Out(assertFn: AssertFn): MethodDecorator { | |
return function(target: any, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor { | |
const originalValue = descriptor.value; | |
function newValue(...args) { | |
result = originalValue.apply(this, args); | |
assertFn(result); | |
return result; | |
} | |
descriptor.value = newValue; | |
return descriptor; | |
}; | |
} | |
} | |
class ContractTest { | |
@TSContract.In(a => { | |
TSContract.assert(typeof a === 'number', 'Not a number!'); | |
}) | |
@TSContract.Out(retVal => { | |
TSContract.assert(!retVal, 'Wat?! Return?!'); | |
}) | |
doSome(a: number, withReturn: boolean = false): void { | |
console.log(a); | |
if (withReturn) { | |
return a; | |
} | |
} | |
} | |
TSContract.setupContractInterruptionCb((message: string) => { | |
console.error(message); | |
}); | |
// cant setup another callback | |
TSContract.setupContractInterruptionCb((message: string) => { | |
console.error(message); | |
}); | |
const ct = new ContractTest(); | |
ct.doSome(12); | |
ct.doSome('12'); | |
ct.doSome(12, true); |
Hello @ericbolikowski!
I know one solution for Ruby, here is it: https://github.com/sclinede/blood_contracts
And I know that author of that solution want to make it for frontend world.
But what I think about contract-based pre/post conditions and them implementation - thats only the tip of the iceberg. What shall we doing next? How to process contract interruption? Only log message? Or break our business-logic? Throw exception? How to proceed that exception? What if we have very very deep callstack and somewhere in down contract interruption happens? How to notify user about that exception? And what if that exception is not critical? Should we skip some exceptions and raise other?
Too much questions :D
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey @SoEasy, thanks for putting this out there. I was researching Design-by-Contract + TypeScript and came across this (and this).
I'm surprised there isn't more resources or libraries out there around JS/TS and DBC. For JS, babel-plugin-contracts looks like the best thing out there.
Do you know of any other good resources implementing this?