fix: build a rule selector from args, log it in list mode
This commit is contained in:
parent
4159004dd4
commit
59e7c138c0
10
src/index.ts
10
src/index.ts
|
@ -3,8 +3,7 @@ import { createLogger } from 'bunyan';
|
||||||
import { loadConfig } from './config';
|
import { loadConfig } from './config';
|
||||||
import { CONFIG_ARGS_NAME, CONFIG_ARGS_PATH, parseArgs } from './config/args';
|
import { CONFIG_ARGS_NAME, CONFIG_ARGS_PATH, parseArgs } from './config/args';
|
||||||
import { YamlParser } from './parser/YamlParser';
|
import { YamlParser } from './parser/YamlParser';
|
||||||
import { loadRules, resolveRules } from './rule';
|
import { createRuleSelector, loadRules, resolveRules, visitRules } from './rule';
|
||||||
import { visitRules } from './rule/SchemaRule';
|
|
||||||
import { loadSource, writeSource } from './source';
|
import { loadSource, writeSource } from './source';
|
||||||
import { VERSION_INFO } from './version';
|
import { VERSION_INFO } from './version';
|
||||||
import { VisitorContext } from './visitor/VisitorContext';
|
import { VisitorContext } from './visitor/VisitorContext';
|
||||||
|
@ -44,11 +43,12 @@ export async function main(argv: Array<string>): Promise<number> {
|
||||||
logger,
|
logger,
|
||||||
});
|
});
|
||||||
|
|
||||||
const rules = await loadRules(args.rules, ctx);
|
const selector = createRuleSelector(args);
|
||||||
const activeRules = await resolveRules(rules, args);
|
const loadedRules = await loadRules(args.rules, ctx);
|
||||||
|
const activeRules = await resolveRules(loadedRules, selector);
|
||||||
|
|
||||||
if (mode === 'list') {
|
if (mode === 'list') {
|
||||||
logger.info({ rules: activeRules }, 'listing active rules');
|
logger.info({ rules: activeRules, selector }, 'listing active rules');
|
||||||
return STATUS_SUCCESS;
|
return STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { ValidateFunction } from 'ajv';
|
import { ValidateFunction } from 'ajv';
|
||||||
import { applyDiff, diff } from 'deep-diff';
|
|
||||||
import { JSONPath } from 'jsonpath-plus';
|
import { JSONPath } from 'jsonpath-plus';
|
||||||
import { cloneDeep, defaultTo, isNil } from 'lodash';
|
import { cloneDeep, defaultTo, isNil } from 'lodash';
|
||||||
import { LogLevel } from 'noicejs';
|
import { LogLevel } from 'noicejs';
|
||||||
|
@ -85,36 +84,3 @@ export class SchemaRule implements RuleData, Visitor<RuleResult> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function visitRules(ctx: VisitorContext, rules: Array<SchemaRule>, data: any): Promise<VisitorContext> {
|
|
||||||
for (const rule of rules) {
|
|
||||||
const items = await rule.pick(ctx, data);
|
|
||||||
for (const item of items) {
|
|
||||||
const itemResult = cloneDeep(item);
|
|
||||||
const ruleResult = await rule.visit(ctx, itemResult);
|
|
||||||
|
|
||||||
if (ruleResult.errors.length > 0) {
|
|
||||||
ctx.logger.warn({ count: ruleResult.errors.length, rule }, 'rule failed');
|
|
||||||
ctx.mergeResult(ruleResult);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const itemDiff = diff(item, itemResult);
|
|
||||||
if (hasItems(itemDiff)) {
|
|
||||||
ctx.logger.info({
|
|
||||||
diff: itemDiff,
|
|
||||||
item,
|
|
||||||
rule: rule.name,
|
|
||||||
}, 'rule passed with modifications');
|
|
||||||
|
|
||||||
if (ctx.innerOptions.mutate) {
|
|
||||||
applyDiff(item, itemResult);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx.logger.info({ rule: rule.name }, 'rule passed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { Dictionary, intersection, isNil } from 'lodash';
|
import { applyDiff, diff } from 'deep-diff';
|
||||||
|
import { cloneDeep, Dictionary, intersection, isNil } from 'lodash';
|
||||||
import { LogLevel } from 'noicejs';
|
import { LogLevel } from 'noicejs';
|
||||||
|
|
||||||
import { YamlParser } from '../parser/YamlParser';
|
import { YamlParser } from '../parser/YamlParser';
|
||||||
import { readFile } from '../source';
|
import { readFile } from '../source';
|
||||||
import { ensureArray } from '../utils';
|
import { ensureArray, hasItems } from '../utils';
|
||||||
import { VisitorContext } from '../visitor/VisitorContext';
|
import { VisitorContext } from '../visitor/VisitorContext';
|
||||||
import { SchemaRule } from './SchemaRule';
|
import { SchemaRule } from './SchemaRule';
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ export interface RuleSource {
|
||||||
rules: Array<RuleData>;
|
rules: Array<RuleData>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeSelector(options: Partial<RuleSelector>) {
|
export function createRuleSelector(options: Partial<RuleSelector>) {
|
||||||
return {
|
return {
|
||||||
excludeLevel: ensureArray(options.excludeLevel),
|
excludeLevel: ensureArray(options.excludeLevel),
|
||||||
excludeName: ensureArray(options.excludeName),
|
excludeName: ensureArray(options.excludeName),
|
||||||
|
@ -103,3 +104,36 @@ export async function resolveRules(rules: Array<SchemaRule>, selector: RuleSelec
|
||||||
|
|
||||||
return Array.from(activeRules);
|
return Array.from(activeRules);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function visitRules(ctx: VisitorContext, rules: Array<SchemaRule>, data: any): Promise<VisitorContext> {
|
||||||
|
for (const rule of rules) {
|
||||||
|
const items = await rule.pick(ctx, data);
|
||||||
|
for (const item of items) {
|
||||||
|
const itemResult = cloneDeep(item);
|
||||||
|
const ruleResult = await rule.visit(ctx, itemResult);
|
||||||
|
|
||||||
|
if (ruleResult.errors.length > 0) {
|
||||||
|
ctx.logger.warn({ count: ruleResult.errors.length, rule }, 'rule failed');
|
||||||
|
ctx.mergeResult(ruleResult);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemDiff = diff(item, itemResult);
|
||||||
|
if (hasItems(itemDiff)) {
|
||||||
|
ctx.logger.info({
|
||||||
|
diff: itemDiff,
|
||||||
|
item,
|
||||||
|
rule: rule.name,
|
||||||
|
}, 'rule passed with modifications');
|
||||||
|
|
||||||
|
if (ctx.innerOptions.mutate) {
|
||||||
|
applyDiff(item, itemResult);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.logger.info({ rule: rule.name }, 'rule passed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ import { expect } from 'chai';
|
||||||
import { ConsoleLogger } from 'noicejs';
|
import { ConsoleLogger } from 'noicejs';
|
||||||
import { mock, spy } from 'sinon';
|
import { mock, spy } from 'sinon';
|
||||||
|
|
||||||
import { makeSelector, resolveRules } from '../../src/rule';
|
import { createRuleSelector, resolveRules, visitRules } from '../../src/rule';
|
||||||
import { SchemaRule, visitRules } from '../../src/rule/SchemaRule';
|
import { SchemaRule } from '../../src/rule/SchemaRule';
|
||||||
import { VisitorContext } from '../../src/visitor/VisitorContext';
|
import { VisitorContext } from '../../src/visitor/VisitorContext';
|
||||||
import { describeLeaks, itLeaks } from '../helpers/async';
|
import { describeLeaks, itLeaks } from '../helpers/async';
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ const TEST_RULES = [new SchemaRule({
|
||||||
describeLeaks('rule resolver', async () => {
|
describeLeaks('rule resolver', async () => {
|
||||||
describeLeaks('include by level', async () => {
|
describeLeaks('include by level', async () => {
|
||||||
itLeaks('should include info rules', async () => {
|
itLeaks('should include info rules', async () => {
|
||||||
const info = await resolveRules(TEST_RULES, makeSelector({
|
const info = await resolveRules(TEST_RULES, createRuleSelector({
|
||||||
includeLevel: ['info'],
|
includeLevel: ['info'],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ describeLeaks('rule resolver', async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
itLeaks('should include warn rules', async () => {
|
itLeaks('should include warn rules', async () => {
|
||||||
const info = await resolveRules(TEST_RULES, makeSelector({
|
const info = await resolveRules(TEST_RULES, createRuleSelector({
|
||||||
includeLevel: ['warn'],
|
includeLevel: ['warn'],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ describeLeaks('rule resolver', async () => {
|
||||||
|
|
||||||
describeLeaks('include by name', async () => {
|
describeLeaks('include by name', async () => {
|
||||||
itLeaks('should include foo rules', async () => {
|
itLeaks('should include foo rules', async () => {
|
||||||
const rules = await resolveRules(TEST_RULES, makeSelector({
|
const rules = await resolveRules(TEST_RULES, createRuleSelector({
|
||||||
includeName: ['foo'],
|
includeName: ['foo'],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ describeLeaks('rule resolver', async () => {
|
||||||
|
|
||||||
describeLeaks('include by tag', async () => {
|
describeLeaks('include by tag', async () => {
|
||||||
itLeaks('should include test rules', async () => {
|
itLeaks('should include test rules', async () => {
|
||||||
const rules = await resolveRules(TEST_RULES, makeSelector({
|
const rules = await resolveRules(TEST_RULES, createRuleSelector({
|
||||||
includeTag: ['test'],
|
includeTag: ['test'],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ describeLeaks('rule resolver', async () => {
|
||||||
|
|
||||||
describeLeaks('exclude by name', async () => {
|
describeLeaks('exclude by name', async () => {
|
||||||
itLeaks('should exclude foo rules', async () => {
|
itLeaks('should exclude foo rules', async () => {
|
||||||
const rules = await resolveRules(TEST_RULES, makeSelector({
|
const rules = await resolveRules(TEST_RULES, createRuleSelector({
|
||||||
excludeName: ['foo'],
|
excludeName: ['foo'],
|
||||||
includeTag: ['all'],
|
includeTag: ['all'],
|
||||||
}));
|
}));
|
||||||
|
@ -90,7 +90,7 @@ describeLeaks('rule resolver', async () => {
|
||||||
|
|
||||||
describeLeaks('exclude by tag', async () => {
|
describeLeaks('exclude by tag', async () => {
|
||||||
itLeaks('should exclude test rules', async () => {
|
itLeaks('should exclude test rules', async () => {
|
||||||
const rules = await resolveRules(TEST_RULES, makeSelector({
|
const rules = await resolveRules(TEST_RULES, createRuleSelector({
|
||||||
excludeTag: ['test'],
|
excludeTag: ['test'],
|
||||||
includeTag: ['all'],
|
includeTag: ['all'],
|
||||||
}));
|
}));
|
||||||
|
|
Loading…
Reference in New Issue