@wmfs/j2119
Advanced tools
Comparing version 1.5.1 to 1.6.0
class AllowedFields { | ||
constructor () { | ||
this.allowed = new Map() | ||
this.any = [ ] | ||
} // constructor | ||
@@ -14,8 +15,19 @@ | ||
setAny (role) { | ||
this.any.push(role) | ||
} // setAny | ||
isAllowed (roles, child) { | ||
return roles.some(role => | ||
const any = this.allowsAny(roles) | ||
return any || roles.some(role => | ||
this.allowed.has(role) && this.allowed.get(role).includes(child) | ||
) | ||
} | ||
} // isAllowed | ||
allowsAny (roles) { | ||
return roles.some(role => | ||
this.any.includes(role) | ||
) | ||
} // allowsAny | ||
toString () { | ||
@@ -26,2 +38,5 @@ let msg = 'Allowed fields: ' | ||
}) | ||
this.any.forEach(role => { | ||
msg += `\n ${role} => any fields` | ||
}) | ||
return msg | ||
@@ -28,0 +43,0 @@ } |
@@ -5,2 +5,3 @@ const oxford = require('./oxford') | ||
const deduce = require('./deduce') | ||
const TYPES = require('./types') | ||
@@ -99,11 +100,18 @@ class Assigner { | ||
// there can be role defs there too | ||
if (assertion['child_type']) { | ||
this.matcher.addRole(assertion['child_role']) | ||
const childType = assertion['child_type'] | ||
const childType = assertion['child_type'] | ||
if (childType) { | ||
const childRole = assertion['child_role'] | ||
this.matcher.addRole(childRole) | ||
if (childType === 'value') { | ||
this.roles.addChildRole(role, fieldName, assertion['child_role']) | ||
this.roles.addChildRole(role, fieldName, childRole) | ||
} else if (childType === 'element' || childType === 'field') { | ||
this.roles.addGrandchildRole(role, fieldName, assertion['child_role']) | ||
this.roles.addGrandchildRole(role, fieldName, childRole) | ||
} | ||
} | ||
// object field without a defined child role | ||
if (fieldName && !childType && type === TYPES.object) { | ||
this.roles.addGrandchildRole(role, fieldName, fieldName) | ||
this.allowedFields.setAny(fieldName) | ||
} | ||
} // assignConstraints | ||
@@ -145,5 +153,5 @@ | ||
type.split('-').forEach(part => { | ||
if (constraint.TYPES.includes(part)) { | ||
if (part === 'array' && isArray) return | ||
for (const part of type.split('-')) { | ||
if (TYPES.includes(part)) { | ||
if (part === TYPES.array && isArray) return | ||
@@ -170,3 +178,3 @@ this.addConstraint( | ||
} | ||
}) | ||
} // for ... | ||
} // addTypeConstraints | ||
@@ -173,0 +181,0 @@ } // class Assigner |
const DateTime = require('luxon').DateTime | ||
const jsonPathChecker = require('./json_path_checker') | ||
const TYPES = require('./types') | ||
@@ -101,16 +102,2 @@ class Constraint { | ||
// Verify type of a field in a node | ||
const TYPES = [ | ||
'array', | ||
'object', | ||
'string', | ||
'boolean', | ||
'numeric', | ||
'integer', | ||
'float', | ||
'timestamp', | ||
'JSONPath', | ||
'referencePath', | ||
'URI' | ||
] | ||
class FieldTypeConstraint extends Constraint { | ||
@@ -156,34 +143,34 @@ constructor (name, type, isArray, isNullable) { | ||
switch (this.type) { | ||
case 'object': | ||
case TYPES.object: | ||
if (typeof value !== 'object' && !Array.isArray(value)) report('an Object') | ||
break | ||
case 'array': | ||
case TYPES.array: | ||
if (!Array.isArray(value)) report('an Array') | ||
break | ||
case 'string': | ||
case TYPES.string: | ||
if (typeof value !== 'string') report('a String') | ||
break | ||
case 'integer': | ||
case TYPES.integer: | ||
if (!Number.isInteger(value)) report('an Integer') | ||
break | ||
case 'float': | ||
case TYPES.float: | ||
if (typeof value !== 'number' || Number.isInteger(value)) report('a Float') | ||
break | ||
case 'boolean': | ||
case TYPES.boolean: | ||
if (typeof value !== 'boolean') report('a Boolean') | ||
break | ||
case 'numeric': | ||
case TYPES.numeric: | ||
if (typeof value !== 'number') report('numeric') | ||
break | ||
case 'JSONPath': | ||
case TYPES.JSONPath: | ||
if (!jsonPathChecker.isPath(value)) report('a JSONPath') | ||
break | ||
case 'referencePath': | ||
case TYPES.referencePath: | ||
if (!jsonPathChecker.isReferencePath(value)) report('a Reference Path') | ||
break | ||
case 'timestamp': | ||
case TYPES.timestamp: | ||
const ts = DateTime.fromISO(value) | ||
if (!ts.isValid) report('an ISO8601 timestamp') // Spec actually says RFC3339, but close enough | ||
break | ||
case 'URI': | ||
case TYPES.URI: | ||
if ((typeof value !== 'string') || (!value.match(/^[a-z]+:/))) report('a URI') | ||
@@ -283,4 +270,3 @@ break | ||
fieldType: (name, type, isArray, isNullable) => new FieldTypeConstraint(name, type, isArray, isNullable), | ||
fieldValue: (name, params) => new FieldValueConstraint(name, params), | ||
TYPES | ||
fieldValue: (name, params) => new FieldValueConstraint(name, params) | ||
} |
@@ -5,3 +5,3 @@ const XRegExp = require('xregexp') | ||
const MUST = '(?<modal>MUST|MAY|MUST NOT)' | ||
const TYPES = require('./constraints').TYPES | ||
const TYPES = require('./types') | ||
@@ -158,3 +158,3 @@ const RELATIONS = [ | ||
const types = [...TYPES] | ||
const numberTypes = ['float', 'integer', 'numeric'] | ||
const numberTypes = [TYPES.float, TYPES.integer, TYPES.numeric] | ||
const numberModifiers = ['positive', 'negative', 'nonnegative'] | ||
@@ -161,0 +161,0 @@ numberTypes.forEach(numberType => |
@@ -13,3 +13,3 @@ class NodeValidator { | ||
validateNode (node, path, roles, problems) { | ||
if ((node === null) || (typeof node !== 'object')) return | ||
if ((node === null) || (typeof node !== 'object') || Array.isArray(node)) return | ||
@@ -19,2 +19,15 @@ // may have more roles based on field presence/value etc | ||
this.checkConstraints(node, path, roles, problems) | ||
// for each field | ||
for (const [name, value] of Object.entries(node)) { | ||
this.validateField(name, path, roles, problems) | ||
this.validateChild(value, name, path, roles, problems) | ||
this.validateGrandchildren(value, name, path, roles, problems) | ||
} // for ... | ||
} // validateNode | ||
checkConstraints (node, path, roles, problems) { | ||
// constraints are attached per-role | ||
@@ -29,35 +42,43 @@ for (const role of roles) { | ||
} | ||
} // checkConstraints | ||
// for each field | ||
const isArray = Array.isArray(node) | ||
for (const [name, value] of Object.entries(node)) { | ||
if (!isArray) { | ||
if (!this.parser.isFieldAllowed(roles, name)) { | ||
problems.push(`Field "${name}" not allowed in ${path}`) | ||
} | ||
validateField (name, path, roles, problems) { | ||
if (!this.parser.isFieldAllowed(roles, name)) { | ||
problems.push(`Field "${name}" not allowed in ${path}`) | ||
} | ||
} // validateField | ||
// recurse into children if they have roles | ||
const childRoles = this.parser.findChildRoles(roles, name) | ||
if (childRoles.length) { | ||
this.validateNode(value, `${path}.${name}`, childRoles, problems) | ||
} | ||
} // if(!isArray) | ||
validateChild (childNode, name, path, roles, problems) { | ||
const childRoles = this.parser.findChildRoles(roles, name) | ||
if (!childRoles.length) return | ||
if (typeof value === 'object') { | ||
// find inheritance-based roles for that field | ||
const grandchildRoles = this.parser.findGrandchildRoles(roles, name) | ||
const gcIndex = Array.isArray(value) ? i => `[${i}]` : i => `.${i}` | ||
for (const [index, childValue] of Object.entries(value)) { | ||
this.validateNode( | ||
childValue, | ||
`${path}.${name}${gcIndex(index)}`, | ||
[...grandchildRoles], | ||
problems | ||
) | ||
} // for ... | ||
} // if ... | ||
// recurse into children if they have roles | ||
this.validateNode( | ||
childNode, | ||
`${path}.${name}`, | ||
childRoles, | ||
problems | ||
) | ||
} // validateChild | ||
validateGrandchildren (childNode, name, path, roles, problems) { | ||
if (typeof childNode !== 'object') return | ||
// only objects & arrays have grand children | ||
// find inheritance-based roles for that field | ||
const grandchildRoles = this.parser.findGrandchildRoles(roles, name) | ||
if (this.parser.allowsAnyField(grandchildRoles)) return | ||
const gcIndex = Array.isArray(childNode) ? i => `[${i}]` : i => `.${i}` | ||
for (const [index, gcValue] of Object.entries(childNode)) { | ||
this.validateNode( | ||
gcValue, | ||
`${path}.${name}${gcIndex(index)}`, | ||
[...grandchildRoles], | ||
problems | ||
) | ||
} // for ... | ||
} // validateNode | ||
} // validateGrandchildren | ||
} // class NodeValidator | ||
module.exports = parser => new NodeValidator(parser) |
@@ -130,2 +130,6 @@ const roleConstraints = require('./role_constraints') | ||
allowsAnyField (roles) { | ||
return this.allowedFields.allowsAny(roles) | ||
} | ||
toString () { | ||
@@ -132,0 +136,0 @@ return `${this.finder}\n${this.constraints}\n${this.allowedFields}` |
@@ -109,2 +109,3 @@ const deduce = require('./deduce') | ||
.map(descendants => descendants.get(fieldName)) | ||
.filter(fieldRoles => !!fieldRoles) | ||
} // descendantRoles | ||
@@ -111,0 +112,0 @@ |
{ | ||
"name": "@wmfs/j2119", | ||
"version": "1.5.1", | ||
"version": "1.6.0", | ||
"description": "A general-purpose validator generator that uses RFC2119-style assertions as input.", | ||
@@ -5,0 +5,0 @@ "author": "West Midlands Fire Service", |
@@ -23,3 +23,24 @@ /* eslint-env mocha */ | ||
x: { | ||
Type: 'Pass', | ||
Type: 'Task', | ||
Resource: 'module:monkeyPunk', | ||
ResourceConfig: { | ||
param: 'tree', | ||
obj: { | ||
x: 'X', | ||
y: { | ||
down: { | ||
deep: { | ||
and: { | ||
deeper: { | ||
and: { | ||
deeper: { | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
End: true | ||
@@ -68,2 +89,3 @@ } | ||
const v = validator(SCHEMA, EXTENSION) | ||
const p = v.validate(STATE_MACHINE) | ||
@@ -70,0 +92,0 @@ expect(p.length).to.eql(2) // missing extensions! |
Sorry, the diff of this file is not supported yet
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
135355
40
2773