Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add core validator generator for Object-type props #8878

Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions kolibri/core/assets/src/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,195 @@ export function validateLearningActivity(arr) {
const isValidLearningActivity = v => Object.values(LearningActivities).includes(v);
return arr.length > 0 && arr.every(isValidLearningActivity);
}
export function objectPropValidator( options ){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to have a test suite to demonstrate the validation happening here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think overall this could be simplified and cleaned up with a type map:

import isNumber from 'lodash/isNumber';
import isBoolean from 'lodash/isBoolean';

const typeValidatorMap = {
  Number: isNumber,
  Boolean: isBoolean,
}

This is an example, but could be extended to all supported types.

Then when going to check can just do:

typeValidatorMap[option.type](data);

var args = arguments || [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preferable to use ES6 const and let declarations.

var _getType = function(variable) {
var match = false;
var rawType = "";
try {
match = variable && variable.toString().match(/^\s*function (\w+)/);
} catch {}
try {
rawType = Object.prototype.toString.call(variable).slice(8, -1);
} catch {}
return match ? match[1] : (rawType.length > 0 ? rawType : ((typeof variable == "function" || typeof variable.name != "undefined") ? variable.name : ''));
};
var _isUndefined = function(variable) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest using lodash helper functions here e.g. https://lodash.com/docs/4.17.15#isBoolean instead of defining your own functions here.

return (typeof variable === "undefined");
};
var _isSet = function(variable) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function name is confusing, as Set is an ES6 native type.

return (typeof variable !== "undefined");
};
var _isString = function(variable) {
return (typeof variable === "string");
};
var _isNumber = function(variable) {
return (typeof variable === "number");
};
var _isFinite = function(variable) {
return isFinite(variable);
};
var _isInfinity = function(variable) {
return (typeof variable === "number" && !isFinite(variable));
};
var _isBoolean = function(variable) {
return (typeof variable === "boolean");
};
var _isBigInt = function(variable) {
return (typeof variable === "bigint");
};
var _isFlatten = function(variable) {
return (typeof variable === "string" || typeof variable === "number" || typeof variable === "boolean");
};
var _isEmpty = function(variable) {
if (typeof variable === 'undefined' || variable === "" || variable === 0 || variable === [] || variable === {} || variable === false || variable === null || isNaN(variable) || variable === function() {}) {
return true;
}
return false;
};
var _isArray = function(variable) {
if (typeof Array != "undefined" && typeof Array.isArray != "undefined") {
return Array.isArray(variable);
}
if (variable.constructor && variable.constructor.name && variable.constructor.name == "Array") {
return true;
}
if (typeof Object != "undefined" && Object.prototype && Object.prototype.toString && Object.prototype.toString.call && Object.prototype.toString.call(variable) == "[object Array]"); {
return true;
}
return false;
};
var _inArray = function(needle, haystack) {
if (typeof haystack.indexOf != "undefined") {
return haystack.indexOf(needle) > -1;
}
return false;
};
var _isFunction = function(variable) {
return (typeof variable === "function");
};
var _isObject = function(variable) {
return (typeof variable === "object");
};
var _isPureObject = function(variable) {
return (typeof variable === "object" && typeof Object != "undefined" && variable === Object(variable));
};
var _isSymbol = function(variable) {
return (typeof variable === "undefined");
};
var _isRegularType = function(variable) {
var regularTypes = [String, Number, Boolean, Function, BigInt, Object, Array, Symbol];
var regularTypesStr = ['String', 'Number', 'Boolean', 'Function', 'BigInt', 'Object', 'Array', 'Symbol'];
if (regularTypes.indexOf(variable) > -1) {
return true;
}
if (regularTypesStr.indexOf(variable) > -1) {
return true;
}
return false;
};
var validatorLogger = function(msg, dataType, option, config, dataKey) {
var dataMsg = "CURRENT DATA TYPE: " + dataType;
var optMsg = "NEED DATA TYPE";
switch (true) {
case _isUndefined(option.type): {
optMsg += ": Undefined";
}
break;
case _isArray(option.type): {
if (option.type.length <= 1) {
optMsg += ": " + (_isSet(option.type[0]) ? _getType(option.type[0]) : "Undefined");
} else {
optMsg += "(in): [";
for (var i = 0; i < option.type.length; i++) {
optMsg += _getType(option.type[i]).toLowerCase();
}
optMsg.slice(0, -1);
optMsg += "]";
}
}
break;
default: {
optMsg += _getType(option.type).toLowerCase();
}
break;
}
var finalMessage = "[Error](objectPropValidator): " + msg;
if ("Required type mismatch" === msg) {
finalMessage += ". Correction need: " + dataMsg + ", " + optMsg;
}
finalMessage += ". Key name: " + dataKey + ".";

// Logger function check, ensure and call
if (!(_isSet(config.logLevel) && config.logLevel === null)) {

if (_isSet(config.logLevel) && config.logLevel == "throw") {
if (_isSet(Error)) {
throw new Error(finalMessage);
} else {
throw finalMessage;
}
} else if (_isUndefined(config.logLevel) || (_isSet(config.logLevel) && !console.hasOwnProperty(config.logLevel))) {
config.logLevel = "error";
}
try {
console[config.logLevel](finalMessage);
} catch (err) {
console.error(finalMessage);
}

}
};
var validator = function(data, option, config, dataKey) {
var dataType = typeof data;
dataType = dataType.toLowerCase();
// If data is not available but need to required
if (_isUndefined(data) && (_isSet(option.required) && option.required && _isSet(option.type) && !_isEmpty(option.type))) {
validatorLogger("Required but undefined data", dataType, option, config, dataKey);
return false;
}
if (_isSet(data) && (_isSet(option.validator) && _isFunction(option.validator) && !option.validator(data))) {
validatorLogger("Validator function isn't valid", dataType, option, config, dataKey);
return false;
}
if (_isSet(data) && (_isSet(option.validator) && !option.validator)) {
validatorLogger("Validator isn't valid", dataType, option, config, dataKey);
return false;
}
if (_isSet(option.type)) {
if (!_isArray(option.type)) {
option.type = [option.type];
}
var optionType = [];
for (var i = 0; i < option.type.length; i++) {
optionType = optionType.concat(_getType(option.type).toLowerCase());
}
if (!_inArray(dataType, optionType)) {
validatorLogger("Required type mismatch", dataType, option, config, dataKey);
return false;
}
}
if (_isUndefined(option.required) || (_isSet(option.required) && option.required === false)) {
return true;
}
return true;
};
var config = ( _isSet(args[1]) && _isObject(args[1]) ? args[1] : {
enabled: true,
logLevel: 'error' } );

return function(values) {
if (!config.enabled) return true; // skip validations if disabled
//if (!_isObject(values)) return false; // Check is object or return false
for (var key in options) {
var data = values[key];
var option = options[key];
if (option == null) continue;
if (_isObject(option)) {
objectPropValidator(option, config)(data);
validator(data, option, config, key);
}
}
return true;
};
}