synctos
Version:
The Syncmaker. A tool to build comprehensive sync functions for Couchbase Sync Gateway.
294 lines (263 loc) • 11.1 kB
JavaScript
function comparisonModule(utils, buildItemPath, timeModule) {
return {
checkItemEquality: checkItemEquality,
validateMinValueInclusiveConstraint: validateMinValueInclusiveConstraint,
validateMaxValueInclusiveConstraint: validateMaxValueInclusiveConstraint,
validateMinValueExclusiveConstraint: validateMinValueExclusiveConstraint,
validateMaxValueExclusiveConstraint: validateMaxValueExclusiveConstraint,
validateImmutable: validateImmutable,
validateEquality: validateEquality
};
function validateMinValueInclusiveConstraint(itemStack, rangeConstraint, validatorType) {
return validateRangeConstraint(itemStack, rangeConstraint, minValueInclusiveViolationComparator(validatorType), 'less than');
}
function validateMaxValueInclusiveConstraint(itemStack, rangeConstraint, validatorType) {
return validateRangeConstraint(itemStack, rangeConstraint, maxValueInclusiveViolationComparator(validatorType), 'greater than');
}
function validateMinValueExclusiveConstraint(itemStack, rangeConstraint, validatorType) {
return validateRangeConstraint(
itemStack,
rangeConstraint,
minValueExclusiveViolationComparator(validatorType),
'less than or equal to');
}
function validateMaxValueExclusiveConstraint(itemStack, rangeConstraint, validatorType) {
return validateRangeConstraint(
itemStack,
rangeConstraint,
maxValueExclusiveViolationComparator(validatorType),
'greater than or equal to');
}
function validateRangeConstraint(itemStack, rangeConstraint, violationComparator, violationDescription) {
var itemValue = itemStack[itemStack.length - 1].itemValue;
if (violationComparator(itemValue, rangeConstraint)) {
return 'item "' + buildItemPath(itemStack) + '" must not be ' + violationDescription + ' ' + convertToString(rangeConstraint);
} else {
return null;
}
}
function validateImmutable(itemStack, onlyEnforceIfHasValue, validatorType) {
var currentItemEntry = itemStack[itemStack.length - 1];
var itemValue = currentItemEntry.itemValue;
var oldItemValue = currentItemEntry.oldItemValue;
if (onlyEnforceIfHasValue && utils.isValueNullOrUndefined(oldItemValue)) {
// No need to continue; the constraint only applies if the old document has a value for this item
return null;
}
// Only compare the item's value to the old item if the item's parent existed in the old document. For example, if the item in
// question is the value of a property in an object that is itself in an array, but the object did not exist in the array in the old
// document, then there is nothing to validate.
var oldParentItemValue = (itemStack.length >= 2) ? itemStack[itemStack.length - 2].oldItemValue : null;
var constraintSatisfied;
if (utils.isValueNullOrUndefined(oldParentItemValue)) {
constraintSatisfied = true;
} else {
constraintSatisfied = checkItemEquality(itemValue, oldItemValue, validatorType);
}
return !constraintSatisfied ? 'item "' + buildItemPath(itemStack) + '" cannot be modified' : null;
}
function validateEquality(itemStack, expectedItemValue, validatorType) {
var currentItemEntry = itemStack[itemStack.length - 1];
var currentItemValue = currentItemEntry.itemValue;
if (!checkItemEquality(currentItemValue, expectedItemValue, validatorType)) {
return 'value of item "' + buildItemPath(itemStack) + '" must equal ' + utils.jsonStringify(expectedItemValue);
} else {
return null;
}
}
function checkItemEquality(itemValue, expectedItemValue, validatorType) {
if (simpleTypeEqualityComparator(validatorType)(itemValue, expectedItemValue)) {
// Both have the same simple type (string, number, boolean, null) value
return true;
} else if (utils.isValueNullOrUndefined(itemValue) && utils.isValueNullOrUndefined(expectedItemValue)) {
// Both values are missing, which means they can be considered equal
return true;
} else if (utils.isValueNullOrUndefined(itemValue) !== utils.isValueNullOrUndefined(expectedItemValue)) {
// One has a value while the other does not
return false;
} else {
if (Array.isArray(itemValue) || Array.isArray(expectedItemValue)) {
return checkArrayEquality(itemValue, expectedItemValue);
} else if (typeof itemValue === 'object' || typeof expectedItemValue === 'object') {
return checkObjectEquality(itemValue, expectedItemValue);
} else {
return false;
}
}
}
function checkArrayEquality(itemValue, expectedItemValue) {
if (!Array.isArray(itemValue) || !Array.isArray(expectedItemValue)) {
return false;
} else if (itemValue.length !== expectedItemValue.length) {
return false;
}
for (var elementIndex = 0; elementIndex < itemValue.length; elementIndex++) {
var elementValue = itemValue[elementIndex];
var expectedElementValue = expectedItemValue[elementIndex];
if (!checkItemEquality(elementValue, expectedElementValue)) {
return false;
}
}
// If we got here, all elements match
return true;
}
function checkObjectEquality(itemValue, expectedItemValue) {
if (typeof itemValue !== 'object' || typeof expectedItemValue !== 'object') {
return false;
}
var itemProperties = [ ];
for (var itemProp in itemValue) {
itemProperties.push(itemProp);
}
for (var expectedItemProp in expectedItemValue) {
if (itemProperties.indexOf(expectedItemProp) < 0) {
itemProperties.push(expectedItemProp);
}
}
for (var propIndex = 0; propIndex < itemProperties.length; propIndex++) {
var propertyName = itemProperties[propIndex];
var propertyValue = itemValue[propertyName];
var expectedPropertyValue = expectedItemValue[propertyName];
if (!checkItemEquality(propertyValue, expectedPropertyValue)) {
return false;
}
}
// If we got here, all properties match
return true;
}
function minValueInclusiveViolationComparator(validatorType) {
switch (validatorType) {
case 'time':
return function(candidateValue, constraintValue) {
return timeModule.compareTimes(candidateValue, constraintValue) < 0;
};
case 'date':
case 'datetime':
return function(candidateValue, constraintValue) {
return timeModule.compareDates(candidateValue, constraintValue) < 0;
};
case 'timezone':
return function(candidateValue, constraintValue) {
return timeModule.compareTimeZones(candidateValue, constraintValue) < 0;
};
case 'uuid':
return function(candidateValue, constraintValue) {
return normalizeUuid(candidateValue) < normalizeUuid(constraintValue);
};
default:
return function(candidateValue, constraintValue) {
return candidateValue < constraintValue;
};
}
}
function minValueExclusiveViolationComparator(validatorType) {
switch (validatorType) {
case 'time':
return function(candidateValue, constraintValue) {
return timeModule.compareTimes(candidateValue, constraintValue) <= 0;
};
case 'date':
case 'datetime':
return function(candidateValue, constraintValue) {
return timeModule.compareDates(candidateValue, constraintValue) <= 0;
};
case 'timezone':
return function(candidateValue, constraintValue) {
return timeModule.compareTimeZones(candidateValue, constraintValue) <= 0;
};
case 'uuid':
return function(candidateValue, constraintValue) {
return normalizeUuid(candidateValue) <= normalizeUuid(constraintValue);
};
default:
return function(candidateValue, constraintValue) {
return candidateValue <= constraintValue;
};
}
}
function maxValueInclusiveViolationComparator(validatorType) {
switch (validatorType) {
case 'time':
return function(candidateValue, constraintValue) {
return timeModule.compareTimes(candidateValue, constraintValue) > 0;
};
case 'date':
case 'datetime':
return function(candidateValue, constraintValue) {
return timeModule.compareDates(candidateValue, constraintValue) > 0;
};
case 'timezone':
return function(candidateValue, constraintValue) {
return timeModule.compareTimeZones(candidateValue, constraintValue) > 0;
};
case 'uuid':
return function(candidateValue, constraintValue) {
return normalizeUuid(candidateValue) > normalizeUuid(constraintValue);
};
default:
return function(candidateValue, constraintValue) {
return candidateValue > constraintValue;
};
}
}
function maxValueExclusiveViolationComparator(validatorType) {
switch (validatorType) {
case 'time':
return function(candidateValue, constraintValue) {
return timeModule.compareTimes(candidateValue, constraintValue) >= 0;
};
case 'date':
case 'datetime':
return function(candidateValue, constraintValue) {
return timeModule.compareDates(candidateValue, constraintValue) >= 0;
};
case 'timezone':
return function(candidateValue, constraintValue) {
return timeModule.compareTimeZones(candidateValue, constraintValue) >= 0;
};
case 'uuid':
return function(candidateValue, constraintValue) {
return normalizeUuid(candidateValue) >= normalizeUuid(constraintValue);
};
default:
return function(candidateValue, constraintValue) {
return candidateValue >= constraintValue;
};
}
}
function simpleTypeEqualityComparator(validatorType) {
switch (validatorType) {
case 'time':
return function(candidateValue, constraintValue) {
return timeModule.compareTimes(candidateValue, constraintValue) === 0;
};
case 'date':
case 'datetime':
return function(candidateValue, constraintValue) {
return timeModule.compareDates(candidateValue, constraintValue) === 0;
};
case 'timezone':
return function(candidateValue, constraintValue) {
return timeModule.compareTimeZones(candidateValue, constraintValue) === 0;
};
case 'uuid':
return function(candidateValue, constraintValue) {
return normalizeUuid(candidateValue) === normalizeUuid(constraintValue);
};
default:
return function(candidateValue, constraintValue) {
return candidateValue === constraintValue;
};
}
}
function convertToString(value) {
if (value instanceof Date) {
return value.toISOString();
} else {
return !utils.isValueNullOrUndefined(value) ? value.toString() : 'null';
}
}
function normalizeUuid(value) {
return !utils.isValueNullOrUndefined(value) ? value.toLowerCase() : null;
}
}