-
Notifications
You must be signed in to change notification settings - Fork 1
/
model-validator.ts
155 lines (128 loc) · 4.59 KB
/
model-validator.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import {
ObjectValidator, Validator, StringValidator, MaxLengthValidator,
IntValidator, NumberValidator, DateValidator, BooleanValidator,
NotNullValidator, ValidationMetadata
} from 'bsy-validation';
import {
metaFactory, ColumnMetadata, RelationshipMetadata
} from '../../metadata/';
import { SubModelTypeValidator, KeyValType } from '../';
/**
* A specialized ObjectValidator (from bsy-validation) that's used to validate
* objects against Formn Entity metadata.
*/
export class ModelValidator extends ObjectValidator {
/**
* Initialize with an ObjectValidator instance. This class decorates
* ObjectValidator's validate method.
*/
constructor(
protected objectValidator: ObjectValidator = new ObjectValidator()) {
super();
}
/**
* Verify that each property of obj meets the requirements defined by
* [[Column]] and [[Relationship]] decoration, such as data type, maximum
* length, and nullability. If valid, then check any user-defined
* validation, such as email and phone number validation. (Reference the
* bsy-validation package, as the ObjectValidator class is used for
* validation.)
* @param obj - The object to validate against class Entity.
* @param Entity - A class that has properties decorated with @Validate.
* This is the schema against which obj will be validated.
*/
async validate(
obj: object,
Entity: {new(): any}): Promise<void> {
// First check if obj is valid based on the ColumnMetadata (e.g. data type,
// max length, etc.) and RelationshipMetadata using the custom
// getValidationMetadata method.
await super
.validate(obj, Entity);
// If the column- and relationship-level validation passes, that passes,
// then validate the object using a standard ObjectValidator (i.e. any
// custom validation added by a user, like email and phone checks).
//
// Note that objectValidator may be null (no chaining).
if (this.objectValidator) {
await this.objectValidator
.validate(obj, Entity);
}
// Next, validate any sub-resources.
const relMeta = metaFactory
.getRelationshipStore()
.getRelationships(Entity, null, true);
for (let meta of relMeta) {
if ((obj as KeyValType)[meta.mapTo]) {
if (meta.cardinality === 'OneToMany') {
const subResources = (obj as KeyValType)[meta.mapTo];
for (let resource of subResources) {
await this
.validate(resource, meta.to());
}
}
else {
const subResource = (obj as KeyValType)[meta.mapTo];
await this
.validate(subResource, meta.to());
}
}
}
}
/**
* Generate validation metadata (see bsy-validation) for the Entity.
*/
getValidationMetadata(Entity: {new(): any}): ValidationMetadata[] {
// Column-level validation.
const colMeta = metaFactory
.getColumnStore()
.getColumnMetadata(Entity);
const colValMeta = colMeta
.map((meta: ColumnMetadata) => {
const validators: Validator[] = [];
// Datatype validation.
switch (meta.dataType) {
case 'String':
validators.push(new StringValidator());
if (meta.maxLength !== undefined)
validators.push(new MaxLengthValidator(meta.maxLength));
break;
case 'Date':
validators.push(new DateValidator());
break;
case 'Number':
switch (meta.sqlDataType) {
case 'int':
case 'smallint':
case 'mediumint':
case 'bigint':
validators.push(new IntValidator());
break;
default:
validators.push(new NumberValidator());
break;
}
break;
case 'Boolean':
validators.push(new BooleanValidator());
break;
}
// Null validation.
if (meta.isNullable === false)
validators.push(new NotNullValidator());
return new ValidationMetadata(Entity, meta.mapTo, validators);
});
// Relationship-level validation (e.g. checks that a sub-resource
// is an array or an object, depending on the cardinality of the
// relationship).
const relMeta = metaFactory
.getRelationshipStore()
.getRelationships(Entity, null, true);
const relValMeta = relMeta
.map((meta: RelationshipMetadata) =>
new ValidationMetadata(Entity, meta.mapTo,
[new SubModelTypeValidator(meta.cardinality)]));;
return colValMeta
.concat(relValMeta)
}
}