-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
284 lines (242 loc) · 9.12 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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
var strings = {
data_missing: "The dataset.data property does not exist.",
data_not_array: "The dataset.data property is not an array, its type is '%type%'.",
data_not_array_of_objects: [
"The dataset.data property is not an array of row objects,",
" it is an array whose elements are of type '%type%'."
].join(""),
metadata_missing: "The dataset.metadata property is missing.",
metadata_not_object: "The dataset.metadata property is not an object, its type is '%type%'.",
metadata_missing_columns: "The dataset.metadata.columns property is missing.",
metadata_columns_not_array: "The dataset.metadata.columns property is not an array, its type is '%type%'.",
metadata_columns_not_array_of_objects: [
"The dataset.metadata.columns property is not an array of column descriptor objects,",
" it is an array whose elements are of type '%type%'."
].join(""),
metadata_columns_name_missing: "The 'name' property is missing from a column descriptor entry in dataset.metadata.columns.",
metadata_columns_name_not_string: "The 'name' property of a column descriptor entry in dataset.metadata.columns is not a string.",
metadata_columns_type_missing: "The 'type' property is missing from the '%column%' column descriptor entry in dataset.metadata.columns.",
metadata_columns_type_not_valid: "The 'type' property for the '%column%' column descriptor is not a valid value.",
column_in_data_not_metadata: "The column '%column%' is present in the data, but there is no entry for it in dataset.metadata.columns.",
column_in_metadata_not_data: "The column '%column%' is present in dataset.metadata.columns, but this column is missing from the row objects in dataset.data.",
column_type_mismatch: "The column '%column%' is present in the data, but its type does not match that declared in dataset.metadata.columns. The type of the data value '%value%' for column '%column' is '%typeInData%', but is declared to be of type '%typeInMetadata%' in dataset.metadata.columns.",
column_metadata_missing: "There is no metadata present for the column '%column%'"
};
var validTypes = {
string: true,
number: true,
date: true
};
function error(id, params){
return Error(errorMessage(id, params));
}
function errorMessage(id, params){
return template(strings[id], params);
}
// Simple templating from http://stackoverflow.com/questions/377961/efficient-javascript-string-replacement
function template(str, params){
return str.replace(/%(\w*)%/g, function(m, key){
return params[key];
});
}
function validate(dataset){
return new Promise(function (resolve, reject){
//////////////////
// dataset.data //
//////////////////
// Validate that the `data` property exists.
if(!dataset.data){
return reject(error("data_missing"));
}
// Validate that the `data` property is an array.
if(dataset.data.constructor !== Array){
return reject(error("data_not_array", {
type: typeof dataset.data
}));
}
// Validate that the `data` property is an array of objects.
var nonObjectType;
var allRowsAreObjects = dataset.data.every(function (d){
var type = typeof d;
if(type === "object"){
return true;
} else {
nonObjectType = type;
return false;
}
});
if(!allRowsAreObjects){
return reject(error("data_not_array_of_objects", {
type: nonObjectType
}));
}
//////////////////////
// dataset.metadata //
//////////////////////
// Validate that the `metadata` property exists.
if(!dataset.metadata){
return reject(error("metadata_missing"));
}
// Validate that the `metadata` property is an object.
if(typeof dataset.metadata !== "object"){
return reject(error("metadata_not_object", {
type: typeof dataset.metadata
}));
}
// Validate that the `metadata.columns` property exists.
if(!dataset.metadata.columns){
return reject(error("metadata_missing_columns"));
}
// Validate that the `metadata.columns` property is an array.
if(dataset.metadata.columns.constructor !== Array){
return reject(error("metadata_columns_not_array", {
type: typeof dataset.metadata.columns
}));
}
// Validate that the `metadata.columns` property is an array of objects.
var nonObjectType;
var allColumnsAreObjects = dataset.metadata.columns.every(function (d){
var type = typeof d;
if(type === "object"){
return true;
} else {
nonObjectType = type;
return false;
}
});
if(!allColumnsAreObjects){
return reject(error("metadata_columns_not_array_of_objects", {
type: nonObjectType
}));
}
// Validate that the each column descriptor has a "name" field.
if(!dataset.metadata.columns.every(function (column){
return column.name;
})){
return reject(error("metadata_columns_name_missing"));
}
// Validate that the each column descriptor has a "name" field that is a string.
if(!dataset.metadata.columns.every(function (column){
return (typeof column.name) === "string";
})){
return reject(error("metadata_columns_name_not_string"));
}
// Validate that the each column descriptor has a "type" field.
var columnNameMissingType;
if(!dataset.metadata.columns.every(function (column){
if(!column.type){
columnNameMissingType = column.name;
}
return column.type;
})){
return reject(error("metadata_columns_type_missing", {
column: columnNameMissingType
}));
}
// Validate that the each column descriptor has a "type" field that is a valid value.
var columnNameInvalidType;
if(!dataset.metadata.columns.every(function (column){
if(validTypes[column.type]){
return true;
} else {
columnNameInvalidType = column.name;
return false;
}
})){
return reject(error("metadata_columns_type_not_valid", {
column: columnNameInvalidType
}));
}
//////////////////////
// dataset.data //
// AND //
// dataset.metadata //
//////////////////////
// Index the columns in the metadata.
var columnsInMetadata = {};
dataset.metadata.columns.forEach(function (column){
//columnsInMetadata[column.name] = true;
columnsInMetadata[column.name] = column.type;
});
//// Index the columns in the data (based on the first row only).
var columnsInData = {};
Object.keys(dataset.data[0]).forEach(function (columnName){
columnsInData[columnName] = true;
});
// Validate that all columns present in the data are also present in metadata.
var columnInDataNotInMetadata;
// In the same pass over the data, validate that types match.
var typeMismatchParams;
var allIsWell = dataset.data.every(function (row){
return Object.keys(row).every(function (columnInData){
var typeInMetadata = columnsInMetadata[columnInData];
// Check that the column is present in metadata.
if(typeInMetadata){
// Check that the actual type matches the declared type.
var value = row[columnInData];
var typeInData = typeof value;
// Detect Date types.
if(typeInData === "object" && value.constructor === Date){
typeInData = "date";
}
if(typeInData !== typeInMetadata){
typeMismatchParams = {
column: columnInData,
value: value,
typeInData: typeInData,
typeInMetadata: typeInMetadata
};
return false;
}
return true;
} else {
columnInDataNotInMetadata = columnInData
return false;
}
});
});
if(!allIsWell){
if(columnInDataNotInMetadata){
return reject(error("column_in_data_not_metadata", {
column: columnInDataNotInMetadata
}));
} else {
// If we got here, then there was a type mismatch.
return reject(error("column_type_mismatch", typeMismatchParams));
}
}
// Validate that all columns present in the metadata are also present in the data.
var columnInMetadataNotInData;
var allColumnsInMetadataAreInData = dataset.metadata.columns.every(function (column){
var columnInMetadata = column.name;
if(columnsInData[columnInMetadata]){
return true;
} else {
columnInMetadataNotInData = columnInMetadata
return false;
}
});
if(!allColumnsInMetadataAreInData){
return reject(error("column_in_metadata_not_data", {
column: columnInMetadataNotInData
}));
}
// If we got here, then all the validation tests passed.
resolve();
});
}
function getColumnMetadata(dataset, columnName){
var matchingColumns = dataset.metadata.columns.filter(function (column){
return column.name === columnName;
})
if(matchingColumns.length === 0){
throw error("column_metadata_missing", { column: columnName });
} else {
return matchingColumns[0];
}
}
module.exports = {
errorMessage: errorMessage,
validate: validate,
getColumnMetadata: getColumnMetadata
};