Add field type validators

- Add validators for specific field types
This commit is contained in:
Clemente Raposo 2020-12-29 18:41:12 +00:00 committed by Dillon-Brown
parent ba542821c2
commit 801632388b
11 changed files with 582 additions and 2 deletions

View file

@ -1,4 +1,5 @@
import {FieldManager} from '@services/record/field/field.manager'; import {FieldManager} from '@services/record/field/field.manager';
import {validationManagerMock} from '@services/record/validation/validation.manager.spec.mock'; import {validationManagerMock} from '@services/record/validation/validation.manager.spec.mock';
import {dataTypeFormatterMock} from '@services/formatters/data-type.formatter.spec.mock';
export const fieldManagerMock = new FieldManager(validationManagerMock); export const fieldManagerMock = new FieldManager(validationManagerMock, dataTypeFormatterMock);

View file

@ -1,4 +1,26 @@
import {ValidationManager} from '@services/record/validation/validation.manager'; import {ValidationManager} from '@services/record/validation/validation.manager';
import {RequiredValidator} from '@services/record/validation/validators/required.validator'; import {RequiredValidator} from '@services/record/validation/validators/required.validator';
import {RangeValidator} from '@services/record/validation/validators/range.validator';
import {CurrencyValidator} from '@services/record/validation/validators/currency.validator';
import {DateValidator} from '@services/record/validation/validators/date.validator';
import {DateTimeValidator} from '@services/record/validation/validators/datetime.validator';
import {EmailValidator} from '@services/record/validation/validators/email.validator';
import {FloatValidator} from '@services/record/validation/validators/float.validator';
import {IntValidator} from '@services/record/validation/validators/int.validator';
import {PhoneValidator} from '@services/record/validation/validators/phone.validator';
import {numberFormatterMock} from '@services/formatters/number/number-formatter.spec.mock';
import {dateFormatterMock} from '@services/formatters/datetime/date-formatter.service.spec.mock';
import {datetimeFormatterMock} from '@services/formatters/datetime/datetime-formatter.service.spec.mock';
import {phoneFormatterMock} from '@services/formatters/phone/phone-formatter.spec.mock';
export const validationManagerMock = new ValidationManager(new RequiredValidator()); export const validationManagerMock = new ValidationManager(
new RequiredValidator(),
new RangeValidator(),
new CurrencyValidator(numberFormatterMock),
new DateValidator(dateFormatterMock),
new DateTimeValidator(datetimeFormatterMock),
new EmailValidator(),
new FloatValidator(numberFormatterMock),
new IntValidator(numberFormatterMock),
new PhoneValidator(phoneFormatterMock)
);

View file

