Skip to content

Commit

Permalink
Merge pull request #327 from Kashoo/324-any-type
Browse files Browse the repository at this point in the history
Issue 324: Support for any JSON data type
  • Loading branch information
dkichler authored Jul 10, 2018
2 parents 3368678 + d20f35e commit 63d7835
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). All notable c
## [Unreleased]
### Added
- [#323](https://github.com/Kashoo/synctos/issues/323): Option to ignore item validation errors when value is unchanged
- [#324](https://github.com/Kashoo/synctos/issues/324): Validation type that accepts any type of value

## [2.5.0] - 2018-05-30
### Added
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ Validation for simple data types (e.g. integers, floating point numbers, strings
* `supportedContentTypes`: An array of content/MIME types that are allowed for the attachment's contents (e.g. "image/png", "text/html", "application/xml"). Takes precedence over the document-wide `supportedContentTypes` constraint for the referenced attachment. No restriction by default.
* `maximumSize`: The maximum file size, in bytes, of the attachment. May not be greater than 20MB (20,971,520 bytes), as Couchbase Server/Sync Gateway sets that as the hard limit per document or attachment. Takes precedence over the document-wide `maximumIndividualSize` constraint for the referenced attachment. Unlimited by default.
* `regexPattern`: A regular expression pattern that must be satisfied by the value. Takes precedence over the document-wide `attachmentConstraints.filenameRegexPattern` constraint for the referenced attachment. No restriction by default.
* `any`: The value may be any JSON data type: number, string, boolean, array or object. No additional parameters.

##### Complex type validation

Expand Down
11 changes: 10 additions & 1 deletion src/validation/document-definitions-validator.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,12 @@ describe('Document definitions validator:', () => {
type: 'object',
mustEqual: (a, b, c, d) => d,
propertyValidators: { } // Must specify at least one property validator
},
anyProperty: {
type: 'any',
minimumValue: 32, // Not supported by the "any" type
mustNotBeEmpty: false, // Not supported by the "any" type
regexPattern: /^foo$/ // Not supported by the "any" type
}
}
}
Expand Down Expand Up @@ -310,7 +316,10 @@ describe('Document definitions validator:', () => {
'myDoc1.propertyValidators.nestedObject.propertyValidators.arrayProperty.arrayElementsValidator.propertyValidators.invalidMustEqualConstraintProperty.mustEqual: \"mustEqual\" must be an object',
'myDoc1.propertyValidators.nestedObject.propertyValidators.arrayProperty.arrayElementsValidator.propertyValidators.emptyPropertyValidatorsProperty.propertyValidators: \"propertyValidators\" must have at least 1 children',
'myDoc1.propertyValidators.nestedObject.propertyValidators.arrayProperty.arrayElementsValidator.propertyValidators.noTypeProperty.type: "type" is required',
'myDoc1.propertyValidators.nestedObject.propertyValidators.unrecognizedTypeProperty.type: "type" must be one of [array, attachmentReference, boolean, date, datetime, enum, float, hashtable, integer, object, string, time, timezone, uuid]',
'myDoc1.propertyValidators.nestedObject.propertyValidators.arrayProperty.arrayElementsValidator.propertyValidators.anyProperty.minimumValue: \"minimumValue\" is not allowed',
'myDoc1.propertyValidators.nestedObject.propertyValidators.arrayProperty.arrayElementsValidator.propertyValidators.anyProperty.mustNotBeEmpty: \"mustNotBeEmpty\" is not allowed',
'myDoc1.propertyValidators.nestedObject.propertyValidators.arrayProperty.arrayElementsValidator.propertyValidators.anyProperty.regexPattern: \"regexPattern\" is not allowed',
'myDoc1.propertyValidators.nestedObject.propertyValidators.unrecognizedTypeProperty.type: \"type\" must be one of [any, array, attachmentReference, boolean, date, datetime, enum, float, hashtable, integer, object, string, time, timezone, uuid]',
'myDoc1.expiry: \"expiry\" with value \"20180415T1357-0700\" fails to match the required pattern: /^\\d{4}-(((0[13578]|1[02])-(0[1-9]|[12]\\d|3[01]))|((0[469]|11)-(0[1-9]|[12]\\d|30))|(02-(0[1-9]|[12]\\d)))T([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(Z|[+-]([01]\\d|2[0-3]):[0-5]\\d)$/',
]);
});
Expand Down
5 changes: 5 additions & 0 deletions src/validation/property-validator-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const timeOnlySchema = joi.string().regex(/^((([01]\d|2[0-3])(:[0-5]\d)(:[0-5]\d
const timezoneSchema = joi.string().regex(/^(Z|([+-])([01]\d|2[0-3]):([0-5]\d))$/);

const typeEqualitySchemas = {
any: joi.any(),
string: joi.string(),
integer: integerSchema,
float: joi.number(),
Expand All @@ -51,6 +52,9 @@ const validPropertyTypes = Object.keys(typeEqualitySchemas).sort();
const schema = joi.object().keys({
type: dynamicConstraintSchema(joi.string().only(validPropertyTypes)).required()
})
.when(
joi.object().unknown().keys({ type: 'any' }),
{ then: makeTypeConstraintsSchema('any') })
.when(
joi.object().unknown().keys({ type: 'string' }),
{ then: makeTypeConstraintsSchema('string') })
Expand Down Expand Up @@ -107,6 +111,7 @@ module.exports = exports = schema;
// references between the complex types (e.g. "array", "object", "hashtable") and the main "propertyValidators" schema
function typeSpecificConstraintSchemas() {
return {
any: { },
string: {
mustNotBeEmpty: dynamicConstraintSchema(joi.boolean()),
mustBeTrimmed: dynamicConstraintSchema(joi.boolean()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ function documentPropertiesValidationModule(utils, simpleTypeFilter, typeIdValid
}

switch (validatorType) {
case 'any':
// Any type of value is allowed - no further validation required
break;
case 'string':
if (typeof itemValue !== 'string') {
validationErrors.push('item "' + buildItemPath(itemStack) + '" must be a string');
Expand Down
142 changes: 142 additions & 0 deletions test/any-type.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
const testFixtureMaker = require('../src/testing/test-fixture-maker');
const errorFormatter = require('../src/testing/validation-error-formatter');

describe('Any validation type:', () => {
const testFixture = testFixtureMaker.initFromSyncFunction('build/sync-functions/test-any-type-sync-function.js');

afterEach(() => {
testFixture.resetTestEnvironment();
});

describe('for array elements', () => {
it('allows string, number, boolean, array and object values in an array', () => {
const doc = {
_id: 'my-doc',
type: 'anyTypeDoc',
arrayProp: [
'a-string',
-117.8,
true,
[ 'foo', 'bar' ],
{ baz: 'qux' }
]
};

testFixture.verifyDocumentCreated(doc);
});

it('respects universal constraints (e.g. "required")', () => {
const doc = {
_id: 'my-doc',
type: 'anyTypeDoc',
arrayProp: [
'',
0,
null
]
};

testFixture.verifyDocumentNotCreated(doc, 'anyTypeDoc', errorFormatter.requiredValueViolation('arrayProp[2]'));
});
});

describe('for hashtable elements', () => {
it('allows string, number, boolean, array and object values in a hashtable', () => {
const doc = {
_id: 'my-doc',
type: 'anyTypeDoc',
hashtableProp: {
1: 'another-string',
2: 13,
3: false,
4: [ 0, 1, 2 ],
5: { }
}
};

testFixture.verifyDocumentCreated(doc);
});

it('respects universal constraints (e.g. "immutableWhenSet")', () => {
const oldDoc = {
_id: 'my-doc',
type: 'anyTypeDoc',
hashtableProp: {
1: 1.9,
2: true,
3: 'one-more-string',
4: null // Can be changed since it doesn't have a value yet
}
};

const doc = {
_id: 'my-doc',
type: 'anyTypeDoc',
hashtableProp: {
1: 85, // Changed
2: true,
3: 'one-more-string',
4: [ ]
}
};

testFixture.verifyDocumentNotReplaced(
doc,
oldDoc,
'anyTypeDoc',
errorFormatter.immutableItemViolation('hashtableProp[1]'));
});
});

describe('for object properties', () => {
it('allows a string value', () => {
const doc = {
_id: 'my-doc',
type: 'anyTypeDoc',
anyProp: 'a-string'
};

testFixture.verifyDocumentCreated(doc);
});

it('allows a numeric value', () => {
const doc = {
_id: 'my-doc',
type: 'anyTypeDoc',
anyProp: -115.8
};

testFixture.verifyDocumentCreated(doc);
});

it('allows a boolean value', () => {
const doc = {
_id: 'my-doc',
type: 'anyTypeDoc',
anyProp: false
};

testFixture.verifyDocumentCreated(doc);
});

it('allows an array value', () => {
const doc = {
_id: 'my-doc',
type: 'anyTypeDoc',
anyProp: [ 'foo', 'bar' ]
};

testFixture.verifyDocumentCreated(doc);
});

it('allows an object value', () => {
const doc = {
_id: 'my-doc',
type: 'anyTypeDoc',
anyProp: { foo: 'bar' }
};

testFixture.verifyDocumentCreated(doc);
});
});
});
27 changes: 27 additions & 0 deletions test/resources/any-type-doc-definitions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
function() {
return {
anyTypeDoc: {
typeFilter: simpleTypeFilter,
channels: { write: 'write' },
propertyValidators: {
arrayProp: {
type: 'array',
arrayElementsValidator: {
type: 'any',
required: true
}
},
hashtableProp: {
type: 'hashtable',
hashtableValuesValidator: {
type: 'any',
immutableWhenSet: true
}
},
anyProp: {
type: 'any'
}
}
}
};
}

0 comments on commit 63d7835

Please sign in to comment.