-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #29 from trayio/jon-urry/CSP-2182/add-schema-gener…
…ator-to-connector-utils [CSP-2182] add schema generator to connector utils
- Loading branch information
Showing
27 changed files
with
7,921 additions
and
4,325 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
.vscode | ||
/coverage | ||
/node_modules | ||
/coverage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
/* eslint-disable no-console */ | ||
const _ = require('lodash'); | ||
const logger = require('./internal/logger'); | ||
|
||
const MISSING_KEYS_MESSAGE = | ||
'There are missing schema keys that should be provided:'; | ||
|
||
const flattenAndCompact = ({ array }) => _.flattenDeep(_.compact(array)); | ||
|
||
const logIssuesToConsole = ({ issues }) => { | ||
if (issues.some(error => error.missing === 'type')) { | ||
logger.log('error', MISSING_KEYS_MESSAGE); | ||
} else { | ||
logger.log('warn', MISSING_KEYS_MESSAGE); | ||
} | ||
logger.log( | ||
'table', | ||
issues.map(error => ({ | ||
key: error.key, | ||
[error.missing]: 'missing', | ||
})), | ||
['key', 'type', 'description'], | ||
); | ||
}; | ||
|
||
const shouldInvokeIteratee = ({ value, key }) => | ||
// ignore lookups, object properties and oneOf arrays | ||
!['lookup', 'properties'].includes(key) && !Array.isArray(value.oneOf); | ||
|
||
const shouldParseChildren = ({ key }) => !['lookup'].includes(key); | ||
|
||
/** | ||
* Deep recursive iteration through a full schema object definition. | ||
* Returns a flat array of objects specifying the missing keys. | ||
* Validation rules are specified in the iteratee. | ||
*/ | ||
const deepValidateSchema = ({ collection, iteratee, path = 'schema' }) => { | ||
const recursiveArray = ({ col, fn, oPath }) => { | ||
return Array.isArray(col) | ||
? col.map(element => | ||
deepValidateSchema({ | ||
collection: element, | ||
iteratee: fn, | ||
path: oPath, | ||
}), | ||
) | ||
: []; | ||
}; | ||
|
||
const issues = []; | ||
|
||
issues.push(recursiveArray({ col: collection, fn: iteratee, oPath: path })); | ||
|
||
if (_.isPlainObject(collection)) { | ||
_.forEach(collection, (value, key) => { | ||
issues.push( | ||
recursiveArray({ | ||
col: value, | ||
fn: iteratee, | ||
oPath: `${path}.${key}`, | ||
}), | ||
); | ||
|
||
if (_.isPlainObject(value)) { | ||
if (shouldInvokeIteratee({ value, key })) { | ||
issues.push( | ||
iteratee({ element: value, key: `${path}.${key}` }), | ||
); | ||
} | ||
if (shouldParseChildren({ key })) { | ||
issues.push( | ||
deepValidateSchema({ | ||
collection: value, | ||
iteratee, | ||
path: `${path}.${key}`, | ||
}), | ||
); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
return flattenAndCompact({ array: issues }); | ||
}; | ||
|
||
// Schema elements must include 'type' and 'description' keys. | ||
const checkForIncompleteSchemaElements = ({ element, key }) => { | ||
const keys = Object.keys(element); | ||
const incompleteSchemaElements = []; | ||
if (_.isPlainObject(element)) { | ||
if (!keys.includes('type')) { | ||
incompleteSchemaElements.push({ key, missing: 'type' }); | ||
} | ||
if (!keys.includes('description')) { | ||
incompleteSchemaElements.push({ key, missing: 'description' }); | ||
} | ||
} | ||
return incompleteSchemaElements; | ||
}; | ||
|
||
/** | ||
* Generates an operation input schema. | ||
* Will log to the console if a requested key does not exist. | ||
* Will not log to the console if requested key does not exist, | ||
* but is overridden with at least a type and description. | ||
* | ||
* @param {Object} schema The full connector schema definition. | ||
* @param {Object} keys The keys that you wish to extract from the schema with any override values. | ||
* @param {String} operation The name of the connector operation that you are generating the schema for. | ||
* This will be used as the root of the object path when logging validation issues. | ||
* @return {object} A copy of the requested schema elements. | ||
*/ | ||
|
||
const generateInputSchema = ({ schema, keys, operation = 'schema' }) => { | ||
// map the required input parameters to their individual schemas | ||
// and override with any additionally provided values | ||
const mappedSchema = _.map(keys, (value, key) => ({ | ||
[key]: { ...schema[key], ...value }, | ||
})); | ||
|
||
// find incomplete schema definitions | ||
const incompleteSchemaErrors = deepValidateSchema({ | ||
collection: mappedSchema, | ||
iteratee: checkForIncompleteSchemaElements, | ||
path: operation, | ||
}); | ||
|
||
// Log issues for missing schema definitions to console | ||
if (incompleteSchemaErrors.length > 0) { | ||
logIssuesToConsole({ issues: incompleteSchemaErrors }); | ||
} | ||
|
||
// combine the individual schemas to a single operation schema | ||
const combinedSchema = mappedSchema.reduce( | ||
(acc, curr) => ({ ...acc, ...curr }), | ||
{}, | ||
); | ||
|
||
// deep clone the schema so that only copies of schema elements are returned | ||
return _.cloneDeep(combinedSchema); | ||
}; | ||
|
||
module.exports = generateInputSchema; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/** | ||
* wrapper for the console function | ||
* allows us to log to the console within the utils library | ||
* easy to stub or mock logging in tests without affecting production logging | ||
*/ | ||
|
||
// eslint-disable-next-line no-console | ||
exports.log = (level, ...toLog) => console[level].apply(this, toLog); |
Oops, something went wrong.