@ -6,6 +6,14 @@ import {AsyncValidatorFn, ValidatorFn} from '@angular/forms';
import {AsyncValidatorInterface} from '@services/record/validation/aync-validator.Interface'; import {AsyncValidatorInterface} from '@services/record/validation/aync-validator.Interface';
import {OverridableMap} from '@app-common/types/OverridableMap'; import {OverridableMap} from '@app-common/types/OverridableMap';
import {RequiredValidator} from '@services/record/validation/validators/required.validator'; import {RequiredValidator} from '@services/record/validation/validators/required.validator';
import {CurrencyValidator} from '@services/record/validation/validators/currency.validator';
import {DateValidator} from '@services/record/validation/validators/date.validator';
import {DateTimeValidator} from '@services/record/validation/validators/datetime.validator';
import {FloatValidator} from '@services/record/validation/validators/float.validator';
import {IntValidator} from '@services/record/validation/validators/int.validator';
import {EmailValidator} from '@services/record/validation/validators/email.validator';
import {PhoneValidator} from '@services/record/validation/validators/phone.validator';
import {RangeValidator} from '@services/record/validation/validators/range.validator';
export interface ValidationManagerInterface { export interface ValidationManagerInterface {
registerValidator(module: string, key: string, validator: ValidatorInterface): void; registerValidator(module: string, key: string, validator: ValidatorInterface): void;
@ -30,12 +38,28 @@ export class ValidationManager implements ValidationManagerInterface {
constructor( constructor(
protected requiredValidator: RequiredValidator, protected requiredValidator: RequiredValidator,
protected rangeValidator: RangeValidator,
protected currencyValidator: CurrencyValidator,
protected dateValidator: DateValidator,
protected datetimeValidator: DateTimeValidator,
protected emailValidator: EmailValidator,
protected floatValidator: FloatValidator,
protected intValidator: IntValidator,
protected phoneValidator: PhoneValidator,
) { ) {
this.validators = new OverridableMap<ValidatorInterface>(); this.validators = new OverridableMap<ValidatorInterface>();
this.asyncValidators = new OverridableMap<AsyncValidatorInterface>(); this.asyncValidators = new OverridableMap<AsyncValidatorInterface>();
this.validators.addEntry('default', 'required', requiredValidator); this.validators.addEntry('default', 'required', requiredValidator);
this.validators.addEntry('default', 'range', rangeValidator);
this.validators.addEntry('default', 'currency', currencyValidator);
this.validators.addEntry('default', 'date', dateValidator);
this.validators.addEntry('default', 'datetime', datetimeValidator);
this.validators.addEntry('default', 'email', emailValidator);
this.validators.addEntry('default', 'float', floatValidator);
this.validators.addEntry('default', 'int', intValidator);
this.validators.addEntry('default', 'phone', phoneValidator);
} }
public registerValidator(module: string, key: string, validator: ValidatorInterface): void { public registerValidator(module: string, key: string, validator: ValidatorInterface): void {

View file

@ -0,0 +1,64 @@
import {ValidatorInterface} from '@services/record/validation/validator.Interface';
import {AbstractControl} from '@angular/forms';
import {Record} from '@app-common/record/record.model';
import {ViewFieldDefinition} from '@app-common/metadata/metadata.model';
import {Injectable} from '@angular/core';
import {NumberFormatter} from '@services/formatters/number/number-formatter.service';
import {StandardValidationErrors, StandardValidatorFn} from '@app-common/services/validators/validators.model';
export const currencyValidator = (formatter: NumberFormatter): StandardValidatorFn => (
(control: AbstractControl): StandardValidationErrors | null => {
if (control.value == null || control.value.length === 0) {
return null;
}
const pattern = formatter.getFloatUserFormatPattern();
const regex = new RegExp(pattern);
if (regex.test(control.value)) {
return null;
}
return {
currencyValidator: {
valid: false,
format: pattern,
message: {
labelKey: 'LBL_VALIDATION_ERROR_CURRENCY_FORMAT',
context: {
value: control.value,
expected: formatter.toUserFormat('1000.50')
}
}
}
};
}
);
@Injectable({
providedIn: 'root'
})
export class CurrencyValidator implements ValidatorInterface {
constructor(protected formatter: NumberFormatter) {
}
applies(record: Record, viewField: ViewFieldDefinition): boolean {
if (!viewField || !viewField.fieldDefinition) {
return false;
}
return viewField.type === 'currency';
}
getValidator(viewField: ViewFieldDefinition): StandardValidatorFn[] {
if (!viewField || !viewField.fieldDefinition) {
return [];
}
return [currencyValidator(this.formatter)];
}
}

View file

@ -0,0 +1,54 @@
import {ValidatorInterface} from '@services/record/validation/validator.Interface';
import {AbstractControl} from '@angular/forms';
import {Record} from '@app-common/record/record.model';
import {ViewFieldDefinition} from '@app-common/metadata/metadata.model';
import {Injectable} from '@angular/core';
import {DateFormatter} from '@services/formatters/datetime/date-formatter.service';
import {StandardValidationErrors, StandardValidatorFn} from '@app-common/services/validators/validators.model';
export const dateValidator = (formatter: DateFormatter): StandardValidatorFn => (
(control: AbstractControl): StandardValidationErrors | null => {
const invalid = formatter.validateUserFormat(control.value);
return invalid ? {
invalidDate: {
value: control.value,
message: {
labelKey: 'LBL_VALIDATION_ERROR_DATE_FORMAT',
context: {
value: control.value,
expected: formatter.toUserFormat('2020-01-12')
}
}
},
} : null;
}
);
@Injectable({
providedIn: 'root'
})
export class DateValidator implements ValidatorInterface {
constructor(protected formatter: DateFormatter) {
}
applies(record: Record, viewField: ViewFieldDefinition): boolean {
if (!viewField || !viewField.fieldDefinition) {
return false;
}
return viewField.type === 'date';
}
getValidator(viewField: ViewFieldDefinition): StandardValidatorFn[] {
if (!viewField || !viewField.fieldDefinition) {
return [];
}
return [dateValidator(this.formatter)];
}
}

View file

@ -0,0 +1,51 @@
import {ValidatorInterface} from '@services/record/validation/validator.Interface';
import {AbstractControl} from '@angular/forms';
import {Record} from '@app-common/record/record.model';
import {ViewFieldDefinition} from '@app-common/metadata/metadata.model';
import {Injectable} from '@angular/core';
import {DatetimeFormatter} from '@services/formatters/datetime/datetime-formatter.service';
import {StandardValidationErrors, StandardValidatorFn} from '@app-common/services/validators/validators.model';
export const dateTimeValidator = (formatter: DatetimeFormatter): StandardValidatorFn => (
(control: AbstractControl): StandardValidationErrors | null => {
const invalid = formatter.validateUserFormat(control.value);
return invalid ? {
dateTimeValidator: {
value: control.value,
message: {
labelKey: 'LBL_VALIDATION_ERROR_DATETIME_FORMAT',
context: {
value: control.value,
expected: formatter.toUserFormat('2020-01-12 12:30:40')
}
}
},
} : null;
}
);
@Injectable({
providedIn: 'root'
})
export class DateTimeValidator implements ValidatorInterface {
constructor(protected formatter: DatetimeFormatter) {
}
applies(record: Record, viewField: ViewFieldDefinition): boolean {
if (!viewField || !viewField.fieldDefinition) {
return false;
}
return viewField.type === 'datetime';
}
getValidator(viewField: ViewFieldDefinition): StandardValidatorFn[] {
if (!viewField || !viewField.fieldDefinition) {
return [];
}
return [dateTimeValidator(this.formatter)];
}
}

View file

@ -0,0 +1,54 @@
import {ValidatorInterface} from '@services/record/validation/validator.Interface';
import {AbstractControl, Validators} from '@angular/forms';
import {Record} from '@app-common/record/record.model';
import {ViewFieldDefinition} from '@app-common/metadata/metadata.model';
import {Injectable} from '@angular/core';
import {StandardValidationErrors, StandardValidatorFn} from '@app-common/services/validators/validators.model';
export const emailValidator = (): StandardValidatorFn => (
(control: AbstractControl): StandardValidationErrors | null => {
const result = Validators.email(control);
if (result === null) {
return null;
}
return {
emailValidator: {
...result,
message: {
labelKey: 'LBL_VALIDATION_ERROR_EMAIL_FORMAT',
context: {
value: control.value,
expected: 'example@example.org'
}
}
}
};
}
);
@Injectable({
providedIn: 'root'
})
export class EmailValidator implements ValidatorInterface {
applies(record: Record, viewField: ViewFieldDefinition): boolean {
if (!viewField || !viewField.fieldDefinition) {
return false;
}
return viewField.type === 'email';
}
getValidator(viewField: ViewFieldDefinition): StandardValidatorFn[] {
if (!viewField || !viewField.fieldDefinition) {
return [];
}
return [emailValidator()];
}
}

View file

@ -0,0 +1,63 @@
import {ValidatorInterface} from '@services/record/validation/validator.Interface';
import {AbstractControl} from '@angular/forms';
import {Record} from '@app-common/record/record.model';
import {ViewFieldDefinition} from '@app-common/metadata/metadata.model';
import {Injectable} from '@angular/core';
import {NumberFormatter} from '@services/formatters/number/number-formatter.service';
import {StandardValidationErrors, StandardValidatorFn} from '@app-common/services/validators/validators.model';
export const floatValidator = (formatter: NumberFormatter): StandardValidatorFn => (
(control: AbstractControl): StandardValidationErrors | null => {
if (control.value == null || control.value.length === 0) {
return null;
}
const pattern = formatter.getFloatUserFormatPattern();
const regex = new RegExp(pattern);
if (regex.test(control.value)) {
return null;
}
return {
floatValidator: {
valid: false,
format: pattern,
message: {
labelKey: 'LBL_VALIDATION_ERROR_FLOAT_FORMAT',
context: {
value: control.value,
expected: formatter.toUserFormat('1000.50')
}
}
}
};
}
);
@Injectable({
providedIn: 'root'
})
export class FloatValidator implements ValidatorInterface {
constructor(protected formatter: NumberFormatter) {
}
applies(record: Record, viewField: ViewFieldDefinition): boolean {
if (!viewField || !viewField.fieldDefinition) {
return false;
}
return viewField.type === 'float';
}
getValidator(viewField: ViewFieldDefinition): StandardValidatorFn[] {
if (!viewField || !viewField.fieldDefinition) {
return [];
}
return [floatValidator(this.formatter)];
}
}

View file

@ -0,0 +1,63 @@
import {ValidatorInterface} from '@services/record/validation/validator.Interface';
import {AbstractControl} from '@angular/forms';
import {Record} from '@app-common/record/record.model';
import {ViewFieldDefinition} from '@app-common/metadata/metadata.model';
import {Injectable} from '@angular/core';
import {NumberFormatter} from '@services/formatters/number/number-formatter.service';
import {StandardValidationErrors, StandardValidatorFn} from '@app-common/services/validators/validators.model';
export const intValidator = (formatter: NumberFormatter): StandardValidatorFn => (
(control: AbstractControl): StandardValidationErrors | null => {
if (control.value == null || control.value.length === 0) {
return null;
}
const pattern = formatter.getIntUserFormatPattern();
const regex = new RegExp(pattern);
if (regex.test(control.value)) {
return null;
}
return {
intValidator: {
valid: false,
format: pattern,
message: {
labelKey: 'LBL_VALIDATION_ERROR_INT_FORMAT',
context: {
value: control.value,
expected: formatter.toUserFormat('1000')
}
}
}
};
}
);
@Injectable({
providedIn: 'root'
})
export class IntValidator implements ValidatorInterface {
constructor(protected formatter: NumberFormatter) {
}
applies(record: Record, viewField: ViewFieldDefinition): boolean {
if (!viewField || !viewField.fieldDefinition) {
return false;
}
return viewField.type === 'int';
}
getValidator(viewField: ViewFieldDefinition): StandardValidatorFn[] {
if (!viewField || !viewField.fieldDefinition) {
return [];
}
return [intValidator(this.formatter)];
}
}

View file

@ -0,0 +1,62 @@
import {ValidatorInterface} from '@services/record/validation/validator.Interface';
import {AbstractControl} from '@angular/forms';
import {Record} from '@app-common/record/record.model';
import {ViewFieldDefinition} from '@app-common/metadata/metadata.model';
import {Injectable} from '@angular/core';
import {PhoneFormatter} from '@services/formatters/phone/phone-formatter.service';
import {StandardValidationErrors, StandardValidatorFn} from '@app-common/services/validators/validators.model';
export const phoneValidator = (formatter: PhoneFormatter): StandardValidatorFn => (
(control: AbstractControl): StandardValidationErrors | null => {
if (control.value == null || control.value.length === 0) {
return null;
}
const pattern = formatter.getUserFormatPattern();
const regex = new RegExp(pattern, 'g');
if (regex.test(control.value)) {
return null;
}
return {
phoneValidator: {
valid: false,
format: pattern,
message: {
labelKey: 'LBL_VALIDATION_ERROR_PHONE_FORMAT',
context: {
value: control.value
}
}
}
};
}
);
@Injectable({
providedIn: 'root'
})
export class PhoneValidator implements ValidatorInterface {
constructor(protected formatter: PhoneFormatter) {
}
applies(record: Record, viewField: ViewFieldDefinition): boolean {
if (!viewField || !viewField.fieldDefinition) {
return false;
}
return viewField.type === 'phone';
}
getValidator(viewField: ViewFieldDefinition): StandardValidatorFn[] {
if (!viewField || !viewField.fieldDefinition) {
return [];
}
return [phoneValidator(this.formatter)];
}
}

View file

@ -0,0 +1,122 @@
import {ValidatorInterface} from '@services/record/validation/validator.Interface';
import {AbstractControl, Validators} from '@angular/forms';
import {Record} from '@app-common/record/record.model';
import {ViewFieldDefinition} from '@app-common/metadata/metadata.model';
import {FieldDefinition, ValidationDefinition} from '@app-common/record/field.model';
import {Injectable} from '@angular/core';
import {StandardValidationErrors, StandardValidatorFn} from '@app-common/services/validators/validators.model';
export const minValidator = (min: number): StandardValidatorFn => (
(control: AbstractControl): StandardValidationErrors | null => {
const result = Validators.min(min)(control);
if (result === null) {
return null;
}
return {
emailValidator: {
...result,
message: {
labelKey: 'LBL_VALIDATION_ERROR_MIN',
context: {
value: control.value,
min: '' + min
}
}
}
};
}
);
export const maxValidator = (max: number): StandardValidatorFn => (
(control: AbstractControl): StandardValidationErrors | null => {
const result = Validators.max(max)(control);
if (result === null) {
return null;
}
return {
emailValidator: {
...result,
message: {
labelKey: 'LBL_VALIDATION_ERROR_MAX',
context: {
value: control.value,
max: '' + max
}
}
}
};
}
);
@Injectable({
providedIn: 'root'
})
export class RangeValidator implements ValidatorInterface {
applies(record: Record, viewField: ViewFieldDefinition): boolean {
if (!viewField || !viewField.fieldDefinition) {
return false;
}
const definition = viewField.fieldDefinition;
return this.getRangeValidation(definition) !== null;
}
getValidator(viewField: ViewFieldDefinition): StandardValidatorFn[] {
if (!viewField || !viewField.fieldDefinition) {
return [];
}
const validation = this.getRangeValidation(viewField.fieldDefinition);
if (!validation) {
return [];
}
const min = validation.min && parseInt('' + validation.min, 10);
const max = validation.max && parseInt('' + validation.max, 10);
const validations = [];
if (isFinite(min)) {
validations.push(minValidator(min));
}
if (isFinite(max)) {
validations.push(maxValidator(max));
}
return validations;
}
protected getRangeValidation(definition: FieldDefinition): ValidationDefinition {
if (this.isRangeValidation(definition.validation)) {
return definition.validation;
}
if (!definition.validations || !definition.validations.length) {
return null;
}
let validation: ValidationDefinition = null;
definition.validations.some(entry => {
validation = entry;
return this.isRangeValidation(entry);
});
return validation;
}
protected isRangeValidation(validation: ValidationDefinition): boolean {
return validation && validation.type === 'range';
}
}