UNPKG

21.1 kBJavaScriptView Raw
1'use strict';
2
3var path = require('path');
4var util = require('util');
5var async = require('async');
6var fileSystem = process.requireApi('lib/fileSystem.js');
7
8/**
9 * Provides functions for common JavaScript operations.
10 *
11 * // Load module "util"
12 * var util = require('@openveo/api').util;
13 *
14 * @module util
15 * @class util
16 * @main util
17 */
18
19/**
20 * Merges, recursively, all properties of object2 in object1.
21 *
22 * This will not create copies of objects.
23 *
24 * @method merge
25 * @static
26 * @param {Object} object1 The JavaScript final object
27 * @param {Object} object2 A second JavaScript object to merge into
28 * the first one
29 * @return {Object} object1
30 */
31module.exports.merge = function(object1, object2) {
32 if (!object2)
33 return object1;
34
35 if (!object1)
36 return object2;
37
38 for (var property in object2) {
39
40 try {
41
42 // Object property is an object
43 // Recusively merge its properties
44 if (typeof object2[property] === 'object' && !util.isArray(object2[property])) {
45 object1[property] = object1[property] || {};
46 object1[property] = this.merge(object1[property], object2[property]);
47 } else
48 object1[property] = object2[property];
49
50 } catch (e) {
51
52 // Property does not exist in object1, create it
53 object1[property] = object2[property];
54
55 }
56
57 }
58
59 return object1;
60};
61
62/**
63 * Makes union of two arrays.
64 *
65 * @method joinArray
66 * @static
67 * @param {Array} [array1] An array
68 * @param {Array} [array2] An array
69 * @return {Array} The union of the two arrays
70 */
71module.exports.joinArray = function(array1, array2) {
72 return array1.concat(array2.filter(function(item) {
73 return array1.indexOf(item) < 0;
74 }));
75};
76
77/**
78 * Makes intersection of two arrays.
79 *
80 * @method intersectArray
81 * @static
82 * @param {Array} [array1] An array
83 * @param {Array} [array2] An array
84 * @return {Array} The intersection of the two arrays
85 */
86module.exports.intersectArray = function(array1, array2) {
87 return array2.filter(function(item) {
88 return array1.indexOf(item) >= 0;
89 });
90};
91
92/**
93 * Compares two arrays.
94 *
95 * Shallow validates that two arrays contains the same elements, no more no less.
96 *
97 * @method areSameArrays
98 * @static
99 * @param {Array} [array1] An array
100 * @param {Array} [array2] An array
101 * @return {Boolean} true if arrays are the same, false otherwise
102 */
103module.exports.areSameArrays = function(array1, array2) {
104 if (array1.length === array2.length && this.intersectArray(array1, array2).length === array1.length)
105 return true;
106 else
107 return false;
108};
109
110/**
111 * Checks if an email address is valid or not.
112 *
113 * @method isEmailValid
114 * @static
115 * @param {String} email The email address
116 * @return {Boolean} true if the email is valid, false otherwise
117 */
118module.exports.isEmailValid = function(email) {
119 var reg = new RegExp('[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9]' +
120 '(?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?');
121 return reg.test(email);
122};
123
124/**
125 * Checks if a value is isContained into another comparing primitive types.
126 *
127 * All values in expectedValue must be found in value to pass the test.
128 *
129 * @method isContained
130 * @static
131 * @param {Object|Number|String|Array} expectedValue The value expecting to be found in "value"
132 * @return {Boolean} true if the expected value has been found in value
133 */
134module.exports.isContained = function(expectedValue, value) {
135 if (Object.prototype.toString.call(expectedValue) === '[object Array]') {
136 if (Object.prototype.toString.call(value) !== '[object Array]')
137 return false;
138
139 for (var i = 0; i < expectedValue.length; i++) {
140 if (!this.isContained(expectedValue[i], value[i]))
141 return false;
142 }
143 } else if (Object.prototype.toString.call(expectedValue) === '[object Object]') {
144 if (Object.prototype.toString.call(value) !== '[object Object]')
145 return false;
146
147 for (var property in expectedValue) {
148 if (!this.isContained(expectedValue[property], value[property]))
149 return false;
150 }
151 } else if (expectedValue !== value)
152 return false;
153
154 return true;
155};
156
157/**
158 * Validates first level object properties using the given validation description object.
159 *
160 * It helps validating that an object, coming from a request query string parameters correspond to the expected
161 * type, if it has to be required, if it must be contained into a list of values etc.
162 *
163 * Available features by types :
164 * - **string**
165 * - **default** Specify a default value
166 * - **required** Boolean to indicate if the value is required (if default is specified, value will always be set)
167 * - **in** Specify an array of strings to validate that the value is inside this array
168 * - **number**
169 * - **default** Specify a default value
170 * - **required** Boolean to indicate if the value is required (if default is specified, value will always be set)
171 * - **in** Specify an array of numbers to validate that the value is inside this array
172 * - **gt** Specify a number to validate that the value is greater than this number
173 * - **lt** Specify a number to validate that the value is lesser than this number
174 * - **gte** Specify a number to validate that the value is greater or equal to this number
175 * - **lte** Specify a number to validate that the value is lesser or equal to this number
176 * - **array&lt;string&gt;**
177 * - **required** Boolean to indicate if the value is required (an empty array is not an error)
178 * - **in** Specify an array of values to validate that each value of the array is inside this array
179 * - **array&lt;number&gt;**
180 * - **required** Boolean to indicate if the value is required (an empty array is not an error)
181 * - **in** Specify an array of values to validate that each value of the array is inside this array
182 * - **array&lt;object&gt;**
183 * - **required** Boolean to indicate if the value is required (an empty array is not an error)
184 * - **date**
185 * - **required** Boolean to indicate if the value is required
186 * - **gt** Specify a date to validate that the value is greater than this date
187 * - **lt** Specify a date to validate that the value is lesser than this date
188 * - **gte** Specify a date to validate that the value is greater or equal to this date
189 * - **lte** Specify a date to validate that the value is lesser or equal to this date
190 * - **object**
191 * - **default** Specify a default value
192 * - **required** Boolean to indicate if the value is required (if default is specified, value will always be set)
193 * - **boolean**
194 * - **default** Specify a default value
195 * - **required** Boolean to indicate if the value is required (if default is specified, value will always be set)
196 * - **file**
197 * - **required** Boolean to indicate if the value is required
198 * - **in** Specify an array of types to validate that the file's type is inside this array
199 *
200 * @example
201 *
202 * // Get util
203 * var util = require('@openveo/api').util;
204 * var fileSystem = require('@openveo/api').fileSystem;
205 *
206 * // Validate parameters
207 * var params = util.shallowValidateObject({
208 * myStringProperty: 'my value',
209 * myNumberProperty: 25,
210 * myArrayStringProperty: ['value1', 'value2'],
211 * myArrayNumberProperty: [10, 5],
212 * myArrayObjectProperty: [{}, {}],
213 * myDateProperty: '02/25/2016',
214 * myObjectProperty: {firstKey: 'firstValue'},
215 * myBooleanProperty: true,
216 * myFileProperty: 88 13 70 17 // At least the first 300 bytes of the file
217 * }, {
218 * myStringProperty: {type: 'string', required: true, default: 'default', in: ['my value', 'value']},
219 * myNumberProperty: {type: 'number', required: true, default: 0, in: [0, 5, 10], gte: 0, lte: 5},
220 * myArrayStringProperty: {type: 'array<string>', required: true, in: ['value1', 'value2']},
221 * myArrayNumberProperty: {type: 'array<number>', required: true, in: [42, 43]},
222 * myArrayObjectProperty: {type: 'array<object>', required: true},
223 * myDateProperty: {type: 'date', required: true, gte: '02/20/2016', lte: '03/30/2016'},
224 * myObjectProperty: {type: 'object', required: true},
225 * myBooleanProperty: {type: 'boolean', required: true},
226 * myFileProperty: {type: 'file', required: true, in: [
227 * fileSystem.FILE_TYPES.JPG,
228 * fileSystem.FILE_TYPES.PNG,
229 * fileSystem.FILE_TYPES.GIF,
230 * fileSystem.FILE_TYPES.MP4,
231 * fileSystem.FILE_TYPES.TAR
232 * ]}
233 * });
234 *
235 * console.log(params);
236 *
237 * @method shallowValidateObject
238 * @static
239 * @param {Object} objectToAnalyze The object to analyze
240 * @param {Object} validationDescription The validation description object
241 * @return {Object} A new object with the list of properties as expected
242 * @throws {Error} An error if a property does not respect its associated rules
243 */
244module.exports.shallowValidateObject = function(objectToAnalyze, validationDescription) {
245 var properties = {};
246
247 // Iterate through the list of expected properties
248 for (var name in validationDescription) {
249 var expectedProperty = validationDescription[name];
250 var value = objectToAnalyze[name];
251
252 if (expectedProperty) {
253
254 // This property was expected
255
256 // Options
257 var required = expectedProperty.required || false;
258 var inside = expectedProperty.in || null;
259 var defaultValue = expectedProperty.default !== undefined ? expectedProperty.default : null;
260 var gt = expectedProperty.gt !== undefined ? expectedProperty.gt : null;
261 var lt = expectedProperty.lt !== undefined ? expectedProperty.lt : null;
262 var gte = expectedProperty.gte !== undefined ? expectedProperty.gte : null;
263 var lte = expectedProperty.lte !== undefined ? expectedProperty.lte : null;
264
265 switch (expectedProperty.type) {
266 case 'string':
267 value = value !== undefined ? String(value) : defaultValue;
268 if (inside && inside.indexOf(value) < 0)
269 throw new Error('Property ' + name + ' must be one of ' + inside.join(', '));
270 break;
271 case 'number':
272 value = value !== undefined ? parseInt(value) : defaultValue;
273 value = isNaN(value) ? defaultValue : value;
274 if (gt !== null) gt = parseInt(gt);
275 if (lt !== null) lt = parseInt(lt);
276 if (gte !== null) gte = parseInt(gte);
277 if (lte !== null) lte = parseInt(lte);
278
279 if (value === null) break;
280
281 if (gt !== null && value <= gt)
282 throw new Error('Property ' + name + ' must be greater than ' + gt);
283
284 if (lt !== null && value >= lt)
285 throw new Error('Property ' + name + ' must be lesser than ' + lt);
286
287 if (gte !== null && value < gte)
288 throw new Error('Property ' + name + ' must be greater or equal to ' + gte);
289
290 if (lte !== null && value > lte)
291 throw new Error('Property ' + name + ' must be lesser or equal to ' + lte);
292
293 if (inside && inside.indexOf(value) < 0)
294 throw new Error('Property ' + name + ' must be one of ' + inside.join(', '));
295
296 break;
297 case 'array<string>':
298 case 'array<number>':
299 case 'array<object>':
300 var arrayType = /array<([^>]*)>/.exec(expectedProperty.type)[1];
301
302 if ((typeof value === 'string' || typeof value === 'number') && arrayType !== 'object') {
303 value = arrayType === 'string' ? String(value) : parseInt(value);
304 value = value ? [value] : null;
305 } else if (Object.prototype.toString.call(value) === '[object Array]') {
306 var arrayValues = [];
307 for (var i = 0; i < value.length; i++) {
308
309 if (arrayType === 'string' || arrayType === 'number') {
310 var convertedValue = arrayType === 'string' ? String(value[i]) : parseInt(value[i]);
311 if (convertedValue) {
312
313 if (inside && inside.indexOf(convertedValue) < 0)
314 throw new Error('Property ' + name + ' has a value (' + convertedValue +
315 ') which is not part of ' + inside.join('or '));
316
317 arrayValues.push(convertedValue);
318 }
319 }
320
321 if (arrayType === 'object' && Object.prototype.toString.call(value[i]) === '[object Object]')
322 arrayValues.push(value[i]);
323 }
324
325 value = arrayValues.length ? arrayValues : null;
326 } else if (typeof value !== 'undefined')
327 throw new Error('Property ' + name + ' must be a "' + expectedProperty.type + '"');
328 else
329 value = null;
330
331 break;
332 case 'file':
333 if (typeof value === 'string' || (value instanceof Buffer)) {
334 var fileBuffer = (value instanceof Buffer) ? value : Buffer.from(value, 'binary');
335 var fileType = fileSystem.getFileTypeFromBuffer(fileBuffer);
336
337 if (!fileType) {
338 throw new Error(
339 'Property ' + name + ' must be a supported file (' +
340 Object.keys(fileSystem.FILE_TYPES).join(', ') + ')'
341 );
342 }
343
344 if (inside && inside.indexOf(fileType) < 0)
345 throw new Error('Property ' + name + ' must be a ' + inside.join('or ') + ' file');
346
347 value = {type: fileType, file: fileBuffer};
348 break;
349 }
350
351 value = null;
352 break;
353 case 'date':
354 var date;
355
356 if (!value)
357 value = null;
358 else {
359 if (typeof value === 'string') {
360
361 // Convert literal date into Date object
362 if (!isNaN(new Date(value).getTime()))
363 date = new Date(value).getTime();
364 else if (!isNaN(parseInt(value)))
365 date = new Date(parseInt(value)).getTime();
366 else
367 date = null;
368
369 } else if (Object.prototype.toString.call(value) === '[object Date]') {
370
371 // Already a Date object
372 date = value.getTime();
373
374 }
375
376 if (date)
377 value = date;
378
379 if (gt) {
380 var gtDate = typeof gt === 'object' ? gt : new Date(gt);
381 if (value <= gtDate.getTime())
382 throw new Error('Property ' + name + ' must be greater than ' + gtDate.toString());
383 }
384
385 if (lt) {
386 var ltDate = typeof lt === 'object' ? lt : new Date(lt);
387 if (value >= ltDate.getTime())
388 throw new Error('Property ' + name + ' must be lesser than ' + ltDate.toString());
389 }
390
391 if (gte) {
392 var gteDate = typeof gte === 'object' ? gte : new Date(gte);
393 if (value < gteDate.getTime())
394 throw new Error('Property ' + name + ' must be greater or equal to ' + gteDate.toString());
395 }
396
397 if (lte) {
398 var lteDate = typeof lte === 'object' ? lte : new Date(lte);
399 if (value > lteDate.getTime())
400 throw new Error('Property ' + name + ' must be lesser or equal to ' + lteDate.toString());
401 }
402 }
403 break;
404 case 'object':
405 var valueType = Object.prototype.toString.call(value);
406 value = value !== undefined && valueType ? value : defaultValue;
407 break;
408 case 'boolean':
409 value = (value === undefined || value === null) ? defaultValue : Boolean(value);
410 break;
411 default:
412 value = null;
413 }
414
415 if (required && (value === null || typeof value === 'undefined'))
416 throw new Error('Property ' + name + ' required');
417 else if (value !== null && typeof value !== 'undefined')
418 properties[name] = value;
419
420 }
421
422 }
423
424 return properties;
425};
426
427/**
428 * Validates that files are in the expected type.
429 *
430 * Available features for validation object:
431 * - **in** Specify an array of types to validate that the file type is inside this array
432 *
433 * @example
434 *
435 * // Get util
436 * var util = require('@openveo/api').util;
437 * var fileSystem = require('@openveo/api').fileSystem;
438 *
439 * // Validate parameters
440 * var params = util.validateFiles({
441 * myFirstFile: '/tmp/myFirstFile.mp4',
442 * mySecondFile: '/tmp/mySecondFile.tar'
443 * }, {
444 * myFirstFile: {in: [fileSystem.FILE_TYPES.MP4]},
445 * mySecondFile: {in: [fileSystem.FILE_TYPES.TAR]}
446 * }, function(error, files) {
447 * if (error) {
448 * console.log('An error occurred during validation with message: ' + error.message);
449 * }
450 *
451 * console.log('Is file valid ? ' + files.myFirstFile.isValid);
452 * console.log('File type: ' + files.myFirstFile.type);
453 * });
454 *
455 * console.log(params);
456 *
457 * @method validateFiles
458 * @static
459 * @async
460 * @param {Object} filesToAnalyze Files to validate with keys as files identifiers and values as
461 * files absolute paths
462 * @param {Object} validationDescription The validation description object with keys as files identifiers
463 * and values as validation objects
464 * @param {Function} callback The function to call when done
465 * - **Error** The error if an error occurred, null otherwise
466 * - **Object** Files with keys as the files identifiers and values as Objects containing validation
467 * information: isValid and type (from util.FILE_TYPES)
468 */
469module.exports.validateFiles = function(filesToAnalyze, validationDescription, callback) {
470 var files = {};
471 var asyncFunctions = [];
472
473 var getAsyncFunction = function(id, filePath) {
474 return function(callback) {
475 fileSystem.readFile(filePath, 0, 300, function(error, buffer) {
476 if (error) return callback(error);
477
478 var pathDescriptor = path.parse(filePath);
479 var fileType = fileSystem.getFileTypeFromBuffer(buffer);
480 files[id] = {isValid: false};
481
482 if (fileType === fileSystem.FILE_TYPES.UNKNOWN && pathDescriptor.ext === '.tar')
483 files[id].type = fileSystem.FILE_TYPES.TAR;
484 else
485 files[id].type = fileType;
486
487 if (validationDescription[id].in.indexOf(files[id].type) > -1 &&
488 (!validationDescription[id].validateExtension || pathDescriptor.ext.toLowerCase() === '.' + fileType))
489 files[id].isValid = true;
490
491 callback();
492 });
493 };
494 };
495
496 for (var id in filesToAnalyze) {
497 if (filesToAnalyze[id] && validationDescription && validationDescription[id])
498 asyncFunctions.push(getAsyncFunction(id, filesToAnalyze[id]));
499 }
500
501 if (!asyncFunctions.length)
502 return callback(new Error('No files to analyze'));
503
504 async.parallel(asyncFunctions, function(error) {
505 if (error) return callback(error);
506 callback(null, files);
507 });
508};
509
510/**
511 * Gets a specific property from an Array of Objects.
512 *
513 * @example
514 *
515 * // Get util
516 * var util = require('@openveo/api').util;
517 *
518 * // Get property 'id' of each objects of the array
519 * var params = util.getPropertyFromArray('id', [
520 * {id: 0},
521 * {id: 1},
522 * {id: 2, items: [{id: 3}]}
523 * ], 'items');
524 *
525 * // [0, 1, 2, 3]
526 * console.log(params);
527 *
528 * @method getPropertyFromArray
529 * @static
530 * @param {String} property The name of the property to fetch
531 * @param {Array} list The list of objects to look into
532 * @param {String} [recursiveProperty] The name of the recursive property to look into
533 * @return {Array} The list of values for the given property
534 */
535module.exports.getPropertyFromArray = function(property, list, recursiveProperty) {
536 var self = this;
537 var values = [];
538
539 if (!list || !list.length || !property)
540 return values;
541
542 list.forEach(function(item) {
543 values.push(item[property]);
544
545 if (recursiveProperty && item[recursiveProperty])
546 values = values.concat(self.getPropertyFromArray(property, item[recursiveProperty], recursiveProperty));
547 });
548
549 return values;
550};
551
552/**
553 * Evaluates a path of properties on an object.
554 *
555 * It does not use the JavaScript eval function.
556 *
557 * @example
558 *
559 * // Get util
560 * var util = require('@openveo/api').util;
561 *
562 * // Get property 'my.deep.property' of the object
563 * var value = util.evaluateDeepObjectProperties('my.deep.property', {
564 * my {
565 * deep {
566 * property: 'My deep property value'
567 * }
568 * }
569 * });
570 *
571 * // "My deep property value"
572 * console.log(value);
573 *
574 * @method evaluateDeepObjectProperties
575 * @static
576 * @param {String} propertyPath The path of the property to retreive from the object
577 * @param {Object} objectToAnalyze The object containing the requested property
578 * @return {Mixed} The value of the property
579 */
580module.exports.evaluateDeepObjectProperties = function(propertyPath, objectToAnalyze) {
581 if (!propertyPath) return null;
582
583 var propertyNames = propertyPath.split('.');
584 var value = objectToAnalyze;
585
586 for (var i = 0; i < propertyNames.length; i++) {
587 if (!value[propertyNames[i]]) return null;
588 value = value[propertyNames[i]];
589 }
590
591 return value;
592};