-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
183 lines (157 loc) · 4.66 KB
/
index.js
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/**
* Prototype of a validator function.
*
* @callback validatorFunction
* @param {any} value Value to be validated.
* @returns {boolean} Result of the validation.
*/
class ScoringModel {
/**
* A model that determines how an object can be scored.
*
* @param {string} name Name of the model.
*/
constructor(name) {
this.name = name;
this._fields = [];
}
/**
* The total weights for this model.
*/
get weights() {
return this._fields.reduce((prev, curr) => prev + curr.weight, 0);
}
/**
* Add a new field to this scoring model.
*
* @param {string} name Exact name of the field to score.
* @param {float} [weight=1.0] Max score possible from this field. This weight will
* be divided equally between all validators on this field.
* @param {boolean} [required=false] Is this field required to exist?
*/
field(name, weight=1.0, required=false) {
let field = this._fields.find(f => f.name === name);
if (field === undefined) {
field = new ScoringField(name, weight, required);
this._fields.push(field);
}
return field;
}
/**
* Score an individual object against this model.
*
* @param {Object} data Object to score using this model.
* @param {boolean} [showMessages=false] Set to true to return detailed messages
* for each field explaining the score.
*/
score(data, showMessages=false) {
let score = 0;
let isObjectValid = true;
const fieldResults = {};
for (let field of this._fields) {
const results = field.score(data[field.name]);
score += results.score;
fieldResults[field.name] = results;
// Check if one of the required validators failed
if (!results.valid) {
isObjectValid = false;
}
}
// If one of the required validators failed, mark object as 0
if (!isObjectValid) {
score = 0;
}
if (showMessages) {
return { score, fields: fieldResults, valid: isObjectValid };
} else {
return score;
}
}
/**
* Simple factory to create a model.
*
* @param {string} name Name for the new model.
*/
static create(name) {
return new ScoringModel(name);
}
}
class ScoringField {
/**
* Create a new field.
*
* @param {string} name Name of the key to for this field.
* @param {float} [weight=1.0] Max score possible from this field. This weight will
* be divided equally between all validators on this field.
* @param {boolean} [required=false] Is this field required to exist?
*/
constructor(name, weight=1.0, required=false) {
this.name = name;
this.weight = weight;
this.required = required;
this._validators = [];
}
/**
* Set the weight of this field.
*
* @param {float} value Set weight of this field to value.
*/
setWeight(value) {
if (typeof value !== 'number') {
throw new TypeError('Weight passed to setWeight() needs to be a number');
}
this.weight = value;
return this;
}
/**
* Add a new validator to the list that will be applied to this field.
*
* @param {validatorFunction} func A function that receives a value and determines
* whether or not that value passes a validation.
* @param {string} message Message to be returned if showMessages is enabled
* during scoring.
* @param {boolean} [required=false] Is this validator required to pass.
*/
validator(func, message, required=false) {
this._validators.push({ func, message, required });
return this;
}
/**
* Score a value against the validators in this field.
*
* @param {any} value Value to run through validators.
*/
score(value) {
let score = 0;
let isFieldValid = true;
const messages = [];
const weightPerValidator = this.weight / (this._validators.length + 1);
if (value !== undefined && value !== null) {
// Value exists. Increase score for that
score += weightPerValidator;
for (let { func, message, required } of this._validators) {
const passed = func(value);
if (passed) {
score += weightPerValidator;
} else if (message !== undefined && message !== null) {
messages.push(message);
// If this validator is required, mark the field as invalid
if (required) {
isFieldValid = false;
}
}
}
} else {
messages.push(`Field ${this.name} is not defined.`);
if (this.required) {
isFieldValid = false;
}
}
// If the field is invalid, set score to 0
if (!isFieldValid) {
score = 0;
}
return { score, messages, valid: isFieldValid };
}
}
module.exports = { ScoringModel, ScoringField };