8000 https://github.com/woutervh-/typescript-is/issues/104 · SebastienGllmt/typescript-is@62648cc · GitHub
[go: up one dir, main page]

Skip to content

Commit 62648cc

Browse files
author
Piotr Hitori Bosak
committed
https://github.com/woutervh-/typescript-is/issues/104
1 parent 1202b01 commit 62648cc

File tree

7 files changed

+130
-5
lines changed

7 files changed

+130
-5
lines changed

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,74 @@ new A().method(42) === 42; // true
257257
new A().method('42' as any); // will throw error
258258
```
259259

260+
### async and `Promise` returning methods
261+
`AssertType` can also work correctly with `async` methods, returning promise rejected with `TypeGuardError`
262+
263+
To enable this functionality, you need to emit decorators metadata for your TypeScript project.
264+
265+
```json
266+
{
267+
"compilerOptions": {
268+
"emitDecoratorMetadata": true
269+
}
270+
}
271+
```
272+
273+
Then `AssertType` will work with async methods and `Promise` returning methods automatically.
274+
```typescript
275+
import { ValidateClass, AssertType } from 'typescript-is';
276+
277+
@ValidateClass()
278+
class A {
279+
async method(@AssertType({ async: true }) value: number) {
280+
// You can safely use value as a number
281+
return value;
282+
}
283+
284+
methodPromise(@AssertType({ async: true }) value: number): Promise<number> {
285+
// You can safely use value as a number
286+
return Promise.resolve(value);
287+
}
288+
}
289+
290+
new A().method(42).then(value => value === 42 /* true */);
291+
new A().method('42' as any).catch(error => {
292+
// error will be of TypeGuardError type
293+
})
294+
new A().methodPromise('42' as any).catch(error => {
295+
// error will be of TypeGuardError type
296+
})
297+
```
298+
299+
If you want to throw synchronously for some reason, you can override the behaviour using with `@AssertType({ async: false })`:
300+
```typescript
301+
import { ValidateClass, AssertType } from 'typescript-is';
302+
303+
@ValidateClass()
304+
class A {
305+
async method(@AssertType({ async: false }) value: number) {
306+
// You can safely use value as a number
307+
return value;
308+
}
309+
}
310+
311+
new A().method(42).then(value => value === 42 /* true */);
312+
new A().method('42' as any); // will throw error
313+
```
314+
315+
If you cannot or don't want to enable decorators metadata, you still make AssertType reject with promise using `@AssertType({ async: true })`
316+
```typescript
317+
import { ValidateClass, AssertType } from 'typescript-is';
318+
319+
@ValidateClass()
320+
class A {
321+
async method(@AssertType({ async: true }) value: number) {
322+
// You can safely use value as a number
323+
return value;
324+
}
325+
}
326+
```
327+
260328
## Strict equality (`equals`, `createEquals`, `assertEquals`, `createAssertEquals`)
261329

262330
This family of functions check not only whether the passed object is assignable to the specified type, but also checks that the passed object does not contain any more than is necessary. In other words: the type is also "assignable" to the object. This functionality is equivalent to specifying `disallowSuperfluousObjectProperties` in the options, the difference is that this will apply only to the specific function call. For example:

index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export function createAssertEquals<T>(): (object: any) => T;
125125
new A().method('0' as any); // will throw an error
126126
```
127127
*/
128-
export function AssertType(): (target: object, propertyKey: string | symbol, parameterIndex: number) => void;
128+
export function AssertType(options?: { async: boolean }): (target: object, propertyKey: string | symbol, parameterIndex: number) => void;
129129

