Skip to content

Commit 9fede0b

Browse files
authored
Merge pull request #339 from Kashoo/338-null-properties
Issue 338: Deprecated mustNotBeMissing and mustNotBeNull constraints
2 parents bb40fa4 + 7609fe9 commit 9fede0b

9 files changed

+402
-265
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
This project adheres to [Semantic Versioning](http://semver.org/). All notable changes will be documented in this file.
33

44
## [Unreleased]
5-
Nothing yet.
5+
### Fixed
6+
- [#338](https://github.com/Kashoo/synctos/issues/338): The `mustNotBeMissing` and `mustNotBeNull` constraints do not behave as expected
7+
8+
### Deprecated
9+
- `mustNotBeMissing` constraint
10+
- `mustNotBeNull` constraint
611

712
## [2.7.0] - 2018-10-01
813
### Added

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -605,8 +605,6 @@ entries: {
605605
Validation for all simple and complex data types support the following additional parameters:
606606

607607
* `required`: The value cannot be `null` or missing/`undefined`. Defaults to `false`.
608-
* `mustNotBeMissing`: The value cannot be missing/`undefined`. Defaults to `false`. **WARNING:** This constraint exists for advanced users only. Generally the `required` constraint should be favoured because many programming languages are incapable of distinguishing between `null` and missing values, potentially leading to a situation in which a client application cannot satisfy this constraint depending on the JSON serialization strategy it uses.
609-
* `mustNotBeNull`: The value cannot be `null`. Defaults to `false`. **WARNING:** This constraint exists for advanced users only. Generally the `required` constraint should be favoured because many programming languages are incapable of distinguishing between `null` and missing values, potentially leading to a situation in which a client application cannot satisfy this constraint depending on the JSON serialization strategy it uses.
610608
* `immutable`: The item cannot be changed from its existing value if the document is being replaced. The constraint is applied recursively so that, even if a value that is nested an arbitrary number of levels deep within an immutable complex type is modified, the document change will be rejected. A value of `null` is treated as equal to missing/`undefined` and vice versa. Does not apply when creating a new document or deleting an existing document. Differs from `immutableStrict` in that it checks for semantic equality of specialized string validation types (e.g. `date`, `datetime`, `time`, `timezone`, `uuid`); for example, the two `uuid` values of "d97b3a52-78d5-4112-9705-e4ab436f5114" and "D97B3A52-78D5-4112-9705-E4AB436F5114" are considered equal with this constraint since they differ only by case. Defaults to `false`.
611609
* `immutableStrict`: The item cannot be changed from its existing value if the document is being replaced. Differs from `immutable` in that specialized string validation types (e.g. `date`, `datetime`, `time`, `timezone`, `uuid`) are not compared semantically; for example, the two `time` values of "12:45" and "12:45:00.000" are _not_ considered equal because the strings are not strictly equal. Defaults to `false`.
612610
* `immutableWhenSet`: As with the `immutable` property, the item cannot be changed from its existing value if the document is being replaced. However, it differs in that it does not prevent modification if the item is either `null` or missing/`undefined` in the existing document. Differs from `immutableWhenSetStrict` in that it checks for semantic equality of specialized string validation types (e.g. `date`, `datetime`, `time`, `timezone`, `uuid`); for example, the two `datetime` values of "2018-01-01T21:09:00.000Z" and "2018T16:09-05:00" are considered equal with this constraint since they represent the same point in time. Defaults to `false`.

src/validation/document-definitions-validator.spec.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ describe('Document definitions validator:', () => {
100100
conditionalTypeProperty: {
101101
type: 'conditional',
102102
immutableWhenSetStrict: true,
103+
mustNotBeNull: false, // "mustNotBeNull" is deprecated
103104
minimumValue: -15, // Unsupported constraint for this validation type
104105
validationCandidates: [
105106
{
@@ -216,7 +217,7 @@ describe('Document definitions validator:', () => {
216217
invalidIntegerProperty: {
217218
type: 'integer',
218219
required: true,
219-
mustNotBeMissing: true, // Must not be defined if "required" is defined
220+
mustNotBeMissing: true, // "mustNotBeMissing" is deprecated
220221
minimumValueExclusive: 1,
221222
maximumValue: 1, // Must be greater than "minimumValueExclusive"
222223
maximumValueExclusive: 1 // Must be greater than "minimumValueExclusive"
@@ -287,6 +288,7 @@ describe('Document definitions validator:', () => {
287288
'myDoc1.attachmentConstraints.filenameRegexPattern: \"filenameRegexPattern\" must be an instance of \"RegExp\"',
288289
'myDoc1.accessAssignments: \"accessAssignments\" must have an arity lesser or equal to 2',
289290
'myDoc1.customActions: \"customActions\" must have at least 1 children',
291+
'myDoc1.propertyValidators.conditionalTypeProperty.mustNotBeNull: \"mustNotBeNull\" is deprecated; use "required" instead',
290292
'myDoc1.propertyValidators.conditionalTypeProperty.minimumValue: \"minimumValue\" is not allowed',
291293
'myDoc1.propertyValidators.conditionalTypeProperty.validationCandidates.0.condition: \"condition\" must have an arity lesser or equal to 4',
292294
'myDoc1.propertyValidators.conditionalTypeProperty.validationCandidates.0.foobar: \"foobar\" is not allowed',
@@ -335,8 +337,7 @@ describe('Document definitions validator:', () => {
335337
'myDoc1.propertyValidators.nestedObject.propertyValidators.arrayProperty.arrayElementsValidator.propertyValidators.stringProperty.mustBeTrimmed: \"mustBeTrimmed\" must be a boolean',
336338
'myDoc1.propertyValidators.nestedObject.propertyValidators.arrayProperty.arrayElementsValidator.propertyValidators.stringProperty.maximumLength: \"maximumLength\" must be larger than or equal to 0',
337339
'myDoc1.propertyValidators.nestedObject.propertyValidators.arrayProperty.arrayElementsValidator.propertyValidators.booleanProperty.mustEqual: \"mustEqual\" must be a boolean',
338-
'myDoc1.propertyValidators.nestedObject.propertyValidators.arrayProperty.arrayElementsValidator.propertyValidators.invalidIntegerProperty.required: \"required\" conflict with forbidden peer \"mustNotBeMissing\"',
339-
'myDoc1.propertyValidators.nestedObject.propertyValidators.arrayProperty.arrayElementsValidator.propertyValidators.invalidIntegerProperty.mustNotBeMissing: \"mustNotBeMissing\" conflict with forbidden peer \"required\"',
340+
'myDoc1.propertyValidators.nestedObject.propertyValidators.arrayProperty.arrayElementsValidator.propertyValidators.invalidIntegerProperty.mustNotBeMissing: \"mustNotBeMissing\" is deprecated; use "required" instead',
340341
'myDoc1.propertyValidators.nestedObject.propertyValidators.arrayProperty.arrayElementsValidator.propertyValidators.invalidIntegerProperty.maximumValue: \"maximumValue\" must be greater than 1',
341342
'myDoc1.propertyValidators.nestedObject.propertyValidators.arrayProperty.arrayElementsValidator.propertyValidators.invalidIntegerProperty.maximumValue: \"maximumValue\" conflict with forbidden peer \"maximumValueExclusive\"',
342343
'myDoc1.propertyValidators.nestedObject.propertyValidators.arrayProperty.arrayElementsValidator.propertyValidators.invalidIntegerProperty.maximumValueExclusive: \"maximumValueExclusive\" must be greater than 1',

src/validation/property-validator-schema.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,6 @@ function makeTypeConstraintsSchema(typeName) {
211211
const constraints = Object.assign({ }, universalConstraintSchemas(typeEqualitySchemas[typeName]), allTypeConstraints[typeName]);
212212

213213
return joi.object().keys(constraints)
214-
// Prevent the use of more than one constraint from the "required value" category
215-
.without('required', [ 'mustNotBeMissing', 'mustNotBeNull' ])
216-
.without('mustNotBeMissing', [ 'required', 'mustNotBeNull' ])
217-
.without('mustNotBeNull', [ 'required', 'mustNotBeMissing' ])
218-
219214
// Prevent the use of more than one constraint from the "equality" category
220215
.without('mustEqual', [ 'mustEqualStrict', 'mustEqualIgnoreCase' ])
221216
.without('mustEqualStrict', [ 'mustEqual', 'mustEqualIgnoreCase' ])
@@ -247,8 +242,10 @@ function universalConstraintSchemas(typeEqualitySchema) {
247242
return {
248243
type: dynamicConstraintSchema(joi.string()).required(),
249244
required: dynamicConstraintSchema(joi.boolean()),
250-
mustNotBeMissing: dynamicConstraintSchema(joi.boolean()),
251-
mustNotBeNull: dynamicConstraintSchema(joi.boolean()),
245+
mustNotBeMissing: joi.any().forbidden().error(() =>
246+
({ message: '"mustNotBeMissing" is deprecated; use "required" instead' })),
247+
mustNotBeNull: joi.any().forbidden().error(() =>
248+
({ message: '"mustNotBeNull" is deprecated; use "required" instead' })),
252249
immutable: dynamicConstraintSchema(joi.boolean()),
253250
immutableStrict: dynamicConstraintSchema(joi.boolean()),
254251
immutableWhenSet: dynamicConstraintSchema(joi.boolean()),

templates/sync-function/document-properties-validation-module.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,10 +263,10 @@ function documentPropertiesValidationModule(utils, simpleTypeFilter, typeIdValid
263263
} else if (resolveItemConstraint(validator.required)) {
264264
// The item has no value (either it's null or undefined), but the validator indicates it is required
265265
validationErrors.push('item "' + buildItemPath(itemStack) + '" must not be null or missing');
266-
} else if (resolveItemConstraint(validator.mustNotBeMissing) && itemValue === void 0) {
266+
} else if (resolveItemConstraint(validator.mustNotBeMissing)) {
267267
// The item is missing (i.e. it's undefined), but the validator indicates it must not be
268268
validationErrors.push('item "' + buildItemPath(itemStack) + '" must not be missing');
269-
} else if (resolveItemConstraint(validator.mustNotBeNull) && itemValue === null) {
269+
} else if (resolveItemConstraint(validator.mustNotBeNull)) {
270270
// The item is null, but the validator indicates it must not be
271271
validationErrors.push('item "' + buildItemPath(itemStack) + '" must not be null');
272272
}

test/must-not-be-missing.spec.js

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('Non-missing value constraint', () => {
2929
testFixture.verifyDocumentCreated(doc);
3030
});
3131

32-
it('allows a doc with top-level values that are null', () => {
32+
it('blocks a doc with top-level values that are null', () => {
3333
const doc = {
3434
_id: 'staticDoc',
3535
stringProp: null,
@@ -45,10 +45,25 @@ describe('Non-missing value constraint', () => {
4545
hashtableProp: null,
4646
};
4747

48-
testFixture.verifyDocumentCreated(doc);
48+
testFixture.verifyDocumentNotCreated(
49+
doc,
50+
'staticDoc',
51+
[
52+
errorFormatter.mustNotBeMissingValueViolation('hashtableProp'),
53+
errorFormatter.mustNotBeMissingValueViolation('objectProp'),
54+
errorFormatter.mustNotBeMissingValueViolation('arrayProp'),
55+
errorFormatter.mustNotBeMissingValueViolation('attachmentReferenceProp'),
56+
errorFormatter.mustNotBeMissingValueViolation('enumProp'),
57+
errorFormatter.mustNotBeMissingValueViolation('dateProp'),
58+
errorFormatter.mustNotBeMissingValueViolation('datetimeProp'),
59+
errorFormatter.mustNotBeMissingValueViolation('booleanProp'),
60+
errorFormatter.mustNotBeMissingValueViolation('floatProp'),
61+
errorFormatter.mustNotBeMissingValueViolation('integerProp'),
62+
errorFormatter.mustNotBeMissingValueViolation('stringProp')
63+
]);
4964
});
5065

51-
it('allows a doc with nested values that are null', () => {
66+
it('blocks a doc with nested values that are null', () => {
5267
const doc = {
5368
_id: 'staticDoc',
5469
stringProp: 'foobar',
@@ -64,7 +79,14 @@ describe('Non-missing value constraint', () => {
6479
hashtableProp: { 'key': null },
6580
};
6681

67-
testFixture.verifyDocumentCreated(doc);
82+
testFixture.verifyDocumentNotCreated(
83+
doc,
84+
'staticDoc',
85+
[
86+
errorFormatter.mustNotBeMissingValueViolation('arrayProp[0]'),
87+
errorFormatter.mustNotBeMissingValueViolation('objectProp.subProp'),
88+
errorFormatter.mustNotBeMissingValueViolation('hashtableProp[key]')
89+
]);
6890
});
6991

7092
it('blocks a doc with top-level values that are undefined', () => {
@@ -220,7 +242,7 @@ describe('Non-missing value constraint', () => {
220242
testFixture.verifyDocumentCreated(doc);
221243
});
222244

223-
it('allows a doc with top-level values that are null', () => {
245+
it('blocks a doc with top-level values that are null', () => {
224246
const doc = {
225247
_id: 'dynamicDoc',
226248
dynamicPropsRequired: true,
@@ -237,10 +259,25 @@ describe('Non-missing value constraint', () => {
237259
hashtableProp: null,
238260
};
239261

240-
testFixture.verifyDocumentCreated(doc);
262+
testFixture.verifyDocumentNotCreated(
263+
doc,
264+
'dynamicDoc',
265+
[
266+
errorFormatter.mustNotBeMissingValueViolation('hashtableProp'),
267+
errorFormatter.mustNotBeMissingValueViolation('objectProp'),
268+
errorFormatter.mustNotBeMissingValueViolation('arrayProp'),
269+
errorFormatter.mustNotBeMissingValueViolation('attachmentReferenceProp'),
270+
errorFormatter.mustNotBeMissingValueViolation('enumProp'),
271+
errorFormatter.mustNotBeMissingValueViolation('dateProp'),
272+
errorFormatter.mustNotBeMissingValueViolation('datetimeProp'),
273+
errorFormatter.mustNotBeMissingValueViolation('booleanProp'),
274+
errorFormatter.mustNotBeMissingValueViolation('floatProp'),
275+
errorFormatter.mustNotBeMissingValueViolation('integerProp'),
276+
errorFormatter.mustNotBeMissingValueViolation('stringProp')
277+
]);
241278
});
242279

243-
it('allows a doc with nested values that are null', () => {
280+
it('blocks a doc with nested values that are null', () => {
244281
const doc = {
245282
_id: 'dynamicDoc',
246283
dynamicPropsRequired: true,
@@ -257,7 +294,14 @@ describe('Non-missing value constraint', () => {
257294
hashtableProp: { 'key': null },
258295
};
259296

260-
testFixture.verifyDocumentCreated(doc);
297+
testFixture.verifyDocumentNotCreated(
298+
doc,
299+
'dynamicDoc',
300+
[
301+
errorFormatter.mustNotBeMissingValueViolation('arrayProp[0]'),
302+
errorFormatter.mustNotBeMissingValueViolation('objectProp.subProp'),
303+
errorFormatter.mustNotBeMissingValueViolation('hashtableProp[key]')
304+
]);
261305
});
262306

263307
it('blocks a doc with top-level values that are undefined', () => {

0 commit comments

Comments
 (0)