Skip to content

Commit e82a6c7

Browse files
authored
Merge pull request #52 from Lemoncode/issue#50-validation-rules
Issue#50 validation rules
2 parents 8820a24 + 2b7ede8 commit e82a6c7

19 files changed

Lines changed: 888 additions & 57 deletions

File tree

lib/lcformvalidation.d.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,35 @@ export interface FieldValidationFunction {
3333
(value: any, vm: any, customParams: any): ValidationResult;
3434
}
3535

36-
export interface FieldValidationConstraint{
36+
export interface FieldValidationConstraint {
3737
validator: FieldValidationFunction;
3838
eventFilters?: ValidationEventsFilter;
3939
customParams?: any;
4040
}
4141

42-
export interface ValidationConstraints{
42+
export interface ValidationConstraints {
4343
global?: FormValidationFunction[];
4444
fields?: { [key: string]: FieldValidationConstraint[] }
4545
}
4646

4747
export function createFormValidation(validationCredentials: ValidationConstraints): FormValidation;
48+
49+
export interface LengthParams {
50+
length: number;
51+
}
52+
53+
export interface PatternParams {
54+
pattern: string | RegExp;
55+
}
56+
57+
export interface RequiredParams {
58+
trim: boolean;
59+
}
60+
61+
export namespace Validators {
62+
function required(value: any, vm: any, customParams: RequiredParams): FieldValidationResult;
63+
function minLength(value: any, vm: any, customParams: LengthParams): FieldValidationResult;
64+
function maxLength(value: any, vm: any, customParams: LengthParams): FieldValidationResult;
65+
function email(value: any, vm: any): FieldValidationResult;
66+
function pattern(value: any, vm: any, customParams: PatternParams): FieldValidationResult;
67+
}

lib/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { FormValidationResult, FieldValidationResult } from "./entities";
22
import { createFormValidation } from './baseFormValidation';
3+
import { Validators } from './rules';
34

45
export {
56
FormValidationResult,
67
FieldValidationResult,
78
createFormValidation,
9+
Validators,
810
}

lib/src/rules/email.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { FieldValidationResult, FieldValidationFunction } from '../entities';
2+
3+
// RegExp from http://emailregex.com
4+
const EMAIL_PATTERN = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
5+
6+
export const email: FieldValidationFunction = (value: string) => {
7+
const validationResult = new FieldValidationResult();
8+
const isValid = isValidEmail(value);
9+
10+
validationResult.succeeded = isValid;
11+
validationResult.type = 'EMAIL';
12+
validationResult.errorMessage = isValid ? '' : 'Please enter a valid email address.';
13+
return validationResult;
14+
};
15+
16+
function isValidEmail(value): boolean {
17+
return isEmptyValue(value) ?
18+
true :
19+
EMAIL_PATTERN.test(value);
20+
}
21+
22+
function isEmptyValue(value): boolean {
23+
return value === null ||
24+
value === undefined ||
25+
value === '';
26+
}

lib/src/rules/index.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { required } from './required';
2+
import { minLength } from './minLength';
3+
import { maxLength } from './maxLength';
4+
import { email } from './email';
5+
import { pattern } from './pattern';
6+
import { FieldValidationFunction } from '../entities';
7+
8+
9+
interface ValidatorFunctions {
10+
required: FieldValidationFunction;
11+
minLength: FieldValidationFunction;
12+
maxLength: FieldValidationFunction;
13+
email: FieldValidationFunction;
14+
pattern: FieldValidationFunction;
15+
}
16+
17+
export const Validators: ValidatorFunctions = {
18+
required,
19+
minLength,
20+
maxLength,
21+
email,
22+
pattern,
23+
};

lib/src/rules/length.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export interface LengthParams {
2+
length: number;
3+
}
4+
5+
export function parseLengthParams(customParams: LengthParams, errorMessage: string) {
6+
const length = customParams.length === null ? NaN : Number(customParams.length);
7+
if (isNaN(length)) {
8+
throw new Error(errorMessage);
9+
}
10+
11+
return length;
12+
}
13+
14+
export function isLengthValid(
15+
value,
16+
length: number,
17+
validatorFn: (value: string, length: number) => boolean
18+
): boolean {
19+
// Don't try to validate non string values
20+
return typeof value === 'string' ?
21+
validatorFn(value, length) :
22+
true;
23+
}

lib/src/rules/maxLength.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { FieldValidationResult, FieldValidationFunction } from '../entities';
2+
import {
3+
LengthParams,
4+
parseLengthParams,
5+
isLengthValid,
6+
} from './length';
7+
8+
const DEFAULT_PARAMS = { length: undefined };
9+
const BAD_PARAMETER = 'FieldValidationError: Parameter "length" for maxLength in customParams is mandatory and should be a valid number. Example: { length: 4 }.';
10+
11+
export function maxLength(value: string, vm, customParams: LengthParams = DEFAULT_PARAMS) {
12+
const length = parseLengthParams(customParams, BAD_PARAMETER);
13+
const isValid = isLengthValid(value, length, isStringLengthValid);
14+
const validationResult = new FieldValidationResult();
15+
16+
validationResult.succeeded = isValid;
17+
validationResult.type = 'MAX_LENGTH';
18+
validationResult.errorMessage = isValid ? '' : `The value provided is too long. Length must not exceed ${length} characters.`;
19+
return validationResult;
20+
}
21+
22+
function isStringLengthValid(value: string, length: number): boolean {
23+
return isNaN(length) ?
24+
false :
25+
value.length <= length;
26+
}

lib/src/rules/minLength.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { FieldValidationResult, FieldValidationFunction } from '../entities';
2+
import {
3+
LengthParams,
4+
parseLengthParams,
5+
isLengthValid,
6+
} from './length';
7+
8+
const DEFAULT_PARAMS = { length: undefined };
9+
const BAD_PARAMETER = 'FieldValidationError: Parameter "length" for minLength in customParams is mandatory and should be a valid number. Example: { length: 4 }.';
10+
11+
export const minLength: FieldValidationFunction = (value: string, vm, customParams: LengthParams = DEFAULT_PARAMS) => {
12+
const length = parseLengthParams(customParams, BAD_PARAMETER);
13+
const isValid = isLengthValid(value, length, isStringLengthValid);
14+
const validationResult = new FieldValidationResult();
15+
16+
validationResult.errorMessage = isValid ? '' : `The value provided must have at least ${length} characters.`;
17+
validationResult.succeeded = isValid;
18+
validationResult.type = 'MIN_LENGTH';
19+
return validationResult;
20+
}
21+
22+
function isStringLengthValid(value: string, length: number): boolean {
23+
return value.length >= length;
24+
}

lib/src/rules/pattern.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { FieldValidationResult } from '../entities';
2+
3+
export interface PatternParams {
4+
pattern: string | RegExp;
5+
}
6+
7+
const BAD_PARAMETER = 'FieldValidationError: pattern option for pattern validation is mandatory. Example: { pattern: /\d+/ }.';
8+
9+
export function pattern(value: string, vm, customParams: PatternParams): FieldValidationResult {
10+
const pattern = parsePattern(customParams);
11+
const isValid = pattern.test(value);
12+
const validationResult = new FieldValidationResult();
13+
14+
validationResult.succeeded = isValid;
15+
validationResult.type = 'PATTERN';
16+
validationResult.errorMessage = isValid ? '' : `Please provide a valid format.`;
17+
18+
return validationResult;
19+
}
20+
21+
function parsePattern({ pattern }: PatternParams): RegExp {
22+
// Avoid RegExp like /true/ /false/ and /null/ without an explicit "true", "false" or "null"
23+
if (typeof pattern === 'boolean' || pattern === null) {
24+
throw new Error(BAD_PARAMETER);
25+
}
26+
return getRegExp(pattern);
27+
}
28+
29+
function getRegExp(pattern): RegExp {
30+
return pattern instanceof RegExp ?
31+
pattern :
32+
new RegExp(pattern);
33+
}

lib/src/rules/required.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { FieldValidationResult, FieldValidationFunction } from '../entities';
2+
export interface RequiredParams {
3+
trim: boolean;
4+
}
5+
6+
const DEFAULT_PARAMS: RequiredParams = { trim: true };
7+
8+
export const required: FieldValidationFunction = (value, vm, customParams: RequiredParams = DEFAULT_PARAMS) => {
9+
const validationResult = new FieldValidationResult();
10+
const isValid = isValidField(value, Boolean(customParams.trim));
11+
validationResult.errorMessage = isValid ? '' : 'Please fill in this mandatory field.';
12+
validationResult.succeeded = isValid;
13+
validationResult.type = 'REQUIRED';
14+
return validationResult;
15+
}
16+
17+
function isValidField(value, trim: boolean): boolean {
18+
return typeof value === 'string' ?
19+
isStringValid(value, trim) :
20+
value === true;
21+
}
22+
23+
function isStringValid(value: string, trim: boolean): boolean {
24+
return trim ?
25+
value.trim().length > 0 :
26+
value.length > 0;
27+
}

lib/src/rules/spec/email.spec.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { email } from '../email';
2+
import { FieldValidationResult } from '../../entities';
3+
4+
describe('[email] validation rule tests =>', () => {
5+
describe('When validating a non string value', () => {
6+
it('should return true if value is null', () => {
7+
// Arrange
8+
const value = null;
9+
const vm = undefined;
10+
const customParams = undefined;
11+
12+
// Act
13+
const validationResult = email(value, vm, customParams) as FieldValidationResult;
14+
15+
// Assert
16+
expect(validationResult.succeeded).to.be.true;
17+
expect(validationResult.type).to.be.equals('EMAIL');
18+
expect(validationResult.errorMessage).to.be.empty;
19+
});
20+
21+
it('should return true if value is undefined', () => {
22+
// Arrange
23+
const value = undefined;
24+
const vm = undefined;
25+
const customParams = undefined;
26+
27+
// Act
28+
const validationResult = email(value, vm, customParams) as FieldValidationResult;
29+
30+
// Assert
31+
expect(validationResult.succeeded).to.be.true;
32+
expect(validationResult.type).to.be.equals('EMAIL');
33+
expect(validationResult.errorMessage).to.be.empty;
34+
});
35+
36+
it('should return false if value is number', () => {
37+
// Arrange
38+
const value = Math.PI;
39+
const vm = undefined;
40+
const customParams = undefined;
41+
42+
// Act
43+
const validationResult = email(value, vm, customParams) as FieldValidationResult;
44+
45+
// Assert
46+
expect(validationResult.succeeded).to.be.false;
47+
expect(validationResult.type).to.be.equals('EMAIL');
48+
expect(validationResult.errorMessage).to.be.equals('Please enter a valid email address.');
49+
});
50+
51+
it('should return false if value is an object', () => {
52+
// Arrange
53+
const value = {};
54+
const vm = undefined;
55+
const customParams = undefined;
56+
57+
// Act
58+
const validationResult = email(value, vm, customParams) as FieldValidationResult;
59+
60+
// Assert
61+
expect(validationResult.succeeded).to.be.false;
62+
expect(validationResult.type).to.be.equals('EMAIL');
63+
expect(validationResult.errorMessage).to.be.equals('Please enter a valid email address.');
64+
});
65+
66+
it('should return false if value is an array', () => {
67+
// Arrange
68+
const value = ['a', '@', 'b', '.', 'c', 'o', 'm'];
69+
const vm = undefined;
70+
const customParams = undefined;
71+
72+
// Act
73+
const validationResult = email(value, vm, customParams) as FieldValidationResult;
74+
75+
// Assert
76+
expect(validationResult.succeeded).to.be.false;
77+
expect(validationResult.type).to.be.equals('EMAIL');
78+
expect(validationResult.errorMessage).to.be.equals('Please enter a valid email address.');
79+
});
80+
81+
it('should return false if value is a function', () => {
82+
// Arrange
83+
const value = () => { };
84+
const vm = undefined;
85+
const customParams = undefined;
86+
87+
// Act
88+
const validationResult = email(value, vm, customParams) as FieldValidationResult;
89+
90+
// Assert
91+
expect(validationResult.succeeded).to.be.false;
92+
expect(validationResult.type).to.be.equals('EMAIL');
93+
expect(validationResult.errorMessage).to.be.equals('Please enter a valid email address.');
94+
});
95+
});
96+
describe('When validating a string value', () => {
97+
it('should return false for invalid email address', () => {
98+
// Arrange
99+
const value = 'some text';
100+
const vm = undefined;
101+
const customParams = undefined;
102+
103+
// Act
104+
const validationResult = email(value, vm, customParams) as FieldValidationResult;
105+
106+
// Assert
107+
expect(validationResult.succeeded).to.be.false;
108+
expect(validationResult.type).to.be.equals('EMAIL');
109+
expect(validationResult.errorMessage).to.be.equals('Please enter a valid email address.');
110+
});
111+
it('should return true for a valid email address', () => {
112+
// Arrange
113+
const value = 'a@b.com';
114+
const vm = undefined;
115+
const customParams = undefined;
116+
117+
// Act
118+
const validationResult = email(value, vm, customParams) as FieldValidationResult;
119+
120+
// Assert
121+
expect(validationResult.succeeded).to.be.true;
122+
expect(validationResult.type).to.be.equals('EMAIL');
123+
expect(validationResult.errorMessage).to.be.empty;
124+
});
125+
});
126+
});

0 commit comments

Comments
 (0)