130130
/**
131131
* Overrides methods in the target class with a proxy that will first validate the argument types.

index.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ function AssertType(assertion, options = {}) {
4545
require('reflect-metadata');
4646
return function (target, propertyKey, parameterIndex) {
4747
const assertions = Reflect.getOwnMetadata(assertionsMetadataKey, target, propertyKey) || [];
48-
assertions[parameterIndex] = { assertion, options };
48+
if(Reflect.getOwnMetadata('design:returntype', target, propertyKey) === Promise) {
49+
assertions[parameterIndex] = { assertion, options: Object.assign({ async: true }, options) };
50+
} else {
51+
assertions[parameterIndex] = { assertion, options };
52+
}
4953
Reflect.defineMetadata(assertionsMetadataKey, assertions, target, propertyKey);
5054
};
5155
}
@@ -64,7 +68,12 @@ function ValidateClass(errorConstructor = TypeGuardError) {
6468
}
6569
const errorObject = assertions[i].assertion(args[i]);
6670
if (errorObject !== null) {
67-
throw new errorConstructor(errorObject, args[i]);
71+
const errorInstance = new errorConstructor(errorObject, args[i]);
72+
if(assertions[i].options.async) {
73+
return Promise.reject(errorInstance);
74+
} else {
75+
throw errorInstance;
76+
}
6877
}
6978
}
7079
return originalMethod.apply(this, args);

test-fixtures/issue-104.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {AssertType, ValidateClass} from '../index';
2+
3+
@ValidateClass()
4+
export class AsyncMethods {
5+
async asyncMethod(@AssertType() body: { test: string }): Promise<boolean> {
6+
return true
7+
}
8+
async asyncMethodNoExplicitReturn(@AssertType() body: { test: string }) {
9+
return true
10+
}
11+
promiseReturnMethod(@AssertType() body: { test: string }): Promise<boolean> {
12+
return Promise.resolve(true)
13+
}
14+
async asyncOverride(@AssertType({ async: false }) body: { test: string }): Promise<boolean> {
15+
return true
16+
}
17+
promiseOrOtherReturnMethod(@AssertType() body: { test: string }): Promise<boolean> | boolean{
18+
return Promise.resolve(true)
19+
}
20+
}
21+

test/issue-104.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import * as assert from 'assert';
2+
import {AsyncMethods} from '../test-fixtures/issue-104';
3+
4+
describe('@ValidateClass(), @AssertType()', () => {
5+
it('should return rejected promise for async methods', () => {
6+
const instance = new AsyncMethods()
7+
assert.rejects(instance.asyncMethod({invalid: 123} as any))
8+
})
9+
it('should return rejected promise for async methods with not explicit return type', () => {
10+
const instance = new AsyncMethods()
11+
assert.rejects(instance.asyncMethodNoExplicitReturn({invalid: 123} as any))
12+
})
13+
it('should return rejected promise for methods returning promise', () => {
14+
const instance = new AsyncMethods()
15+
assert.rejects(instance.promiseReturnMethod({invalid: 123} as any))
16+
})
17+
it('should throw synchronously if { async: false } option is set', () => {
18+
const instance = new AsyncMethods()
19+
assert.throws(() => instance.asyncOverride({invalid: 123} as any))
20+
})
21+
it('should throw synchronously method may return something other than promise', () => {
22+
const instance = new AsyncMethods()
23+
assert.throws(() => instance.promiseOrOtherReturnMethod({invalid: 123} as any))
24+
})
25+
})

tsconfig-test.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"lib": [
66
"es6"
77
],
8+
"emitDecoratorMetadata": true,
89
"experimentalDecorators": true,
910
"noImplicitAny": true,
1011
"noUnusedLocals": true,
@@ -20,4 +21,4 @@
2021
"include": [
2122
"test"
2223
]
23-
}
24+
}

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"lib": [
66
"es6"
77
],
8+
"emitDecoratorMetadata": true,
89
"experimentalDecorators": true,
910
"noImplicitAny": 589E true,
1011
"noUnusedLocals": true,
@@ -17,4 +18,4 @@
1718
"include": [
1819
"src"
1920
]
20-
}
21+
}

0 commit comments

Comments
 (0)
0