Skip to content

Commit e2965ff

Browse files
authored
Merge pull request #17 from storyofams/feat/parser-options
Parser options
2 parents 6dc537c + 331ceb3 commit e2965ff

File tree

8 files changed

+77
-25
lines changed

8 files changed

+77
-25
lines changed

lib/e2e.test.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class TestHandler {
4747
public read(
4848
@Header('Content-Type') contentType: string,
4949
@Query('id') id: string,
50-
@Query('step', ParseNumberPipe) step: number,
50+
@Query('step', ParseNumberPipe({ nullable: false })) step: number,
5151
@Query('redirect', ParseBooleanPipe) redirect: boolean
5252
) {
5353
return { contentType, id, step, redirect, test: this.testField };
@@ -77,7 +77,7 @@ class TestHandler {
7777
const { 'content-type': contentType } = headers;
7878
const { id } = query;
7979

80-
return res.status(200).json({ contentType, id, receivedBody: body, test: this.testField });
80+
res.status(200).json({ contentType, id, receivedBody: body, test: this.testField });
8181
}
8282
}
8383

@@ -112,6 +112,19 @@ describe('E2E', () => {
112112
})
113113
));
114114

115+
it('read', () =>
116+
request(server)
117+
.get('/?id=my-id&redirect=true')
118+
.set('Content-Type', 'application/json')
119+
.expect(400)
120+
.then(res =>
121+
expect(res).toMatchObject({
122+
body: {
123+
message: 'step is a required parameter.'
124+
}
125+
})
126+
));
127+
115128
it('create', () =>
116129
request(server)
117130
.post('/')

lib/internals/handler.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,15 @@ export function Handler(method?: HttpVerb): MethodDecorator {
6767
metaParameters.map(async ({ location, name, pipes, index }) => {
6868
let returnValue = await getParameterValue(req, res, bodyParamType, { location, name, index });
6969

70-
if (returnValue && pipes && pipes.length) {
71-
pipes.forEach(pipeFn => (returnValue = pipeFn(returnValue)));
70+
if (pipes && pipes.length) {
71+
pipes.forEach(
72+
pipeFn =>
73+
(returnValue = pipeFn.name
74+
? // Bare pipe function. i.e: `ParseNumberPipe`
75+
(pipeFn as Function).call(null, null).call(null, returnValue, name)
76+
: // Pipe with options. i.e: `ParseNumberPipe({ nullable: false })`
77+
pipeFn.call(null, returnValue, name))
78+
);
7279
}
7380

7481
return returnValue;

lib/pipes/ParameterPipe.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
export type ParameterPipe<T> = (value: any) => T;
1+
export interface PipeOptions {
2+
nullable?: boolean;
3+
}
4+
5+
export type ParameterPipe<T> = (value: any, name?: string) => T;
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { ParseBooleanPipe } from './parseBoolean.pipe';
22

33
describe('ParseBooleanPipe', () => {
4-
it('Should parse the given string as boolean (true)', () => expect(ParseBooleanPipe('true')).toStrictEqual(true));
4+
it('Should parse the given string as boolean (true)', () => expect(ParseBooleanPipe()('true')).toStrictEqual(true));
55

6-
it('Should parse the given string as boolean (false)', () => expect(ParseBooleanPipe('false')).toStrictEqual(false));
6+
it('Should parse the given string as boolean (false)', () =>
7+
expect(ParseBooleanPipe()('false')).toStrictEqual(false));
8+
9+
it('Should throw required error the given value is empty', () =>
10+
expect(() => ParseBooleanPipe({ nullable: false })('')).toThrow());
711

812
it('Should throw when the given string is not a boolean string', () =>
9-
expect(() => ParseBooleanPipe('test')).toThrow());
13+
expect(() => ParseBooleanPipe()('test')).toThrow());
1014
});

lib/pipes/parseBoolean.pipe.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import { BadRequestException } from '../exceptions';
2+
import type { ParameterPipe, PipeOptions } from './ParameterPipe';
3+
import { validatePipeOptions } from './validatePipeOptions';
24

3-
export function ParseBooleanPipe(value: any): boolean {
4-
if (value === true || value === 'true') {
5-
return true;
6-
}
5+
export function ParseBooleanPipe(options?: PipeOptions): ParameterPipe<boolean> {
6+
return (value: any, name?: string) => {
7+
validatePipeOptions(value, name, options);
78

8-
if (value === false || value === 'false') {
9-
return false;
10-
}
9+
if (value === true || value === 'true') {
10+
return true;
11+
}
1112

12-
throw new BadRequestException('Validation failed (boolean string is expected)');
13+
if (value === false || value === 'false') {
14+
return false;
15+
}
16+
17+
throw new BadRequestException(`Validation failed${name ? ` for ${name}` : ''} (boolean string is expected)`);
18+
};
1319
}

lib/pipes/parseNumber.pipe.spec.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { ParseNumberPipe } from './parseNumber.pipe';
22

33
describe('ParseNumberPipe', () => {
4-
it('Should parse the given string as number', () => expect(ParseNumberPipe('10')).toStrictEqual(10));
4+
it('Should parse the given string as number', () => expect(ParseNumberPipe()('10')).toStrictEqual(10));
55

66
it('Should parse the given string as number with decimal points', () =>
7-
expect(ParseNumberPipe('07.99')).toStrictEqual(7.99));
7+
expect(ParseNumberPipe()('07.99')).toStrictEqual(7.99));
8+
9+
it('Should throw required error the given value is empty', () =>
10+
expect(() => ParseNumberPipe({ nullable: false })('')).toThrow());
811

912
it('Should throw when the given string is not a numeric string', () =>
10-
expect(() => ParseNumberPipe('test')).toThrow());
13+
expect(() => ParseNumberPipe()('test')).toThrow());
1114
});

lib/pipes/parseNumber.pipe.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import { BadRequestException } from '../exceptions';
2+
import type { ParameterPipe, PipeOptions } from './ParameterPipe';
3+
import { validatePipeOptions } from './validatePipeOptions';
24

3-
export function ParseNumberPipe(value: any): number {
4-
const isNumeric = ['string', 'number'].includes(typeof value) && !isNaN(parseFloat(value)) && isFinite(value);
5-
if (!isNumeric) {
6-
throw new BadRequestException('Validation failed (numeric string is expected)');
7-
}
8-
return parseFloat(value);
5+
export function ParseNumberPipe(options?: PipeOptions): ParameterPipe<number> {
6+
return (value: any, name?: string) => {
7+
validatePipeOptions(value, name, options);
8+
9+
const isNumeric = ['string', 'number'].includes(typeof value) && !isNaN(parseFloat(value)) && isFinite(value);
10+
if (!isNumeric) {
11+
throw new BadRequestException(`Validation failed${name ? ` for ${name}` : ''} (numeric string is expected)`);
12+
}
13+
14+
return parseFloat(value);
15+
};
916
}

lib/pipes/validatePipeOptions.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { BadRequestException } from '../exceptions';
2+
import type { PipeOptions } from './ParameterPipe';
3+
4+
export function validatePipeOptions(value: any, name?: string, options?: PipeOptions) {
5+
if (!options?.nullable && (value == null || value.toString().trim().length === 0)) {
6+
throw new BadRequestException(name ? `${name} is a required parameter.` : 'Missing a required parameter.');
7+
}
8+
}

0 commit comments

Comments
 (0)