Skip to content

Commit

Permalink
Merge pull request #45 from Kashoo/issue-32-exclusive-ranges
Browse files Browse the repository at this point in the history
Issue #32: Option to exclude min/max values in range validation constraints
  • Loading branch information
ziemek authored Jul 21, 2016
2 parents 7a1403a + 27d12b4 commit b04e8b2
Show file tree
Hide file tree
Showing 7 changed files with 449 additions and 41 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- [#29](https://github.com/Kashoo/synctos/issues/29): Parameter to indicate that an item cannot be modified if it has a value
- [#30](https://github.com/Kashoo/synctos/issues/30): Parameter to prevent documents from being replaced
- [#31](https://github.com/Kashoo/synctos/issues/31): Parameter to prevent documents from being deleted
- [#32](https://github.com/Kashoo/synctos/issues/32): Range validation parameters that exclude the minimum/maximum values
- [#39](https://github.com/Kashoo/synctos/issues/39): Test helper convenience functions to build validation error messages

### Fixed
Expand Down
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,18 +233,26 @@ Validation for simple data types:
* `minimumLength`: The minimum number of characters (inclusive) allowed in the string. Undefined by default.
* `maximumLength`: The maximum number of characters (inclusive) allowed in the string. Undefined by default.
* `integer`: The value is a number with no fractional component. Additional parameters:
* `minimumValue`: The smallest (inclusive) value that is allowed. Undefined by default.
* `maximumValue`: The largest (inclusive) value that is allowed. Undefined by default.
* `minimumValue`: Reject values that are less than this. No restriction by default.
* `minimumValueExclusive`: Reject values that are less than or equal to this. No restriction by default.
* `maximumValue`: Reject values that are greater than this. No restriction by default.
* `maximumValueExclusive`: Reject values that are greater than or equal to this. No restriction by default.
* `float`: The value is a number with an optional fractional component (i.e. it is either an integer or a floating point number). Additional parameters:
* `minimumValue`: The smallest (inclusive) value that is allowed. Undefined by default.
* `maximumValue`: The largest (inclusive) value that is allowed. Undefined by default.
* `minimumValue`: Reject values that are less than this. No restriction by default.
* `minimumValueExclusive`: Reject values that are less than or equal to this. No restriction by default.
* `maximumValue`: Reject values that are greater than this. No restriction by default.
* `maximumValueExclusive`: Reject values that are greater than or equal to this. No restriction by default.
* `boolean`: The value is either `true` or `false`. No additional parameters.
* `datetime`: The value is an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date string with optional time and time zone components (e.g. "2016-06-18T18:57:35.328-08:00"). Additional parameters:
* `minimumValue`: The earliest (inclusive) date/time that is allowed. If the value of this parameter or the property value to which it is to be applied are missing their time and time zone components, they will default to midnight UTC of the date in question. No restriction by default.
* `maximumValue`: The latest (inclusive) date/time that is allowed. If the value of this parameter or the property value to which it is to be applied are missing their time and time zone components, they will default to midnight UTC of the date in question. No restriction by default.
* `minimumValue`: Reject date/times that are less than this. If the value of this parameter or the property value to which it is to be applied are missing their time and time zone components, they will default to midnight UTC of the date in question. No restriction by default.
* `minimumValueExclusive`: Reject date/times that are less than or equal to this. If the value of this parameter or the property value to which it is to be applied are missing their time and time zone components, they will default to midnight UTC of the date in question. No restriction by default.
* `maximumValue`: Reject date/times that are greater than this. If the value of this parameter or the property value to which it is to be applied are missing their time and time zone components, they will default to midnight UTC of the date in question. No restriction by default.
* `maximumValueExclusive`: Reject date/times that are greater than or equal to this. If the value of this parameter or the property value to which it is to be applied are missing their time and time zone components, they will default to midnight UTC of the date in question. No restriction by default.
* `date`: The value is an ISO 8601 date string _without_ time and time zone components (e.g. "2016-06-18"). Additional parameters:
* `minimumValue`: The earliest (inclusive) date that is allowed. No restriction by default.
* `maximumValue`: The latest (inclusive) date that is allowed. No restriction by default.
* `minimumValue`: Reject dates that are less than this. No restriction by default.
* `minimumValueExclusive`: Reject dates that are less than or equal to this. No restriction by default.
* `maximumValue`: Reject dates that are greater than this. No restriction by default.
* `maximumValueExclusive`: Reject dates that are greater than or equal to this. No restriction by default.
* `attachmentReference`: The value is the name of one of the document's file attachments. Note that, because the addition of an attachment is often a separate Sync Gateway API operation from the creation/replacement of the associated document, this validation type is only applied if the attachment is actually present in the document. However, since the sync function is run twice in such situations (i.e. once when the document is created/replaced and once when the attachment is created/replaced), the validation will be performed eventually. Additional parameters:
* `supportedExtensions`: An array of case-insensitive file extensions that are allowed for the attachment's filename (e.g. "txt", "jpg", "pdf"). No restriction by default.
* `supportedContentTypes`: An array of content/MIME types that are allowed for the attachment's contents (e.g. "image/png", "text/html", "application/xml"). No restriction by default.
Expand Down
34 changes: 30 additions & 4 deletions etc/sync-function-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,14 +235,40 @@ function synctos(doc, oldDoc) {
var minComparator = function(left, right) {
return left < right;
};
validateRangeConstraint(validator.minimumValue, validator.type, itemStack, minComparator, 'less', validationErrors);
validateRangeConstraint(validator.minimumValue, validator.type, itemStack, minComparator, 'less than', validationErrors);
}

if (!isValueNullOrUndefined(validator.minimumValueExclusive)) {
var minExclusiveComparator = function(left, right) {
return left <= right;
};
validateRangeConstraint(
validator.minimumValueExclusive,
validator.type,
itemStack,
minExclusiveComparator,
'less than or equal to',
validationErrors);
}

if (!isValueNullOrUndefined(validator.maximumValue)) {
var maxComparator = function(left, right) {
return left > right;
};
validateRangeConstraint(validator.maximumValue, validator.type, itemStack, maxComparator, 'greater', validationErrors);
validateRangeConstraint(validator.maximumValue, validator.type, itemStack, maxComparator, 'greater than', validationErrors);
}

if (!isValueNullOrUndefined(validator.maximumValueExclusive)) {
var maxExclusiveComparator = function(left, right) {
return left >= right;
};
validateRangeConstraint(
validator.maximumValueExclusive,
validator.type,
itemStack,
maxExclusiveComparator,
'greater than or equal to',
validationErrors);
}

if (!isValueNullOrUndefined(validator.minimumLength) && itemValue.length < validator.minimumLength) {
Expand Down Expand Up @@ -415,7 +441,7 @@ function synctos(doc, oldDoc) {
return true;
}

function validateRangeConstraint(rangeLimit, validationType, itemStack, comparator, violationType, validationErrors) {
function validateRangeConstraint(rangeLimit, validationType, itemStack, comparator, violationDescription, validationErrors) {
var itemValue = itemStack[itemStack.length - 1].itemValue;
var outOfRange;
if (validationType === 'datetime') {
Expand All @@ -431,7 +457,7 @@ function synctos(doc, oldDoc) {
}

if (outOfRange) {
validationErrors.push('item "' + buildItemPath(itemStack) + '" must not be ' + violationType + ' than ' + rangeLimit);
validationErrors.push('item "' + buildItemPath(itemStack) + '" must not be ' + violationDescription + ' ' + rangeLimit);
}
}

Expand Down
20 changes: 20 additions & 0 deletions etc/validation-error-message-formatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ exports.maximumValueViolation = function(itemPath, maxValue) {
return 'item "' + itemPath + '" must not be greater than ' + maxValue;
};

/**
* Formats a message for the error that occurs when a value is greater than or equal to the maximum allowed.
*
* @param {string} itemPath The full path of the property or element in which the error occurs (e.g. "objectProp.dateProp")
* @param {(float|integer|string)} maxValue The maximum value (exclusive) that is allowed
*/
exports.maximumValueExclusiveViolation = function(itemPath, maxValue) {
return 'item "' + itemPath + '" must not be greater than or equal to ' + maxValue;
};

/**
* Formats a message for the error that occurs when a string or array's length is less than the minimum allowed.
*
Expand All @@ -116,6 +126,16 @@ exports.minimumValueViolation = function(itemPath, minValue) {
return 'item "' + itemPath + '" must not be less than ' + minValue;
};

/**
* Formats a message for the error that occurs when a value is less than or equal to the minimum allowed.
*
* @param {string} itemPath The full path of the property or element in which the error occurs (e.g. "arrayProp[0].datetimeProp")
* @param {(float|integer|string)} minValue The minimum value (exclusive) that is allowed
*/
exports.minimumValueExclusiveViolation = function(itemPath, minValue) {
return 'item "' + itemPath + '" must not be less than or equal to ' + minValue;
};

/**
* Formats a message for the error that occurs when there is an attempt to assign an empty string or array to a property or element where
* that is forbidden.
Expand Down
29 changes: 0 additions & 29 deletions test/date-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,33 +43,4 @@ describe('Date validation type', function() {
testHelper.verifyDocumentNotCreated(doc, 'dateDoc', errorFormatter.dateFormatInvalid('formatValidationProp'));
});
});

describe('range validation', function() {
it('can create a doc with a date that is within the minimum and maximum values', function() {
var doc = {
_id: 'dateDoc',
rangeValidationProp: '2016-06-23'
};

testHelper.verifyDocumentCreated(doc);
});

it('cannot create a doc with a date that is before the minimum value', function() {
var doc = {
_id: 'dateDoc',
rangeValidationProp: '2016-06-22'
};

testHelper.verifyDocumentNotCreated(doc, 'dateDoc', errorFormatter.minimumValueViolation('rangeValidationProp', '2016-06-23'));
});

it('cannot create a doc with a date that is after than the maximum value', function() {
var doc = {
_id: 'dateDoc',
rangeValidationProp: '2016-06-24'
};

testHelper.verifyDocumentNotCreated(doc, 'dateDoc', errorFormatter.maximumValueViolation('rangeValidationProp', '2016-06-23'));
});
});
});
Loading

0 comments on commit b04e8b2

Please sign in to comment.