feat: validate rules while loading
This commit is contained in:
parent
1b15952c05
commit
dbfe0429fa
|
@ -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';
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ const TEST_FILES = {
|
|||
name: test,
|
||||
rules: [{
|
||||
name: test,
|
||||
desc: test,
|
||||
desc: test-rule,
|
||||
level: info,
|
||||
tags: [test],
|
||||
check: {
|
||||
|
|
|
@ -13,6 +13,7 @@ const EXAMPLE_RULES = `{
|
|||
definitions: {},
|
||||
rules: [{
|
||||
name: test,
|
||||
desc: test-rule,
|
||||
level: info,
|
||||
tags: []
|
||||
}]
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue