UNPKG

kinesalite

Version:

An implementation of Amazon's Kinesis built on LevelDB

289 lines (271 loc) 12.4 kB
exports.checkTypes = checkTypes exports.checkValidations = checkValidations exports.toLowerFirst = toLowerFirst function checkTypes(data, types) { var key for (key in data) { // TODO: deal with nulls if (!types[key] || data[key] == null) delete data[key] } return Object.keys(types).reduce(function(newData, key) { var val = checkType(data[key], types[key]) if (val != null) newData[key] = val return newData }, {}) function typeError(msg) { var err = new Error(msg) err.statusCode = 400 err.body = { __type: 'SerializationException', Message: msg, } return err } function checkType(val, type) { if (val == null) return null var actualType = type.type || type switch (actualType) { case 'Boolean': switch (typeof val) { case 'number': throw typeError('class com.amazon.coral.value.json.numbers.TruncatingBigNumber can not be converted to an Boolean') case 'string': // "\'HELLOWTF\' can not be converted to an Boolean" // seems to convert to uppercase // 'true'/'false'/'1'/'0'/'no'/'yes' seem to convert fine val = val.toUpperCase() throw typeError('\'' + val + '\' can not be converted to an Boolean') case 'object': if (Array.isArray(val)) throw typeError('Start of list found where not expected') throw typeError('Start of structure or map found where not expected.') } return val case 'Short': case 'Integer': case 'Long': case 'Double': switch (typeof val) { case 'boolean': throw typeError('class java.lang.Boolean can not be converted to an ' + actualType) case 'number': if (actualType != 'Double') val = Math.floor(val) if (actualType == 'Short') val = Math.min(val, 32767) if (actualType == 'Integer') val = Math.min(val, 2147483647) break case 'string': throw typeError('class java.lang.String can not be converted to an ' + actualType) case 'object': if (Array.isArray(val)) throw typeError('Start of list found where not expected') throw typeError('Start of structure or map found where not expected.') } return val case 'String': switch (typeof val) { case 'boolean': throw typeError('class java.lang.Boolean can not be converted to an String') case 'number': throw typeError('class com.amazon.coral.value.json.numbers.TruncatingBigNumber can not be converted to an String') case 'object': if (Array.isArray(val)) throw typeError('Start of list found where not expected') throw typeError('Start of structure or map found where not expected.') } return val case 'Blob': switch (typeof val) { case 'boolean': throw typeError('class java.lang.Boolean can not be converted to a Blob') case 'number': throw typeError('class com.amazon.coral.value.json.numbers.TruncatingBigNumber can not be converted to a Blob') case 'object': if (Buffer.isBuffer(val)) return val if (Array.isArray(val)) throw typeError('Start of list found where not expected') throw typeError('Start of structure or map found where not expected.') } if (val.length % 4) throw typeError('\'' + val + '\' can not be converted to a Blob: ' + 'Base64 encoded length is expected a multiple of 4 bytes but found: ' + val.length) var match = val.match(/[^a-zA-Z0-9+/=]|\=[^=]/) if (match) throw typeError('\'' + val + '\' can not be converted to a Blob: ' + 'Invalid Base64 character: \'' + match[0][0] + '\'') // TODO: need a better check than this... if (Buffer.from(val, 'base64').toString('base64') != val) throw typeError('\'' + val + '\' can not be converted to a Blob: ' + 'Invalid last non-pad Base64 character dectected') return val case 'Timestamp': switch (typeof val) { case 'boolean': throw typeError('class java.lang.Boolean can not be converted to milliseconds since epoch') case 'string': throw typeError('class java.lang.String can not be converted to milliseconds since epoch') case 'object': if (Array.isArray(val)) throw typeError('Start of list found where not expected') throw typeError('Start of structure or map found where not expected.') } return val case 'List': switch (typeof val) { case 'boolean': case 'number': case 'string': throw typeError('Expected list or null') case 'object': if (!Array.isArray(val)) throw typeError('Start of structure or map found where not expected.') } return val.map(function(child) { return checkType(child, type.children) }) case 'Map': switch (typeof val) { case 'boolean': case 'number': case 'string': throw typeError('Expected map or null') case 'object': if (Array.isArray(val)) throw typeError('Start of list found where not expected') } return Object.keys(val).reduce(function(newVal, key) { newVal[key] = checkType(val[key], type.children) return newVal }, {}) case 'Structure': switch (typeof val) { case 'boolean': case 'number': case 'string': throw typeError('Expected null') case 'object': if (Array.isArray(val)) throw typeError('Start of list found where not expected') } return checkTypes(val, type.children) default: throw new Error('Unknown type: ' + actualType) } } } var validateFns = {} function checkValidations(data, validations, custom) { var attr, msg, errors = [] function validationError(msg) { var err = new Error(msg) err.statusCode = 400 err.body = { __type: 'ValidationException', message: msg, } return err } for (attr in validations) { if (validations[attr].required && data[attr] == null) { throw validationError('The paramater \'' + toLowerFirst(attr) + '\' is required but was not present in the request') } } function checkNonRequireds(data, types, parent) { for (attr in types) { checkNonRequired(attr, data[attr], types[attr], parent) } } checkNonRequireds(data, validations) function checkNonRequired(attr, data, validations, parent) { if (validations == null || typeof validations != 'object') return for (var validation in validations) { if (errors.length >= 10) return if (~['type', 'required', 'memberStr'].indexOf(validation)) continue if (validation != 'notNull' && data == null) continue if (validation == 'children') { if (validations.type == 'List') { for (var i = 0; i < data.length; i++) { checkNonRequired('member', data[i], validations.children, (parent ? parent + '.' : '') + toLowerFirst(attr) + '.' + (i + 1)) } continue } else if (validations.type == 'Map') { // TODO: Always reverse? Object.keys(data).reverse().forEach(function(key) { // eslint-disable-line no-loop-func checkNonRequired('member', data[key], validations.children, (parent ? parent + '.' : '') + toLowerFirst(attr) + '.' + key) }) continue } checkNonRequireds(data, validations.children, (parent ? parent + '.' : '') + toLowerFirst(attr)) continue } validateFns[validation](parent, attr, validations[validation], data, validations.type, validations.memberStr, errors) } } if (errors.length) throw validationError(errors.length + ' validation error' + (errors.length > 1 ? 's' : '') + ' detected: ' + errors.join('; ')) if (custom) { msg = custom(data) if (msg) throw validationError(msg) } } validateFns.notNull = function(parent, key, val, data, type, memberStr, errors) { validate(data != null, 'Member must not be null', data, type, memberStr, parent, key, errors) } validateFns.greaterThanOrEqual = function(parent, key, val, data, type, memberStr, errors) { validate(data >= val, 'Member must have value greater than or equal to ' + val, data, type, memberStr, parent, key, errors) } validateFns.lessThanOrEqual = function(parent, key, val, data, type, memberStr, errors) { validate(data <= val, 'Member must have value less than or equal to ' + val, data, type, memberStr, parent, key, errors) } validateFns.regex = function(parent, key, pattern, data, type, memberStr, errors) { validate(RegExp('^' + pattern + '$').test(data), 'Member must satisfy regular expression pattern: ' + pattern, data, type, memberStr, parent, key, errors) } validateFns.lengthGreaterThanOrEqual = function(parent, key, val, data, type, memberStr, errors) { if (type == 'Blob') data = Buffer.from(data, 'base64') var length = (typeof data == 'object' && !Array.isArray(data) && !Buffer.isBuffer(data)) ? Object.keys(data).length : data.length validate(length >= val, 'Member must have length greater than or equal to ' + val, data, type, memberStr, parent, key, errors) } validateFns.lengthLessThanOrEqual = function(parent, key, val, data, type, memberStr, errors) { if (type == 'Blob') data = Buffer.from(data, 'base64') var length = (typeof data == 'object' && !Array.isArray(data) && !Buffer.isBuffer(data)) ? Object.keys(data).length : data.length validate(length <= val, 'Member must have length less than or equal to ' + val, data, type, memberStr, parent, key, errors) } validateFns.enum = function(parent, key, val, data, type, memberStr, errors) { validate(~val.indexOf(data), 'Member must satisfy enum value set: [' + val.join(', ') + ']', data, type, memberStr, parent, key, errors) } validateFns.childLengths = function(parent, key, val, data, type, memberStr, errors) { validate(data.every(function(x) { return x.length >= val[0] && x.length <= val[1] }), 'Member must satisfy constraint: [Member must have length less than or equal to ' + val[1] + ', Member must have length greater than or equal to ' + val[0] + ']', data, type, memberStr, parent, key, errors) } validateFns.childKeyLengths = function(parent, key, val, data, type, memberStr, errors) { validate(Object.keys(data).every(function(x) { return x.length >= val[0] && x.length <= val[1] }), 'Map keys must satisfy constraint: [Member must have length less than or equal to ' + val[1] + ', Member must have length greater than or equal to ' + val[0] + ']', data, type, memberStr, parent, key, errors) } validateFns.childValueLengths = function(parent, key, val, data, type, memberStr, errors) { validate(Object.keys(data).every(function(x) { return data[x].length >= val[0] && data[x].length <= val[1] }), 'Map value must satisfy constraint: [Member must have length less than or equal to ' + val[1] + ', Member must have length greater than or equal to ' + val[0] + ']', data, type, memberStr, parent, key, errors) } function validate(predicate, msg, data, type, memberStr, parent, key, errors) { if (predicate) return var value = valueStr(data, type, memberStr) if (value != 'null') value = '\'' + value + '\'' parent = parent ? parent + '.' : '' errors.push('Value ' + value + ' at \'' + parent + toLowerFirst(key) + '\' failed to satisfy constraint: ' + msg) } function toLowerFirst(str) { return str[0].toLowerCase() + str.slice(1) } function valueStr(data, type, memberStr) { if (data == null) return 'null' switch (type) { case 'Blob': var length = Buffer.from(data, 'base64').length return 'java.nio.HeapByteBuffer[pos=0 lim=' + length + ' cap=' + length + ']' case 'List': return '[' + data.map(function(item) { return memberStr || item }).join(', ') + ']' case 'Map': return '{' + Object.keys(data).map(function(key) { return key + '=' + (memberStr || data[key]) }).join(', ') + '}' default: return typeof data == 'object' ? JSON.stringify(data) : data } }