1
0
Fork 0

feat: validate rules while loading

This commit is contained in:
ssube 2019-11-17 11:55:33 -06:00 committed by Sean Sube
parent 1b15952c05
commit dbfe0429fa
7 changed files with 123 additions and 27 deletions

View File

@ -27,6 +27,7 @@ const bundle = {
external,
input: {
include: [
// join(rootPath, 'rules', '*.yml'),
join(rootPath, 'src', 'index.ts'),
join(rootPath, 'test', 'harness.ts'),
join(rootPath, 'test', '**', 'Test*.ts'),
@ -49,7 +50,7 @@ const bundle = {
return 'index';
}
if (id.includes(`${sep}src${sep}`)) {
if (id.includes(`${sep}src${sep}`) || id.includes(`${sep}rules${sep}`)) {
return 'main';
}

16
docs/roadmap.md Normal file
View File

@ -0,0 +1,16 @@
# Roadmap
## v0.9
- architecture: split up main into commands
- architecture: wire up dependency injection with noicejs
- feature: visitor events (#21)
- feature: improve diff messages (#133)
- feature: interactive diffs (#9, requires #21 and #133)
- feature: instantiate rules based on type (#113, requires #?)
- fix: pass structured errors
- fix: type or eliminate flash data (#137)
## v1.0
- fix: all the bugs

View File

@ -1,5 +1,13 @@
name: salty-dog-meta
definitions:
name-safe:
type: string
pattern: "[-a-z0-9]+"
name-tag:
type: string
pattern: "[-:a-z0-9]+"
rule:
type: object
additionalProperties: false
@ -13,8 +21,7 @@ definitions:
- check
properties:
name:
type: string
pattern: "[-a-z0-9]+"
$ref: "salty-dog-meta#/definitions/name-safe"
desc:
type: string
minLength: 8
@ -29,8 +36,7 @@ definitions:
tags:
type: array
items:
type: string
pattern: "[-:a-z0-9]+"
$ref: "salty-dog-meta#/definitions/name-tag"
select:
type: string
default: '$'
@ -40,6 +46,25 @@ definitions:
check:
$ref: "http://json-schema.org/draft-07/schema#"
source:
type: object
required:
- name
- rules
properties:
name:
$ref: "salty-dog-meta#/definitions/name-safe"
definitions:
type: object
additionalProperties: false
patternProperties:
"[-a-z]+":
type: object
rules:
type: array
items:
$ref: "salty-dog-meta#/definitions/rule"
rules:
- name: salty-dog-rule
desc: rules must be complete
@ -60,21 +85,10 @@ rules:
- salty-dog
check:
type: object
additionalProperties: false
required: [name, rules]
properties:
definitions:
type: object
additionalProperties: false
patternProperties:
"[-a-z]+":
type: object
name:
type: string
pattern: "[-a-z]+"
rules:
type: array
minItems: 1
items:
$ref: "salty-dog-meta#/definitions/rule"
allOf:
- $ref: "salty-dog-meta#/definitions/source"
- type: object
properties:
rules:
type: array
minItems: 1

View File

@ -4,6 +4,7 @@ import { Minimatch } from 'minimatch';
import { LogLevel } from 'noicejs';
import recursive from 'recursive-readdir';
import ruleSchemaData from '../../rules/salty-dog.yml';
import { YamlParser } from '../parser/YamlParser';
import { readFile } from '../source';
import { ensureArray } from '../utils';
@ -107,6 +108,14 @@ export async function loadRuleFiles(paths: Array<string>, ctx: VisitorContext):
const docs = parser.parse(contents) as Array<RuleSourceData>;
for (const data of docs) {
if (!validateRules(ctx, data)) {
ctx.logger.error({
file: data,
path,
}, 'error loading rule file');
continue;
}
if (!isNil(data.definitions)) {
ctx.addSchema(data.name, data.definitions);
}
@ -147,7 +156,12 @@ export async function loadRuleModules(modules: Array<string>, ctx: VisitorContex
try {
/* eslint-disable-next-line @typescript-eslint/no-var-requires */
const module: RuleSourceModule = r(name);
// TODO: ensure module has definitions, name, and rules
if (!validateRules(ctx, module)) {
ctx.logger.error({
module: name,
}, 'error loading rule module');
continue;
}
if (!isNil(module.definitions)) {
ctx.addSchema(module.name, module.definitions);
@ -193,3 +207,18 @@ export async function resolveRules(rules: Array<Rule>, selector: RuleSelector):
return Array.from(activeRules);
}
export function validateRules(ctx: VisitorContext, root: any): boolean {
const { definitions, name } = ruleSchemaData as any;
const validCtx = new VisitorContext(ctx);
validCtx.addSchema(name, definitions);
const ruleSchema = validCtx.compile(definitions.source);
if (ruleSchema(root) === true) {
return true;
} else {
ctx.logger.error({ errors: ruleSchema.errors }, 'error validating rules');
return false;
}
}

View File

@ -18,7 +18,7 @@ const TEST_FILES = {
name: test,
rules: [{
name: test,
desc: test,
desc: test-rule,
level: info,
tags: [test],
check: {

View File

@ -13,6 +13,7 @@ const EXAMPLE_RULES = `{
definitions: {},
rules: [{
name: test,
desc: test-rule,
level: info,
tags: []
}]

View File

@ -1,8 +1,9 @@
import { expect } from 'chai';
import { LogLevel } from 'noicejs';
import { ConsoleLogger, LogLevel, NullLogger } from 'noicejs';
import { createRuleSelector, createRuleSources, resolveRules } from '../../src/rule';
import { createRuleSelector, createRuleSources, resolveRules, validateRules } from '../../src/rule';
import { SchemaRule } from '../../src/rule/SchemaRule';
import { VisitorContext } from '../../src/visitor/VisitorContext';
import { describeLeaks, itLeaks } from '../helpers/async';
const TEST_RULES = [new SchemaRule({
@ -133,3 +134,37 @@ describe('create rule selector helper', () => {
expect(sources).to.have.deep.property('includeTag', []);
});
});
describeLeaks('validate rule helper', async () => {
itLeaks('should accept valid modules', async () => {
const ctx = new VisitorContext({
logger: ConsoleLogger.global,
schemaOptions: {
coerce: false,
defaults: false,
mutate: false,
},
});
expect(validateRules(ctx, {
name: 'test',
rules: [],
})).to.equal(true);
});
itLeaks('should reject partial modules', async () => {
const ctx = new VisitorContext({
logger: NullLogger.global,
schemaOptions: {
coerce: false,
defaults: false,
mutate: false,
},
});
expect(validateRules(ctx, {})).to.equal(false);
expect(validateRules(ctx, {
name: '',
})).to.equal(false);
});
});