Skip to content
This repository has been archived by the owner on May 17, 2024. It is now read-only.

Commit

Permalink
Merge pull request #25 from zapier/feature-warn-on-mutually-exclusive…
Browse files Browse the repository at this point in the history
…-fields

Adding mutually-exclusive fields functional validation
  • Loading branch information
Bruno Bernardino authored Sep 4, 2017
2 parents 8d97b17 + fbac4a0 commit 4fdab85
Show file tree
Hide file tree
Showing 3 changed files with 245 additions and 1 deletion.
3 changes: 2 additions & 1 deletion lib/functional-constraints/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
*/
const checks = [
require('./searchOrCreateKeys'),
require('./deepNestedFields')
require('./deepNestedFields'),
require('./mutuallyExclusiveFields'),
];

const runFunctionalConstraints = (definition) => {
Expand Down
59 changes: 59 additions & 0 deletions lib/functional-constraints/mutuallyExclusiveFields.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use strict';

const _ = require('lodash');
const jsonschema = require('jsonschema');

// NOTE: While it would be possible to accomplish this with a solution like
// https://stackoverflow.com/questions/28162509/mutually-exclusive-property-groups#28172831
// it was harder to read and understand.

const incompatibleFields = [
['children', 'list'], // This is actually a Feature Request (https://github.com/zapier/zapier-platform-cli/issues/115)
['children', 'dict'], // dict is ignored
['children', 'type'], // type is ignored
['children', 'placeholder'], // placeholder is ignored
['children', 'helpText'], // helpText is ignored
['children', 'default'], // default is ignored
['dict', 'list'], // Use only one or the other
['dynamic', 'dict'], // dict is ignored
['dynamic', 'choices'], // choices are ignored
];

const verifyIncompatibilities = (inputFields, path) => {
const errors = [];

_.each(inputFields, (inputField, index) => {
_.each(incompatibleFields, ([firstField, secondField]) => {
if (_.has(inputField, firstField) && _.has(inputField, secondField)) {
errors.push(new jsonschema.ValidationError(
`must not contain ${firstField} and ${secondField}, as they're mutually exclusive.`,
inputField,
'/FieldSchema',
`instance.${path}.inputFields[${index}]`,
'invalid',
'inputFields'
));
}
});
});

return errors;
};

const mutuallyExclusiveFields = (definition) => {
let errors = [];

_.each(['triggers', 'searches', 'creates'], (typeOf) => {
if (definition[typeOf]) {
_.each(definition[typeOf], (actionDef) => {
if (actionDef.operation && actionDef.operation.inputFields) {
errors = [...errors, ...verifyIncompatibilities(actionDef.operation.inputFields, `${typeOf}.${actionDef.key}`)];
}
});
}
});

return errors;
};

module.exports = mutuallyExclusiveFields;
184 changes: 184 additions & 0 deletions test/functional-constraints/mutuallyExclusiveFields.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
'use strict';

require('should');
const schema = require('../../schema');

describe('mutuallyExclusiveFields', () => {

it('should not error on fields not mutually exclusive', () => {
const definition = {
version: '1.0.0',
platformVersion: '1.0.0',
creates: {
foo: {
key: 'foo',
noun: 'Foo',
display: {
label: 'Create Foo',
description: 'Creates a...',
},
operation: {
perform: '$func$2$f$',
inputFields: [
{key: 'orderId', type: 'number'},
{
key: 'line_items',
children: [
{
key: 'product', type: 'string',
},
],
},
],
},
},
},
};

const results = schema.validateAppDefinition(definition);
results.errors.should.have.length(0);
});

it('should error on fields that have children and list', () => {
const definition = {
version: '1.0.0',
platformVersion: '1.0.0',
creates: {
foo: {
key: 'foo',
noun: 'Foo',
display: {
label: 'Create Foo',
description: 'Creates a...',
},
operation: {
perform: '$func$2$f$',
inputFields: [
{key: 'orderId', type: 'number'},
{
key: 'line_items',
children: [
{
key: 'product',
},
],
list: true,
},
],
},
},
},
};

const results = schema.validateAppDefinition(definition);
results.errors.should.have.length(1);
results.errors[0].stack.should.eql('instance.creates.foo.inputFields[1] must not contain children and list, as they\'re mutually exclusive.');
});

it('should error on fields that have list and dict', () => {
const definition = {
version: '1.0.0',
platformVersion: '1.0.0',
creates: {
foo: {
key: 'foo',
noun: 'Foo',
display: {
label: 'Create Foo',
description: 'Creates a...',
},
operation: {
perform: '$func$2$f$',
inputFields: [
{key: 'orderId', type: 'number'},
{
key: 'line_items',
dict: true,
list: true,
},
],
},
},
},
};

const results = schema.validateAppDefinition(definition);
results.errors.should.have.length(1);
results.errors[0].stack.should.eql('instance.creates.foo.inputFields[1] must not contain dict and list, as they\'re mutually exclusive.');
});

it('should error on fields that have dynamic and dict', () => {
const definition = {
version: '1.0.0',
platformVersion: '1.0.0',
creates: {
foo: {
key: 'foo',
noun: 'Foo',
display: {
label: 'Create Foo',
description: 'Creates a...',
},
operation: {
perform: '$func$2$f$',
inputFields: [
{
key: 'orderId',
type: 'number',
dynamic: 'foo.id.number',
dict: true,
},
{
key: 'line_items',
list: true,
},
],
},
},
},
};

const results = schema.validateAppDefinition(definition);
results.errors.should.have.length(1);
results.errors[0].stack.should.eql('instance.creates.foo.inputFields[0] must not contain dynamic and dict, as they\'re mutually exclusive.');
});

it('should error on fields that have dynamic and choices', () => {
const definition = {
version: '1.0.0',
platformVersion: '1.0.0',
creates: {
foo: {
key: 'foo',
noun: 'Foo',
display: {
label: 'Create Foo',
description: 'Creates a...',
},
operation: {
perform: '$func$2$f$',
inputFields: [
{
key: 'orderId',
type: 'number',
dynamic: 'foo.id.number',
choices: {
uno: 1,
dos: 2
},
},
{
key: 'line_items',
list: true,
},
],
},
},
},
};

const results = schema.validateAppDefinition(definition);
results.errors.should.have.length(1);
results.errors[0].stack.should.eql('instance.creates.foo.inputFields[0] must not contain dynamic and choices, as they\'re mutually exclusive.');
});
});

0 comments on commit 4fdab85

Please sign in to comment.