@wmfs/tymly-statelint
Advanced tools
Comparing version 1.8.0 to 1.9.0
@@ -0,1 +1,14 @@ | ||
# [1.9.0](https://github.com/wmfs/tymly-statelint/compare/v1.8.0...v1.9.0) (2019-03-21) | ||
### ✨ Features | ||
* Complete Parameters validation. ([85d3f8b](https://github.com/wmfs/tymly-statelint/commit/85d3f8b)) | ||
* Start on Parameters validation ([62b63c3](https://github.com/wmfs/tymly-statelint/commit/62b63c3)) | ||
### 🐛 Bug Fixes | ||
* Silence one of the warnings. Too many atm, so not helpful. ([d17fc39](https://github.com/wmfs/tymly-statelint/commit/d17fc39)) | ||
# [1.8.0](https://github.com/wmfs/tymly-statelint/compare/v1.7.0...v1.8.0) (2019-03-20) | ||
@@ -2,0 +15,0 @@ |
@@ -5,2 +5,5 @@ const path = require('path') | ||
const jsonSchemaValidator = require('./json_schema_loader') | ||
const kebabCase = require('lodash.kebabcase') | ||
const cloneDeep = require('lodash.clonedeep') | ||
const isPath = require('@wmfs/j2119/lib/j2119/json_path_checker').isPath | ||
@@ -18,7 +21,11 @@ const moduleRegex = /^(module|function):(.+)$/ | ||
this.functions = functions | ||
this.validators = { } | ||
this.validators = { | ||
'Resource': { }, | ||
'ResourceConfig': { }, | ||
'Parameters': { } | ||
} | ||
this.logger = logger | ||
if (!this.resources) { | ||
this.logger.warn('State Resources not available. Will not validate Resource or ResourceConfig') | ||
this.logger.warn('State Resources not available. Will not validate Resource, ResourceConfig or Parameters') | ||
} | ||
@@ -58,3 +65,7 @@ } // constructor | ||
const [ , resourceType, resourceName ] = resource | ||
if (resourceType === 'module') { this.checkResource(name, state, resourceName) } else if (resourceType === 'function') { this.checkFunction(name, resourceName) } | ||
if (resourceType === 'module') { | ||
this.checkResource(name, state, resourceName) | ||
} else if (resourceType === 'function') { | ||
this.checkFunction(name, resourceName) | ||
} | ||
} // checkTask | ||
@@ -69,21 +80,107 @@ | ||
this.checkResourceConfig(name, resourceName, state.ResourceConfig) | ||
const resourceConfig = state.ResourceConfig | ||
const [parameters, fixUps] = processReplacements(state.Parameters) | ||
const rcOk = this.checkResourceConfig(name, resourceName, resourceConfig) | ||
const pOk = this.checkParameters(name, resourceName, parameters, fixUps) | ||
if (rcOk && pOk) return | ||
const validator = this.findValidator(resourceName, 'Resource') | ||
if (validator && !resourceConfig && !parameters) { | ||
/* silence this for the moment | ||
this.problems.push( | ||
`State Machine.States.${name} has no ResourceConfig or Parameters object, but validation schema found.` | ||
) | ||
*/ | ||
return | ||
} | ||
const usedValidator = this.recheckResourceConfig(name, resourceConfig, rcOk, validator) | ||
this.recheckParameters(name, state.Parameters, pOk, usedValidator ? null : validator, fixUps) | ||
} // checkResource | ||
recheckResourceConfig (name, resourceConfig, alreadyOk, validator) { | ||
return this.recheckSubObject(name, resourceConfig, 'ResourceConfig', alreadyOk, validator) | ||
} // recheckResourceConfig | ||
recheckParameters (name, parameters, alreadyOk, validator, fixUps) { | ||
return this.recheckSubObject(name, parameters, 'Parameters', alreadyOk, validator, fixUps) | ||
} // recheckParameters | ||
recheckSubObject (name, subObject, objectType, alreadyOk, validator, fixUps) { | ||
if (alreadyOk || !subObject) { | ||
return false | ||
} | ||
if (validator) { | ||
return this.validateSubObject(name, subObject, objectType, validator, fixUps) | ||
} else { | ||
this.problems.push( | ||
`State Machine.States.${name}.${objectType} exists, but no validator found` | ||
) | ||
} | ||
} // recheckSubObject | ||
checkResourceConfig (name, resourceName, resourceConfig) { | ||
const validator = this.findValidator(resourceName) | ||
return this.checkSubObject(name, resourceName, resourceConfig, 'ResourceConfig') | ||
} | ||
const problems = validator.validate(resourceConfig || {}) | ||
checkParameters (name, resourceName, parameters, fixUps) { | ||
for (const [field, path] of fixUps) { | ||
if (!isPath(path)) { | ||
this.problems.push( | ||
`State Machine.States.${name} Field "${field}" must be a Path` | ||
) | ||
} | ||
} | ||
return this.checkSubObject(name, resourceName, parameters, 'Parameters', fixUps) | ||
} // checkParameters | ||
checkSubObject (name, resourceName, subObject, objectType, fixUps) { | ||
const validator = this.findValidator(resourceName, objectType) | ||
if (!validator) return | ||
return this.validateSubObject(name, subObject, objectType, validator, fixUps) | ||
} // checkSubObject | ||
validateSubObject (name, subObject, objectType, validator, fixUps = []) { | ||
const problems = validator.validate(subObject || {}) | ||
.filter(p => this.applyFixUps(p, fixUps)) | ||
problems.forEach(p => | ||
this.problems.push(`State Machine.States.${name}.ResourceConfig ${p}`) | ||
this.problems.push(`State Machine.States.${name}.${objectType} ${p}`) | ||
) | ||
} // checkResourceConfig | ||
return true | ||
} // validateSubObject | ||
findValidator (resourceName) { | ||
if (this.validators[resourceName]) { | ||
return this.validators[resourceName] | ||
applyFixUps (p, fixUps = []) { | ||
for (const [replacementName] of fixUps) { | ||
const notAllowed = `Field "${replacementName}" not allowed` | ||
if (p.indexOf(notAllowed) !== -1) return false | ||
const fieldName = replacementName.substring(0, replacementName.length - 2) | ||
const doesNotHave = `does not have required field "${fieldName}"` | ||
if (p.indexOf(doesNotHave) !== -1) return false | ||
} // for ... | ||
return true | ||
} // applyFixUps | ||
findValidator (resourceName, objectType) { | ||
const validators = this.validators[objectType] | ||
if (validators[resourceName]) { | ||
return validators[resourceName] | ||
} | ||
const validator = loadValidator(resourceName, this.resources[resourceName].rootDirPath, this.logger) | ||
this.validators[resourceName] = validator | ||
const validator = loadValidator( | ||
resourceName, | ||
this.resources[resourceName].rootDirPath, | ||
objectType, | ||
this.logger | ||
) | ||
validators[resourceName] = validator | ||
return validator | ||
@@ -101,9 +198,10 @@ } // findValidator | ||
const nullValidator = { | ||
validate: () => [] | ||
} // nullValidator | ||
/// /////////////////////////////////////////////// | ||
function loadValidator (resourceName, rootDir, objectType, logger) { | ||
const schemaName = (objectType !== 'Resource') | ||
? `${kebabCase(objectType)}-schema` | ||
: 'schema' | ||
function loadValidator (resourceName, rootDir, logger) { | ||
for (const [schemaType, validatorLoader] of Object.entries(schemaTypes)) { | ||
const schemaFile = path.resolve(rootDir, `schema.${schemaType}`) | ||
const schemaFile = path.resolve(rootDir, `${schemaName}.${schemaType}`) | ||
if (fs.existsSync(schemaFile)) { | ||
@@ -113,4 +211,4 @@ try { | ||
} catch (err) { | ||
logger.error(`Couldn't load ResourceConfig schema for ${resourceName}:\n ${err.message}`) | ||
return nullValidator | ||
logger.error(`Couldn't load ${objectType} schema for ${resourceName}:\n ${err.message}`) | ||
return null | ||
} | ||
@@ -120,4 +218,3 @@ } // if ... | ||
logger.warn(`No ResourceConfig schema available for ${resourceName}`) | ||
return nullValidator | ||
return null | ||
} // loadValidator | ||
@@ -129,2 +226,30 @@ | ||
/// /////////////////////////////////////////////////// | ||
function processReplacements (parameters) { | ||
if (!parameters) { | ||
return [ null, [] ] | ||
} | ||
const params = cloneDeep(parameters) | ||
const replacements = findSelectors(params) | ||
return [ params, replacements ] | ||
} | ||
function findSelectors (params, path = [], selectors = []) { | ||
for (const [key, value] of Object.entries(params)) { | ||
if (isSelector(key)) { | ||
selectors.push([ key, value ]) | ||
} else if (typeof value === 'object') { | ||
findSelectors(value, [...path, key], selectors) | ||
} | ||
} // findSelectors | ||
return selectors | ||
} // findSelectors | ||
function isSelector (path) { | ||
return path.endsWith('.$') | ||
} // isSelector | ||
module.exports = (stateResources, functions, logger) => new CheckTaskNodes(stateResources, functions, logger) |
{ | ||
"name": "@wmfs/tymly-statelint", | ||
"version": "1.8.0", | ||
"version": "1.9.0", | ||
"description": "Validator for Amazon States Language with Tymly Extensions JSON.", | ||
@@ -28,3 +28,5 @@ "author": "West Midlands Fire Service", | ||
"@wmfs/statelint": "1.7.1", | ||
"ajv": "6.5.5" | ||
"ajv": "6.5.5", | ||
"lodash.clonedeep": "4.5.0", | ||
"lodash.kebabcase": "4.1.1" | ||
}, | ||
@@ -31,0 +33,0 @@ "devDependencies": { |
@@ -16,2 +16,8 @@ /* eslint-env mocha */ | ||
}, | ||
RCFunction: { | ||
rootDirPath: path.resolve(__dirname, './fixtures/run-function-with-resource-config/') | ||
}, | ||
ParamFunction: { | ||
rootDirPath: path.resolve(__dirname, './fixtures/run-function-with-parameters/') | ||
}, | ||
CallFunction: { | ||
@@ -117,3 +123,3 @@ rootDirPath: path.resolve(__dirname, './fixtures/call-function/') | ||
describe('Task ResourceConfig validation with J2119 validator', () => { | ||
describe('Task ResourceConfig validation with resource-config-schema.j2119', () => { | ||
const machine = { | ||
@@ -124,3 +130,3 @@ StartAt: 'A', | ||
Type: 'Task', | ||
Resource: 'module:RunFunction', | ||
Resource: 'module:RCFunction', | ||
End: true | ||
@@ -176,2 +182,77 @@ } | ||
describe('Task Parameters validation with parameters-schema.j2119', () => { | ||
const machine = { | ||
StartAt: 'A', | ||
States: { | ||
A: { | ||
Type: 'Task', | ||
Resource: 'module:ParamFunction', | ||
End: true | ||
} | ||
} | ||
} | ||
verify( | ||
'Parameters is missing', | ||
machine, | ||
1 | ||
) | ||
machine.States.A['Parameters'] = { | ||
'function': 'getFruitName', | ||
'parameter': 'chirimoya' | ||
} | ||
verify( | ||
'Parameters has incorrect fields', | ||
machine, | ||
3 | ||
) | ||
machine.States.A['Parameters'] = { | ||
functionName: 100 | ||
} | ||
verify( | ||
'Parameters is has incorrect type', | ||
machine, | ||
1 | ||
) | ||
machine.States.A['Parameters'] = { | ||
functionName: 'isBanana', | ||
debug: true | ||
} | ||
verify( | ||
'Parameters is has additional field', | ||
machine, | ||
1 | ||
) | ||
machine.States.A['Parameters'] = { | ||
functionName: 'isBanana' | ||
} | ||
verify( | ||
'Parameters validates', | ||
machine, | ||
0 | ||
) | ||
machine.States.A['Parameters'] = { | ||
'functionName.$': '$.isBanana' | ||
} | ||
verify( | ||
'Parameters validates when extracting values', | ||
machine, | ||
0 | ||
) | ||
machine.States.A['Parameters'] = { | ||
'functionName.$': 'Wango' | ||
} | ||
verify( | ||
'Parameters extract value must be a Path ', | ||
machine, | ||
1 | ||
) | ||
}) | ||
describe('Task ResourceConfig validation with JSON schema validator', () => { | ||
@@ -192,3 +273,3 @@ const machine = { | ||
machine, | ||
1 | ||
0 | ||
) | ||
@@ -234,2 +315,143 @@ | ||
}) | ||
describe('Task ResourceConfig & Parameter validation with schema.j2119', () => { | ||
const machine = { | ||
StartAt: 'A', | ||
States: { | ||
A: { | ||
Type: 'Task', | ||
Resource: 'module:RunFunction', | ||
End: true | ||
} | ||
} | ||
} | ||
verify( | ||
'Both ResourceConfig and Parameters are missing', | ||
machine, | ||
0 | ||
) | ||
describe('ResourceConfig present so will be validated', () => { | ||
machine.States.A['ResourceConfig'] = { | ||
'function': 'getFruitName', | ||
'parameter': 'chirimoya' | ||
} | ||
verify( | ||
'ResourceConfig has incorrect fields', | ||
machine, | ||
3 | ||
) | ||
machine.States.A['ResourceConfig'] = { | ||
functionName: 100 | ||
} | ||
verify( | ||
'ResourceConfig is has incorrect type', | ||
machine, | ||
1 | ||
) | ||
machine.States.A['ResourceConfig'] = { | ||
functionName: 'isBanana', | ||
debug: true | ||
} | ||
verify( | ||
'ResourceConfig is has additional field', | ||
machine, | ||
1 | ||
) | ||
machine.States.A['ResourceConfig'] = { | ||
functionName: 'isBanana' | ||
} | ||
verify( | ||
'ResourceConfig validates', | ||
machine, | ||
0 | ||
) | ||
delete machine.States.A['ResourceConfig'] | ||
}) | ||
describe('No ResourceConfig, Parameters present so validate those', () => { | ||
machine.States.A['Parameters'] = { | ||
'function': 'getFruitName', | ||
'parameter': 'chirimoya' | ||
} | ||
verify( | ||
'Parameters has incorrect fields', | ||
machine, | ||
3 | ||
) | ||
machine.States.A['Parameters'] = { | ||
functionName: 100 | ||
} | ||
verify( | ||
'Parameters is has incorrect type', | ||
machine, | ||
1 | ||
) | ||
machine.States.A['Parameters'] = { | ||
functionName: 'isBanana', | ||
debug: true | ||
} | ||
verify( | ||
'Parameters is has additional field', | ||
machine, | ||
1 | ||
) | ||
machine.States.A['Parameters'] = { | ||
functionName: 'isBanana' | ||
} | ||
verify( | ||
'Parameters validates', | ||
machine, | ||
0 | ||
) | ||
}) | ||
describe('Both ResourceConfig and Parameters present. Validate ResourceConfig error on Parameters', () => { | ||
machine.States.A['ResourceConfig'] = { | ||
'function': 'getFruitName', | ||
'parameter': 'chirimoya' | ||
} | ||
verify( | ||
'ResourceConfig has incorrect fields', | ||
machine, | ||
4 | ||
) | ||
machine.States.A['ResourceConfig'] = { | ||
functionName: 100 | ||
} | ||
verify( | ||
'ResourceConfig is has incorrect type', | ||
machine, | ||
2 | ||
) | ||
machine.States.A['ResourceConfig'] = { | ||
functionName: 'isBanana', | ||
debug: true | ||
} | ||
verify( | ||
'ResourceConfig is has additional field', | ||
machine, | ||
2 | ||
) | ||
machine.States.A['ResourceConfig'] = { | ||
functionName: 'isBanana' | ||
} | ||
verify( | ||
'ResourceConfig validates', | ||
machine, | ||
1 | ||
) | ||
}) | ||
}) | ||
}) | ||
@@ -236,0 +458,0 @@ |
@@ -6,3 +6,2 @@ /* eslint-env mocha */ | ||
const expect = chai.expect | ||
const fs = require('fs') | ||
const path = require('path') | ||
@@ -21,3 +20,9 @@ | ||
for (const file of fs.readdirSync(fixturesDir).filter(f => f.endsWith('.json'))) { | ||
const fixtures = [ | ||
['timestamp.json', 0], | ||
['hmo.json', 2], | ||
['building-dna.json', 11] | ||
] | ||
for (const [file, errors] of fixtures) { | ||
it(file, () => { | ||
@@ -30,3 +35,3 @@ const linter = tymlyStateLint(stateResources, nullLogger) | ||
expect(problems).to.eql([]) | ||
expect(problems.length).to.eql(stateResources ? errors : 0) | ||
}) | ||
@@ -49,3 +54,3 @@ } | ||
describe('TymlyStateMachineLint', () => { | ||
tests() | ||
tests(null) | ||
tests(stateResources) | ||
@@ -64,13 +69,5 @@ describe('logger', () => { | ||
expect(logger.logs).to.eql(['State Resources not available. Will not validate Resource or ResourceConfig']) | ||
expect(logger.logs).to.eql(['State Resources not available. Will not validate Resource, ResourceConfig or Parameters']) | ||
}) | ||
it('warning when no state resource schema available', () => { | ||
const logger = new TestLogger() | ||
const linter = tymlyStateLint(stateResources, null, logger) | ||
linter.validate(require(path.join(fixturesDir, 'timestamp.json'))) | ||
expect(logger.logs).to.eql(['No ResourceConfig schema available for timestamp']) | ||
}) | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
42869
23
949
3
5
+ Addedlodash.clonedeep@4.5.0
+ Addedlodash.kebabcase@4.1.1
+ Addedlodash.clonedeep@4.5.0(transitive)
+ Addedlodash.kebabcase@4.1.1(transitive)